1. 程式人生 > >【進階3-5期】深度解析 new 原理及模擬實現(轉)

【進階3-5期】深度解析 new 原理及模擬實現(轉)

這是我在公眾號(高階前端進階)看到的文章,現在做筆記

https://github.com/yygmind/blog/issues/24

new 運算子建立一個使用者定義的物件型別的例項或具有建構函式的內建物件的例項。 ——(來自於MDN)

舉個例子:

function Car(color) {
    this.color = color;
}
Car.prototype.start = function() {
    console.log(this.color + " car start");
}

var car = new Car("black");
car.color; // 訪問建構函式裡的屬性
// black car.start(); // 訪問原型裡的屬性 // black car start

可以看出 new 建立的例項有以下 2 個特性

  • 1、訪問到建構函式裡的屬性
  • 2、訪問到原型裡的屬性

注意點

ES6新增 symbol 型別,不可以使用 new Symbol(),因為 symbol 是基本資料型別,每個從Symbol()返回的 symbol 值都是唯一的。

Number("123"); // 123
String(123); // "123"
Boolean(123); //
true Symbol(123); // Symbol(123) new Number("123"); // Number {123} new String(123); // String {"123"} new Boolean(true); // Boolean {true} new Symbol(123); // Symbol is not a constructor

模擬實現

當代碼 new Foo(...) 執行時,會發生以下事情:

  1. 一個繼承自 Foo.prototype 的新物件被建立。
  2. 使用指定的引數呼叫建構函式 Foo ,並將 this
     繫結到新建立的物件。new Foo 等同於 new Foo(),也就是沒有指定引數列表,Foo 不帶任何引數呼叫的情況。
  3. 由建構函式返回的物件就是 new 表示式的結果。如果建構函式沒有顯式返回一個物件,則使用步驟1建立的物件。
模擬實現第一步

new 是關鍵詞,不可以直接覆蓋。這裡使用 create 來模擬實現 new 的效果。

new 返回一個新物件,通過 obj.__proto__ = Con.prototype 繼承建構函式的原型,同時通過 Con.apply(obj, arguments)呼叫父建構函式實現繼承,獲取建構函式上的屬性(【進階3-3期】)。

實現程式碼如下:

  // 第一版
  function create() {
    // 建立一個空的物件
    var obj = new Object(),
      // 獲得建構函式,arguments中去除第一個引數
      Con = [].shift.call(arguments);
    // 連結到原型,obj 可以訪問到建構函式原型中的屬性
    obj.__proto__ = Con.prototype;
    // 繫結 this 實現繼承,obj 可以訪問到建構函式中的屬性
    Con.apply(obj, arguments);
    // 返回物件
    return obj;
  };

測試一下

// 測試用例
function Car(color) {
    this.color = color;
}
Car.prototype.start = function() {
    console.log(this.color + " car start");
}

var car = create(Car, "black");
car.color;
// black

car.start();
// black car start

完美!

不熟悉 apply / call 的點選檢視:【進階3-3期】深度解析 call 和 apply 原理、使用場景及實現

不熟悉繼承的點選檢視:JavaScript常用八種繼承方案

模擬實現第二步

上面的程式碼已經實現了 80%,現在繼續優化。

建構函式返回值有如下三種情況:

  • 1、返回一個物件
  • 2、沒有 return,即返回 undefined
  • 3、返回undefined 以外的基本型別

情況1:返回一個物件

function Car(color, name) {
    this.color = color;
    return {
        name: name
    }
}

var car = new Car("black", "BMW");
car.color;
// undefined

car.name;
// "BMW"

例項 car 中只能訪問到返回物件中的屬性。

情況2:沒有 return,即返回 undefined

function Car(color, name) {
    this.color = color;
}

var car = new Car("black", "BMW");
car.color;
// black

car.name;
// undefined

例項 car 中只能訪問到建構函式中的屬性,和情況1完全相反。

情況3:返回undefined 以外的基本型別

function Car(color, name) {
    this.color = color;
    return "new car";
}

var car = new Car("black", "BMW");
car.color;
// black

car.name;
// undefined

例項 car 中只能訪問到建構函式中的屬性,和情況1完全相反,結果相當於沒有返回值。

所以需要判斷下返回的值是不是一個物件,如果是物件則返回這個物件,不然返回新建立的 obj物件。

所以實現程式碼如下:

// 第二版
function create() {
    // 建立一個空的物件
    var obj = new Object(),
    // 獲得建構函式,arguments中去除第一個引數
    Con = [].shift.call(arguments);
    // 連結到原型,obj 可以訪問到建構函式原型中的屬性
    obj.__proto__ = Con.prototype;
    // 繫結 this 實現繼承,obj 可以訪問到建構函式中的屬性
    var ret = Con.apply(obj, arguments);
    // 優先返回建構函式返回的物件
    return typeof ret === 'object' ? ret : obj;
};