this 是 JavaScript 中重要的一环。由于其灵活的指向,如果没有深刻的理解,在实际运用中也是最容易产生bug的环节。同时,如果能熟练使用它,那我们可能会更加自如的应对一些复杂的使用场景,让开发更容易更简洁。
this到底指向谁,这是我们在使用this时最纠结的问题。如果用一句话来描述,通俗的来讲:
谁调用它,this就指向谁。
用专业一点的话来说,this与当前的执行上下文息息相关,其指向是由使用时根据执行上下文动态确定的。
总结下来,有下面的一些确定this指向的规则:
- call / apply / bind
- new 操作符构造对象
- 全局对象,非严格模式指向window/global,严格模式指向Undefined
- ES6,箭头函数
- 函数执行时有上下文对象,这时函数里面的this指向该上下文对象。
下面详细介绍一下这些规则:
1. 全局状态的this
首先,有如下示例:
function f(){
console.log(this);
}
function g(){
'use strict'
console.log(this);
}
f(); //window
g(); //undefined
上面这种情况就属于函数在浏览器器的全局环境中调用,相当与当前的执行上下文为浏览器的全局环境变量。即非严格模式在Window,严格模式为undefined.
2. 上下文绑定this
当函数调用时有明确执行上下文时,这时候this则指向当前的执行上下文。看如下代码:
var name = "mini";
var girlfriend = {
name:"angelababy",
getName:function(){
console.log(this.name);
}
}
var yourGirl = girlfriend.getName;
yourGirl(); //mini
girlfriend.getName(); // angelababy
其中:console.log(yourGirl())由于是在全局环境中执行的,其还是指向全局环境(window),相当于执行window.name = "mini"。而console.log(girlfriend.getName());这个时候this就指向当前调用它的对象,即this指向girlfriend,this.name就等价于girlfriend.name = “angelababy” 。
如果存在嵌套的链式调用呢?
var girlfriend= {
name: 'angelababy',
china: {
name: '杨颖',
fn: function() {
return this.name
}
}
}
girlfriend.china.fn() // 杨颖
在这种嵌套链式调用的关系中,this 指向最后调用它的对象,也就是最靠近它的那个对象。 总的来说:this 指向最后调用它的对象。
3. 显示绑定之 call / apply / bind
js 提供了三种函数call / apply / bind可以改变当前this的指向。
call: 第一个参数设置this指向的对象,从第二个参数起都是原函数的参数,立即执行。
apply: 第一个参数设置this指向的对象,第二个参数必须是数组,这个数组代表原函数的参数列表,立即执行。
bind: 不会执行相关函数,只是将一个值绑定到函数的this上,并将绑定好的函数返回,我们需要手动调用返回的函数。
从下面的代码来看其区别(它们是等价的):
var girlfriend;
fn.call(girlfriend, 'arg1', 'arg2');
fn.apply(girlfriend, ['arg1', 'arg2']);
fn.bind(girlfriend, 'arg1', 'arg2')();
下面看一下实际的使用:
var girlfriend = {
name:"angelababy",
getName:function(){
console.log(this.name);
}
}
var chinaName = {
name: "杨颖"
}
girlfriend.getName.call(chinaName) //杨颖
girlfriend.getName.apply(chinaName) //杨颖
girlfriend.getName.bind(chinaName)() //杨颖
可以看出this都指向了chinaName。
4. new操作符绑定
js 中的new操作符调用构造函数,首先我们看一下new操作的执行过程:
- 创建一个新的对象
- 将这个新对象绑定到 此函数的this上
- 为这个对象添加属性、方法等(包括原型链的继承)
- 返回新对象,如果这个函数没有返回其他对象 操作实例:
function girlfriend(){
this.name = "angelababy";
}
girlfriend(); //undefined
var obj = new girlfriend();
obj.name;
此时,可以看出使用new 调用函数后,会创建一个新对象,且将this指向它,并返回。 另:如果构造函数显示return的情况下, 如果返回一个对象类型,那么 this 就指向这个返回的对象。
function girlfriend(){
this.name = "angelababy";
return {name:"杨颖"};
}
var obj = new girlfriend();
obj.name; //杨颖。
5. 箭头函数
箭头函数的绑定规则和上诉的都有所不同,它基本上有两种规则: 根据外层(函数或者全局)上下文来决定。 箭头函数的this绑定无法被修改, 代码示例:
var name = "mini";
var girlfriend = {
name:"angelababy",
getName:() =>{
console.log(this.name);
}
}
var chinaName = {
name: "杨颖"
}
girlfriend.getName.call(chinaName); // mini
girlfriend.getName(); //mini
上面说了这么多规则,那么他们优先级怎么样呢?
var name = "mini";
var girlfriend = {
name:"angelababy",
getName:function(){
console.log(this.name);
}
}
var chinaName = {
name: "杨颖"
}
girlfriend.getName.call(chinaName); // 杨颖
function girlfriend(name){
this.name = name;
}
var chinaName = {}
girlfriend.bind(chinaName)("杨颖");
console.log(chinaName.name); //杨颖
var newGirl = new girlfriend("angelababy");
console.log(newGirl.name); //angelababy
注:由于箭头函数的特殊性,此时不做排序参考。
结论: new > call/apply/bind > 隐式绑定(1,2)
基本上只要充分理解上面这些规则,我们基本都能明白this的指向。