深刻理解Js中的繼承

clipboard.png

1.引言

把以前的文章重寫了一遍,以爲真的把繼承講的很透徹了,引個流,你們能夠看看。vue

明確一點:JavaScript並非真正的面嚮對象語言,沒有真正的類,因此咱們也沒有類繼承es6

實現繼承==有且僅有兩種方式,call和原型鏈==編程

在介紹繼承前咱們先介紹下其餘概念數組

2.函數的三種角色

一個函數,有三種角色。
當成普通函數,當成構造函數(類),當成對象babel

function Person (nickname) {
        var age = 15 //當普通函數使 私有屬性
        this.age = 30    //當構造函數使 實例屬性
    }
    Person.prototype.age = 50 //當構造函數使   原型屬性
    Person.age =100  //當對象使 靜態屬性(類屬性)

舉例:
Array.isArray是類上的方法
Array.push是Array原型上的方法
Array.toString是沿着原型鏈查找到的object類上的原型方法app

3.繼承的方式

繼承原則:
使用call繼承實例上的屬性
使用原型鏈繼承原型上的屬性mvvm

3.1 組合繼承

const Person = function (name) {
        this.name = name
    }
Person.prototype.introduce = function(){
  Object.entries(this).forEach((item)=>{
        console.log(`my ${item[0]} is ${item[1]}`)
    })
    
}

const Student = function (name,age) {
        Person.call(this,name)
        this.age = age
    }
Student.prototype = new Person()      //這裏new了父類一次,增長了額外開銷
Student.prototype.constructor =  Student         //這一句可讓student.constructor.name由Person變爲Student 方便確認構造函數

let student = new Student('小明',15)
student.introduce()  繼承父類原型方法的同時繼承父類實例上的屬性
//my name is 小明
//my age is 15

組合繼承有一個缺點,會額外new父類一次,增長了額外開銷(想想若是父類特別大這消耗會有多大)函數

3.2 Student.prototype = new Person() 作了什麼

咱們仔細研究一下這一句話,爲何它就能實現原型鏈繼承優化

在上一篇文章中咱們學過,實例能訪問類上的原型this

若是子類實例能訪問父類的原型,那麼咱們是否是能夠說子類繼承了父類?

可是子類實例只能訪問子類原型呀,因此可我可讓子類的原型等於父類的實例,由於父類的實例能夠訪問父類的原型,這就至關於子類實例能夠訪問父類原型了

這裏你可能會問,爲何不直接這麼寫student.prototype = Person.prototype,這樣子實例也能夠訪問父實例呀

**沒錯!單從訪問上來講,你是對的。可是若是我後面先重寫子類的原型,
好比我想寫student.prototype = null,由於如今子類父類原型共用同一地址,父類也被改了,這個不符合咱們的初衷 **

3.3 優化原理

還記得3.1說的原型鏈繼承有個地方能夠優化嗎?在咱們知道了3.2原型鏈繼承的寫法後,咱們產生這樣一個疑問,

  1. 真的要new一個對象,咱麼實際只須要_Proto_來創建關係,能不能不復制父類的各類屬性?
  2. 必定要new一個對象嗎?實例和類原型的關係是經過_proto_來創建的,我手動設置這玩意,不new行不行?

這也是優化的兩個方向

3.4 優化1

自動生成_proto_,改的是類的原型的指向
先說第二種優化,使用new自動生成_proto_,可是確定不能直接new父類吧,咱們new出一個空對象,而後改變這個類的原型指向咱們須要繼承的

好比咱們須要繼承obj
也就是能訪問obj
實例能訪問類的原型,讓類的原型的地址指向Obj,實現繼承
類爲空類,減小開銷

var create = function(obj){

var fn = funcion(){} //空類
fn.prototype = obj //改變類的原型的指向,指向要繼承的對象
reurturn new fn() //自動生成_proto_

}

var A = create(B) //A能找到B(經過_proto_)

這個方法被es6實現了

Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。
const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);

me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction(); 
//My name is Matthew. Am I human? true

3.5 優化2

不用new,直接改_proto_
不就是讓子類原型指向父類的原型嗎

student.prototype 直接指向 Person.prototype 有問題,那咱就不讓它直接指向了,讓它間接指向

student.prototype指針不變,它找不到屬性,會找它的_proto_吧,我讓它的_proto_指向Person.prototype不就好了

也就是 student.prototype._proto_ = Person.prototype

3.6總結一下優化

因此對於這句話 Student.prototype = new Person() 的優化,
重心放在了怎麼減小開銷來創建聯繫上

咱們能夠既經過增長一箇中間空對象(減小開銷),來完成優化
Student.prototype = Object.create(Person.prototype)

也能夠增長一箇中間屬性來完成優化
Student.prototype.__proto__ = Person.prototype

都能創建父類子類的聯繫

4.new幹了啥

既然new在「類」的建立裏面必須使用,那麼咱們就說一下new到底幹了啥事情
題外話,new幹了啥事,必定要從new完之後實例和類的關係來入手記憶,實例和類啥關係?兩個關係實例是否是又類上面的實例屬性,同時_proto_的指向關係
因此new 辦了三件事
1.建立一個對象o繼承構造函數
2.讓構造函數的this變爲o,並執行構造函數,將返回值設置爲k
3.若是k是對象則返回對象,若是不是則返回o

//仿寫new
function new1(func) {
        var o = Object.create(func.prototype)
        var k = func.apply(o,arguments[1])
        return typeof k === 'object'? k: o
    }
const x = new1(Student,['張三'])
x.name //'張三'
x.eat //'i am hungry,i want to eat!'

咱們回過頭再分析一下構造函數模式繼承

const Person = function (name) {
        this.name = name
    }
const Students = function (name) {
        Person.call(this,name) //this是student實例
    }
const xm = new Students('小明')  //分析這裏幹了什麼
console.log(xm)  //Students {name: "小明"}

1.讓空對象o繼承Students(o能訪問Students的原型)
2.student執行,執行Person的代碼,this是o,而且傳入name, o.name='小明'返回的k是undefined
3.返回o,也就是返回{name:'小明'}

5.es6繼承

class Person {
}
class Student extends person{
}

在babel es2015-loose模式下編譯後的源碼以下

"use strict";

    function _inheritsLoose(subClass, superClass) {
        subClass.prototype = Object.create(superClass.prototype);
        subClass.prototype.constructor = subClass;  //修正constructor,避免constructor判斷的不對,也通常用不到
        subClass.__proto__ = superClass;  //這句話看不懂,感受沒啥用呀
    }

    var Person = function Person() {
    };

    var Student =
        /*#__PURE__*/
        function (_person) {
            _inheritsLoose(Student, _person);

            function Student() {
                return _person.apply(this, arguments) || this;
            }

            return Student;
        }(person);

嚴格模式下,高級單例模式返回一個Student, 能夠看到Person的實例屬性用的Person的構造函數+apply繼承的
原型屬性用的_inheritsLoose這個方法繼承的
_inheritsLoose方法貌似就是咱們以前說的寄生組合繼承

6.繼承的應用:vue數組變異方法的實現

咱們知道vue裏面的數組有變異方法,變異方法有啥功能呢,就拿push來講,一方面數組會變,另一方面有響應式(假設觸發render方法)
思路:APO編程思想
數組之因此有push方法,是由於Array.prototype上有push方法
咱們須要實現本身的push方法,掛在Array.prototype上對原型鏈追蹤進行攔截,可是呢又不能改變原型鏈上對非變異方法
原型鏈示意:

Vue裏面添加過監控的數組實例--->咱們本身實現的變異方法-->原來的Array.prototype
const arrList = ['push','pop','shift','unshfit','reverse','sort','splice']
const render = ()=>{console.log('響應式,渲染視圖')}
const proto = Object.create(Array)
arrList.forEach((method)=>{
    proto[method] = function(){
        render()
        Array.prototype[method].call(this,...arguments)
    }
})
var data = [1,2,3]
data.__proto__ = proto    //mvvm響應式原理,若是添加響應式的目標是數組,我就執行這個操做

data.push(4)   // 響應式,渲染視圖,(data[1,2,3,4])

7.總結

本節詳細介紹了繼承的原理以及優化,對於es6的繼承語法糖也作了剖析。同時介紹了一下mvvm下數組借用繼承實現響應式的用法,因爲本人水平有限,若是有什麼不對的地方,歡迎留言指出。

相關文章
相關標籤/搜索