在JavaScript中,this的指向一直是面试的热点问题,这篇文章就带大家彻底搞懂this的指向问题,面试遇到this问题不再害怕。
1.this的定义
在绝大多数情况下,函数的调用方式决定了this的值,this不能在执行期间被赋值,并且在每次函数调用的时候,this的值也可能不同。此外,在严格模式和非严格模式之间this的值也会有差别。
2.为什么使用this
在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:
- 常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在
类的方法
中。 - 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
- 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。
使用this有什么意义呢?下面的代码中,我们通过对象字面量创建出来一个对象,当我们调用对象的方法时,希望将对象的名称一起进行打印。
如果没有this,那么我们的代码会是下面的写法:
- 在方法中,为了能够获取到name名称,必须通过obj的引用(变量名称)来获取。
- 但是这样做有一个很大的弊端:如果我将obj的名称换成了info,那么所有的方法中的obj都需要换成info。
js
var obj = {
name: "word",
running: function() {
console.log(obj.name + " playing");
},
eating: function() {
console.log(obj.name + " drinking");
},
studying: function() {
console.log(obj.name + " studying");
}
}
事实上,上面的代码,在实际开发中,我们都会使用this来进行优化:
- 当我们通过obj去调用playing、drinking、studying这些方法时,this就是指向的obj对象
js
var obj = {
name: "word",
running: function() {
console.log(this.name + " playing");
},
eating: function() {
console.log(this.name + " drinking");
},
studying: function() {
console.log(this.name + " studying");
}
}
所以我们会发现,在某些函数或者方法的编写中,this可以让我们更加便捷的方式来引用对象,在进行一些API设计时,代码更加的简洁和易于复用。
当然,上面只是应用this的一个场景而已,开发中使用到this的场景到处都是,这也是为什么它不容易理解的原因。
3.this指向什么
我们先说一个最简单的,this在全局作用域下指向什么?
- 这个问题非常容易回答,在浏览器中测试就是指向window
- 所以,在全局作用域下,我们可以认为this就是指向的window
js
console.log(this); // window
var name = "word";
console.log(this.name); // word
console.log(window.name); // word
但是,开发中很少直接在全局作用域下去使用this,通常都是在函数中使用。
所有的函数在被调用时,都会创建一个执行上下文:
- 这个上下文中记录着函数的调用栈、函数的调用方式、传入的参数信息等;
- this也是其中的一个属性;
我们先来看一个让人困惑的问题:
- 定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
js
// 定义一个函数
function foo() {
console.log(this);
}
// 1.调用方式一: 直接调用
foo(); // window
// 2.调用方式二: 将foo放到一个对象中,再调用
var obj = {
name: "word",
foo: foo
}
obj.foo() // obj对象
// 3.调用方式三: 通过call/apply调用
foo.call("abc"); // String {"abc"}对象
上面的案例可以给我们什么样的启示呢?
- 1.函数在调用时,JavaScript会默认给this绑定一个值;
- 2.this的绑定和定义的位置(编写的位置)没有关系;
- 3.this的绑定和调用方式以及调用的位置有关系;
- 4.this是在运行时被绑定的;
那么this到底是怎么样的绑定规则呢?让我们一起探索一下吧
4.this绑定规则
我们现在已经知道this无非就是在函数调用时被绑定的一个对象,我们就需要知道它在不同的场景下的绑定规则即可。
4.1默认绑定规则
当函数独立调用时,不带任何修饰符,this会指向全局对象(在浏览器中为window对象,而在Node.js环境中为global对象)。
js
function foo() {
console.log(this);
}
foo(); // 在浏览器中输出window对象,在Node.js环境中输出global对象
4.2 隐式绑定规则
当函数作为对象的方法调用时,this会指向调用该方法的对象。
js
var obj = {
name: 'John',
greet: function() {
console.log('Hello, ' + this.name);
}
};
obj.greet(); // 输出:Hello, John
4.3 显式绑定规则
通过函数的call
、apply
或bind
方法,我们可以显式地指定函数执行时的this值。
js
function greet() {
console.log('Hello, ' + this.name);
}
var obj1 = { name: 'John' };
var obj2 = { name: 'Jane' };
greet.call(obj1); // 输出:Hello, John
greet.apply(obj2); // 输出:Hello, Jane
var boundGreet = greet.bind(obj1);
boundGreet(); // 输出:Hello, John
4.4 new绑定规则
当使用new
关键字调用构造函数时,this会指向新创建的对象。
js
function Person(name) {
this.name = name;
}
var john = new Person('John');
console.log(john.name); // 输出:John
4.5 箭头函数中的this
箭头函数与普通函数的行为不同,它没有自己的this值,而是继承外层作用域的this值。
js
var obj = {
name: 'John',
greet: function() {
setTimeout(() => {
console.log('Hello, ' + this.name);
}, 1000);
}
};
obj.greet(); // 输出:Hello, John(箭头函数继承了obj对象的this值)
5.使用bind、call和apply改变this指向
通过使用bind
、call
和apply
,我们可以显式地改变函数执行时的this指向。
6.注意事项和常见问题
- 在箭头函数中,不能使用
bind
、call
和apply
来改变this的指向。 - 在回调函数中,this的指向可能会发生变化,常见的解决方法是使用箭头函数或将this存储在变量中。
- 在使用setTimeout、setInterval等定时器函数时,this的指向可能会出现问题,可以通过将this存储在变量中或使用箭头函数来解决。
总结
本文详细解析了JavaScript中this的指向问题。通过理解默认绑定、隐式绑定、显式绑定、new绑定以及箭头函数的行为,我们可以更好地掌握this关键字的用法和规则。同时,我们还介绍了如何使用bind
、call
和apply
来改变函数执行时的this指向,以及一些注意事项和常见问题。通过深入理解和应用this关键字,您将能够更加灵活地编写JavaScript代码,并避免this指向问题带来的困扰。