JavaScript深入之new的模擬實現
new
一句話介紹new:
The new operator creates an instance of a user-defined object type or of one of the built-in object types that has a constructor function.
依然是來自MDN,之所以不使用中文版的,是因為翻譯的沒看懂……
我們自己來翻譯下這句話:
new運算符創建了一個用戶自定義對象類型或者一個內置對象類型的實例,該實例具有一個constructor函數。
也許有點難懂,我們在模擬new之前,先看看new實現了哪些功能。
舉個例子:
function Person (name, age) {
this.hobbit = 'game';
this.name = name;
this.age = age;
}
Person.prototype.strength = 100;
Person.prototype.sayName = function () {
console.log(this.name);
}
var person = new Person('kevin', '18');
console.log(person.name) // kevin
console.log(person.hobbit) // game
console.log(person.strength) // 100
person.sayName(); // kevin</code></pre>
我們可以看到:
實例person可以
-
訪問到Person構造函數里的屬性
-
訪問到Person.prototype中的屬性
那接下來,我們可以嘗試著去模擬一下了。
因為new是關鍵字,所以無法直接更改new的效果,所以我們寫一個函數,叫做objectFactory,來實現new的效果。
用的時候是這樣的:
function Person () {
……
}
// var person = new Person(……);
var person = objectFactory(Person, ……)</code></pre>
初步實現
分析:
因為new的結果是一個新對象,所以在模擬實現的時候,我們也要建立一個新對象,假設這個對象叫obj,因為obj會具有Person構造函數里的屬性,想想經典繼承的例子,我們可以使用Person.apply(obj, arguments)來給obj添加新的屬性。
在JavaScript深入系列第一篇中,我們便講了原型與原型鏈,我們知道實例的__proto__屬性會指向構造函數的prototype,也正是因為建立起這樣的關系,實例可以訪問原型上的屬性。
現在,我們可以嘗試著寫第一版:
// 第一版代碼
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
};</code></pre>
在這個示例中,我們
-
用new Object()的方式新建了一個對象obj
-
取出第一個參數,就是我們要傳入的構造函數。此外因為shift會修改原數組,所以arguments會被去除第一個參數
-
將obj的原型指向構造函數,這樣obj就可以訪問到構造函數原型中的屬性
-
使用apply,改變this的指向到新建的對象,這樣obj就可以訪問到構造函數中的屬性
-
返回obj
如果對原型鏈這部分不是很清楚,可以看《JavaScript深入之從原型到原型鏈》
如果對apply這部分不是很清楚,可以看《JavaScript深入之call和apply的模擬實現》
如果對經典繼承不是很清楚,可以看《JavaScript深入之繼承》,但是還未發布,哈哈
復制以下的代碼,到瀏覽器中,我們可以做一下測試:
function Person (name, age) {
this.hobbit = 'game';
this.name = name;
this.age = age;
}
Person.prototype.strength = 100;
Person.prototype.sayName = function () {
console.log(this.name);
}
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
};
var person = objectFactory(Person, 'kevin', '18')
console.log(person.name) // kevin
console.log(person.hobbit) // game
console.log(person.strength) // 100
person.sayName(); // kevin</code></pre>
[]~( ̄▽ ̄)~**
返回值效果實現
接下來我們再來看一種情況,假如構造函數有返回值,舉個例子:
function Person (name, age) {
this.strength = 10;
this.age = age;
return {
name: name,
hobbit: 'game'
}
}
var person = new Person('kevin', '18');
console.log(person.name) // kevin
console.log(person.hobbit) // game
console.log(person.strength) // undefined
console.log(person.age) // undefined</code></pre>
在這個例子中,構造函數返回了一個對象,在實例person中只能訪問返回的對象中的屬性。
而且還要注意一點,在這里我們返回了一個對象,假如我們只是返回一個基本類型的值呢?
再舉個例子:
function Person (name, age) {
this.strength = 10;
this.age = age;
return 'handsome boy';
}
var person = new Person('kevin', '18');
console.log(person.name) // undefined
console.log(person.hobbit) // undefined
console.log(person.strength) // 10
console.log(person.age) // 18</code></pre>
結果完全顛倒過來,這次盡管有返回值,但是相當于沒有返回值進行處理。
所以我們還需要判斷返回的值是不是一個對象,如果是一個對象,我們就返回這個對象,如果沒有,我們該返回什么返回什么
再來看第二版的代碼,就是最后一版的代碼:
// 第二版的代碼
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};</code></pre>
來自:https://segmentfault.com/a/1190000009286643