由 React 性能优化扩展出来的 bind 与闭包的比较(性能)

github 的地址 欢迎 star

前言

最近由于在看一些 react 优化的方面,注意到了之前没有注意到的东西,都知道在 react DOM 事件的绑定方式有几种方式:

  1. 在 constructor 中进行绑定。

  2. 或者用箭头函数(无 this)。

  3. 对于需要动态传参的形式:

    • 可以使用闭包的方式

    • 或者可以直接把处理函数传入子组件,子组建时可以拿到参数,再执行父组件的处理函数就可以了

    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>
        )
    }
}
复制代码

重点介绍了需要传参的方式,对于性能比较是要看具体的环境(和浏览器,平台的优化都有关系),没有什么是绝对性能好的。

看到了这篇博客的结论,就想具体实际比较一下:

如果每次都在 render 里面的 jsx 去 bind 这个方法,会消耗性能,因为每次bind都会返回一个新函数,重复创建静态函数肯定是不合适的(闭包也是这样,但 bind 内部有一系列的算法,比闭包复杂多了)
复制代码

对于 react 中使用 bind 和闭包传参性能的比较

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

react version "16.8.3"

node version v10.15.1

通过 chrome 的 JavaScript Profiler 进行性能分析,发现渲染1千次事件的渲染时间是差不多的(均采用首次刷新渲染)

  1. 首先 bind 方式:

  2. 采用闭包的形式:

  3. 采用不带参数的模式:

可以看到性能上双方相差不多,而 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…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值