對於大部分前端開發者而言,new一個構造函數或類得到對應實例,是非常普遍的操作了。下面的例子中分別通過構造函數與class類實現了一個簡單的創建實例的過程。
// ES5構造函數 let Parent = function (name, age) { this.name = name; this.age = age; }; Parent.prototype.sayName = function () { console.log(this.name); }; const child = new Parent('test', 26); child.sayName() //'test' //ES6 class類 class Parent { constructor(name, age) { this.name = name; this.age = age; } sayName() { console.log(this.name); } }; const child = new Parent('test', 26); child.sayName() //test
一、new操作中發生了什么?
比較直觀的感覺,當我們new一個構造函數,得到的實例繼承了構造器的構造屬性(this.name這些)以及原型上的屬性。
在《JavaScript模式》這本書中,new的過程說的比較直白,當以new操作符調用構造函數時,函數內部將會發生以下情況:
• 創建一個空對象並且this變量引用了該對象,同時還繼承了該函數的原型。
• 屬性和方法被添加至this引用的對象中。
• 新創建的對象由this所引用,並且最后隱式地返回 this(如果沒有顯式的返回其它的對象)
我們改寫上面的例子,大概就是這樣:
// ES5構造函數 let Parent = function (name, age) { //1.創建一個新對象,賦予this,這一步是隱性的, // let this = {}; //2.給this指向的對象賦予構造屬性 this.name = name; this.age = age; //3.如果沒有手動返回對象,則默認返回this指向的這個對象,也是隱性的 // return this; }; const child = new Parent();
將this賦予一個新的變量(例如that),最后返回這個變量:
// ES5構造函數 let Parent = function (name, age) { let that = this; that.name = name; that.age = age; return that; }; const child = new Parent('test', '26');
this的創建與返回是隱性的,手動返回that的做法;這也驗證了隱性的這兩步確實是存在的。
二、實現一個簡單的new方法(winter大神)
• 以構造器的prototype屬性為原型,創建新對象;
• 將this(也就是上一句中的新對象)和調用參數傳給構造器,執行;
• 如果構造器沒有手動返回對象,則返回第一步創建的新對象,如果有,則舍棄掉第一步創建的新對象,返回手動return的對象。
new過程中會新建對象,此對象會繼承構造器的原型與原型上的屬性,最后它會被作為實例返回這樣一個過程。
// 構造器函數 let Parent = function (name, age) { this.name = name; this.age = age; }; Parent.prototype.sayName = function () { console.log(this.name); }; //自己定義的new方法 let newMethod = function (Parent, ...rest) { // 1.以構造器的prototype屬性為原型,創建新對象; let child = Object.create(Parent.prototype); // 2.將this和調用參數傳給構造器執行 let result = Parent.apply(child, rest); // 3.如果構造器沒有手動返回對象,則返回第一步的對象 return typeof result === 'object' ? result : child; }; //創建實例,將構造函數Parent與形參作為參數傳入 const child = newMethod(Parent, 'echo', 26); child.sayName() //'echo'; //最后檢驗,與使用new的效果相同 child instanceof Parent//true child.hasOwnProperty('name')//true child.hasOwnProperty('age')//true child.hasOwnProperty('sayName')//false