深入理解New操作符
前言
當我們對函數進行實例化時,需要用new操作符來實現。那么,對于它的底層實現原理你是否清楚呢?
原理分析
我們通過一個具體的例子來看下一個函數在new之后都能做些什么,如下所示:
function Person(name, age) {
this.name = name;
this.age = age;
this.height = "175cm";
this.bodyWeight = "65kg";
return "some anything";
}
Person.prototype.dailyExercise = "300 kcal";
Person.prototype.printBodyWeight = function() {
console.log(this.name + "體重為: " + this.bodyWeight);
};
接下來,我們用new關鍵字將Person函數進行實例化,我們發現實例化后,可以訪問到:
- 函數內部的屬性
- 函數原型上的屬性
const person = new Person("神奇的程序員", "22");
console.log(person.age);
console.log(person.bodyWeight);
console.log(person.dailyExercise);
person.printBodyWeight();
眼尖的開發者可能已經發現我們的構造函數中返回了一個字符串,它是屬于基本類型,如果我們返回一個對象會發生什么?
function Person(name, age) {
return {
bodyWeight: this.bodyWeight
};
}
再次運行代碼后,我們發現:
只能訪問我們在構造函數中所返回的屬性。
構造函數中聲明的其它屬性以及掛載在原型上的屬性均無法訪問。
實現思路
經過前面的分析,我們知道了函數在new完之后會返回一個新的對象,這個對象上掛載了構造函數內的所有屬性以及函數原型上的所有屬性。
我們在實現的時候,也需要建立一個新的對象,這個對象上需要包含構造函數里的屬性,因此我們可以使用apply方法來給此對象添加新屬性。
在深入理解原型鏈與繼承文章中,我們知道實例的 __proto__?屬性會指向構造函數的prototype,建立起這樣的關系后,實例才可以訪問原型上的屬性。
有了這些知識點作為鋪墊后,我們就可以寫出這個模擬函數了,如下所示:
- 創建一個對象用來存儲構造函數的屬性
- 從arguments中取出第一個參數,這個參數便是調用時的構造函數
- 將新對象的原型通過__proto__?指向構造函數的prototype
- 通過apply方法改變構造函數的this指向,從而實現將構造函數內部的屬性添加進新創建的對象中
- 判斷構造函數是否有返回值
有返回值且其類型為一個對象或者一個函數,則返回構造函數的返回值
否則就返回我們新創建的對象
function instantiateFactory() {
const obj = {};
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const result = Constructor.apply(obj, arguments);
if (result && (typeof result == "object" || typeof result == "function")) {
return result;
}
return obj;
}
測試用例
我們用原理分析中的例子來驗證下我們實現的這個工廠函數能否正確執行。
const factory = instantiateFactory(Person, "神奇的程序員", "22");
console.log(factory.age);
console.log(factory.bodyWeight);
console.log(factory.dailyExercise);
factory.printBodyWeight();
假設函數沒有返回值或者返回值是一個字符串類型時,執行結果如下所示:
當函數的返回值是一個對象時,執行結果如下所示: