由 React 性能優化擴展出來的 bind 與閉包的比較(性能)

github 的地址 歡迎 starhtml

前言

最近因爲在看一些 react 優化的方面,注意到了以前沒有注意到的東西,都知道在 react DOM 事件的綁定方式有幾種方式:node

  1. 在 constructor 中進行綁定。react

  2. 或者用箭頭函數(無 this)。git

  3. 對於須要動態傳參的形式:github

    • 可使用閉包的方式web

    • 或者能夠直接把處理函數傳入子組件,子組建時能夠拿到參數,再執行父組件的處理函數就能夠了算法

class App extends Component {
    removeCharacter = index => () => {
        const {list} = this.state;
        list.splice(index, 1);
        this.setState({
            list
        })
    }

    render() {
        return (
            <div>
               {
                    this.state.list.map((value, index) =>(
                        <div 
                            onClick={this.removeCharacter(index)}
                            key={value.id}
                            data={value}
                        >
                            點擊我
                        </div>
                    ))
               }
            </div>
        )
    }
}

// 子組件處理的方式
class OtherApp extends Component {
    removeCharacter = index => {
        const {list} = this.state;
        list.splice(index, 1);
        this.setState({
            list
        })
    }
    render() {
        return (
            <div>
                {
                    this.state.list.map((value, index) =>(
                        <Child 
                            onClick={this.removeCharacter}
                            index={index}
                            key={value.id}
                            data={value}
                        />
                    ))
                }
            </div>
        )
    }
}

class Child extends Component {
    handleClick = () => {
        const { index, onClick} = this.props;
        onClick(index)
    }
    render() {
        return (
            <div onClick={this.handleClick}>
                {this.props.data}
            </div>
        )
    }
}
複製代碼

重點介紹了須要傳參的方式,對於性能比較是要看具體的環境(和瀏覽器,平臺的優化都有關係),沒有什麼是絕對性能好的。chrome

看到了這篇博客的結論,就想具體實際比較一下:瀏覽器

若是每次都在 render 裏面的 jsx 去 bind 這個方法,會消耗性能,由於每次bind都會返回一個新函數,重複建立靜態函數確定是不合適的(閉包也是這樣,但 bind 內部有一系列的算法,比閉包複雜多了)
複製代碼

對於 react 中使用 bind 和閉包傳參性能的比較

Chrome Version 72.0.3626.119 (Official Build) (64-bit)bash

react version "16.8.3"

node version v10.15.1

經過 chrome 的 JavaScript Profiler 進行性能分析,發現渲染1千次事件的渲染時間是差很少的(均採用首次刷新渲染)

  1. 首先 bind 方式:

    bind方式

  2. 採用閉包的形式:

    clipboard.png

  3. 採用不帶參數的模式:

clipboard.png

能夠看到性能上雙方相差很少,而 Stack Overflow 上的 stackoverflow.com/questions/1… 以及在 chrome 中各個版本 bind 和 closure 以及 proxy 性能測試(點擊)發現 bind 是要比 closure 慢的多, 對於bind的實現以下:

Function.prototype.bind = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
複製代碼

發現內部的實現也是閉包!固然這個不是完整的處理,從實現上 bind 是對閉包的封裝,可讀性來講 bind 好,所以 bind 是要比 closure 慢的,可是 v8 作了優化,致使在 react 中差別也不是很大。

v8 裏面的實現(來自stackoverflow.com/questions/1…),尚未查看最新的實現。

function FunctionBind(this_arg) { // Length is 1.
  if (!IS_SPEC_FUNCTION(this)) {
    throw new $TypeError('Bind must be called on a function');
  }
  var boundFunction = function () {
    // Poison .arguments and .caller, but is otherwise not detectable.
    "use strict";
    // This function must not use any object literals (Object, Array, RegExp),
    // since the literals-array is being used to store the bound data.
    if (%_IsConstructCall()) {
      return %NewObjectFromBound(boundFunction);
    }
    var bindings = %BoundFunctionGetBindings(boundFunction);

    var argc = %_ArgumentsLength();
    if (argc == 0) {
      return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
    }
    if (bindings.length === 2) {
      return %Apply(bindings[0], bindings[1], arguments, 0, argc);
    }
    var bound_argc = bindings.length - 2;
    var argv = new InternalArray(bound_argc + argc);
    for (var i = 0; i < bound_argc; i++) {
      argv[i] = bindings[i + 2];
    }
    for (var j = 0; j < argc; j++) {
      argv[i++] = %_Arguments(j);
    }
    return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
  };

  %FunctionRemovePrototype(boundFunction);
  var new_length = 0;
  if (%_ClassOf(this) == "Function") {
    // Function or FunctionProxy.
    var old_length = this.length;
    // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
    if ((typeof old_length === "number") &&
        ((old_length >>> 0) === old_length)) {
      var argc = %_ArgumentsLength();
      if (argc > 0) argc--;  // Don't count the thisArg as parameter. new_length = old_length - argc; if (new_length < 0) new_length = 0; } } // This runtime function finds any remaining arguments on the stack, // so we don't pass the arguments object.
  var result = %FunctionBindArguments(boundFunction, this,
                                      this_arg, new_length);

  // We already have caller and arguments properties on functions,
  // which are non-configurable. It therefore makes no sence to
  // try to redefine these as defined by the spec. The spec says
  // that bind should make these throw a TypeError if get or set
  // is called and make them non-enumerable and non-configurable.
  // To be consistent with our normal functions we leave this as it is.
  // TODO(lrn): Do set these to be thrower.
  return result;
複製代碼

咱們在 bind 的實現方法裏面能夠看到一些額外的開銷,如'%_IsConstructCall()',這些額外的東西是爲了實現 bind 的規範,這也形成了在大多數狀況下 bind 比簡單的閉包慢的狀況。 另外一方面,bind 方法也有稍微的不一樣,使用 Function.prototype.bind 創造的函數沒有原型屬性或者是 [[Code]], [[FormalParameters]], 和 [[Scope]] 內部屬性。

總之,在 bind 和 closure 不成爲性能瓶頸的時候,優先考慮可讀性,儘可能保證代碼的簡潔

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!

參考

  1. wulv.site/2017-07-02/…
  2. stackoverflow.com/questions/1…
  3. github.com/mqyqingfeng…
  4. developers.google.com/web/tools/c…
相關文章
相關標籤/搜索