JS核心知識點梳理——原型、繼承(下)

clipboard.png

引言

正如上篇所提到的,有些人認爲JavaScript並非真正的面嚮對象語言,在經典的面嚮對象語言中,您可能傾向於定義類對象,而後您能夠簡單地定義哪些類繼承哪些類,JavaScript使用了另外一套實現方式,繼承的對象函數並非經過複製而來,而是經過原型鏈繼承(一般被稱爲 原型式繼承 —— prototypal inheritance)。ios

函數的三種角色

一個函數,有三種角色。
當成普通函數,當成構造函數,當成對象es6

function Person (nickname) {
        var age = 15 //普通函數    標記1
        this.nickname = nickname     //構造函數
        this.sayName = function() {
            console.log(this.nickname)
        }
        console.log(age) 標記2
    }
    Person.nickname = '張三' //對象

    var p1 = new Person('李四') //'15'       
    console.log(Person.nickname) //'張三'    
    p1.sayName() //'李四'  標記4

若是標記1處改爲 var nickname = 'fyy' 標記2處改爲 console.log(nickname)
則後面的輸出分別爲 'fyy' '張三' 'fyy'axios

實例屬性、原型屬性、私有屬性、靜態屬性

實例屬性:又稱成員就是實例裏面的屬性babel

var p1 = new Person('fyy',11)
p1// {age:11,name:'fyy'} 
//name和age就是實例屬性

原型屬性:就是類的原型上的屬性
Person.prototype.say = function(){}
ar p1 = new Person('fyy',11)
p1.say // say就是調用的Person的原型屬性app

私有屬性:就是隻能在類的內部訪問的方法和屬性mvvm

class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}
const counter = new IncreasingCounter();
counter.#count // 報錯
counter.#count = 42 // 報錯

靜態方法:類上的靜態方法就表示該方法不會被實例繼承,而是直接經過類來調用
Array的isArray就是靜態方法,只能經過實例調用由於掛在原型上沒有意義,必然是Array的實例才能調用
同理還有String的fromCharCode等等函數

Array.isArray([1,2,3])    //

//定義
class Array {
    static isArray(){//...}
    constructor(){
    //...
    }
    //...
}

繼承的方式

說到繼承,首先得明白繼承的是什麼東西。我的認爲繼承應該分爲成員屬性和原型屬性的繼承。
實例屬性是不能共用的屬性或者方法,好比身份證。
原型屬性是能共用的屬性或者方法,好比愛好屬性,吃飯方法。this

實例屬性的繼承(構造函數+call)

這個比較簡單,實例屬性用利用構造函數和call或者applyspa

const Person = function (name) {
        this.name = name
    }
const Students = function (name) {
        Person.call(this,name)
    }
const xm = new Students('小明')
console.log(xm)  //Students {name: "小明"}

原型屬性的繼承

這裏裏面坑有點多,你們聽我娓娓道來。
經過原型鏈實現原型屬性繼承確定沒錯,可是咱們設計的時候有個原則 子類須要有本身的原型,父類也必需要有本身的原型,子實例在本身的原型上找不到屬性的時候纔會到父原型上去找
子類.prototype = 父類.prototype 這樣確定不行,雖然能繼承父類原型的方法,可是子類的原型和父類的原型是同一個,給子類原型添加方法的時候,至關於給父類的原型也添加了一個方法。so咱們應該有一個緩衝prototype

子類實例---->子類原型------->中間對象------->父類原型 //沿着箭頭能訪問,表現上符合咱們的設計原則

最多見的是使用父類的實例當這個中間對象。

Children.prototype = new Parent()

可是了這麼作有個很差的地方。會實例化一次父類。若是父類特別複雜,好比axios,那麼會帶來不少額外的開銷。

咱們看一下中間對象有什麼做用,實際上只起了一個隔離和原型重定向的做用。徹底能夠用一個空對象實現這個功能

//實現中間對象
var fn = function() {}
fn.prototype = Parent.prototype
Children.prototype = new fn()

實際上,這個就是Oject.create()的實現

//Oject.create()
Object.create = function(obj){
    var fn = funcion(){}
    fn.prototype = obj
    reurturn new fn() 
}

終極繼承解決方案

如今既要繼承實例屬性,又要繼承原型屬性。

const Person = function (name) {
        this.name = name
    }
    Person.prototype.eat = function () {
        console.log('i am hungry,i want to eat!')
    }
    const Student = function (name) {
        Person.call(this,name)
    }
    Student.prototype = Object.create(Person.prototype)   //注意這裏!原型重定向的後遺症
    const xm = new Student ('小明')
    xm.name //'小明' 
    xm.eat() //i am hungry,i want to eat!
    console.log(xm)
    //Student {name: "小明"}
    //  name: "小明"
    //  __proto__: Person
    //      __proto__: //這一層是個空對象,只有一個__proto__屬性指向Person的原型
    //          eat: ƒ ()
    //            constructor: ƒ (name)
    //            __proto__: Object

可是這裏 xm.constructor.name // Person
這裏由於原型重定向後沒有重置construtor,xm自己沒有construtor只能找咱們建立的空對象,空對象沒有construtor因此繼續往上找,找到了Person.prototype上的construtor爲 Person
因此解決思路很簡單,在改寫原型鏈的時候重置一下constructor就好了

...
var temObj =  Object.create(Person.prototype)
temObj.constructor = Student
Student.prototype =temObj
...
 xm.constructor.name // Student  ok搞定

new幹了啥

既然new在「類」的建立裏面必須使用,那麼咱們就說一下new到底幹了啥事情

1.建立一個對象o繼承構造函數
2.讓構造函數的this變爲o,並執行構造函數,將返回值設置爲k
3.若是k

//仿寫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:'小明'}

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;
        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方法貌似就是咱們以前說的原型屬性繼承的終極解決方案吧?
未完待續....

mvvm中definePrototype

綜合訓練

總結

相關文章
相關標籤/搜索