JavaScript深入系列第十二篇,通過new的模擬實現(xiàn),帶大家揭開使用new獲得構(gòu)造函數(shù)實例的真相
new
一句話介紹 new:
new 運算符創(chuàng)建一個用戶定義的對象類型的實例或具有構(gòu)造函數(shù)的內(nèi)置對象類型之一
也許有點難懂,我們在模擬 new 之前,先看看 new 實現(xiàn)了哪些功能。
舉個例子:
// Otaku 御宅族,簡稱宅
function Otaku (name, age) {
this.name = name;
this.age = age;
this.habit = 'Games';
}
// 因為缺乏鍛煉的緣故,身體強度讓人擔憂
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () {
console.log('I am ' + this.name);
}
var person = new Otaku('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
person.sayYourName(); // I am Kevin
從這個例子中,我們可以看到,實例 person 可以:
- 訪問到 Otaku 構(gòu)造函數(shù)里的屬性
- 訪問到 Otaku.prototype 中的屬性
接下來,我們可以嘗試著模擬一下了。
因為 new 是關(guān)鍵字,所以無法像 bind 函數(shù)一樣直接覆蓋,所以我們寫一個函數(shù),命名為 objectFactory,來模擬 new 的效果。用的時候是這樣的:
function Otaku () {
……
}
// 使用 new
var person = new Otaku(……);
// 使用 objectFactory
var person = objectFactory(Otaku, ……)
初步實現(xiàn)
分析:
因為 new 的結(jié)果是一個新對象,所以在模擬實現(xiàn)的時候,我們也要建立一個新對象,假設(shè)這個對象叫 obj,因為 obj 會具有 Otaku 構(gòu)造函數(shù)里的屬性,想想經(jīng)典繼承的例子,我們可以使用 Otaku.apply(obj, arguments)來給 obj 添加新的屬性。
在 JavaScript 深入系列第一篇中,我們便講了原型與原型鏈,我們知道實例的 __proto__ 屬性會指向構(gòu)造函數(shù)的 prototype,也正是因為建立起這樣的關(guān)系,實例可以訪問原型上的屬性。
現(xiàn)在,我們可以嘗試著寫第一版了:
// 第一版代碼
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
};
在這一版中,我們:
- 用new Object() 的方式新建了一個對象 obj
- 取出第一個參數(shù),就是我們要傳入的構(gòu)造函數(shù)。此外因為 shift 會修改原數(shù)組,所以 arguments 會被去除第一個參數(shù)
- 將 obj 的原型指向構(gòu)造函數(shù),這樣 obj 就可以訪問到構(gòu)造函數(shù)原型中的屬性
- 使用 apply,改變構(gòu)造函數(shù) this 的指向到新建的對象,這樣 obj 就可以訪問到構(gòu)造函數(shù)中的屬性
- 返回 obj
更多關(guān)于:
原型與原型鏈,可以看《JavaScript深入之從原型到原型鏈》
apply,可以看《JavaScript深入之call和apply的模擬實現(xiàn)》
經(jīng)典繼承,可以看《JavaScript深入之繼承》
復制以下的代碼,到瀏覽器中,我們可以做一下測試:
function Otaku (name, age) {
this.name = name;
this.age = age;
this.habit = 'Games';
}
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () {
console.log('I am ' + 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(Otaku, 'Kevin', '18')
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
person.sayYourName(); // I am Kevin
[]~( ̄▽ ̄)~**
返回值效果實現(xiàn)
接下來我們再來看一種情況,假如構(gòu)造函數(shù)有返回值,舉個例子:
function Otaku (name, age) {
this.strength = 60;
this.age = age;
return {
name: name,
habit: 'Games'
}
}
var person = new Otaku('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
在這個例子中,構(gòu)造函數(shù)返回了一個對象,在實例 person 中只能訪問返回的對象中的屬性。
而且還要注意一點,在這里我們是返回了一個對象,假如我們只是返回一個基本類型的值呢?
再舉個例子:
function Otaku (name, age) {
this.strength = 60;
this.age = age;
return 'handsome boy';
}
var person = new Otaku('Kevin', '18');
console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18
結(jié)果完全顛倒過來,這次盡管有返回值,但是相當于沒有返回值進行處理。
所以我們還需要判斷返回的值是不是一個對象,如果是一個對象,我們就返回這個對象,如果沒有,我們該返回什么就返回什么。
再來看第二版的代碼,也是最后一版的代碼:
// 第二版的代碼
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;
};
作者:冴羽
github:https://github.com/mqyqingfeng/Blog
掘金主頁:https://juejin.im/user/58e4b9b261ff4b006b3227f4
segmentfault主頁:https://segmentfault.com/u/yayu/articles
Vicky丶Amor 經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。
求關(guān)注,求點贊
