这个回答非常清晰,转载自知乎-dmumatt:
代码一:
// 使用 ES6 的 class 语法
class Cat {
sayThis () {
console.log(this); // 这里的 `this` 指向谁?
}
exec (cb) {
cb();
}
render () {
this.exec(this.sayThis);
}
}
const tom = new Cat();
tom.render(); // 输出结果是什么?
代码二:
const jerry = {
sayThis: function () {
console.log(this); // 这里的 `this` 指向谁?
},
exec: function (cb) {
cb();
},
render: function () {
this.exec(this.sayThis);
},
}
jerry.render(); // 输出结果是什么?
代第一段代码的结果是 undefined
,这和第一节中 React 出现的结果完全一致。这代表 this
指向了 undefined
。其实是 JS 的行为而并非 React。
第二段代码的结果是,你所使用的环境里面的全局对象——在浏览器中就是 window
对象,在 Node.js 中就是 global
对象。
你看到输出结果的时候,一定感到很困惑吧?到底 this
干了什么??
JS 中的 this
this
不指向定义它的函数的那个对象的情形
var name = 'Global'
const fish = {
name: 'Fish',
greet: function() {
console.log('Hello, I am ', this.name);
}
};
fish.greet(); // Hello, I am Fish
const greetCopy = fish.greet;
greetCopy(); // Chrome: Hello, I am Global
// Node.js: Hello, I am undefined
当你使用“点”操作符 .
来调用 greet
函数的时候,fish.greet()
,this
指向了 fish
,fish
正是定义了 greet
方法的那个对象。在这种情况下,我们称 fish
是这个函数的调用者。
事实上,fish.greet
在内存中只是一个普通的函数。不管它是在什么对象中定义的,它都可以和普通的函数一样,赋值给另一个变量,比如前面的 greetCopy
。如果你用 console.log
打印 console.log(fish.greet)
或者 console.log(greetCopy)
,控制台输出的结果都是一样的。
console.log(fish.greet); // function () { … }
console.log(greetCopy); // function () { … }
如果你不用调用者显式地调用一个函数,JS 的解释器就会把全局对象当作调用者。所以 greetCopy()
这个语句在 Chrome 中的行为就和 greetCopy.call(window)
是一样的,在 Node.js 中就和 greetCopy.call(global)
是一样的。
但是有一种例外,如果你使用了严格模式,那么没有显式的使用调用者 的情况下, this
永远不会自动绑定到全局对象上。如果此时你调用 greetCopy
,你就会得到报错,因为这时候 this
不指向任何对象,this
这时候就是 undefined
。
'use strict';
var name = 'Global'
const fish = {
name: 'Fish',
greet: function() {
console.log('Hello, I am ', this.name);
}
};
fish.greet(); // Hello, I am Global
const greetCopy = fish.greet;
greetCopy(); // Uncaught TypeError: Cannot read property 'name' of undefined
注意,在上面这种情况下,greetCopy
在 Chrome 中和在 Node.js 中行为不太一样。正如你看到的那样,在 Node.js 中,this.name
的值是 undefined
。在浏览器中,如果你在最外层作用于定义了一个变量,它就会自动变成全局对象的一个属性。相反,在 Node.js 中,最外层对象不会自动被赋给全局对象,除非你显式地使用 global.name = 'Global'
。
如果我想使用另一个对象作为调用者来调用 fish.greet
,我该怎么做?这时候就要用到 Function.prototype.call
。
// 前面代码一的上下文
const pig = {
name: "Pig"
};
fish.greet.call(pig); // Hello, I am Pig
call
方法强制性地把 fish.greet
的调用者绑定到了 pig
对象上,pig
这时候用作 this
方法的参数。
回调函数中的 this
回调函数简单的来说,就是把一个函数作为另一函数的参数,并且在另一个函数执行的时候调用这个函数。看一下下面的例子:
var name = 'Global';
const matt = {
name: "Matt",
sayName: function () {
console.log(this.name);
}
}
function exec(cb) {
cb();
}
exec(matt.sayName); // `Global` (浏览器), `undefined` (Node.js)
如果你阅读了上面的章节,这个输出结果对你来说就很好理解了。我们来看一下在解释器调用 exec()
函数的时候都做了什么。
当这个程序运行到 exec
函数的时候,实参 matt.sayName
被传递给了形参 cb
。这就和前面的章节中说的赋值语句的情况类似:const greetCopy = fish.greet;
。这里 cb
在调用的时候并没有显式的调用者,所以此时,this
在非严格模式下就会指向全局对象,在严格模式下就会指向 undefined
。
我们来看一下另一个很相似的情形。思考一下结果是什么?
const jerry = {
sayThis: function () {
console.log(this); // `this` 指向什么?
},
exec: function (cb) {
cb();
},
render: function () {
this.exec(this.sayThis);
},
}
jerry.render(); // 输出结果是什么?
是的!你在上一章中看到了这个例子了。你现在一定知道了为什么输出结果是全局对象了吧!
即使我们使用点操作符.
来显式地调用 exec
方法,然而 cb
函数仍然没有一个显式的调用者。因此,你就会看到 this
指向了全局对象。
下面这句话非常重要!一般人可能不知道
如果你使用了 ES6 的 class 语法,所有在 class 中声明的方法都会自动地使用严格模式
当你使用 onClick={this.handleClick}
来绑定事件监听函数的时候,handleClick
函数实际上会作为回调函数,传入 addEventListener()
。这就是为什么你在 React 的组件中添加事件处理函数为什么会得到 undefnied
而不是全局对象或者别的什么东西。
箭头函数
箭头函数使得 this
更简单和直接。
关于箭头函数的资料其实很多,在这里我就不多说了。你只要记住一个规则就足够了,如果你仔细阅读了上文,你应该能理解这个规则
this
永远绑定了定义箭头函数所在的那个对象