深刻淺出面向對象和原型【概念篇3】—— 原型鏈和繼承

1.由一個問題引起的思考

let arr1 = [1, 2, 3]
    let arr2 = [4, 5, 6]
    arr1.concat(arr2) // [1, 2, 3, 4, 5, 6]

concat這個方法是從哪兒蹦出來的??

首先咱們要清楚 數組也是對象,並且是Array對象的實例
    
    console.log(arr1 instanceof Array) // true

    也就是說,下面兩種形式是徹底等價的
    let arr1 = [1, 2, 3]
    let arr2 = new Array(1, 2, 3)

    只不過[1,2,3]是一種字面量的寫法

ok,在深刻淺出面向對象和原型【概念篇2】文章裏,咱們提到過segmentfault

類會有一個prototype屬性,而這個類的實例能夠經過__proto__屬性訪問類的prototype屬性

既然arr1是Array對象的實例,那麼arr1天然能夠經過其__proto__屬性訪問到其類Array的屬性
只要Array的prototype裏有concat()這個方法,那麼arr1天然能用數組

console.log(arr1.__proto__ === Array.prototype) // true

2.第二個問題

arr1.valueOf(),valueOf從哪兒來的?

按照第一個問題的解決思路,valueOf應該是Array的prototype屬性裏的
可是在Array的prototype屬性裏並無找到valueOf方法


可是咱們又看到了Array的prototype里居然也有__proto__屬性,而且指向Object,在這裏咱們找到了valueOf

90cP8H.md.png

clipboard.png

這是爲何呢?

如今咱們須要知道的是Array是Object的實例this

console.log(Array instanceof Object) // true
好了,如今咱們知道了
1.arr1 是 Array 的實例
2.Array 是 Object 的實例

當arr1找不到valueOf時,會經過其自身的__proto__屬性去找Array的prototype,看看裏面有沒有
若是Array的prototype裏沒有的話,接下來會經過Array的prototype裏的__proto__屬性去找Object的prototype,看看裏面有沒有

而反觀arr1尋找concat時,由於直接就在Array的prototype裏找到了,因此不會再去經過__proto__尋找Object的prototype裏有沒有
咱們把這個過程抽象化表達

當一個實例調用一個方法時spa

  1. 若是實例自己沒有這個方法,那麼這個實例會經過自身的__proto__屬性去訪問其類的prototype屬性
  2. 若是該實例的類的prototype屬性也沒有,那麼會經過該類prototype屬性裏的__proto__屬性去訪問該類的類的prototype屬性
  3. 若尚未找到,則以此類推,直到找到該方法或者父級類的prototype內的__proto__爲null時爲止

這個不斷經過__proto__屬性和prototype屬性尋找的過程,叫作原型鏈prototype

3.本身實現繼承

3.1 什麼是繼承

繼承是指一個對象直接使用另外一對象的屬性和方法

3.2 繼承的目的

繼承的目的其實就是省代碼,什麼意思呢?

假設你如今是js設計師,你已經設計了Object對象
如今你須要基於Object對象再設計一個Array對象
而且Array對象能夠直接使用Object對象的屬性和方法
這個時候你確定但願可以經過一種方式能直接讓Array對象使用Object對象的屬性和方法
而不是把Object對象的屬性和方法從新寫到Array裏去,這樣作太累了

而這種方式就叫繼承

3.3 實現繼承的要點

由繼承的定義,咱們可知,實現繼承必須知足兩個條件
1.獲得一個類的屬性
2.獲得一個類的方法

第一步:咱們先定義兩個類

// 第一個類
function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype = {
    printName: function () {
        console.log(this.name)
    }
}

// 第二個類
function Male(sex) {
    this.sex = sex
}

Male.prototype = {
    printSex: function () {
        console.log(this.sex)
    }
}

第二步:讓Male的每一個實例都獲取到People的屬性

function Male(sex, name, age) {
    // 注意Male的實例在這裏就是this
    // 因此咱們要讓this獲取到People的屬性便可
    // 實現方式——讓People的this被替換成Male的this,且讓People被直接調用
    People.bind(this)(name, age)
    this.sex = sex
}

console.log(new Male('man', 'bruce', '16')) // {name: "bruce", age: "16", sex: "man"}

第三步:讓Male的每一個實例都獲取到People的方法

Object.create()設計

// 例子
    let a = Object.create({'zxz': 1})
    console.log(a.__proto__) // {zxz: 1}
    // Object.create() 方法會返回一個對象,這個對象擁有被指定的原型
    // 且這個對象可經過自身的__proto__屬性訪問這個原型
Male.prototype = Object.create(People.prototype)
    // 這一步結束後,Male的實例就能夠經過原型鏈擁有People的方法

    // 接着,咱們才能對Male本身要新增的方法進行添加,不然會被覆蓋掉

    Male.prototype.printSex = function () {
        console.log(this.sex)
    }

    console.log((new Male('man', 'bruce', '16')).__proto__.__proto__ === People.prototype)

第四步:注意constructor屬性

咱們都知道constructor屬性指向其類
當咱們完成第三步後
須要注意的是

console.log(Male.prototype.constructor === People.prototype.constructor) // true

由於執行了Object.create()的緣由,此時Male原型上的constructor被指定成People了
如今須要咱們手動賦值將其更改

Male.prototype.constructor = Male

如今,咱們能夠把上述步驟統一塊兒來

function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype = {
    printName: function () {
        console.log(this.name)
    }
}

function Male(sex, name, age) {
    People.bind(this)(name, age)
    this.sex = sex
}

Male.prototype = Object.create(People.prototype)
Male.prototype.constructor = Male
Male.prototype.printSex = function () {
    console.log(this.sex)
}

3.4 把繼承獲取方法的關鍵步驟封裝起來

function inherit(fatherObject, childObject) {
    let _prototype = Object.create(fatherObject.prototype);
    _prototype.constructor = childObject;
    childObject.prototype = _prototype;
}

3.5 完整代碼

function inherit(fatherObject, childObject) {
    let _prototype = Object.create(fatherObject.prototype);
    _prototype.constructor = childObject;
    childObject.prototype = _prototype;
}

function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype = {
    printName: function () {
        console.log(this.name)
    }
}

function Male(sex, name, age) {
    People.bind(this)(name, age)
    this.sex = sex
}

inherit(People, Male)
Male.prototype.printSex = function () {
    console.log(this.sex)
}

let bruce = new Male('man', 'bruce', 16)

4.實現繼承的其它方法

function People(name, age) {
    this.name = name
    this.age = age
}

People.prototype.sayName = function () {
    console.log(this.name)
}

function Student(name, age, score) {
    People.call(this, name, age)
    this.score = score
}

function create(prototypeObj) {
    let empty = function () {}
    empty.prototype = prototypeObj
    return new empty()
    // return值以下
    // {
    //     __proto__:prototypeObj
    // }
}

Student.prototype = create(People.prototype)

Student.prototype.work = function () {
    console.log('work')
}

5.hasOwnProperty

當Male的實例繼承了Male的方法和People的屬性和方法後,如何分辨某個屬性或方法是本身的仍是經過原型鏈繼承來的呢?
這就要用到 hasOwnProperty
該方法能夠判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性
而且會忽略掉那些從原型鏈上繼承到的屬性

console.log(bruce.hasOwnProperty('age')) // true
console.log(bruce.hasOwnProperty('printSex')) // false

由於printSex方法是bruce實例經過原型鏈從Male類的原型上獲取的,所以會被hasOwnProperty忽略
相關文章
相關標籤/搜索