深入掌握 JavaScript 中的 this 关键字
this 的概念
this 是什么
this
是 JavaScript
中一个很重要的关键字,它表示函数执行时,自动生成的一个内部对象,只能在函数内部使用。
this 的指向决定
this
的指向,始终坚持一个原理:this
永远指向最后调用它的那个对象。
this 各种情况下的指向
全局环境中 this
很多时候都是在 window
下这个对象进行调用的,
const a = 1;
function b() {
console.log(this.a); // 1
}
当使用 setTimeout
等函数时,this
指向的是 window
对象。
setTimeout(function(){
console.log(this); // window
}, 1000);
函数内部的 this
var name = "windowsName";
function a() {
var name = "Hit";
console.log(this.name); // windowsName
console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this) // outer: Window
对象方法调用中的 this
var name = "windowsName";
var a = {
name: "Hit",
fn : function () {
console.log(this.name); // Hit
}
}
a.fn();
构造函数中的 this
当使用 new
关键字来调用函数时,this
指向的是新创建的对象。
class createImageBitmap {
constructor() {
this.name = 'createImageBitmap';
}
}
var obj = new createImageBitmap();
console.log(obj.name); // createImageBitmap
事件回调中的 this
当使用 addEventListener
等函数时,this
指向的是元素对象。
document.getElementById('xxx').addEventListener('click', function() {
console.log(this) // 这里的 this 指向的是元素对象
})
强制设置 this 指向的方法
使用 call、apply、bind
当使用 apply()
或 call()
函数时,this
指向的是指定的对象。
var a = {
name : "Hit",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}
};
a.func2() // Hit
var a = {
name : "Hit",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.call(a),100);
}
};
a.func2() // Hit
当使用 bind()
函数时,this
指向的是 bind()
函数的第一个参数。
var a = {
name : "Hit",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.bind(a)(),100);
}
};
a.func2() // Hit
使用箭头函数
当使用箭头函数时,this
指向的是定义它的那个对象。
var name = "windowsName";
var a = {
name : "Hit",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( () => {
this.func1()
},100);
}
};
a.func2() // Hit
使用 new.target
function Person(name, age) {
// 检测是否被 new 调用
if (!new.target) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
// 不使用 new 调用
let p1 = Person('Jack', 20);
console.log(p1.name); // Jack
// 使用 new 调用
let p2 = new Person('John', 18);
console.log(p2.name); // John
在 Person 构造函数中检查了 new.target,如果检测到不是通过 new 关键字调用的,就使用 new 重新调用了一次,这样就修正了 this 的指向问题。
这样我们在使用 Person 的时候,不管有没有使用 new,最后都可以正确地获取到实例上的属性,而不需要关心 this 指向的问题。
前端框架中的 this
React 中的 this 指向
类组件
在 React 组件中,如果出现下面这些情况,this 很可能会指向全局对象或者 undefined:
- 在组件类中定义的事件处理函数中:
class Foo extends React.Component {
handleClick() {
console.log(this); // this 为 undefined
}
render() {
return <button onClick={this.handleClick}>Click</button>
}
}
- 在组件类中定义的定时器或异步请求回调函数中:
class Foo extends React.Component {
componentDidMount() {
setTimeout(function() {
console.log(this); // this 为 window
}, 1000);
}
render() {
return <div>Foo</div>
}
}
- 将组件的方法作为参数传递给子组件时:
class Parent extends React.Component {
btnClick() {
// ...
}
render() {
return <Child onClick={this.btnClick} />
}
}
这些情况下,回调函数或传递的方法会失去组件实例的 this 绑定,进而指向全局对象或 undefined。
原因在于 JavaScript 中的 this 是动态绑定的,当函数被作为回调或者传参调用时,this 绑定情况就可能会出错。
所以为了避免上述情况,需要注意使用箭头函数或者 bind 方法来正确绑定 this。
解决方案:
为了确保在React组件方法中正确地使用this
,有几种常见的方式:
-
使用箭头函数: 箭头函数在定义时会捕获所在上下文的
this
值,因此它们在React组件中经常用于处理事件回调。例如:class MyComponent extends React.Component { handleClick = () => { // 在这里,this 指向 MyComponent 的实例 console.log(this.props); } render() { return ( <button onClick={this.handleClick}>Click me</button> ); } }
-
在构造函数中绑定方法: 可以在组件的构造函数中使用
bind
方法显式地绑定方法的this
。这通常在类的构造函数中执行,确保方法在整个组件实例的生命周期中都有正确的this
值。class MyComponent extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { // 在这里,this 指向 MyComponent 的实例 console.log(this.props); } render() { return ( <button onClick={this.handleClick}>Click me</button> ); } }
-
使用公共类字段语法(class field syntax): 这是在类中定义方法的一种新的语法,它可以确保方法在声明时被绑定到实例。
class MyComponent extends React.Component { handleClick = () => { // 在这里,this 指向 MyComponent 的实例 console.log(this.props); } render() { return ( <button onClick={this.handleClick}>Click me</button> ); } }
函数式组件
函数式组件没有实例,所以 this
的指向都是 undefined
。
Vue 中的 this 指向
在 Vue
中,一个很常见的操作场景是这样的。
methods: {
handleClick() {
const _this = this
document.addEventListener('click', function() {
// 这里使用 _this 来访问和调用组件的属性和方法
_this.someMethod()
})
}
}
因为正常场景下在 document.addEventListener
回调函数中,this
指向的是 window
对象,而在 Vue
中,this
指向的是组件实例,所以需要使用 _this
来访问和调用组件的属性和方法。
如果不想重新声明个 this
变量来存储,那么可以使用如下方法。
- 使用箭头函数:
methods: {
handleClick() {
document.addEventListener('click', () => {
this.someMethod(); // this 仍然指向当前组件
});
}
}
箭头函数会继承外围函数的 this
指向,所以回调函数中的 this
也会是组件实例。
在回调函数中直接绑定 this
:
methods: {
handleClick() {
document.addEventListener('click', function() {
this.someMethod();
}.bind(this));
}
}
使用 .bind(this)
来手动绑定 this
的指向。
call apply bind 之间的区别
call
fun.call(thisArg[, arg1[, arg2[, ...]]])
apply
fun.apply(thisArg, [argsArray])
所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
var a ={
name : "Hit",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.apply(a,[1,2]) // 3
var a ={
name : "Hit",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.call(a,1,2) // 3
bind
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
var a ={
name : "Hit",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)() // 3
小结
this 的指向性问题与它的调用方式相关,在某些场景下为了 this
的正确指向,我们可以使用 箭头函数
, call
,apply
, bind
, new
关键字等方法去强制绑定 this
。