理解 javascript 的原型鏈及繼承
class Shape{}
class Circle extends Shape{}
const circle = new Circle();
console.log(circle.__proto__ === Circle.prototype);
console.log(Circle.prototype.__proto__ === Shape.prototype);
console.log(Shape.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);
console.log(Circle.__proto__ === Shape);
console.log(Shape.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);
console.log(circle.constructor === Circle)
以上所有的運(yùn)行結(jié)果都是 true
;
三種構(gòu)造對(duì)象的方法:
- 通過對(duì)象字面量構(gòu)造诫欠。
var person1 = {
name: 'cyl',
sex: 'male'
};
形如這個(gè)形式的叫做對(duì)象字面量翁垂。這樣子構(gòu)造出的對(duì)象匀油,其[[prototype]]指向Object.prototype
- 構(gòu)造函數(shù)構(gòu)造凑懂。
function Person(){}
var person1 = new Person();
通過new操作符調(diào)用的函數(shù)就是構(gòu)造函數(shù)。由構(gòu)造函數(shù)構(gòu)造的對(duì)象虽惭,其[[prototype]]指向其構(gòu)造函數(shù)的prototype屬性指向的對(duì)象橡类。每個(gè)函數(shù)都有一個(gè)prototype屬性,其所指向的對(duì)象帶有constructor屬性芽唇,這一屬性指向函數(shù)自身顾画。(在本例中,person1的[[prototype]]指向Person.prototype)
- 函數(shù)Object.create構(gòu)造的匆笤。
var person1 = {
name: 'cyl',
sex: 'male'
};
var person2 = Object.create(person1);
本例中研侣,對(duì)象person2的[[prototype]]指向?qū)ο髉erson1。在沒有Object.create函數(shù)的日子里疚膊,人們是這樣做的:
Object.create = function(p) {
function f(){}
f.prototype = p;
return new f();
}
Object 對(duì)象的屬性
屬性 | 描述 |
---|---|
constructor | 返回創(chuàng)建該對(duì)象的構(gòu)造函數(shù)义辕。 |
prototype | 返回創(chuàng)建該對(duì)象的函數(shù)的原型對(duì)象。 |
一寓盗、原型
__proto__
(隱式原型)
每個(gè)JS對(duì)象一定對(duì)應(yīng)一個(gè)原型對(duì)象灌砖,并從原型對(duì)象繼承屬性和方法。
對(duì)象proto屬性的值就是它所對(duì)應(yīng)的原型對(duì)象傀蚌,隱式原型指向創(chuàng)建這個(gè)對(duì)象的函數(shù)(constructor)的prototype基显。
JavaScript中任意對(duì)象都有一個(gè)內(nèi)置屬性[[prototype]],在ES5之前沒有標(biāo)準(zhǔn)的方法訪問這個(gè)內(nèi)置屬性善炫,但是大多數(shù)瀏覽器都支持通過proto來訪問撩幽。ES5中有了對(duì)于這個(gè)內(nèi)置屬性標(biāo)準(zhǔn)的Get方法Object.getPrototypeOf().
Object.prototype 這個(gè)對(duì)象是個(gè)例外,它的proto值為null
var one = {x: 1};
var two = new Object();
console.log(one.__proto__ === Object.prototype)//true
console.log(two.__proto__ === Object.prototype)//true
作用:
隱式原型的作用:構(gòu)成原型鏈箩艺,同樣用于實(shí)現(xiàn)基于原型的繼承窜醉。舉個(gè)例子,當(dāng)我們?cè)L問obj這個(gè)對(duì)象中的x屬性時(shí)艺谆,如果在obj中找不到榨惰,那么就會(huì)沿著proto依次查找。
prototype(顯式原型)
不像每個(gè)對(duì)象都有proto屬性來標(biāo)識(shí)自己所繼承的原型静汤,只有函數(shù)才有prototype屬性琅催。
JS不像其它面向?qū)ο蟮恼Z言,它沒有類(class虫给,ES6引進(jìn)了這個(gè)關(guān)鍵字藤抡,但更多是語法糖)的概念。JS通過函數(shù)來模擬類抹估。
當(dāng)你創(chuàng)建函數(shù)時(shí)缠黍,JS會(huì)為這個(gè)函數(shù)自動(dòng)添加prototype屬性,值是空對(duì)象药蜻。而一旦你把這個(gè)函數(shù)當(dāng)作構(gòu)造函數(shù)(constructor)調(diào)用(即通過new關(guān)鍵字調(diào)用)瓷式,那么JS就會(huì)幫你創(chuàng)建該構(gòu)造函數(shù)的實(shí)例,實(shí)例繼承構(gòu)造函數(shù)prototype的所有屬性和方法(實(shí)例通過設(shè)置自己的proto指向承構(gòu)造函數(shù)的prototype來實(shí)現(xiàn)這種繼承)谷暮。
JS是單繼承的蒿往,Object.prototype是原型鏈的頂端,所有對(duì)象從它繼承了包括toString等等方法和屬性湿弦。Object.prototype.proto === null瓤漏,說明原型鏈到Object.prototype終止。
作用:
顯式原型的作用:用來實(shí)現(xiàn)基于原型的繼承與屬性的共享颊埃。
Object本身是構(gòu)造函數(shù)蔬充,繼承了Function.prototype;Function也是對(duì)象,繼承了Object.prototype班利。
Object instanceof Function // true
Function instanceof Object // true
instanceof 運(yùn)算符用來測(cè)試一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的prototype 屬性饥漫。
ES規(guī)范是怎么說的?
Function本身就是函數(shù)罗标,F(xiàn)unction.proto是標(biāo)準(zhǔn)的內(nèi)置對(duì)象Function.prototype庸队。
Function.prototype.proto是標(biāo)準(zhǔn)的內(nèi)置對(duì)象Object.prototype积蜻。
例一
//函數(shù)聲明創(chuàng)一個(gè)空函數(shù)
function foo(){}
foo這個(gè)函數(shù)就有個(gè)prototype屬性,并且它默認(rèn)有兩個(gè)屬性:constructor和proto彻消。constructor屬性會(huì)指向它本身foo竿拆,proto是在chrome中暴露的(不是一個(gè)標(biāo)準(zhǔn)屬性),那么foo.prototype的原型會(huì)指向Object.prototype宾尚。因此Object.prototype上的一些方法toString丙笋,valueOf才會(huì)被每個(gè)一般的對(duì)象所使用。
例二
function foo(){}
foo.prototype.x = 1;
var obj3 = new foo();//實(shí)例對(duì)象
console.log(obj3.x); //1
我們這里有個(gè)foo函數(shù)煌贴,這個(gè)函數(shù)有個(gè)prototype的對(duì)象屬性御板,它的作用就是當(dāng)使用new foo()去構(gòu)造實(shí)例的時(shí)候,這個(gè)構(gòu)造器的prototype屬性會(huì)用作new出來的這些對(duì)象的原型牛郑。
在這個(gè)例子中怠肋,obj3.proto===foo.prototype,即,每個(gè)對(duì)象都有一個(gè)proto屬性井濒,指向創(chuàng)建該對(duì)象的函數(shù)的prototype灶似。Object.prototype是一個(gè)特例,它的proto指向的是null瑞你。
例三
// 聲明構(gòu)造函數(shù)
function Person(name, age) {
this.name = name;
this.age = age;
}
// 通過prototye屬性酪惭,將方法放到原型對(duì)象上
Person.prototype.getName = function() {
return this.name;
}
var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
構(gòu)造函數(shù)的prototype與所有實(shí)例對(duì)象的proto都指向原型對(duì)象。而原型對(duì)象的constructor指向構(gòu)造函數(shù)者甲。
二春感、原型鏈
每一個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,當(dāng)我們讓某一原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例虏缸,此時(shí)該原型對(duì)象就包含一個(gè)指針鲫懒,該指針指向這一構(gòu)造函數(shù)的原型對(duì)象,該指針指向的原型對(duì)象中包含一個(gè)指向這一構(gòu)造函數(shù)的指針刽辙,同樣我們可以令該指針指向的原型對(duì)象等于另一構(gòu)造函數(shù)的實(shí)例窥岩,如此遞進(jìn),則形成一條實(shí)例與原型的鏈條宰缤,即原型鏈颂翼。
function Parent() {
this.name = 'parent';
};
Parent.prototype.getName = function() {
return this.name;
};
function Child() {
this.childname = 'child';
}
Child.prototype = new Parent();
Child.prototype.getChildName = function() {
return this.childname;
};
var child = new Child();
console.log(child.getName()); //輸出parent
console.log(child.getChildName()); //輸出child
在上面的例子中,child實(shí)例指向Child原型慨灭,Child原型等于Parent實(shí)例朦乏,即指向Parent原型⊙踔瑁可見本質(zhì)即是以一個(gè)構(gòu)造函數(shù)的實(shí)例重寫原型對(duì)象呻疹,形成原型鏈。
總結(jié)
1筹陵、所有的對(duì)象都有 proto 屬性刽锤,該屬性對(duì)應(yīng)該對(duì)象的原型镊尺;
2、所有的函數(shù)對(duì)象都有 prototype 屬性姑蓝,該屬性的值會(huì)被賦值給該函數(shù)創(chuàng)建的對(duì)象的 proto屬性鹅心;
3吕粗、所有的原型對(duì)象都有constructor屬性纺荧,該屬性對(duì)應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù);
4颅筋、函數(shù)對(duì)象和原型對(duì)象通過prototype和constructor屬性進(jìn)行相互關(guān)聯(lián)宙暇。
extends
用關(guān)鍵詞extends聲明子類關(guān)系:
class Circle extends Shape {}
extends后面可以接駁任意合法且擁有prototype屬性的構(gòu)造函數(shù)。它可以是:
另一個(gè)類
源自現(xiàn)有繼承框架(譯者注:作者指的是原型繼承议泵,即使在JavaScript中類繼承的本質(zhì)也是原型繼承)的近類函數(shù)
一個(gè)普通的函數(shù)
一個(gè)包含一個(gè)函數(shù)或類的變量
一個(gè)對(duì)象上的屬性訪問
一個(gè)函數(shù)調(diào)用
圖示
- 在JS里占贫,萬物皆對(duì)象。方法(Function)是對(duì)象先口,方法的原型(Function.prototype)是對(duì)象型奥。因此,它們都會(huì)具有對(duì)象共有的特點(diǎn)碉京。即:對(duì)象具有屬性proto厢汹,可稱為隱式原型,一個(gè)對(duì)象的隱式原型指向構(gòu)造該對(duì)象的構(gòu)造函數(shù)的原型谐宙,這也保證了實(shí)例能夠訪問在構(gòu)造函數(shù)原型中定義的屬性和方法烫葬。
- 方法(Function)方法這個(gè)特殊的對(duì)象,除了和其他對(duì)象一樣有上述proto屬性之外凡蜻,還有自己特有的屬性——原型屬性(prototype)搭综,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象划栓,這個(gè)對(duì)象的用途就是包含所有實(shí)例共享的屬性和方法(我們把這個(gè)對(duì)象叫做原型對(duì)象)兑巾。原型對(duì)象也有一個(gè)屬性,叫做constructor忠荞,這個(gè)屬性包含了一個(gè)指針蒋歌,指回原構(gòu)造函數(shù)。
上圖解析
1.構(gòu)造函數(shù)Foo()構(gòu)造函數(shù)的原型屬性Foo.prototype指向了原型對(duì)象钻洒,在原型對(duì)象里有共有的方法奋姿,所有構(gòu)造函數(shù)聲明的實(shí)例(這里是f1,f2)都可以共享這個(gè)方法素标。
2.原型對(duì)象Foo.prototypeFoo.prototype保存著實(shí)例共享的方法称诗,有一個(gè)指針constructor指回構(gòu)造函數(shù)。
3.實(shí)例f1和f2是Foo這個(gè)對(duì)象的兩個(gè)實(shí)例头遭,這兩個(gè)對(duì)象也有屬性proto寓免,指向構(gòu)造函數(shù)的原型對(duì)象癣诱,這樣子就可以像上面1所說的訪問原型對(duì)象的所有方法啦。另外:構(gòu)造函數(shù)Foo()除了是方法袜香,也是對(duì)象啊撕予,它也有proto屬性,指向誰呢蜈首?指向它的構(gòu)造函數(shù)的原型對(duì)象唄实抡。函數(shù)的構(gòu)造函數(shù)不就是Function嘛,因此這里的proto指向了Function.prototype欢策。其實(shí)除了Foo()吆寨,F(xiàn)unction(), Object()也是一樣的道理。原型對(duì)象也是對(duì)象啊踩寇,它的proto屬性啄清,又指向誰呢?同理俺孙,指向它的構(gòu)造函數(shù)的原型對(duì)象唄辣卒。這里是Object.prototype.最后,Object.prototype的proto屬性指向null睛榄。
總結(jié):
- 對(duì)象有屬性proto,指向該對(duì)象的構(gòu)造函數(shù)的原型對(duì)象荣茫。
- 方法除了有屬性proto,還有屬性prototype,prototype指向該方法的原型對(duì)象懈费。