React中的事件函数为什么要bind-this

30次阅读

共计 3026 个字符,预计需要花费 8 分钟才能阅读完成。

原文在我的 github 中,欢迎订阅

我们平常写 React 是这样的:

class HelloMessage extends React.Component {constructor(props) {super(props);
    this.handleClick = this.handleClick.bind(this); // 绑定 this
  }
  
  handleClick(){console.log( this)
  }

  render() {const { count} = this.state;
    
    return (<button onClick={ this.handleClick} >Hello</button>
    );
  }
}

ReactDOM.render(<HelloMessage/>,document.getElementById('root')
);

上面代码可以完好运行,handleClick 绑定好 this 后,打印如下:

HelloMessage {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: Hr, …}
    context: {}
    props: {}
    refs: {}
    state: null
    updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
    _reactInternalFiber: Hr {tag: 1, key: null, elementType: ƒ, type: ƒ, stateNode: HelloMessage, …}
    __proto__: b

我们把 .bind(this) 去掉,再打印一次:

undefined

OK,果然 this 就没有任何东西了。

但是如果把 handleClick 改为箭头函数的写法,就不需要 bind(this) 了:

 handleClick = () => {console.log( this)
 }

更改后,this 也能正常打印出来。

我们先看一下 render 方法中被 Babel 编译后的样子:

  render() {
    return React.createElement(
      'button',
      {onClick: this.handleClick},
      'Hello'
    );
  }

React 通过 React.createElement 方法模拟 document.createElement 来创建 DOM(当然 React 创建的是虚拟 DOM)。属性中 onClick 指向 this.handleClick 方法,看起来都没有问题。

下面我们不用 React 来实现上面 this 的现象:

// 创建 DOM
function createElement(dom, params) {var domObj = document.createElement(dom);
  domObj.onclick = params.onclick;
  domObj.innerHTML = 'Hello';
  document.body.appendChild(domObj);
}

// Demo 类
class Demo {handleClick() {console.log(this);
  }

  render() {
    createElement('button', {onclick: this.handleClick});
  }
}

// 执行 render
new Demo().render();

小伙伴们猜猜,这里的 this 指向什么(手动滑稽)?

来打印结果:

<button>Hello</button>

啊哈哈哈啊,没错 this 并不是 undefined,也没有指向 Demo 对象,而是创建的 button DOM 对象本身。

现在再把 handleClick 改为箭头函数:

// Demo 类
class Demo {handleClick = () => {console.log(this);
  }

  render() {
    createElement('button', {onclick: this.handleClick});
  }
}

// 执行 render
new Demo().render();

来看看这次打印的 this:

Demo {handleClick: ƒ}
    handleClick: () => { console.log(this); }
    __proto__: Object

卧槽,居然指向了 Demo 对象,这样就可以在 handleClick 里随意访问 Demo 里的属性和方法了!

说明

其实,如果你 JavaScript 基础够扎实,我想你是不会点进来看这篇文章的。
这里的问题无非就牵扯到两个点:

  1. this 指向问题
  2. 箭头函数的特性

this 指向问题

我打赌,上面的 Demo 实例很多人看的云里雾里的,所有我把它转成 ES5 的写法:

function createElement(dom, params) {var domObj = document.createElement(dom);
  domObj.onclick = params.onclick;
  domObj.innerHTML = 'Hello';
  document.body.appendChild(domObj);
}

// 创建 Demo 类,相当于 class Demo {} 的写法
function Demo() {}

// 在原型上挂载 handleClick 方法
Demo.prototype.handleClick = function() {console.log(this);
};

Demo.prototype.render = function() {
  createElement('button', {onclick: this.handleClick});
};

new Demo().render(); // 运行 render 方法

打印结果:

<button>Hello</button>

严重注意:在ES6 class 内定义方法时,如果不是箭头函数,方法是挂载在 prototype 原型对象上的!

那么下面,把 handleClick 用箭头函数的方式写出来:

function createElement(dom, params) {var domObj = document.createElement(dom);
  domObj.onclick = params.onclick;
  domObj.innerHTML = 'Hello';
  document.body.appendChild(domObj);
}

// 创建 Demo 类,相当于 class Demo {} 的写法
function Demo() {
    // 相当于箭头函数 不了解的同学请恶补一下吧
    var _this = this;
    this.handleClick = function(){console.log( _this);
    }
}

Demo.prototype.render = function() {
  createElement('button', {onclick: this.handleClick});
};

new Demo().render(); // 运行 render 方法

这时候再打印:

Demo {handleClick: ƒ}
    handleClick: ƒ ()
    __proto__: Object

厉害了哈~ this 指向了想要的对象环境中。

总结

通过以上代码解析,能知道 React 中,在不适用箭头函数时,需要通过 bind 将函数内 this 指向当前对象才能正常访问。

而使用箭头函数时,由于箭头函数的特性,函数内的 this 就是当前对象上下文,所以不需要 bind 来指向。

如果哪里有不对的地方,欢迎指正,感谢!

正文完
 0