5

现象

在我们日常开发当中,我们写一个组件,经常遇到需要传递一个方法给点击事件等等,那么我们经常是怎么做的呢?

class Item extends Component {
    jump = () => {
        this.setState({ xxx: true });
    };
    render() {
        return (
            <div onClick={this.jump}>跳转啰</div>
        );
    }
}

如上代码,因为点击回调使用了this,为了绑定this,使用了箭头函数。或者不使用箭头函数了,用bind进行绑定。

class Item extends Component {
    jump() {
        this.setState({ xxx: true });
    }
    render() {
        // 在render里进行bind
        return (
            <div onClick={::this.jump}>跳转啰</div>
        );
    }
}

亦或者是这样

class Item extends Component {
    constructor() {
        // 在初始化就绑定
        this.jump = this.jump.bind(this);
    }
    jump() {
        this.setState({ xxx: true });
    }
    render() {
        return (
            <div onClick={this.jump}>跳转啰</div>
        );
    }
}

是不是!是不是!我相信以上3种绑定方式起码有90%的占有率!!!

这样的绑定坏处是什么

我们一个一个情况来分析。

箭头函数绑定

问题1:

我们知道使用箭头函数的时候,他实际上该方法是放在了实例上面,而不是prototype上面,举个例子。

class A {
    a = '1'
    func1() {
        
    }
    func2 = () => {};
}
const a = new A();

大家可以试试在控制台看一下a究竟长什么样子,如无意外他应该是介个样子的。

{
    a: '1',
    func2: () => {},
    __proto__: {
        func1: function() {}
    }
}

那么我们想想,假设我们在一个列表item组件上使用箭头函数,实际上他的实例是怎么样的。
就是每一个实例上的箭头函数都不是在prototype公用的而是自己创建一个函数!那么类似在商品列表种的商品item,你们现在还觉得使用箭头函数绑定this吗?

问题2

相信大家都使用过了修饰器了,那么大家想想,修饰器 能修饰箭头函数吗?
答案是不能!
修饰器的输入是什么

function boundMethod(target, key, descriptor) {
    return {
        // 新的descriptor
    };
}

其中target是实例本身,key是被修饰的key,descriptor是该属性的描述。
我们来看一下修饰prototype上的方法跟修饰箭头函数有什么不同。

const testDes = function() {
    return function(target, name, descriptor) {
        return {
            ...descriptor,
            value: function() {
                console.log('descaaaa')
                descriptor.value();
            }
        };
    };
};

class A {
    @testDes(1)
    func1() {

    }
    @testDes(1)
    func2 = () => {
        
    }
}

var a = new A();
a.func1();
a.func2();
console.log(a)

以上例子,我们发现只有a.func1()=才会有打印修饰的字符,修饰器是不能修饰prototype上不存在的属性的。

问题3

大家想一想,如果一个类中使用了箭头函数,那么这个类可以被继承吗?
答案就是不可以了。
因为箭头函数并不声明在prototype上,所以该方法不能在子类上被使用,导致一种奇怪的错觉。

在render里bind绑定

其实这个问题比较明显,我们都知道bind方法是返回一个新的方法,就是在每次render的时候都会创建新的函数,销毁上一次的函数。在render触发比较频繁的时候,就会有非常多没有必要的创建销毁开销。

在constructor里bind绑定

这个方式其实跟箭头函数有相似之处,就是在实例本身再次赋值一个通过bind的方法,也就是多个实例之间各自都有一个方法,并且该类prototype上的方法就冗余了。

怎么解决

既然以上的绑定this都或多或少有不好的地方,但是实际使用我们经常需要绑定this,那么怎么绑定this才是最好的呢?
答案就是使用修饰器进行绑定。
大家可以看一下autobind-decorator这个库,非常简单,使用修饰器进行绑定,就是直接在prototype上进行更改,通过getter/setter绑定this,继承的问题就得以解决了,该方法依旧可以被再次修饰。但是这样的方式依旧会在实例上创建新的getter/setter属性,每一个实例的getter返回的都是同样的fn进行自身实例的this绑定,所以会导致实例之间依旧不共享同一个方法。

export function boundMethod(target, key, descriptor) {
  var fn = descriptor.value;

  var definingProperty = false;
  return {
    configurable: true,
    get: function get() {
      if (definingProperty || this === target.prototype || this.hasOwnProperty(key) || typeof fn !== 'function') {
        return fn;
      }

      var boundFn = fn.bind(this);
      definingProperty = true;
      Object.defineProperty(this, key, {
        configurable: true,
        get: function get() {
          return boundFn;
        },
        set: function set(value) {
          fn = value;
          delete this[key];
        }
      });
      definingProperty = false;
      return boundFn;
    },
    set: function set(value) {
      fn = value;
    }
  };
}

一画先生
83 声望12 粉丝

我司长期招聘前端开发工程师,有意的小伙伴+vx: Mr_yihua