前言:構造函數箱叁、原型對象腰鬼、實例
每創(chuàng)建一個構造函數悯辙,該函數就會自動帶有一個prototype屬性甘凭,該屬性是個指針涯竟,指向了一個對象实幕,我們稱之為原型對象规惰。原型對象上默認有一個屬性constructor睬塌,該屬性也是一個指針,指向其相關聯(lián)的構造函數歇万。通過調用構造函數產生的實例揩晴,都有一個內部屬性,指向了原型對象贪磺。
所以三者的關系是:每個構造函數都有一個原型對象硫兰,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針寒锚。通俗點說就是劫映,實例可以通過內部指針訪問到原型對象呻粹,原型對象通過constructor指針,又可以找到構造函數苏研。
一等浊、js繼承之原型繼承
前面我們講到,構造函數產生的實例指向它的原型對象摹蘑,并且可以訪問原型對象上的所有屬性和方法筹燕。doggie實例指向了Dog的原型對象,可以訪問Dog原型對象上的所有屬性和方法衅鹿;如果Dog原型對象變成了某一個類的實例aaa撒踪,這個實例又會指向一個新的原型對象AAA,那么doggie此時就能訪問aaa的實例屬性和AAA原型對象上的所有屬性和方法了大渤。同理新的原型對象AAA又碰巧是另外一個對象的實例bbb制妄,這個實例bbb又會指向新的原型對象BBB,那么doggie此時就能訪問bbb的實例屬性和BBB原型對象上的所有屬性和方法了泵三。
這就是JS通過原型鏈實現繼承的方法了耕捞。看下面一個例子:
以上代碼將Dog的原型對象覆蓋成了animal實例烫幕。當doggie去訪問superSpeak屬性時俺抽,js會先在doggie的實例屬性中查找,發(fā)現找不到较曼,然后js就會去doggie的原型對象上查找磷斧,原型對象已經被覆蓋城了animal實例,那就是去animal實例上去找捷犹,先找animal的實例屬性弛饭,發(fā)現還是沒有,最后去animal的原型對象上去找萍歉,這才找到侣颂。這就說明:我們可以通過原型鏈的方式實現Dog繼承Animal的所有屬性和方法〈滂耄總結來說:就是當重寫了某一個構造函數的prototype指向的原型對象后横蜒,實例的內部指針也發(fā)生了改變胳蛮,指向了新的原型對象销凑,然后就能實現類與類之間的繼承了。
缺點:單純的原型鏈繼承最大的一個缺點仅炊,在于對原型中引用類型值的誤修改斗幼。
我們先看一個例子1:
以上實例實現了Student類對Person類的繼承。我們知道所有的Student實例都共享著原型對象上的屬性抚垄。那么如果我在stu1上改變了head的值蜕窿,是不是會影響原型對象上的head值呢谋逻。看上面的代碼就知道桐经,肯定是不會毁兆。
原因:當實例中存在和原型對象上同名的屬性時,會自動屏蔽原型對象上的同名屬性阴挣。stu1.head = "聰明的腦袋瓜子"?實際上只是給 stu1 添加了一個本地屬性 head 并設置了相關值气堕。所以當我們打印 stu1.head 時,訪問的是該實例的本地屬性畔咧,而不是其原型對象上的 head 屬性(它因和本地屬性名同名已經被屏蔽了)茎芭。
在看例子2:
從上面代碼可以看出,如果一個實例不小心修改了原型對象上引用類型的值誓沸,會導致其他實例也跟著受影響梅桩。
因此,我們得出結論拜隧,原型上任何類型的屬性值都不會通過實例被重寫宿百,但是引用類型的屬性值會受到實例的影響而改變。
二洪添、js繼承之構造函數繼承
在解決原型對象中包含引用類型值所帶來問題的過程中犀呼,開發(fā)人員開始使用一種叫做借用構造函數的技術。實現原理是薇组,在子類的構造函數中外臂,通過apply()、call()和bind()的形式律胀,調用父類構造函數宋光,以實現繼承。
在子類函數中炭菌,通過call()方法調用父類函數后罪佳,子類實例stu1可以訪問到Student構造函數和Person構造函數里的所有屬性和方法。這樣就實現了子類向父類的繼承黑低,而且還解決了原型對象上對引用類型值的誤修改操作赘艳。
缺點:這種形式的繼承,每個子類實例都會拷貝一份父類構造函數中的方法克握,作為實例自己的方法蕾管,這樣做的缺點:
1.每個實例都拷貝一份,占用內存大菩暗。
2.方法都作為了實例自己的方法掰曾,當需求改變,要改動其中一個方法時停团,之前所有的實例他們的該方法都不能及時做出更新旷坦。只有后面的實例才能訪問到新方法掏熬。
call/apply/bind的區(qū)別
obj.myFun.call(a, '成都', '上海') //將obj.myFun的this指向了a
obj.myFun.apply(a, ['成都', '上海'])
obj.myFun.bind(a, '成都', '上海')() //bind返回的是一個函數,必須調用才能執(zhí)行
>> 相同點
* 都代表將obj的this指向a作用域內,a不傳或者傳null,undefined時this指向window(非嚴格模式下)
>> 不同點
* call傳參方式是逗號隔開
* apply傳參是用戶數組形式將所有參數一起傳
* bind返回的是一個函數秒梅,必須調用才能執(zhí)行旗芬,傳參方式跟call一樣
三、結合使用兩種繼承模式
綜前兩點所述捆蜀,構造函數模式用于定義實例的屬性岗屏,而原型模式用于定義方法和共享的屬性