JavaScript深刻之new的模擬實現

JavaScript深刻系列第十二篇,經過new的模擬實現,帶你們揭開使用new得到構造函數實例的真相git

new

一句話介紹 new:github

new 運算符建立一個用戶定義的對象類型的實例或具備構造函數的內置對象類型之一數組

也許有點難懂,咱們在模擬 new 以前,先看看 new 實現了哪些功能。瀏覽器

舉個例子:閉包

// 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 能夠:app

  1. 訪問到 Otaku 構造函數裏的屬性
  2. 訪問到 Otaku.prototype 中的屬性

接下來,咱們能夠嘗試着模擬一下了。函數

由於 new 是關鍵字,因此沒法像 bind 函數同樣直接覆蓋,因此咱們寫一個函數,命名爲 objectFactory,來模擬 new 的效果。用的時候是這樣的:post

function Otaku () {
    ……
}

// 使用 new
var person = new Otaku(……);
// 使用 objectFactory
var person = objectFactory(Otaku, ……)複製代碼

初步實現

分析:測試

由於 new 的結果是一個新對象,因此在模擬實現的時候,咱們也要創建一個新對象,假設這個對象叫 obj,由於 obj 會具備 Otaku 構造函數裏的屬性,想一想經典繼承的例子,咱們能夠使用 Otaku.apply(obj, arguments)來給 obj 添加新的屬性。ui

在 JavaScript 深刻系列第一篇中,咱們便講了原型與原型鏈,咱們知道實例的 __proto__ 屬性會指向構造函數的 prototype,也正是由於創建起這樣的關係,實例能夠訪問原型上的屬性。

如今,咱們能夠嘗試着寫初版了:

// 初版代碼
function objectFactory() {

    var obj = new Object(),

    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    Constructor.apply(obj, arguments);

    return obj;

};複製代碼

在這一版中,咱們:

  1. 用new Object() 的方式新建了一個對象 obj
  2. 取出第一個參數,就是咱們要傳入的構造函數。此外由於 shift 會修改原數組,因此 arguments 會被去除第一個參數
  3. 將 obj 的原型指向構造函數,這樣 obj 就能夠訪問到構造函數原型中的屬性
  4. 使用 apply,改變構造函數 this 的指向到新建的對象,這樣 obj 就能夠訪問到構造函數中的屬性
  5. 返回 obj

更多關於:

原型與原型鏈,能夠看《JavaScript深刻之從原型到原型鏈》

apply,能夠看《JavaScript深刻之call和apply的模擬實現》

經典繼承,能夠看《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複製代碼

[]~( ̄▽ ̄)~**

返回值效果實現

接下來咱們再來看一種狀況,假如構造函數有返回值,舉個例子:

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複製代碼

在這個例子中,構造函數返回了一個對象,在實例 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複製代碼

結果徹底顛倒過來,此次儘管有返回值,可是至關於沒有返回值進行處理。

因此咱們還須要判斷返回的值是否是一個對象,若是是一個對象,咱們就返回這個對象,若是沒有,咱們該返回什麼就返回什麼。

再來看第二版的代碼,也是最後一版的代碼:

// 第二版的代碼
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;

};複製代碼

下一篇文章

JavaScript深刻之類數組對象與arguments

相關連接

《JavaScript深刻之從原型到原型鏈》

《JavaScript深刻之call和apply的模擬實現》

《JavaScript深刻之繼承》

深刻系列

JavaScript深刻系列目錄地址:github.com/mqyqingfeng…

JavaScript深刻系列預計寫十五篇左右,旨在幫你們捋順JavaScript底層知識,重點講解如原型、做用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索