1. 基本概念:
“原型屬性”也可以叫做“原型”(prototype):所有函數(shù)都有prototype,我覺得可以理解為python中的類屬性,不需要通過實例,直接用類(es5就是函數(shù)名)可以調(diào)用,下面列舉了三種創(chuàng)建函數(shù)的方法,函數(shù)創(chuàng)建后都有prototype屬性,prototype指向“原型對象”。
// 函數(shù)聲明
function F1() {
};
// 表達(dá)式定義
let F2 = function () {
};
// 函數(shù)構(gòu)造
let F3 = new Function('n1', 'n2', 'return n1+n2');
console.info(F1.prototype)? ? //F1 {}
console.info(F2.prototype)? ? //F2 {}
console.info(F3.prototype)? ? //anonymous {}
原型對象(prototype所指向的對象):這玩意主要就是用來繼承用的,包含實例的方法和屬性。說白了也就是一個對象,用來定義函數(shù)對象的屬性、方法,默認(rèn)情況下它包含一個constructor屬性,如果你重新定義可以覆蓋constructor屬性。
原型對象與構(gòu)造函數(shù)配合一起,就形成一個類了,然后構(gòu)造函數(shù)接收每次初始化對象的初始值,原型對象就提供類模板。而在其他java、python中都寫在class中,當(dāng)然es6也加入了class;以下的簡單代碼幫助理解上面說的,定義一個動物類,根據(jù)構(gòu)造函數(shù)創(chuàng)造不同的動物,如果需要創(chuàng)建特殊動物,可以繼承然后添加一些特殊屬性、方法再創(chuàng)建。---------總結(jié)es5的類可以用“構(gòu)造函數(shù)+構(gòu)造函數(shù)的prototype”來定義,類的對象使用“new構(gòu)造函數(shù)”來生成。
// 構(gòu)造函數(shù)
let Animal = function (name) {
? ? this.name = name
}
// 原型對象定義
Animal.prototype.getAnimal = function () {
? ? return this.name
}
// 創(chuàng)建對象,會繼承Animal.prototype
let dog = new Animal('dog')
let cat = new Animal('cat')
console.info(dog.getAnimal())
console.info(cat.getAnimal())
上圖展示了構(gòu)造函數(shù)、原型對象、具體對象的屬性、方法及屬性值。
普通對象與函數(shù)對象:通過函數(shù)對象 new一下可以得到普通對象,把函數(shù)對象理解為類,普通對象為實例。如下代碼加強理解,可以看到函數(shù)對象new完以后生產(chǎn)的對象是object;函數(shù)對象中都有prototype前面已經(jīng)說過了(為什么函數(shù)對象中會有這個屬性以后讀取更深以后再解答)
// Animal函數(shù)對象
let Animal = function (name) {
? ? this.name = name
}
// 原型對象
Animal.prototype.getAnimal = function () {
? ? return this.name
}
// 通過函數(shù)對象創(chuàng)建普通對象
let dog = new Animal('dog')
console.info(typeof dog)? ? //object
總結(jié): 函數(shù)對象通過new Function()可以得到,F(xiàn)unction對象可以構(gòu)造函數(shù)對象。而函數(shù)對象又可以new一個普通對象出來。
__proto__:所有對象都有__proto__這個屬性,這個屬性指向?qū)?yīng)“函數(shù)對象(就理解為類)”的prototype,這也是實現(xiàn)原型鏈的根本,在書中一般都用[[prototype]]。
構(gòu)造函數(shù)和constructor屬性:其實前面的圖已經(jīng)標(biāo)注了,概念后續(xù)補上。
原型鏈:ES中的繼承主要用原型鏈來實現(xiàn),記住了這玩意主要用來實現(xiàn)繼承。其基本思想是:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法(js高級程序設(shè)計說的),還是用以上的例子來說明
let Animal = function (name) {
? ? this.name = name
}
// 原型對象定義
Animal.prototype.getAnimal = function () {
? ? return this.name
}
// 創(chuàng)建對象,會繼承Animal.prototype
let dog = new Animal('dog')
let cat = new Animal('cat')
console.info(dog.valueOf())
console.info(cat.valueOf())
跟上面例子就差了最后console打印部分,我們定義的Animal沒有寫valueof()為什么可以調(diào)用這個方法呢?到底是什么鬼?這就是原型鏈搞的鬼。我再把上面的圖形完善一下
上圖還有兩個屬性的指向沒有畫,第一、Animal的__proto__指向;第二、Animal.prototype的__proto__指向。
這個圖把Animal.prototype的__proto__指向添加了,
圖形說明:
1. Animal.prototype是個普通對象,ES中Object是所有對象的基礎(chǔ)。所有引用類型都繼承了Object;所有函數(shù)的默認(rèn)原型是都Object的實例,因此Animal.prototype的__proto__指向Object.prototype,這個一定要理解。
2. 我們調(diào)用cat.valueOf()解釋器會先找dog實例中有沒有這個方法,沒有就在Animal.prototype中查找,沒有再去Object.prototype中查找。然后就有了cat.valueOf()的運行結(jié)果:Animal { name: 'cat' }。
3. Object構(gòu)造函數(shù)的prototype也指向Object.prototype。
最后把Animal的__proto__指向補完基本就差不多了。