大家好萧福,我是林一一涨薪,今天這篇文章是關(guān)于 JS 中的繼承和模擬實(shí)現(xiàn) new 的,我盡量將文章講的通俗易懂袋哼,我們開始閱讀吧 ??
001 繼承
繼承指的是冀墨,子類繼承父類的方法。JS 中的繼承是基于原型和原型鏈實(shí)現(xiàn)的涛贯。對(duì)原型和原型鏈不熟悉的先看看 面試|你不得不懂得 JS 原型和原型鏈
- 繼承的目的:讓子類的實(shí)例也同樣具備父類的屬性和公共方法诽嘉。
思考1:實(shí)例 c1 具備哪些屬性和方法
function Parent(){
this.name = 'parent'
}
Parent.prototype.getParentName = function() {
console.log('Parent')
}
function Child(){
this.name = '一一'
var name = '二二'
}
Child.prototype.getChildName = function() {
console.log('Child')
}
var c1 = new Child
dir(c1)
實(shí)例 c1 具備
name="林一一"
,和原型鏈上的getChildName
(這里忽略Object
上的屬性方法)弟翘。對(duì)這里有疑問的可以看看 面試|你不得不懂得 JS 原型和原型鏈虫腋。如果 c1 想獲取 Parent 中的屬性和方法該怎么獲取稀余?
最簡(jiǎn)單的原型繼承
子類的原型等于父類的實(shí)例即可實(shí)現(xiàn)悦冀。原因通過原型鏈的向上查找機(jī)制,子類可以獲取父類的方法和屬性睛琳。
// 一句話一句代碼即可
Child.prototype = new Parent
prototype
原型繼承中父類的私有屬性和公共屬性都會(huì)變成子類的公共方法盒蟆。原型繼承是指向查找的過程不是拷貝實(shí)現(xiàn)的。需要注意的是掸掏,繼承的父類實(shí)例是堆內(nèi)存地址是唯一的茁影,堆內(nèi)存中的某一個(gè)屬性值改變后,子類的實(shí)例繼承到的就是改變后的屬性丧凤。
- 缺陷:原型繼承是把父類的私有屬性和共有屬性都定義成了子類原型上的共有屬性募闲,如果想要父類的私有屬性成為子類的私有屬性,原型繼承是不能實(shí)現(xiàn)的愿待。
call 繼承
使用 call 繼承解決私有屬性私有化之前要明白浩螺,構(gòu)造函數(shù)是怎樣創(chuàng)建私有屬性的,構(gòu)造函數(shù)中通過 this 指向才可以給實(shí)例創(chuàng)建私有屬性仍侥,那么使用 call 就可以改變父類中 this 的指向
function Child(){
Parent.call(this)
this.name = '一一'
var name = '二二'
}
上面 Parent 中的 this 就會(huì)被寫入到子類中要出,實(shí)例化子類時(shí)就可以創(chuàng)建私有的屬性。
- 缺陷:call 繼承只能繼承父類的私有屬性不能繼承父類的共有屬性农渊。call 繼承相當(dāng)于拷貝了一份父類的私有屬性患蹂。
組合繼承1(call繼承+子類原型鏈proto指向)
上面提到過 call 繼承只能實(shí)現(xiàn)子類繼承父類的私有屬性,那么我們可以只獲取父類的共有屬性賦予給子類的原型即可。即
Child.prototype.__proto__ = Parent.prototype
function Parent(){
this.name = 'parent'
}
Parent.prototype.getParentName = function() {
console.log('Parent')
}
function Child(){
this.name = '一一'
var name = '二二'
Parent.call(this)
}
Child.prototype.__proto__ = Parent.prototype
Child.prototype.getChildName = function() {
console.log('Child')
}
var c1 = new Child()
dir(c1)
- 缺陷:proto并不是所有瀏覽器都提供的传于,IE第版本就不支持
組合繼承2(call繼承 + Object.create()) 推薦使用
- 先介紹一下 Object.create(obj)囱挑,這個(gè)方法可以創(chuàng)建一個(gè)空對(duì)象,且這個(gè)空對(duì)象的原型鏈proto可以指向 obj沼溜,即換句話說使用 Object.create() 可以拷貝一份對(duì)象的屬性平挑,所以這個(gè)方法也可以作為淺拷貝的一種。
let obj = {
name = '林一一'
}
let a = Object.create(obj)
console.log(a.__proto__)
- 函數(shù)的 prototype 屬性也是一個(gè)對(duì)象系草,同樣使用 Object.create() 也可以拷貝父類原型的共有屬性和方法通熄。這句話相當(dāng)于
Child.prototype = Object.create(Parent.prototype)
function Parent() {
this.name = 'parent'
}
Parent.prototype.getParentName = function() {
console.log('Parent')
}
function Child() {
this.name = '一一'
Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
// 子類的 constructor 被覆蓋,可以重新加上
Child.prototype.constructor = Child
Child.prototype.getChildName = function() {
console.log('Child')
}
class 中的 extend
ES6 中的 class 實(shí)現(xiàn)其實(shí)是基于 JS 中的原型和原型鏈的找都。
class Parent{
constructor(){
this.name = 'parent'
}
// 等價(jià)于 Parent.prototype.getName = function(){...}
getParentName() {
console.log(this.name)
}
}
class Child extend Parent{
constructor(){
super()
this.age = 18
}
getChildName() {
console.log(this.name)
}
}
總結(jié)
- 原型繼承是 JS 繼承中最簡(jiǎn)單的實(shí)現(xiàn)方式唇辨,但是不能區(qū)分私有屬性和共有屬性
- 組合繼承中,使用 call 繼承+改變子類 proto 指向的繼承是最合適的方式檐嚣。缺點(diǎn)是 IE 不支持
__proto__
- 組合繼承使用 call 繼承和 Object.create() 可以淺拷貝一份父類原型上的方法助泽。
002 new 構(gòu)造函數(shù)
new 構(gòu)造函數(shù)執(zhí)行相當(dāng)于普通函數(shù)執(zhí)行。
function Person() {
this.name = '林一一'
}
new Person()
new Person() 過程中發(fā)生了什么
- new 為構(gòu)造函數(shù)創(chuàng)建了一個(gè)堆內(nèi)存也就是實(shí)例對(duì)象
- 執(zhí)行構(gòu)造函數(shù)嚎京,將構(gòu)造函數(shù)的 this 指向這個(gè)堆內(nèi)存地址(實(shí)例對(duì)象)
- 將創(chuàng)建好的實(shí)例對(duì)象返回
需要注意的是嗡贺,在構(gòu)造函數(shù)中使用 return 沒有意義。return 一個(gè)基本類型不會(huì)阻礙實(shí)例的返回鞍帝,但是 return 一個(gè) object 會(huì)覆蓋返回的實(shí)例诫睬。更詳細(xì)的內(nèi)容請(qǐng)看 面試| JS 原型和原型鏈
(阿里)面試題,實(shí)現(xiàn)一個(gè) _new()帕涌,得到預(yù)期的結(jié)果
function Dog(name) {
this.name = name
}
Dog.prototype.bark = function() {
console.log('wang wang')
}
Dog.prototype.sayName = function() {
console.log('my name is ' + this.name)
}
function _new() {
// code
}
let sanmao = _new(Dog, '三毛')
sanmao.bark(); // => 'wang wang'
sanmao.sayName(); // => 'my name is 三毛'
console.log(sanmao instanceof Dog) // true
分析:分析這道題其實(shí)就是實(shí)現(xiàn) new 的過程摄凡。按照上面 new 構(gòu)造函數(shù)中發(fā)生的過程可以實(shí)現(xiàn)如下
function _new(ctor, ...params) {
// 創(chuàng)建一個(gè)堆內(nèi)存地址,繼承原型上的共有屬性
let obj = {}
obj.__proto__ = ctor.prototype
// 確定 this 指向堆內(nèi)存地址蚓曼,同時(shí)使用 call 將構(gòu)造函數(shù)的私有屬性指向到 obj 實(shí)例中亲澡,實(shí)現(xiàn)私有屬性繼承
let res = ctor.call(obj, ...params)
// 返回創(chuàng)建的實(shí)例,考慮到構(gòu)造函數(shù)本身執(zhí)行后返回值是對(duì)象的話會(huì)覆蓋返回的實(shí)例纫版,需要先判斷
if(res !== null && typeof res === 'object') return res
return obj
}
執(zhí)行結(jié)果輸出無誤床绪。上面的模擬實(shí)現(xiàn) new 過程中使用了組合繼承
call+原型繼承
結(jié)束
更多的面試系列的文章
面試 |call, apply, bind的模擬實(shí)現(xiàn)和經(jīng)典面試題
面試 | JS 閉包經(jīng)典使用場(chǎng)景和含閉包必刷題
面試 | JS 事件循環(huán) event loop 經(jīng)典面試題含答案
......
感謝閱讀到這里,如果文章能對(duì)你有幫助或啟示歡迎 star 我是林一一其弊,下次見癞己。