淺談構造函數、原型與原型鏈

原型與原型鏈

原型對象

構造函數可以通過new的方法來創建一個實例,而每一個構造函數都有一個prototype的屬性指向一個原型對象。原型對象中所有的屬性與方法都可以被該函數所創建的實例所共享。

原型鏈

每個被構造函數創建出來的實例都有一個隱式的屬性__proto__,該屬性指向構造函數的prototype,即原型對象。瀏覽器在取對象中的方法時,會先在對象中尋找是否存在該方法,如果沒有則通過__proto__到其原型對象上尋找,以此一層層通過__proto__,直至爲null。

原型鏈是基於proto形成的,繼承是通過prototype實現的

Object,Function,__proto__,prototype

在JS中,萬物皆對象,所以函數(方法)也是一個對象。

所有的對象都有一個根源 Object.prototype,Object.prototype只是一個普通對象

根源的原型對象是null

但是null卻不是一個對象,雖然用typeof驗證null爲object,這個是JS的bug。

(typeof Object.prototype) === object;//true
Object.prototype.__proto__=== null;//true
Object.prototype.prototype === undefied;//true

特例:__Object和Function既是對象,又是函數,兩者內部同時含有proto和prototype屬性,他們關係較爲複雜__Function.prototype指向“__內置函數__”,而Object.prototype指向“__根源對象__”

Object.__proto__ === Function.prototype //true
Object.__proto__ === Function.__proto__//true
Object.prototype === Function.prototype.__proto__ // true
//因此
Function instanceof Object //true
Object instanceof Function //true

x instanceof y,當y的原型對象在x的原型鏈之上,返回true,否則返回false

構造函數與普通函數

創建方式相同

構造函數也是個普通函數,但是一般來說,構造函數會採用首字母大寫的方式。

調用方式不同,目的不同

普通函數: 直接調用,返回值爲函數調用的之後return的值,無return則爲空;
構造函數:需要用new調用,以創建實例的方式來調用,目的是創建一個指向構造函數的原型的一個實例對象。

構造函數調用創建實例的時候內含多步驟

這個問題可以換個角度說,就是當我們再new一個對象的時候,內部發生了什麼。
1、首先需要創建一個空對象。
2、將空對象的__proto__指向構造函數的prototype(構造函數的原型對象),以此獲得原型的方法與屬性
3、綁定this的指向
4、返回新對象
在文章下方會手動實現一個簡易版的new方法

構造函數用this構造內部方法與屬性

function Cat(name, age) {
    this.age = age
    this.name = name
    this.run = run
    function run() {
        
    }
}
let cat = new Cat('米粒', '6個月')

構造函數所創建的實例都可以用instanceof

可以用來驗證該實例是否由驗證的構造函數所創建的。原理就是去查找實例的__proto__是否與構造函數的prototype所指向的原型對象是同一個。
文章下方將會手寫一個簡略版的instanceof

手寫new

當我們再new一個對象的時候,內部發生了什麼。

1、首先需要創建一個空對象。
2、將空對象的__proto__指向構造函數的prototype(構造函數的原型對象),以此獲得原型的方法與屬性
3、綁定this的指向
4、返回新對象

function Cat(name, age) {
    this.age = age
    this.name = name
    this.run = run
    function run() {
        
    }
}

function _new() {
    // 
    let newObject = new Object()
    // arguments是傳入函數的參數,因爲是類數組的形式,無法直接使用shift方法
    // 所以使用ES6的新特性進行轉換
    // Array.prototype.shift.apply(arguments)用數組原生方法也是可以的
    arguments = Array.from(arguments)
    // 取出構造函數
    let Constructor =  arguments.shift()
    //  獲得構造函數的原型對象上的方法與屬性
    newObject.__proto__ = Constructor.prototype
    // 綁定this的指向
    Constructor.apply(newObject, arguments)
    return newObject
}

cat = _new(Cat, '米粒', '6個月')
console.log(cat)
// Cat { age: '6個月', name: '米粒', run: [Function: run] }

image.png

手寫instanceof

原理:查找實例的__proto__是否與構造函數的prototype所指向的原型對象是同一個。

function instance_of(L, R) {
    const R_prototype = R.prototype
    let L_proto = L.__proto__
    let flage = false
    while(L_proto !== null) {
        if (L_proto === R_prototype) {
            flage = true
            break
        }
        L_proto = L_proto.__proto__; 
    }
    return flage
}

// 使用上方的_new方法來創建實例
cat = _new(Cat, '米粒', '6個月')
console.log(cat)

console.log(cat instanceof Cat) // true
console.log(instance_of(cat, Cat)) // true
console.log(instance_of(cat, Object)) // true
console.log(instance_of(cat, {})) // false
console.log(instance_of(cat, Boolean)) // false

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章