1. 復(fù)雜的原型鏈
原型是 JavaScript 向面向?qū)ο缶幊陶Z言進(jìn)化的產(chǎn)物徘键。
為什么要面向?qū)ο缶幊塘范裕繛榱舜a復(fù)用,為了抽象出類似的概念吹害,為了讓代碼更有邏輯螟凭,更符合我們本身的世界觀。
在我們的意識(shí)中它呀,其實(shí)世界的事物都是一個(gè)一個(gè)對(duì)象螺男。例如,男人和女人纵穿,都屬于人的一個(gè)子分支下隧,他們兩者都具有人的一些共同點(diǎn)特點(diǎn),或者說谓媒,男人和女人這兩個(gè)對(duì)象都“繼承”了“人”這個(gè)對(duì)象的一些特點(diǎn)淆院,而又有各自的不一樣屬性【涔撸或者說:人土辩,就是男人和女人的原型。
在 JavaScript 中抢野,Function
,Object
分別是函數(shù)拷淘,對(duì)象的原型,而其實(shí)函數(shù)也是一個(gè)特殊的對(duì)象指孤,因此函數(shù)原型創(chuàng)作的函數(shù)也是特殊的對(duì)象启涯。
var fun =function() {
console.log('this is a function');
};
var obj = {
string: 'obj',
};
console.log(Function.prototype.isPrototypeOf(fun));
console.log(Object.prototype.isPrototypeOf(fun));
console.log(Function.prototype.isPrototypeOf(obj));
console.log(Object.prototype.isPrototypeOf(obj));
console.log(Object.prototype.isPrototypeOf(Function));
// true, true, true, false, true
利用Object.prototype.isPrototypeOf(targetObj)
方法,可以檢測(cè)Object
測(cè)試對(duì)象是否在檢測(cè)目標(biāo)的原型鏈上恃轩,根據(jù)輸出可以看到结洼,Function
對(duì)象和 Object
對(duì)象在所有函數(shù)的原型鏈上,其中Object
是Function
的原型详恼,而Object
是所有對(duì)象的原型补君,包括 Function
對(duì)象。
我們可以使用一個(gè) ES5 的方法來獲取一個(gè)對(duì)象的原型:
Object.getPrototypeOf(obj)
var Person = function (name) {
this.name = name;
};
var alice = new Person('alice');
console.log(Object.getPrototypeOf(alice));
console.log(Object.getPrototypeOf(alice) === Person.prototype);
// Person.prototype, true
可以看到昧互,Person
原型挽铁,生成了一個(gè)實(shí)例 alice
伟桅,因此Person
對(duì)象 是alice
的原型。
類似的叽掘,Person
也應(yīng)該有原型楣铁,所謂原型,就是生成它的那個(gè)角色更扁。上面實(shí)例中盖腕,Person
是一個(gè)函數(shù),那它的原型是什么呢浓镜?我們用代碼探究:
console.log(Object.getPrototypeOf(Person));
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// ? () { [native code] } , true
由于創(chuàng)造函數(shù)的函數(shù)(Function.prototype)是 JavaScript 語言的源代碼溃列,因此不存在它是什么,但是我們通過第二個(gè)判斷得知(三個(gè)等號(hào))膛薛,生產(chǎn)函數(shù)的函數(shù)就是Function.prototype
听隐。
而 Function.prototype
本身是一個(gè)對(duì)象,因此它也是有原型的哄啄,而它的原型就是創(chuàng)造對(duì)象的對(duì)象雅任,Object.prototype。
console.log(
Object.getPrototypeOf(Function.prototype) === Object.prototype);
// true
那 Object.prototype
有沒有原型呢咨跌?有的沪么,為了避免原型鏈沒有盡頭,ES 規(guī)定了Object.prototype的原型就是 null
锌半。
因此我們可以畫出原型鏈:
alice -> Person -> Function -> Object -> null
原型鏈之所以是復(fù)雜的禽车,就是因?yàn)樵诖a構(gòu)建工程的過程中,可以會(huì)產(chǎn)生無數(shù)的原型和原型生成的實(shí)際對(duì)象刊殉,而原型可能又會(huì)有它的原型哭当,等等,一層層的關(guān)系形成一個(gè)鏈條冗澈,這就是原型鏈。
但是原型鏈的靈活也在于此:一個(gè)實(shí)例可以調(diào)用它的原型的方法陋葡,以及原型的原型的方法亚亲。
也就是它在使用函數(shù)時(shí),會(huì)在自身的屬性/方法中查找腐缤,如果沒有找到該方法(函數(shù))捌归,它就會(huì)沿著原型鏈,查找它的原型中的方法岭粤,如果仍沒有惜索,繼續(xù)沿原型鏈進(jìn)行查找,知道原型鏈的盡頭:null
剃浇。
可以看到巾兆,所有的對(duì)象都是Function
和Object
原型的下游猎物,因此,所有的函數(shù)生產(chǎn)對(duì)象都可以調(diào)用 Function.prototype
和Object.prototype
中的方法角塑,call(this, arg1,arg2,...), apply(this,arg[]), bind(this), toString(), isPropertyOf(), hasOwnProperty()
2. 構(gòu)造函數(shù)與 new 命令
其實(shí)上面我們已經(jīng)涉及到了構(gòu)造函數(shù)蔫磨。構(gòu)造函數(shù)的實(shí)現(xiàn)沒有脫離原型鏈這一基礎(chǔ),只是在語法實(shí)現(xiàn)上來說圃伶,更靠近其他語言例如 Java 的寫法堤如。一個(gè)典型的構(gòu)造函數(shù):
function Person(name, email) {
this.name = name;
this.email = email;
this.sayName =function() {
console.log(this.name);
};
this.sayEmail = function() {
console.log(this.email);
};
}
一個(gè)典型的構(gòu)造函數(shù),有它的屬性窒朋,name, email
搀罢,有它的方法,sayName(), sayEmail()
侥猩,他的實(shí)例都會(huì)具有它的這些方法 由它可以生出很多實(shí)例榔至,但是需要配合 一個(gè)關(guān)鍵詞使用:new
,這也是借鑒 Java 語言中的寫法拭宁。
// Java class
public class Dog {
string name;
int age;
string color;
public barking() {
System.out.println("wo-wo-wo");
}
}
Dog myDog = new Dog();
myDog.barking();
類似的洛退,在 JavaScript 中,構(gòu)造函數(shù)就相當(dāng)于是 Java 中的類杰标,它們都可以生成一個(gè)實(shí)例兵怯,并且實(shí)例可以調(diào)用構(gòu)造函數(shù)中的方法。
var alice = new Person('alice', 'alice@google.com');
alice.sayName();
alice.sayEmail();
// "alice" "alice@google.com"
這其中涉及到 new 運(yùn)算符的實(shí)現(xiàn)腔剂,大致的實(shí)現(xiàn)步驟來說應(yīng)該是這樣:
- 生成一個(gè)新的空對(duì)象媒区。
- 將1.中生成的空對(duì)象的原型指向構(gòu)造函數(shù)的
prototype
屬性。 - 將這個(gè)空對(duì)象賦值給函數(shù)內(nèi)部中的this關(guān)鍵字掸犬。
- 開始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼袜漩。
- 執(zhí)行完畢之后,如果原構(gòu)造函數(shù)沒有返回其他對(duì)象湾碎,則返回
this
宙攻。
此時(shí)的this
值就指向當(dāng)前的實(shí)例,本例中就是alice
介褥。
面試有可能會(huì)面的:實(shí)現(xiàn)一個(gè) new 命令座掘。
根據(jù)上面的四步,實(shí)現(xiàn)一個(gè) new 命令:
// constructor 是構(gòu)造函數(shù)柔滔,params是傳入構(gòu)造函數(shù)的參數(shù)
function _new (constructor, params) {
var args = [...arguments]; // 將傳入?yún)?shù)變成數(shù)組
var constructor = args.shift(); // 取出構(gòu)造函數(shù)
var context = Object.create(contructor.ptototype);
/* 以構(gòu)造函數(shù)的prototype為原型溢陪,創(chuàng)建一個(gè)prototpye對(duì)象,新建的對(duì)象具有所有構(gòu)造函數(shù)的prototype的屬性和方法睛廊。 */
var result = constructor.apply(context, args); //執(zhí)行構(gòu)造函數(shù)
return (typeof result === 'object' && result!==null) ? result : context;
// 如執(zhí)行結(jié)果非空且類型為對(duì)象形真,則返回該結(jié)果,否則超全,返回新創(chuàng)建的對(duì)象咆霜。
}
3. ES6 的 class
ES6 引入的 class 類的定義方法邓馒,從語法上更靠近了基于類的編程語言,實(shí)際上它的運(yùn)行機(jī)制仍是基于原型的裕便。
class Person {
constructor(name, email) {
this.name = name;
this.email = email;
}
// Methods
sayName () {
console.log(this.name);
};
sayEmail() {
console.log(this.email);
};
}
在 class
的語法中绒净,基本的變量數(shù)據(jù)放到 constructor
構(gòu)造數(shù)據(jù)中,而其他的成員函數(shù)偿衰,可以直接寫成函數(shù)的形式挂疆,不需要function
關(guān)鍵詞,調(diào)用的時(shí)候直接在前面加上this.
下翎。
class
還提供常用的getter
和 setter
可以使用缤言。因此,setter
视事,getter
配合methods
可以令function
+new
的奇怪組合消失了胆萧,讓上帝的歸上帝,凱撒的歸凱撒俐东,function
回歸到它定義函數(shù)的本意跌穗,而不是定義一個(gè)'類'(雖然實(shí)際上仍是原型構(gòu)造函數(shù))。
set setJob(offer) {
this.offer = offer;
}
get job() {
return this.upperName();
}
upperName() {
return this.offer.toUpperCase();
}
var alice = new Person('alice', 'alice@google.com');
alice.setJob = 'Front-end';
console.log(alice.job); // "FRONT-END"
而且class
的繼承也更像繼承了虏辫。一個(gè)子類可以繼承父類蚌吸,直接使用extends
關(guān)鍵字,就像在別的基于類的函數(shù)(Java, C++)一樣砌庄。
class Woman extends Person {
constructor(name, mail) {
super(name, mail);
this.name = name;
this.mail = mail;
this.organ = 'vaginal';
}
specialOrgan () {
console.log(this.name + ' has '+ this.organ);
}
}
var alice = new Woman('alice', 'alice@google.com');
alice.setJob = 'Front-end';
console.log(alice.job);
alice.specialOrgan();
// "FRONT-END" "alice has vaginal"
在繼承的子類Woman
中羹唠,在調(diào)用父類中的成員數(shù)據(jù)前,需要調(diào)用super
關(guān)鍵詞包括所有需要調(diào)用的數(shù)據(jù)娄昆,否則會(huì)出現(xiàn)引用錯(cuò)誤佩微。上面代碼可以看到,子類繼承了父類的setter
和getter
萌焰,而且子類有自己的方法specialOrgan()
哺眯,并且在子類的方法中成功地調(diào)用了父類的name
屬性。
5. 參考閱讀
- javascript-the-core扒俯,Dmitry Soshnikov族购。
- 實(shí)例對(duì)象與 new 命令,阮一峰陵珍。
- extends,MDN违施。