關於JS函數的bind

https://friskfly.github.io/2016/03/24/about-function-bind-in-js/git

昨天被人問到js的bind的做用是什麼?github

這個倒還能回答出來,bind 以後返回一個新的函數,這個函數能夠保持傳遞的this上下文。瀏覽器

接着又問了,那麼bind兩次不一樣的上下文會怎樣?閉包

這個一會兒就蒙了,由於平時也沒這麼用過,因而開始查一下資料。app

首先在瀏覽器中測試一下。函數

function test(){
  console.log(this.a)
}
var bind1 = test.bind({a:1}) //第一次 bind
var bind2 = bind1.bind({a:2}) // 第二次 bind
bind1()
bind2()

結果以下測試

1
1

能夠看到第二次bind並無能再改變this的值。this

查一下MDN,Function.prototype.bind() , 並無解釋bind兩次會怎樣。 可是他提供了一個Polyfill,能夠了解下bind的實現。prototype

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

能夠看到MDN提供的polyfill的實現是oThis作爲參數傳進來,返回一個新的函數,這個時候函數是個閉包,仍然能夠訪問oThis變量,而後調用call/apply來實現指定的上下文。 這種狀況下,若是bind兩次,至關於閉包套閉包,無論套幾層,值都是第一次保存的this值。 即上面polyfill的 oThis 變量。code

光看polyfill是不夠了,由於並不知道polyfill實現是否是標準。因此仍是要看下規範。這裏咱們參考下 ES2015文檔

能夠直接看到 19.2.3.2 節 NOTE 2,If Target is an arrow function or a bound function then the thisArg passed to this method will not be used by subsequent calls to F. 若是調用bind的是一個箭頭函數或者是已經bind過的函數(bound function),那麼再次bind是不會起做用的。 能夠看到規範已經定義了這樣的行爲產生的結果,咱們能夠直接記住這個結論。

可是這裏值得注意的是,咱們看到規範定義的bind操做 和 MDN 上提供的polyfill並不一致。polyfill沒有徹底實現ES2015規定的bind。

好比ES2015 規定了 bound function 的length 和 name 行爲。

Let targetHasLength be HasOwnProperty(Target, "length").
ReturnIfAbrupt(targetHasLength).
If targetHasLength is true, then
  Let targetLen be Get(Target, "length").
  ReturnIfAbrupt(targetLen).
  If Type(targetLen) is not Number, let L be 0.
  Else,
    Let targetLen be ToInteger(targetLen).
      Let L be the larger of 0 and the result of targetLen minus the number of elements of args.
Else let L be 0.

這裏會規定bound function 的 length 屬性,應該和bind以前的length一致。

再看name 的行爲

Let targetName be Get(Target, "name").
ReturnIfAbrupt(targetName).
If Type(targetName) is not String, let targetName be the empty string.
Perform SetFunctionName(F, targetName, "bound").

這裏規定bound function 的name 應該走 SetFunctionName 方法,而這裏SetFunctionName以後的返回值應該是 bound字符串 + 空格 + 原先函數的name

......忽略了一些描述
prefix即bound 字符串
If prefix was passed, then
  Let name be the concatenation of prefix, code unit 0x0020 (SPACE), and name.

function a(){}
var b = a.bind()
console.log(b.name)

結果應該是

bound a

而 MDN 的 polyfill 是沒有實現這些細節的,因此用bind的時候若是依賴於這些,是要注意的。

相關文章
相關標籤/搜索