【進階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(...)
執行時,會發生以下事情:
- 一個繼承自
Foo.prototype
的新物件被建立。 - 使用指定的引數呼叫建構函式
Foo
,並將this
new Foo
等同於new Foo()
,也就是沒有指定引數列表,Foo
不帶任何引數呼叫的情況。 - 由建構函式返回的物件就是
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; };