new操作符原理及實現

[toc]

分析:new做了什麼

new操作符通過執行自定義構造函數或者js內置構造函數,從而生成一個實例對象。

mdn上把內部操作大概分爲4步:

  1. 創建一個空的簡單JavaScript對象(即{ } );
  2. 鏈接該對象(即設置該對象的構造函數)到另一個對象 ;
  3. 將步驟1新創建的對象作爲this的上下文 ;
  4. 如果該函數沒有返回對象,則返回this。

通過一個簡單的demo感受下上面的步驟

function Person (name){
    this.name = name
}
let p = new Person('jack');
console.log(`p:`, p); // { name: 'jack' }
console.log(`p.__proto__===Person.prototype:`,p.__proto__===Person.prototype);  //true

可以看到new操作符執行Person構造函數後,返回了一個內部創建的新對象,並且以這個對象爲上線文環境執行了一遍Person函數,最後將其返回,同時對象p的原型屬性指向構造函數的原型,這樣也就保證了實例能夠訪問在構造函數原型中定義的屬性和方法

上面的demo中構造函數是沒有返回值的,如果說構造函數有返回值呢,如下

function Person (name){
    this.name = name;
    return {age: 18}
}
let p = new Person('jack');
console.log(`p:`, p); // { age: 18 }

如果構造函數最後返回了一個對象,就會直接將其返回,而不是內部創建的新對象。

經過測試發現,除了返回對象,如果返回其他類型,只要最後返回的類型爲引用類型object或者function(Function,Object,Array,Date,Error,Regexp,要排除null,因爲typeof null === 'object')就會直接將其返回,而其他基本類型都會返回內部新創建的對象。

自定義實現

這裏我們嘗試通過封裝一個myNew方法模擬new操作符的主要功能:接受若干參數,第一個參數爲構造函數ctr,其餘爲構造器所需參數,myNew(ctr, arg1, arg2,...)

第一步

這裏的第一步把mdn中的1、2步放在了一起:創建一個新對象,並將其__proto__屬性指向構造函數的prototype屬性

function myNew(ctr) {
    let obj = Object.create(ctr.prototype);
}

也可以使用如下方法

function myNew (ctr){
    let obj = {};
    obj.__proto__ = ctr.prototype;
}

第二步

獲取到參數之後,以內部新創建的對象obj爲上線文執行構造函數,作用是爲obj賦值

function myNew(ctr) {
    let obj = Object.create(ctr.prototype);
    
    const args = [].slice.call(arguments, 1);
    let result = ctr.apply(obj, args);
    console.log(`obj:`,obj);
}

上面的const args = [].slice.call(arguments, 1);用於將arguments類數組轉爲數組並獲取參數,也可以通過Array.form(arguments).slice(1)或者[...arguments].slice(1)實現。

第三步

對執行構造函數後的返回值result做兼容處理。
如果構造函數最終返回對象、函數、數組、日期等其他引用類型及Symbol,會將其直接返回,其他基本類型及nullundefined會返回內部新創建的對象實例。

function myNew(fn) {
    let obj = Object.create(fn.prototype);

    const args = [].slice.call(arguments, 1);
    let result = fn.apply(obj, args);

    var isObj = (typeof result === 'object' && result !== null);
    var isFn = typeof result === 'function';
    return (isObj || isFn) ? result : obj;
}

測試

最後,簡單測試一下
沒有返回值

function Person(name) {
    this.name = name;
}
let p = myNew(Person,'jack');
console.log(`p:`,p);

有返回值

function Person(name) {
    this.name = name;
    return {age: 33}
}
let p = myNew(Person,'jack');
console.log(`p:`,p);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章