iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Modern Web

JavaScript學習筆記系列 第 23

[Day 23] 動手做一個new 運算子吧!

  • 分享至 

  • xImage
  •  

為什麼突然要動手做?有次讀書會Mentor說可以自己做一個new,不過當下我對於prototype原型還不明白,以至於沒有聽懂...這次鐵人賽到這邊,也把prototype了解了,所以想說試試看怎麼自己做一個new運算子~

先看看MDN說的,new它在做什麼。分成這4個步驟,把英文版簡略改成中文意思:

  1. 建立一個新物件。
  2. 將建構函式的prototype與新物件的原型[[prototype]]做連結。
  3. 呼叫建構函式,並將this綁定新物件。
  4. 如果建構函式沒有回傳任何值或回傳基本型別,則回傳新物件;如果建構函式有回傳值(非基本型別),會覆蓋原本的新物件成為最終結果。

動手做new

按照MDN說的那幾個步驟試著做:

function myNew(fn,...args){}

宣告一個myNew函式,參數部分 fn 代表要傳入的建構函式,而 ...argsfn 的參數,args為陣列。

例如:原生寫法 new GetData("John"),現在會像這樣呈現 mynew(GetData,"John")

補充:其餘參數(rest parameter)

為什麼是用...args 的方式帶入?因為我們不知道參數傳入的數量,而這樣不論傳入多少參數,都可以把參數打包成一個「陣列」,為ES6新增的功能。

範例:

function funcA(...args){
    console.log(args); 
}

funcA(1,2,3,4,5); // 印出[1,2,3,4,5]

第一步驟 + 第二步驟:

function myNew(fn,...args){
   const obj = {}; //步驟1 建立一個空物件
   obj.__proto__ = fn.prototype; //步驟2 設定obj物件原型
}

上面這兩個步驟也可以改用Object.create一行來呈現:

function myNew(fn,...args){
   const obj = Object.create(fn.prototype); //步驟1+步驟2
}

第三步驟:

const returnValue = obj.constructor(); //步驟3

這裡是用呼叫物件obj上的constructor屬性,通常constructor屬性是指向「建立該物件的建構函式」,也就是fn建構函式。

但要注意,如果constructor指向不一樣的函式,就會出現錯誤!

所以,有另一種寫法能確保傳入fn建構函式被呼叫,並明確綁定this:

const returnValue = fn.apply(obj, args); //步驟3

也就是呼叫fn建構函式,並用apply明確綁定this在obj。這樣建構函式內的this就會指向obj。

為什麼用apply不是call?

我們知道apply的參數是以一個「陣列」傳入,這樣可以靈活處理不確定數量的參數;而call需要依次列出所有的參數。

也連結到一開始說的,args是一個陣列,因此搭配apply。

第四步驟:

剛剛第四步驟說到建構函式:

  1. 沒有回傳值或回傳基本型別 -->則回傳新物件
  2. 有回傳值(非基本型別,即物件) -->此回傳值取代新物件

所以現在要判斷是否有回傳值,以及判斷回傳值的型別:

return returnValue instanceof Object ? returnValue : obj;

這段意思是returnValue如果是物件,就回傳returnValue作為結果;反之,則回傳新物件obj。

以上步驟就建構完成myNew了。

用instanceof判斷

原本用typeof判斷型別,但陣列、null,都是回傳"object",無法具體區分型別。

而instanceof可檢查某物件是否為某建構函式的實例,透過該物件的原型鍊 [[Prototype]] 來判斷。

範例:

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

const student = new Person("小白");

console.log(student instanceof Person); //true
console.log(student instanceof Object); //true
console.log(Person instanceof Function); //true
console.log(null instanceof Object); //false

myNew完整程式碼:

function myNew(fn, ...args) {
  // 1.建立一個新物件
  // 2.將建構函式的prototype與新物件的原型[[prototype]]做連結
  const obj = Object.create(fn.prototype); 
  
  // 3.呼叫建構函式,並將this綁定新物件
  const returnValue = fn.apply(obj, args);
  
  // 4.如果建構函式沒有回傳任何值或回傳基本型別,則回傳新物件;如果建構函式有回傳值(非基本型別),會覆蓋原本的新物件成為最終結果
  return returnValue instanceof Object ? returnValue : obj;
}

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

const child = myNew(Person, "小黑", 7);

console.log(child); //{name:"小黑",age:7}

myNew運算子

用原生new:

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

const child = new Person("小黑", 7);
console.log(child);

原生new

比較myNew和原生new。

以上分享~謝謝!

參考資料

MDN - new
MDN - instanceof
new operator — JavaScript | 為了瞭解原理,那就來實作一個 new 吧!


上一篇
[Day 22] Class語法糖
下一篇
[Day 24] 柯里化 Currying
系列文
JavaScript學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Chris
iT邦新手 3 級 ‧ 2024-10-16 18:11:55

做一個 myNew 很好玩吧!

我要留言

立即登入留言