腦子:我感覺我會了
手:你會個雞
方便查看风范,先上全圖
一、原型鏈繼承
//父類
function a(name){
this.name = name
this.colors = ['red', 'green']
}
a.prototype.say = function(){
console.log(`姓名:${this.name}`)
}
//子類
function b(age){
this.age = age
}
//繼承父類
//父類的參數(shù)只能在此處傳遞沪么,而不能在子類實例化時傳遞
b.prototype = new a('張三')
//添加方法硼婿,不會影響到父類
//但是添加方法必須在new a之后,如果在之前添加會被覆蓋
b.prototype.speak = function(){
console.log(`姓名:${this.name},年齡:${this.age}`)
}
let obj1 = new b(18)
obj1.say() //姓名:張三
obj1.speak() //姓名:張三,年齡:18
obj1.colors.push('yellow')
let obj2 = new b(24)
console.log(obj2.colors) //[ 'red', 'green', 'yellow' ]
優(yōu)點:
1.既簡單又方便禽车。
2.在子類原型上添加方法不會影響到父類寇漫。
3.子類實例可以使用instanceof
和isPrototypeOf()
識別自己的父類
console.log(a.prototype.isPrototypeOf(obj1)) //true
console.log(obj1 instanceof a) //true
缺點:
1.必須在new a之后刊殉,如果在之前添加會被覆蓋(問題不大)
2.無法實現(xiàn)多繼承,b.prototype只能指向一個對象(用得不多)
3.子類的原型上如果有引用類型數(shù)據(jù)州胳,如{}或[]记焊,
那么子類在操作這些值時會影響到原型(一般只會在原型上定義方法)
4.無法傳遞參數(shù)(很多時候會需要在子類實例化時向父類的構(gòu)造函數(shù)中傳遞參數(shù),而不是在b.prototype = new a('張三')時)
二栓撞、借用構(gòu)造函數(shù)繼承
//父類a1
function a1(name){
this.name = name
this.colors = ['red', 'green']
}
//父類a2
function a2(age){
this.age = age
}
a1.prototype.a1_say = function(){
console.log(`姓名:${this.name}`)
}
a2.prototype.a2_say = function(){
console.log(`年齡:${this.age}`)
}
//子類
function b(name, age){
//可以實現(xiàn)多繼承了
a1.call(this, name)
a2.apply(this, [age])
}
b.prototype.speak = function(){
console.log(`姓名:${this.name},年齡:${this.age}`)
}
let obj1 = new b('張三', 18)
obj1.speak() // 姓名:張三,年齡:18
//無法繼承父類的原型方法
//obj1.a1_say() 報錯遍膜,沒有這個方法
//obj1.a2_say() 報錯,沒有這個方法
obj1.colors.push('yellow')
let obj2 = new b('李四', 24)
console.log(obj2.colors) //[ 'red', 'green' ],沒影響
優(yōu)點:
1.可以實現(xiàn)多繼承瓤湘。
2.子類在實例化父類時瓢颅,得到的都是父類獨立的屬性,多個子類互不影響弛说。
3.可以傳遞參數(shù)挽懦。
缺點:
1.看著不像是繼承,像是投機取巧木人,因為子類實例
不可以使用instanceof
和isPrototypeOf()
識別自己的父類信柿。
2.父類的原型方法利用不到
3.構(gòu)造函數(shù)
本身的缺陷造成的副作用-無法復(fù)用
這個無法復(fù)用不要迷糊,比如這一條:
//obj1.a1_say() 報錯虎囚,沒有這個方法
如果想要讓obj1擁有a1_say()角塑,那么就需要改寫父類a1
//改寫后父類a1
function a1(name){
this.name = name
this.colors = ['red', 'green']
this.a1_say = function(){
console.log(`姓名:${this.name}`)
}
}
這么一來婆翔,子類b的實例obj1就能擁有a1_say(),但是這個方法是獨立的结胀,
如果b在new幾個實例obj2,obj3,....都會擁有獨立的a1_say()方法倦逐,浪費內(nèi)存
所以3說的無法復(fù)用指的是這葡盗。
三挡毅、組合繼承 (也叫偽經(jīng)典繼承鹰贵,=原型鏈繼承 + 借用構(gòu)造函數(shù)繼承)
//父類a1
function a1(name){
this.name = name
}
//父類a2
function a2(age){
this.age = age
}
a1.prototype.a1_say = function(){
console.log(`姓名:${this.name}`)
}
a2.prototype.a2_say = function(){
console.log(`年齡:${this.age}`)
}
//子類
function b(name, age){
//可以實現(xiàn)多繼承
a1.call(this, name)
a2.apply(this, [age])
}
//但是依然只能指向一個父類
b.prototype = new a1()
//手動重定向constructor寓辱,
//并設(shè)置成不可枚舉不可刪除不可改變
Object.defineProperty(b.prototype, 'constructor', {
value: b
})
b.prototype.speak = function(){
console.log(`姓名:${this.name},年齡:${this.age}`)
}
let obj = new b('張三', 18)
obj.a1_say() // 姓名:張三
obj.speak() // 姓名:張三,年齡:18
//obj.a2_say() // 報錯腋粥,沒有這個方法
優(yōu)點:
他集合了原型鏈繼承
和借用構(gòu)造函數(shù)繼承
的全部優(yōu)點蝗岖。
缺點:
1.在實現(xiàn)多繼承時侥猩,子類的原型依然只能指向一個父類的實例,也就意味著子類只能繼承一個父類的原型抵赢。
2.整個過程中欺劳,父類a1的構(gòu)造函數(shù)執(zhí)行了2次,一次是在 a1.call(this, name)铅鲤,一次是在b.prototype = new a1()划提,浪費執(zhí)行時間
3.由于條件2的作用,子類b的實例和原型都存在獨立的父類a1的構(gòu)造函數(shù)屬性name邢享,只不過原型上的屬性值為undifined鹏往,由于屬性訪問的同名屏蔽規(guī)則
,你永遠也訪問不到實例的原型上的屬性name骇塘,但這確實是資源的一種浪費伊履。
四韩容、原型式繼承(注意,不是原型鏈)
//如果我們想創(chuàng)建一個繼承自普通對象的對象時唐瀑,需要以下方法
let a = {
say: function(){
console.log(`姓名:${this.name},年齡:${this.age}`)
}
}
function getObject(o) {
function F() { }
F.prototype = o;
return new F();
}
let b = getObject(a)
//等價于(需要支持ES5)
//let b = Object.create(a)
//也等價于(需要支持ES5)
//let b = {}
//b.__proto__ = a
優(yōu)點:
1.不用興師動眾的創(chuàng)建一個構(gòu)造函數(shù)群凶,寫起來更簡潔,也更易讀介褥。
2.可以使用isPrototypeOf()
識別自己的父類
console.log(a.isPrototypeOf(b)) // true
缺點:
1.看著也不像是繼承座掘,像是工廠模式創(chuàng)建對象递惋。
2.不可以使用instanceof
識別自己的父類柔滔,以為它不是一個函數(shù)
3.不能設(shè)置constructor
,因為指向誰都說不通萍虽。
4.看如下代碼情況
let a = {
say: function () {
console.log(`姓名:${this.name},年齡:${this.age}`)
}
}
let b = {
name: '張三',
age: 18
}
//如果b想繼承a睛廊,那么b就不能直接通過getObject(a)繼承,
//而是需要額外的墊片杉编,
b = Object.assign(getObject(a), b) //(需要支持ES5)
//或者使用for...in
let c = getObject(a)
for(let key in b){
c[key] = b[key]
}
b = c
五超全、寄生式繼承(原型式繼承升級版)
function createAnother(original) {
var clone = getObject(original); //通過調(diào)用函數(shù)創(chuàng)建一個新對象
clone.sayHi = function () { //以某種方式來增強這個對象
alert("hi");
};
return clone; //返回這個對象
}
實際上這個
寄生式繼承
就是在原型式繼承
的基礎(chǔ)上增加了自定義屬性的過程,貌似解決了原型式繼承
的缺點4邓馒,但是不靈活嘶朱,很死板,使用場景會更局限
六光酣、寄生組合式繼承(經(jīng)典繼承疏遏,=組合繼承 + 寄生式繼承 )
//原型式繼承
function getObject(o) {
function F() { }
F.prototype = o;
return new F();
}
//寄生式繼承
function createAnother(b, a){
var prototype = getObject(a.prototype); //創(chuàng)建對象
Object.defineProperty(prototype, 'constructor', {
value: b
})//增強對象
//原型鏈繼承
b.prototype = prototype; //指定對象
}
//父類
function a(name){
this.name = name
}
a.prototype.say = function(){
console.log(`姓名:${this.name}`)
}
//子類
function b(name, age){
//借用構(gòu)造函數(shù)繼承
a.call(this, name)
this.age = age
}
createAnother(b, a)
b.prototype.speak = function(){
console.log(`姓名:${this.name},年齡:${this.age}`)
}
let obj = new b('張三', 18)
obj.say() // 姓名:張三
obj.speak() // 姓名:張三,年齡:18
console.log(obj)
優(yōu)點:
可以把此圖和組合繼承做一下對比,基本上就是利用寄生式繼承
彌補了組合繼承
的不足救军。
缺點:
在實現(xiàn)多繼承時财异,子類的原型依然只能指向一個父類的實例,也就意味著子類只能繼承一個父類的原型唱遭。
但是多繼承用的很少戳寸,就當它是完美繼承就好了,男人何苦難為男人拷泽。
寄生組合式繼承是最終解決辦法疫鹊,所以叫經(jīng)典繼承或者完美繼承應(yīng)該不過分。
------------------------------- 分割線 -------------------------------
*其他
再談constructor
先認識兩個東西
1. 閉環(huán)
在設(shè)計產(chǎn)品原型的時候司致,多數(shù)人都會考慮一個“閉環(huán)”的概念拆吆,閉環(huán)是啥,就是畫一個圓蚌吸。
畫圓時锈拨,無論你的初始點在哪里,終點始終與初始點重合羹唠。
比如奕枢,你要登錄某個網(wǎng)站娄昆,發(fā)現(xiàn)密碼忘了,找回密碼就會在登錄框附近缝彬,你使用了找回密碼操作萌焰,那么操作成功后提示操作成功,自動跳轉(zhuǎn)登錄頁面谷浅,這就是一個小閉環(huán)扒俯。
在用戶操作完某一個流程時,都會有一個按鈕引導(dǎo)你去跳轉(zhuǎn)到另一個頁面一疯,這個頁面很大概率就是此功能初始的那個頁面撼玄。
這個閉環(huán)流程是必須的嗎?當然不是墩邀,只不過是提升體驗或者完善體系掌猛。
2. js既是一門語言,也是一款產(chǎn)品
我們前端根據(jù)產(chǎn)品的原型眉睹,使用js去生產(chǎn)一個web網(wǎng)頁荔茬,使用我們網(wǎng)頁的人我們稱之為用戶。
js這門語言本身也是一款產(chǎn)品竹海,他的生產(chǎn)者就是js的作者慕蔚,那么使用js的人(前端開發(fā))被js的作者稱為用戶。
站在生產(chǎn)者和用戶的角度沒有區(qū)別孔飒。
登錄報錯是 web生產(chǎn)者對于web用戶的錯誤提示。
控制臺報錯是js語言生產(chǎn)者對于js語言用戶的錯誤提示许起。
說回constructor
十偶,這個屬性就是js作者針對js語言做的一個小的邏輯閉環(huán)。
這個閉環(huán)幫助js語言用戶园细,可以根據(jù)實例化對象的constructor
追溯到生產(chǎn)此實例的構(gòu)造函數(shù)【雖然追溯方法還有instanceof
和 isPrototypeOf()
惦积,但constructor
可以使我們在控制臺可以直觀感受】,構(gòu)造函數(shù)可以實例化對象猛频,對象還是擁有constructor
狮崩,如此往復(fù),實現(xiàn)一個完善的體系鹿寻。
constructor這個閉環(huán)是必須的嗎睦柴?當然不是,如果是必須的也就不會有那么多人懷疑它存在的意義了毡熏。