Case3 那個算是新手常見問題,不過我想只是大家忘記參數是看順序,從來都不是看名字

舉例來說,有下面這個範例:

function test(fn) {
let name = "hello"
fn(name) // 重點
}
// 1
test(function(name) {
console.log(name)
})
// 2
test(function(yoyoyo) {
console.log(yoyoyo)
})

在重點那一行,test 只是去呼叫 fn 並且把一個變數 name 傳進去,在底下的兩個呼叫 test 的範例當中,你傳進去的 function 無論用什麼名稱去接都可以,因為函式本來就是這樣子運作的。

所以,可以把瀏覽器內部觸發 event listener 的實作差不多看成是這樣:

// 當事件被呼叫時,呼叫 listener
let event = {
targer: …,
name: …,

}
listener(event) // 這個 listener 就是你的 callback function

只是去呼叫你的 function 並且帶入一個參數而已。按照上面所說的,JS 本來就沒有什麼「按照名字傳參數」這種事情,傳進去的 event 就會是 callback function 的「第一個參數」,而你在 callback 裡面要取什麼名字都行,就像是:

function yo(a){
console.log(a)
}
yo(123) // 123
yo(‘hey’, ‘yo’) // hey
var b = 5
yo(b) // 5
var a = 10
yo(b, a) // 5

yo 裡面的 a 永遠都會是呼叫時傳進來的「第一個參數」,因為 JS 就是這樣運作。

不過以上不是我回這篇的主因就是了XDD

會回這篇是因為以前沒注意過原來傳進去的是一個有帶 handleEvent 的 object 也可以,長知識了,然後還有另外一個在意的點。

那就是原文裡面其實沒有很明確地提到為什麼傳一個 function 也可以。

因為根據規格的定義,callback 應該要是個 EventListener,而 EventListener 需要實作 handleEvent 這個 method,所以傳一個有帶 handleEvent 的 object 十分合理,可是帶一個 function 呢?如果瀏覽器只會呼叫 callback.handleEvent 這個 method,那又是怎麼呼叫到我們傳進去的 function 呢?

然後原文就回到 mdn 了XD

所以我最後再翻了一下規格,發現我好奇的點是在這裡:https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.9. Dispatching events 底下 To inner invoke 的 step 10

Image for post
Image for post

這邊會 “call a user object’s operation”,並且帶入:

  1. callback
  2. “handleEvent”
  3. <<event>>
  4. currentTarget

call a user object’s operation” 是一個超連結,點進去有詳述:

Image for post
Image for post

這邊的語言就切換成 ECMAScript 式的那種寫法了,要完全看懂需要有一些背景知識,剛好最近在看所以覺得特別熟悉。不過儘管沒有背景知識,其實用字還是不難懂啦,就大略看過,這邊我簡單解釋一下

首先根據第一行,傳進來的參數是:

  1. value(我們的 callback)
  2. opName(handleEvent)

其他參數跟我們想研究的不相關,可以略過

接著在 step3 讓 O = value,在 step9 讓 X = O

在 step10 判斷 isCallable(O),如果 O 是 function 這邊才成立,這邊我們先假設我們傳進去的 value 是一個有實作 handleEvent 的 object,那這邊就會是 false,就會進入到 step10–1,讓 getResult 是 Get(O, opName)

意思就是要拿 O[opName] 啦,也就是 value.handleEvent 的意思,不過返回值其實不會就是 handleEvent 這個 function,而是一個經過包裝後的物件:

{
[[Value]]: function,
[[Callable]]: true,

}

接著 step 10–3,讓 X 變成 getResult.[[Value]]所以就是把真正的 function 給取出來,讓 X 變成我們傳進去的 object 裡面的 handleEvent。

最後在 step12,呼叫 Call(X, thisArg, esArgs),呼叫這個 function 並且帶入參數。

好,所以如果我們傳進去一個帶有 handleEvent 的 object,流程就是這樣。那如果我們傳進去一個 function 呢?

那在 step10 判斷 isCallable(O) 結果就會是 true,就不會進入到 step10–1,而會跑到 step11 然後 12,執行 Call(X, thisArg, esArgs),直接去呼叫那個 function。

所以從以上流程可以看出來,如果傳進去的是一個 function,就會執行那個 function。若是傳進去的是一個 object,就會判斷是否有實作 handleEvent,有的話就呼叫,沒有的話就回傳錯誤。

Written by

重度拖延症患者,興趣是光想不做,有很多想做的事,卻一件都沒有執行。無聊的時候喜歡寫文章,發現自己好像有把事情講得比其他人清楚的能力。相信分享與交流可以讓世界更美好。Medium 文章列表請參考:https://aszx87410.github.io/blog/medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store