進(jìn)階路線
3 原型繼承
3.1 優(yōu)秀文章
- 最詳盡的 JS 原型與原型鏈終極詳解 一
- 最詳盡的 JS 原型與原型鏈終極詳解 二
- 最詳盡的 JS 原型與原型鏈終極詳解 三
- 【THE LAST TIME】一文吃透所有JS原型相關(guān)知識點
- 代碼復(fù)用模式
3.2知識點總結(jié)
以下討論都以Person構(gòu)造函數(shù)為例
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) }
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');
3.2.1 對象和函數(shù)
1典予、萬物皆對象蕾盯!但對象也是有區(qū)別的习瑰。分為普通對象和函數(shù)對象阀坏,Object 茵汰、Function 是 JS 自帶的函數(shù)對象奥此。怎么區(qū)分,其實很簡單朵逝,凡是通過 new Function() 創(chuàng)建的對象都是函數(shù)對象,其他的都是普通對象
2乡范、構(gòu)造函數(shù)的實例都有(constructor)屬性配名,該屬性指向構(gòu)造函數(shù)
person1.constructor == Person
3.2.2 原型對象--protoptye
在規(guī)范里啤咽,prototype 被定義為:給其它對象提供共享屬性的對象。
prototype 自己也是對象渠脉,只是被用以承擔(dān)某個職能罷了.
1宇整、每當(dāng)定義一個對象(函數(shù)也是對象)時候,對象中都會包含一些預(yù)定義的屬性芋膘。其中每個函數(shù)對象都有一個prototype 屬性没陡,這個屬性指向函數(shù)的原型對象∷魃停【只有函數(shù)對象才有 prototype 屬性】
2、原型對象(Person.prototype)是構(gòu)造函數(shù)的一個特殊實例贴彼,所以有一個constructor屬性指向構(gòu)造函數(shù)
Person.prototype.constructor == Person
person1.constructor == Person --對比細(xì)品
3潜腻、原型對象其實就是普通對象,但 Function.prototype 除外器仗,它是函數(shù)對象
function Person(){};
console.log(Person.prototype) //Person{}
console.log(typeof Person.prototype) //Object
console.log(typeof Function.prototype) // Function融涣,這個特殊
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined
3.2.3 [__ proto __] 指向構(gòu)造函數(shù)的原型對象
1、JS 在創(chuàng)建對象(不論是普通對象還是函數(shù)對象)的時候精钮,都有一個叫做proto 的內(nèi)置屬性威鹿,用于指向創(chuàng)建它的構(gòu)造函數(shù)的原型對象
person1.__proto__ == Person.prototype
根據(jù)上面的圖片可以得到以下結(jié)論
Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;
3.2.4 構(gòu)造器
1、構(gòu)造函數(shù)(Object)本身就是一個函數(shù)(就是上面說的函數(shù)對象)轨香,它和上面的構(gòu)造函數(shù) Person 差不多忽你,可以創(chuàng)建對象的構(gòu)造器不僅僅有 Object,也可以是 Array臂容,Date科雳,F(xiàn)unction等
ar obj = new Object()
obj.constructor === Object
obj.__proto__ === Object.prototype
var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;
var c = new Date();
c.constructor === Date;
c.__proto__ === Date.prototype;
var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;
這些構(gòu)造器都是函數(shù)對象
3.2.5 原型鏈
1、person1.__proto__ 是什么脓杉?
答:person1.__proto__ == Person.prototype
2糟秘、Person.__proto__ 是什么?
答:Person.__proto__ == Function.prototype
因為 Person.__proto__ === Person的構(gòu)造函數(shù).prototype
因為 Person的構(gòu)造函數(shù) === Function
所以 Person.__proto__ === Function.prototype
3球散、Person.prototype.__proto__ 是什么尿赚?
答:Person.prototype 是一個普通對象凌净,我們無需關(guān)注它有哪些屬性丑婿,只要記住它是一個普通對象约计。
因為一個普通對象的構(gòu)造函數(shù) === Object
所以 Person.prototype.__proto__ === Object.prototype
4细卧、Object.__proto__ 是什么蜘犁?
答:Object.__proto__ == Function.prototype(原因同第二題)
5导披、Object.prototype__proto__ 是什么
答:Object.prototype 對象也有proto屬性,但它比較特殊滓技,為 null 。因為 null 處于原型鏈的頂端纬朝,這個只能記住判没。
Object.prototype.__proto__ === null
3.2.6 函數(shù)對象
1嫉沽、所有函數(shù)對象的proto都指向Function.prototype,它是一個空函數(shù)(Empty function)
Number.__proto__ === Function.prototype // true
Number.constructor == Function //true
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
String.__proto__ === Function.prototype // true
String.constructor == Function //true
// 所有的構(gòu)造器都來自于Function.prototype俏竞,甚至包括根構(gòu)造器Object及Function自身
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
// 所有的構(gòu)造器都來自于Function.prototype绸硕,甚至包括根構(gòu)造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
Array.__proto__ === Function.prototype // true
Array.constructor == Function //true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true
Error.__proto__ === Function.prototype // true
Error.constructor == Function //true
Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
2、所有的構(gòu)造器都來自于 Function.prototype魂毁,甚至包括根構(gòu)造器Object及Function自身玻佩。所有構(gòu)造器都繼承了·Function.prototype·的屬性及方法。Function.prototype也是唯一一個typeof XXX.prototype為 function的prototype席楚。其它的構(gòu)造器的prototype都是一個對象
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object
3夺蛇、知道了所有構(gòu)造器(含內(nèi)置及自定義)的proto都是Function.prototype,那Function.prototype的proto是誰呢 Object.prototype的proto是誰酣胀?
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null // true
4、原型鏈的形成是真正是靠proto 而非prototype
1)Object.setPropertyOf娶聘,給我兩個對象闻镶,我把其中一個設(shè)置為另一個的原型。
2)Object.create丸升,給我一個對象铆农,它將作為我創(chuàng)建的新對象的原型。
3.2.7 問答
面試官:談?wù)勀銓?JS 原型和原型鏈的理解狡耻?
候選人:JS 原型是指為其它對象提供共享屬性訪問的對象墩剖。在創(chuàng)建對象時,每個對象都包含一個隱式引用指向它的原型對象或者 null夷狰。
原型也是對象岭皂,因此它也有自己的原型。這樣構(gòu)成一個原型鏈沼头。
面試官:原型鏈有什么作用爷绘?
候選人:在訪問一個對象的屬性時,實際上是在查詢原型鏈进倍。這個對象是原型鏈的第一個元素土至,先檢查它是否包含屬性名,如果包含則返回屬性值猾昆,否則檢查原型鏈上的第二個元素陶因,以此類推。
面試官:那如何實現(xiàn)原型繼承呢垂蜗?
候選人:有兩種方式楷扬。一種是通過 Object.create 或者 Object.setPrototypeOf 顯式繼承另一個對象解幽,將它設(shè)置為原型。
另一種是通過 constructor 構(gòu)造函數(shù)毅否,在使用 new 關(guān)鍵字實例化時亚铁,會自動繼承 constructor 的 prototype 對象,作為實例的原型螟加。
在 ES2015 中提供了 class 的風(fēng)格徘溢,背后跟 constructor 工作方式一樣,寫起來更內(nèi)聚一些捆探。
3.2.8 繼承
繼承的目的是代碼復(fù)用然爆,繼承只是代碼復(fù)用的一種手段或者實現(xiàn)方式
一個在構(gòu)造函數(shù)上常用的規(guī)則是,用于復(fù)用的成員(譯注:屬性和方法)應(yīng)該被添加到原型上黍图。
3.2.8.1 類式繼承1——默認(rèn)模式
子類構(gòu)造函數(shù)的原型對象指向父類構(gòu)造函數(shù)的實例實現(xiàn)繼承
// 定義繼承函數(shù)
function inherit(C, P) {
C.prototype = new P();
}
//parent構(gòu)造函數(shù)
function Parent(name) {
this.name = name || 'Adam';
}
//給原型增加方法
Parent.prototype.say = function () {
return this.name;
};
//空的child構(gòu)造函數(shù)
function Child(name) {}
//繼承
inherit(Child, Parent);
var child1 = new Child("hh");
alert(child1 .hasOwnProperty('name')); // false
缺點
- 由于子類通過其原型prototype對父類實例化曾雕,繼承了父類,所以說父類中如果共有屬性是引用類型助被,就會在子類中被所有的實例所共享剖张,因此一個子類的實例更改子類原型從父類構(gòu)造函數(shù)中繼承的共有屬性就會直接影響到其他的子類(概括為:子類的實例修改父類的屬性,會影響其他子類的實例的屬性)
- 由于子類實現(xiàn)的繼承是靠其原型prototype對父類進(jìn)行實例化實現(xiàn)的揩环,因此在創(chuàng)建父類的時候搔弄,是無法向父類傳遞參數(shù)的。因而在實例化父類的時候也無法對父類構(gòu)造函數(shù)內(nèi)的屬性進(jìn)行初始化(概括:無法向子類傳遞參數(shù))
3.2.8.2 構(gòu)造函數(shù)繼承
下面這種模式解決了從子對象傳遞參數(shù)到父對象的問題丰滑。它借用了父對象的構(gòu)造函數(shù)顾犹,將子對象綁定到this,同時傳入?yún)?shù).使用這種模式時褒墨,只能繼承在父對象的構(gòu)造函數(shù)中添加到this的屬性炫刷,不能繼承原型上的成員。使用借用構(gòu)造函數(shù)的模式郁妈,子對象通過復(fù)制的方式繼承父對象的成員浑玛,而不是像類式繼承1中那樣獲得引用。下面的例子展示了這兩者的不同:
function Child(a, c, b, d) {
Parent.apply(this, arguments);
}
//父構(gòu)造函數(shù)
function Article() {
this.tags = ['js', 'css'];
}
//StaticPage通過借用構(gòu)造函數(shù)的方式從Article繼承
function StaticPage() {
Article.call(this);
}
var page = new StaticPage();
alert(page.hasOwnProperty('tags')); // true
利用借用構(gòu)造函數(shù)模式實現(xiàn)多繼承
function Cat() {
this.legs = 4;
this.say = function () {
return "meaowww";
}
}
function Bird() {
this.wings = 2;
this.fly = true;
}
function CatWings() {
Cat.apply(this);
Bird.apply(this);
}
var jane = new CatWings();
console.dir(jane);
優(yōu)缺點
- 由于這種類型的繼承沒有涉及到原型prototype噩咪,所以父類的原型方法自然不會被子類繼承锄奢,違背了代碼復(fù)用的原則。
- 這種模式的一個好處是獲得了父對象自己成員的拷貝剧腻,不存在子對象意外改寫父對象屬性的風(fēng)險拘央。
3.2.8.3 組合式繼承
綜合以上兩種模式,首先借用父對象的構(gòu)造函數(shù)书在,然后將子對象的原型設(shè)置為父對象的一個新實例灰伟。這樣做的好處是子對象獲得了父對象自己的成員,也獲得了父對象中可復(fù)用的(在原型中實現(xiàn)的)方法。子對象也可以傳遞任何參數(shù)給父構(gòu)造函數(shù)栏账。這種行為可能是最接近Java的帖族,子對象繼承了父對象的所有東西,同時可以安全地修改自己的屬性而不用擔(dān)心修改到父對象
function Child(a, c, b, d) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
缺點
- 一個弊端是父構(gòu)造函數(shù)被調(diào)用了兩次挡爵,所以不是很高效竖般。最后,(父對象)自己的屬性(比如這個例子中的name)也被繼承了兩次茶鹃。
測試
//父構(gòu)造函數(shù)
function Parent(name) {
this.name = name || 'Adam';
}
//在原型上添加方法
Parent.prototype.say = function () {
return this.name;
};
//子構(gòu)造函數(shù)
function Child(name) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var kid = new Child("Patrick");
kid.name; // "Patrick"
kid.say(); // "Patrick"
delete kid.name;
kid.say(); // "Adam"
.3.2.8.4 原型式繼承(共享原型)
F()函數(shù)是一個空函數(shù)涣雕,它充當(dāng)了子對象和父對象的代理。F()的prototype屬性指向父對象的原型闭翩。子對象的原型是一這個空函數(shù)的一個實例
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
}
這種模式通常情況下都是一種很棒的選擇挣郭,因為原型本來就是存放復(fù)用成員的地方。在這種模式中疗韵,父構(gòu)造函數(shù)添加到this中的任何成員都不會被繼承兑障。
我們來創(chuàng)建一個子對象并且檢查一下它的行為:
var kid = new Child();
如果你訪問kid.name將得到undefined。在這個例子中蕉汪,name是父對象自己的屬性流译,而在繼承的過程中我們并沒有調(diào)用new Parent(),所以這個屬性并沒有被創(chuàng)建者疤。當(dāng)訪問kid.say()時福澡,它在3號對象中不可用,所以在原型鏈中查找宛渐,4號對象也沒有,但是1號對象有眯搭,它在內(nèi)在中的位置會被所有從Parent()創(chuàng)建的構(gòu)造函數(shù)和子對象所共享窥翩。
存儲父類(Superclass)
在上一種模式的基礎(chǔ)上,還可以添加一個指向原始父對象的引用鳞仙。這很像其它語言中訪問超類(superclass)的情況寇蚊,有時候很方便。
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
}
重置構(gòu)造函數(shù)引用
這個近乎完美的模式上還需要做的最后一件事情就是重置構(gòu)造函數(shù)(constructor)的指向,如果不重置構(gòu)造函數(shù)的指向棍好,那所有的子對象都會認(rèn)為Parent()是它們的構(gòu)造函數(shù)仗岸,而這個結(jié)果完全沒有用
// parent, child, inheritance
function Parent() {}
function Child() {}
inherit(Child, Parent);
// testing the waters
var kid = new Child();
kid.constructor.name; // "Parent"
kid.constructor === Parent; // true
原型繼承的最終實現(xiàn)
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
3.2.8.5 現(xiàn)代繼承之原型繼承
讓我們從一個叫作“原型繼承”的模式來討論沒有類的現(xiàn)代繼承模式。在這種模式中借笙,沒有任何類進(jìn)來扒怖,在這里,一個對象繼承自另外一個對象业稼。你可以這樣理解它:你有一個想復(fù)用的對象盗痒,然后你想創(chuàng)建第二個對象,并且獲得第一個對象的功能低散。下面是這種模式的用法
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
//需要繼承的對象
var parent = {
name: "Papa"
};
//新對象
var child = object(parent);
//測試
alert(child.name); // "Papa"
在ECMAScript 5中俯邓,原型繼承已經(jīng)正式成為語言的一部分骡楼。這種模式使用Object.create方法來實現(xiàn)。換句話說稽鞭,你不再需要自己去寫類似object()的函數(shù)鸟整,它是語言原生的了:
var child = Object.create(parent);