JavaScript專題之模擬實現new

dunne21發表於2021-09-09

本文共 1230 字,讀完只需 5 分鐘

寫在前面

最近工作太忙,快接近兩週沒更新部落格,總感覺有一些事情等著自己去做,雖然工作內容對自己提升挺大,但我總覺得,一直埋著頭走路,偶爾也需要抬起頭來,看看現在和自己的期望向是否脫軌,所以週末還是選擇來星巴克寫些文字。

今天記錄 JavaScript 中 new 關鍵字的模擬實現,當我們在模擬實現某個語言行為之前,應該想想這個行為都做了哪些事情,通過實踐,最後也能更加掌握知識點,這就是很多面試題都會問到模擬實現的原因,目的是為了考察候選人知識的深度。

function Person(name) {
    this.name = name;
}

var person = new Person('jayChou');

typeof(person)  // "object"
person instanceof Person  // true
person.__proto__ === Person.prototype  // true
person.constructor === Person  //  true
person.constructor === Person.prototype.constructor  // true
複製程式碼

以上,可以看出:

  1. new 建立並返回了一個新物件,是建構函式的例項
  2. 物件的例項的建構函式屬性其實是建構函式的原型物件的 constructor 屬性
  3. 物件例項的 __proto__ 關聯到建構函式的原型物件

上面的內容有關於 JavaScript 中原型物件和原型鏈的知識,不夠清楚的同學可以檢視我之前的部落格。

由於 new 是 JS 的一個關鍵字,我們無法實現關鍵字,但我們可以通過函式的形式來模擬 new 關鍵字的行為。

一、基本思路

知道 new 關鍵字做了哪些工作,那我們就有了模擬實現的基本思路。

/**
 * 模擬實現 JavaScript new 操作符
 * @param  {Function} constructor [建構函式]
 * @return {Object|Function|Regex|Date|Error}      [返回結果]
 */
function mockNew() {
    // 建立一個空物件
    let resultObj = new Object();

    // 取傳入的第一個引數,即建構函式,並刪除第一個引數。
    // 關於為什麼要用 Array.prototype.shift.call 的形式,見之前的部落格文章 《JavaScript之arguments》
    let constructor =  Array.prototype.shift.call(arguments);
    
    // 型別判斷,錯誤處理
    if(typeof constructor !== "function") {
        throw("建構函式第一個引數應為函式");
    }
    
    // 繫結 constructor 屬性
    resultObj.constructor = constructor;
    
    // 關聯 __proto__ 到 constructor.prototype
    resultObj.__proto__ = constructor.prototype;
    
    // 將建構函式的 this 指向返回的物件
    constructor.apply(resultObj, arguments);
    
    // 返回物件
    return resultObj;
}

function Person(name) {
    this.name = name;
}


var person = mockNew(Person, "jayChou");

console.log(person);

// constructor: ƒ Person(name)
// name: "jayChou"
// __proto__: Object
複製程式碼

基本思路正確! 所以我們完成了 new 關鍵字的初步模擬。夥伴們可以自己動手敲一下,每句程式碼自己是否都能理解。

二、處理返回值

建構函式也是函式,有不同型別返回值。有時候建構函式會返回指定的物件內容,所以要對這部分進行處理。

/**
 * 模擬實現 JavaScript new 操作符
 * @param  {Function} constructor [建構函式]
 * @return {Object|Function|Regex|Date|Error}      [返回結果]
 */
function mockNew() {
    // 建立一個空物件
    let emptyObj = new Object();

    // 取傳入的第一個引數,即建構函式,並刪除第一個引數。
    // 關於為什麼要用 Array.prototype.shift.call 的形式,見之前的部落格文章 《JavaScript之arguments》
    let constructor =  Array.prototype.shift.call(arguments);
    
    // 型別判斷,錯誤處理
    if(typeof constructor !== "function") {
        throw("建構函式第一個引數應為函式");
    }
    
    // 繫結 constructor 屬性
    emptyObj.constructor = constructor;
    
    // 關聯 __proto__ 到 constructor.prototype
    emptyObj.__proto__ = constructor.prototype;
    
    // 將建構函式的 this 指向返回的物件
    let resultObj = constructor.apply(emptyObj, arguments);
    
    // 返回型別判斷, 如果是物件,則返回建構函式返回的物件
    if (typeof resultObj === "object") {
        return resultObj
    }
    
    // 返回物件
    return emptyObj;
}

function Person(name) {
    this.name = name;
    return {
        name: this.name,
        age: 40
    }
}


var person = mockNew(Person, "jayChou");

console.log(person);

// {name: "jayChou", age: 40}
// age: 40
// name: "jayChou"
// __proto__: Object
複製程式碼

當返回值返回了一個自定義物件後,模擬 new 函式就返回該自定義物件。

總結

JavaScript new 關鍵字的意義在於讓普通函式生成一個新物件,並將物件例項的 __proto__ 關聯到函式的 prototype 物件。

本文中有些地方需要一些前置知識,但是總體上理解是比較容易的。如果有迷惑的地方,可以翻看我之前的部落格文章

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript之原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply
  13. JavaScript專題之模擬實現bind

歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。

JavaScript專題之模擬實現new

相關文章