學習bind原始碼,比較bind的方式繫結函式在在記憶體使用上優於箭頭函式

Jayden.李發表於2018-02-07

前言

使用ES的class,到底是使用箭頭函式更有效率,還是在構造器函式(constructor)中使用bind函式給函式繫結context更加有效率?在Demystifying Memory Usage using ES6 React Classes文章中,給出瞭如下的結論。

class properties

上圖是在class中使用箭頭函式建立類的方法。作者稱其為class properties. 可以看得出,使用箭頭函式在類中定義的handler方法,在其的每一個例項中都會有一個獨立的handler函式。當例項的數量不多的時候,這種方式不會帶來很大的記憶體浪費,但當例項成千上萬的時候,這種方式就會顯得在記憶體使用上效率很低。若是能夠共享類上的handler豈不是更好。

constructor bind

而使用構造器函式繫結,則能夠更加有效的減少記憶體的使用。通過在構造器函式裡面繫結函式,如下程式碼:

class MyClass extends Component {
  constructor() {
    super();
    this.state = { clicks: 0 };
    this.handler = this.handler.bind(this);
  }
  handler() {
    this.setState(({ clicks }) => ({ clicks: clicks + 1 }));
  }
  render() {
    const { clicks } = this.state;
    return(
      <button onClick={this.handler}>
        {`You've clicked me ${clicks} times`}
      </button>
    );
  }
}
複製程式碼

每一個例項裡面雖然都有handler方法(this.handler,不是MyClass.prototype.handler),但每個例項中的this.handler實際上都共享MyClass.prototype.handler的函式體。

那麼這是如何做到的呢? Bind函式MDN Polyfill實現:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) { //oThis 代表object this
    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,
                 // 獲取呼叫時(fBound)的傳參.bind 返回的函式入參往往是這麼傳遞的
                 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;
  };
}
複製程式碼

從上面的程式碼可以看出,我們在構造器中使用bind繫結函式以後,實際上返回了一個函式(fBound),所返回的函式的函式體和待繫結(MyClass.prototype.handler)的函式體是不同的。所返回的函式只是在函式體內呼叫了待繫結的函式。換句話說,通過在構造器中使用bind繫結函式以後,所生成的例項中,都會有一個獨立的fBound,函式體相對於待繫結的函式較小,而每個例項中的fBound在被呼叫時都會共享待繫結的函式(MyClass.prototype.handler)

結論

通過對比可以發現,使用構造器bind的方法,每個例項都可以有效地共享函式體,從而更加有效的使用記憶體。但當要繫結的函式較多時,這種方法又顯得相對的枯燥和無聊。所以,我認為在知道例項不多,函式體不大的前提下,使用箭頭函式更加快捷。

相關文章