React this 问题

1,506 阅读3分钟

我是 React 初学者。this是 JavaScript 学习者的难题,虽然可能已经熟知“它和一般的 OOP 语言不同,this是函数的执行环境/上下文,与它在哪里定义没有关系”这样的理论,但是遇到复杂一些的情况还是会蒙(再加上引入箭头函数后)。

在一般的初学教程里,都会提及一个绑定事件时的this问题。比如在《React 小书》中:

class Title extends Component {
  handleClickOnTitle (e) {
    console.log(this)
  }

  render () {
    return (
      <h1 onClick={this.handleClickOnTitle}>React 小书</h1>
    )
  }
}

一般在某个类的实例方法里面的this指的是这个实例本身。但是你在上面的handleClickOnTitle中把this打印出来,你会看到thisnull或者undefined

这是因为 React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClickOnTitle),而是直接通过函数调用(handleClickOnTitle),所以事件监听函数内并不能通过this获取到实例。

如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法bind到当前实例上再传入给 React.js。

而程墨在教程中提到,因为每次都要bind挺麻烦的,也可以用类的成员变量和箭头函数来定义。类似于:

class Title extends Component {
  handleClickOnTitle = (e) => {
    console.log(this)
  }

  render () {
    return (
      <h1 onClick={this.handleClickOnTitle}>React 小书</h1>
    )
  }
}

我现在就是想看看这种情况下直接调用函数时,this是什么情况。

首先,简化一下类的定义:

class A {
    constructor() {
      this.one = function() {
        console.log('one', this);
      };
      this.two = () => { console.log('two', this); };
    }
    three() {
      console.log('three', this);
    }
}
  
var obj = new A();

可以知道obj.one()/obj.tow()/obj.three()this全是obj本身。现在改一下:

var aOne = obj.one;
var aTwo = obj.two;
var aThree = obj.three;
  
aOne(); // one undefined
aTwo(); // two A { one: [Function], two: [Function] }
aThree(); // three undefined

第二个结果依旧是obj本身,原因是箭头函数默认绑定外层this(层:即 js 祖传函数作用域)。在 Babel 中转换为 ES5(我稍微精简了一下,原本是包在一个 IIFE 中的):

function A() {
    var _this = this;
    this.one = function() {
        console.log("one", this);
    };
    this.two = function() {
        console.log("two", _this);
    };
}
A.prototype.three = function three() {
    console.log("three", this);
};

所以,two中的this并非真正的this,而是一个来自函数外的变量_this,它的值等于this,而这个thisvar obj = new A()执行的时候就是obj对象了。

当然类也可以定义成:

class A {
    one = function() {
        console.log('one', this);
    };
    two = () => { console.log('two', this); };
    three() {
      console.log('three', this);
    }
}

不过要注意,“类属性”需要 Node.js v12.0 以上才支持,在 Babel 中需要使用 plugin-proposal-class-properties 才能使用。Babel 转换的 ES5 代码略有不同,定义了一个_defineProperty函数,不过本质上还是和上面的代码片段一样的。

结论

ES6 class就是function的语法糖,而箭头函数的this与外层this一致,多外层呢?就是到最近的 function 函数作用域为止的外层。

var a = {
    b: () => { //箭头函数不可以作为 constructor!
        console.log(this);
    },
    c: {
        d: () => {
            console.log(this);
        }	
    },
    f: () => {
        return () => {
            console.log(this); //无论套多少层箭头函数,this 还是最外层的(定义 a 所在的层)this(undefined)
        }	
    },
    e: function() {
        this.x = 1;
        console.log(this);
    }
};

a.b(); //undefined/window
a.c.d(); //undefined/window
a.f()(); //undefined/window
a.e(); // {b: f, c: {...}, f: f, e: f, x: 1}, this 是 a,x 添加到了 a 上
new a.e() // e {x: 1}