Do ES2015 Classes “not autobind”?

2020-06-04 03:55发布

问题:

I have been using React for a while now, and I have become comfortable with the concept that I must manually bind my component methods to my component instance, as React made the decision to be "idiomatic" in not autobinding:

Therefore we decided not to have this built-in into React's class model. You can still explicitly prebind methods in your constructor if you want.

class Counter extends React.Component {
  constructor() {
    super();
    this.tick = this.tick.bind(this);
  }
  tick() {
    ...
  }
  ...
}

- https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html

We can clearly see the effects of this in this example http://jsbin.com/citafaradu/2/edit?js,console,output , from this similar question: How to properly bind current object context in ES6 using babelify

However, someone asked me recently whether there was any difference between prototype based classes and new, ES2015 classes. Intuitively, this answer should be an emphatic "no!", as the resulting instance objects will naturally have normal prototypes and behave... well, like JS objects! And furthermore, what would be the use of instance methods not bound to the instance?

I tried to search for any indication that this would be "idomatically" true of es6 classes, but all I turned up were other questions from React devs, with answers like this:

React's ES6 classes have no autobinding. This is documented here: https://facebook.github.io/react/docs/reusable-components.html#no-autobinding

The reason is that javascript's ES6 classes have no autobinding neither[sic]. React tries to not reinvent stuff that is already in javascript. ES5 has no nice syntax for classes, so React had to invent it's own classes. But now with ES6 classes we can just use standard javascript.
- "cody", https://github.com/facebook/react/issues/4065

Now I was really confused. Was this perhaps a trick of the JSX transpilation? Taking a look at the output of the render method of the prior example:

{
    key: "render",
    value: function render() {
      return React.createElement("div",null,
        React.createElement("input", { 
          type: "text", onChange: this.handleBindedChange 
        }),
        React.createElement("br", null),
        React.createElement("input", { 
          type: "text", onChange: this.handleUnbindedChange 
        }),
        React.createElement("br", null),
        React.createElement("p",null,"Result: ",this.state.demo)
     );
    }
 }

No dice here either - The babel output uses Object.defineProperty, which will absolutely bind functions added with it to the object they are attached to.

And so, I am at a loss. Most of the responses I found around this are older than the final es2015 spec - as I cannot find anything about it in the spec itself, was there a change that would have invalidated the React team's approach? Is this a strange artifact of transpilation that I have somehow misinterpreted? Is react doing something wacky behind the scenes to cause this? If so, why would they repeatedly claim that this was done to match the ES2015 standard? And if not, what is causing the behavior seen in the first example given?

回答1:

I had similar questions. Methods within your class will be able to reference other methods in the same class, because they are part of the same context (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this).

This example shows that methods in a class can access properties on this without being bound in the constructor: http://jsbin.com/tapokotahi/1/edit?js,console,output. The renderElements method is not bound, but is accessing this.state.

The class methods need to be bound (or defined as arrow functions) when they are passed into event handlers, because the execution context changes from that of the class to that of the event handler.

I agree it seems confusing when we read the React docs and they tell us we need to bind the methods in the constructor, but that is only necessary when passing the methods to React's event handlers such as onClick.



回答2:

The reason why React keeps mentioning autobinding is because React.createClass was autobinding all methods. And that method was the only way to create components.

People, especially those not that familiar with JavaScript, got used to passing methods to other components and this would "magically" work. This feature disappeared with using native ES6 classes, so they needed to emphasize the difference.

But yes, ES6 classes are basically just syntactic sugar for constructor functions + prototype. The methods are not bound to the object:

class Foo {
  bar() {}
}

const foo = new Foo();

console.log(foo.hasOwnProperty('bar')); // false
console.log(typeof Foo === 'function'); // true
console.log(Foo.prototype.hasOwnProperty('bar')); // true



回答3:

You can make you ES6 class methods auto-bind with a little boiler-plate in the constructor:

function autobind() {
  for (let prop of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {
    if (prop === 'constructor' || typeof this[prop] !== 'function') continue;
    this[prop] = this[prop].bind(this);
  }
}

class Test {
  constructor() {
    autobind.call(this);
    this.message = 'hello all!';
  }
  method1(){ return this.method2(); }
  method2(){ console.log(this.message);}
}
let test = new Test();
let b = test.method1;
b();

UPDATE:

I have since found a Babel Plugin that translates the upcomping ES Class Fields & Static Properties which you can use for functions that need to be bound to the this

class Bork {
    //Property initializer syntax
    instanceProperty = "bork";
    boundFunction = () => {
      return this.instanceProperty;
    }

    //Static class properties
    static staticProperty = "babelIsCool";
    static staticFunction = function() {
      return Bork.staticProperty;
    }
}

let myBork = new Bork;

//Property initializers are not on the prototype.
console.log(myBork.__proto__.boundFunction); // > undefined

//Bound functions are bound to the class instance.
console.log(myBork.boundFunction.call(undefined)); // > "bork"

//Static function exists on the class.
console.log(Bork.staticFunction()); // > "babelIsCool"



回答4:

Auto Binding.

This library is working fine for me in Node.js. But I haven't sure about this will work too for React.js.

const AUTO_BIND = require("auto-bind");

class Programmer {

  constructor(name, age) {
    this.Name = name;
    this.Age = age;
    AUTO_BIND(this);
    //* For React component
    //* AUTO_BIND.react(this);
  }

  ShowName() {
    console.log(`My name is ${this.Name}`);
  }

  ShowAge() {
    console.log(`My age is ${this.Age}`);
  }

  ShowDetail() {
    this.ShowName();
    this.ShowAge();
  }
}
const programmer = new Programmer("M. Hamza Rajput", 25);

const { ShowDetail } = programmer;

ShowDetail();

console.log(programmer.hasOwnProperty('ShowDetail')); // true
console.log(typeof programmer === 'object'); // true
console.log(Programmer.prototype.hasOwnProperty('ShowDetail')); // true


回答5:

I ran into problems with methods not autobinding to classes. I made a library to help: https://www.npmjs.com/package/auto-bind-inheritance

I use it and appreciate any feedback to improve it. It was based on several other npm packages, which I believe I improved upon.