當 bind 遇上 apply
在《你不知道的 JavaScript(中卷)》之“非同步和效能” 2.4小節(P175),有一個 asyncify()
函式,該函式的目的是為了將任意被包裝函式變為非同步(無論原函式是非同步的還是同步的)。函式如下:
function asyncify(fn) {
var orig_fn = fn,
intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 )
;
fn = null;
return function() {
// 觸發太快,在`intv`計時器觸發來
// 表示非同步回合已經過去之前?
if (intv) {
fn = orig_fn.bind.apply(
orig_fn,
// 將包裝函式的`this`加入`bind(..)`呼叫的
// 引數,同時currying其他所有的傳入引數
[this].concat( [].slice.call( arguments ) )
);
}
// 已經是非同步
else {
// 呼叫原版的函式
orig_fn.apply( this, arguments );
}
};
}
在函式的後半段,有這樣一些語句:
// firing too quickly, before `intv` timer has fired to
// indicate async turn has passed?
if (intv) {
fn = orig_fn.bind.apply(
orig_fn,
// add the wrapper's `this` to the `bind(..)`
// call parameters, as well as currying any
// passed in parameters
[this].concat([].slice.call(arguments))
);
}
bind()
函式和 apply()
函式組合在一起的形式有些難以理解。好在在這個 issue 中有人向 @getify 大神提出了這個問題,@getify 是這麼回答的:
apply(..)
會呼叫上下文環境中的函式(在這裡就是bind(..)
),但是傳入的引數個數不定。傳遞給
apply(..)
的第一個引數是繫結該函式呼叫(再次提示,在這裡就是bind(..)
)的this
物件。按照常規寫法,一般會這麼寫orig_fn. bind(..)
,也就是說bind(..)
的this
指向函式orig_fn
。因此當我們呼叫apply(..)
的時候,orig_fn
就是這裡要用到的this
物件。接下來我們構造經由
apply(..)
傳遞給bind(..)
的引數陣列。由於bind(..)
的第一個引數為在orig_fn
呼叫中用到的this
,所以使用[this]
將構造的引數陣列中的第一個引數設定為this
。之後通過將arguments
物件轉化為一個真正的陣列的形式新增外界傳入的其餘引數(呼叫return function() {..
返回的函式所獲得的引數),。然後將兩個陣列合並。除了傳遞給bind(..)
的第一個引數,其餘的引數都會作為柯里化引數(預設值)。
bind(..)
呼叫(再次提示,經由apply(..)
呼叫,由此可以以陣列的形式傳入不同數目的引數)的結果是一個經過硬繫結的、柯里化的函式,但是並沒有經過呼叫。我們將其儲存到變數fn
裡面。之後,這個經過硬繫結的、柯里化的函式通過
fn()
的形式呼叫即可,就像程式碼中用到的那樣if(fn) fn()
。
在這裡的關鍵點是,bind
函式是通過 apply(..)
呼叫的,而 bind
自身所需要的 this
物件是一個函式(函式也是物件;在這裡即 orig_fn
)。通常我們會這麼使用 bind
:
fn.bind(obj);
函式 fn
中的 this
會被繫結到物件 obj
上,而 bind
中的 this
的繫結物件是 fn
。注意這裡不要混淆。
無獨有偶,在《你不知道的 JavaScript(中卷)》之“非同步和效能”中 3.8.2 節,有一個 spread
函式:
function spread(fn) {
return Function.apply.bind( fn, null );
}
該函式的應用場景是:
Promise.all(
foo( 10, 20 )
)
.then(
spread( function(x,y){
console.log( x, y ); // 200 599
} )
);
由於 then(..)
中的回撥函式的引數值是一個陣列,而 apply(..)
中的第二個引數也是陣列,所以這個 spread
函式的目的是對引數形式進行轉化。
bind(..)
返回的是一個經過柯里化的 apply
函式,該函式中的 this
指向 fn
函式(物件)。而第二個引數 null
則是傳遞給 apply
的第一個引數(apply
的呼叫者的 this
需要繫結的物件),null
意味著會繫結到全域性物件。
可以這麼理解:
按照常規寫法,傳給 then(..)
的回撥函式的呼叫方式是這樣的:
fn.apply(null, [..])
也就是說,apply
中的 this
指向的是 fn
物件(參見《你不知道的 JavaScript(上卷)》之 “this和物件原型”),而 Function.apply.bind( fn, null )
是它的顯示繫結形式。
(完)
參考資料:
- 《你不知道的 JavaScript(上卷)》之 “this和物件原型”
- 《你不知道的 JavaScript(中卷)》之 “非同步與效能”
- https://github.com/getify/You-Dont-Know-JS/issues/381
- http://www.ituring.com.cn/article/125690
相關文章
- this、apply、call、bindAPP
- Javascript - apply、call、bindJavaScriptAPP
- this, call, apply 和 bindAPP
- apply & call & bind 原始碼APP原始碼
- bind/call/apply 深度理解APP
- 手寫call,apply,bindAPP
- apply,call,bind的用法APP
- call、apply、bind 區別APP
- apply call bind 簡介APP
- 手寫call、apply、bindAPP
- this指向與call,apply,bindAPP
- call apply bind區別APP
- JavaScript-apply、bind、callJavaScriptAPP
- 從一行等式理解JS當中的call, apply和bindJSAPP
- [譯] Javascript: call()、apply() 和 bind()JavaScriptAPP
- JavaScript重識bind、call、applyJavaScriptAPP
- JavaScript 中的 apply、call、bindJavaScriptAPP
- JS中的call、apply、bindJSAPP
- js call,apply,bind總結JSAPP
- 【筆記】apply, call 與 bind筆記APP
- 當 Rust 遇上 FedoraRust
- 當UIColor遇上SwiftUISwift
- 當 React 遇上 KendoUIReactUI
- 當UIColor遇上 SwiftUISwift
- 當transition遇上display
- call,apply,bind,new實現原理APP
- 模擬實現apply/call/bindAPP
- call.apply.bind 走一個!APP
- call,apply和bind的區別APP
- 一文搞懂 this、apply、call、bindAPP
- js call、apply、bind的實現JSAPP
- bind,call,apply模擬實現APP
- JS中的call、apply、bind方法JSAPP
- javascript -- apply/call/bind的區別JavaScriptAPP
- js中call、apply、bind函式JSAPP函式
- js中call、apply、bind的用法JSAPP
- 【面試題】手寫call、apply、bind面試題APP
- 當 Go 遇上了 LuaGo