從規範來看,Function.prototype.bind
是如何工作,以及如何來模擬bind操作。
簡單示例
如下簡單示例,普通對象 testObj
內部有一個b函數,接受一個普通參數,若參數爲空則輸出 this.a
。
const testObj = {
a: 3,
b: function(args) {
console.log(args || this.a);
},
};
testObj.b()
testObj.b(23)
const c = testObj.b
c()
c(23)
const c1 = testObj.b.bind(testObj, 50)
c1(70)
查看結果:
testObj.b
被重新賦值給 c
後,函數的的執行上下文已經改變,導致輸出爲 undefined
。通過上面例子,如果採用 bind
後,則可以改變 testObj
的執行上下文,並可以把默認值傳遞到參數函數列表.
倘若在 testObj.b
內,加入 console.log(arguments)
, 則可以看到如下輸出:
50 70
bind 函數
bind函數是,Function
原型鏈上的函數,主要是改變函數執行上下文的同時,可以傳入函數參數值,並返回新的函數
如圖是mdn上的定義,
bind產生的函數就一個偏函數,就是說使用bind可以參數一個函數,然後接受新的參數。
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
詳細可看: https://github.com/mqyqingfeng/Blog/issues/43
規範定義
在EcmaScript的規範 15.3.4.5 中如下截圖:
bind 方法需要一個或更多參數,thisArg 和(可選的)arg1, arg2, 等等,執行如下步驟返回一個新函數對象:
1. 令 Target 爲 this 值 .
2. 如果 IsCallable(Target) 是 false, 拋出一個 TypeError 異常 .
3. 令 A 爲一個(可能爲空的)新內部列表,它包含按順序的 thisArg 後面的所有參數(arg1, arg2 等等)。
4. 令 F 爲一個新原生 ECMAScript 對象。
5. 依照 8.12 指定,設定 F 的除了 [[Get]] 之外的所有內部方法。
6. 依照 15.3.5.4 指定,設定 F 的 [[Get]] 內部屬性。
7. 設定 F 的 [[TargetFunction]] 內部屬性爲 Target。
8. 設定 F 的 [[BoundThis]] 內部屬性爲 thisArg 的值。
9. 設定 F 的 [[BoundArgs]] 內部屬性爲 A。
10. 設定 F 的 [[Class]] 內部屬性爲 "Function"。
11. 設定 F 的 [[Prototype]] 內部屬性爲 15.3.3.1 指定的標準內置 Function 的 prototype 對象。
12. 依照 15.3.4.5.1 描述,設定 F 的 [[Call]] 內置屬性。
13. 依照 15.3.4.5.2 描述,設定 F 的 [[Construct]] 內置屬性。
14. 依照 15.3.4.5.3 描述,設定 F 的 [[HasInstance]] 內置屬性。
15. 如果 Target 的 [[Class]] 內部屬性是 "Function", 則
a. 令 L 爲 Target 的 length 屬性減 A 的長度。
b. 設定 F 的 length 自身屬性爲 0 和 L 中更大的值。
16. 否則設定 F 的 length 自身屬性爲 0.
17. 設定 F 的 length 自身屬性的特性爲 15.3.5.1 指定的值。
18. 設定 F 的 [[Extensible]] 內部屬性爲 true。
19. 令 thrower 爲 [[ThrowTypeError]] 函數對象 (13.2.3)。
20. 以 "caller", 屬性描述符 {[[Get]]: thrower, [[Set]]: thrower,[[Enumerable]]: false, [[Configurable]]: false}, 和 false 作爲參數調用 F 的 [[DefineOwnProperty]] 內部方法。
21. 以 "arguments", 屬性描述符 {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, 和 false 作爲參數調用 F 的 [[DefineOwnProperty]] 內部方法。
22. 返回 F.
bind 方法的 length 屬性是 1。
Function.prototype.bind 創建的函數對象不包含 prototype 屬性或 [[Code]], [[FormalParameters]], [[Scope]] 內部屬
規範表達過程簡述如下:
- 第1步,創建Target,並把 this 值給Target 【this爲當前執行環境, 可以理解爲當前函數】
- 第3步,內部創建一個參數空的參數列表A,包含了bind參數中除了
thisArg
之外的其他函數 - 第4步,創建一個對象 F
- 第6,7,8,9,10,11步,設置函數內部屬性,這裏第7步中的 [[TargetFunction]] 僅僅只有使用 bind 纔會生成,第8步設置
thisArg
新的函數的 this. - 第12,13,14步,需要設置對象F內部方法
[[Call]]
,[[Construct]]
,[[HasInstance]]
, 讓對象可以被調用 - 第15步,讓對象F變成
[[Function]]
, 並設置新的函數length
, 新的length值 範圍是0 ~ Target.length
- 第18步,設置返回新函數可以任意添加屬性
- 第20步,設置函數描述符號
caller
屬性, - 第21步,設置函數參數描述符號,
arguments
- 最後返回對象F,也就是新執行函數。
在bind過程中,會重新設置 [[Call]]
相關函數內部方法,詳細可以看規範。
isCallable 定義
Object 內部屬性以及方法
用途
- 創建綁定函數,例如react事件參數傳遞
- 偏函數
- 定時器修改this
- 構造函數使用綁定函數
- 快捷調用
最後
知道了過程,倘若不支持,該如何呢?來,搞一個手工的:
Function.prototype.bind2 = function bind2(thisArgs) {
const aArgs = Array.prototype.slice.call(arguments, 1);
const fThis = this;
const fNOP = function() {};
const fBound = function() {
// 這段判斷是不是使用bind返回函數繼續bind
return fThis.apply(this instanceof fBound ? this : fThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
// this === Function.prototype, 保證創建函數的原型鏈也爲undefined
if (this.prototype) fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
我們實現方法依賴一些屬性方法:
- Fucntion.prototype.apply
- Function.prototype.call
- Array.prototype.slice
而且我們實現方法有一個問題在於:
- length 始終返回爲0,並沒有計算
- 返回函數具有
prototype
, 不符合規範
查看更規範的實現,點擊這裏
歡迎交流