深刻一點 - 使用bind的時候發生了什麼呢?

從規範來看,Function.prototype.bind 是如何工做,以及如何來模擬bind操做。react

簡單示例

以下簡單示例,普通對象 testObj 內部有一個b函數,接受一個普通參數,若參數爲空則輸出 this.agit

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)

查看結果:
-w568github

testObj.b 被從新賦值給 c 後,函數的的執行上下文已經改變,致使輸出爲 undefined。經過上面例子,若是採用 bind 後,則能夠改變 testObj的執行上下文,並能夠把默認值傳遞到參數函數列表.app

假若在 testObj.b內,加入 console.log(arguments), 則能夠看到以下輸出:函數

50 70

bind 函數

bind函數是,Function 原型鏈上的函數,主要是改變函數執行上下文的同時,能夠傳入函數參數值,並返回新的函數this


如圖是mdn上的定義,es5

bind產生的函數就一個偏函數,就是說使用bind能夠參數一個函數,而後接受新的參數。spa

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 中以下截圖:
prototype

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]] 內部屬

規範表達過程簡述以下:code

  • 第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, 不符合規範

查看更規範的實現,點擊這裏

歡迎交流
相關文章
相關標籤/搜索