this 是 JavaScript 的一个关键字,一般指向调用它的对象,这句话其实有两层意思,首先 this 指向的应该是一个对象,更具体地说是函数执行的“上下文对象”。其次这个对象指向的是“调用它”的对象,如果调用它的不是对象或对象不存在,则会指向全局对象(严格模式下为 undefined)。
四种调用模式
this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断
第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象
第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象
第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象
第四种是 apply 、 call 和 bind 调用模式
显式绑定 this
首先最容易想到的时显式绑定,包括 call,apply,bind
call 和 apply 用法功能基本类似,都是通过传入 this 指向的对象以及参数来调用函数。区别在于传参方式,前者为逐个参数传递,后者将参数放入一个数组,以数组的形式传递。bind 有些特殊,它不但可以绑定 this 指向也可以绑定函数参数并返回一个新的函数,当 c 调用新的函数时,绑定之后的 this 或参数将无法再被改变
function getName() {
console.log(this.name);
}
var b = getName.bind({ name: "bind" });
b(); //"bind"
getName.call({ name: "call" }); //"call"
getName.apply({ name: "apply" }); //"apply"
由于 this 指向的不确定性,所以很容易在调用时发生意想不到的情况。在编写代码时,应尽量避免使用 this,比如可以写成纯函数的形式,也可以通过参数来传递上下文对象。实在要使用 this 的话,可以考虑使用 bind 等方式将其绑定
隐式绑定 this
全局上下文
function fn() {
console.log(this);
}
fn(); // 浏览器:Window;Node.js:global
全局上下文默认 this 指向 window, 严格模式下指向 undefined
直接调用函数
let obj = {
a: function() {
console.log(this);
},
};
let func = obj.a;
func();
这种情况是直接调用。this 相当于全局上下文的情况
对象.方法的形式调用
let obj = {
a: function() {
console.log(this);
},
};
let func = obj.a();
func();
这种情况 this 指向这个对象
DOM 事件绑定
onclick 和 addEventerListener 中 this 默认指向绑定事件的元素。
IE 比较奇异,使用 attachEvent,里面的 this 默认指向 window。
new + 构造函数
此时构造函数中的 this 指向实例对象。
class A {
fn() {
console.log(this);
}
}
var a = new A();
a.fn(); // a
箭头函数
箭头函数没有 this, 因此也不能绑定。里面的 this 会指向当前最近的非箭头函数的 this,找不到就是 window(严格模式是 undefined)
或者说箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。可以简单地理解为箭头函数的 this 继承自上层的 this,但在全局环境下定义仍会指向全局对象
let obj = {
a: function() {
const fun = () => {
console.log(this);
};
fun();
},
};
obj.a(); // 找到最近的非箭头函数a,a现在绑定着obj, 因此箭头函数中的this是obj
箭头函数和普通函数相比,有以下几个区别
- 不绑定 arguments 对象,也就是说在箭头函数内访问 arguments 对象会报错;
- 不能用作构造器,也就是说不能通过关键字 new 来创建实例;
- 默认不会创建 prototype 原型属性;
- 不能用作 Generator() 函数,不能使用 yeild 关键字。
正常情况:this 指向运行时所在的对象而不是定义时所在对象
箭头函数:this 指向定义时所在的对象而不是使用时所在对象
优先级: new > call、apply、bind > 对象.方法 > 直接调用
稍复杂点的例子
- 如果在函数 fn2() 中调用函数 fn(),那么当调用函数 fn2() 的时候,函数 fn() 的 this 指向哪里呢?
function fn() {
console.log(this);
}
function fn2() {
fn();
}
fn2(); // ?
由于没有找到调用 fn 的对象,所以 this 会指向全局对象,答案就是 window(Node.js 下是 global)
- 让函数 fn2() 作为对象 obj 的属性,通过 obj 属性来调用 fn2,此时函数 fn() 的 this 指向哪里呢?
function fn() {
console.log(this);
}
function fn2() {
fn();
}
var obj = { fn2 };
obj.fn2(); // ?
调用函数 fn() 的是函数 fn2() 而不是 obj。虽然 fn2() 作为 obj 的属性调用,但 fn2()中的 this 指向并不会传递给函数 fn(), 所以答案也是 window(Node.js 下是 global)
- 对象 dx 拥有数组属性 arr,在属性 arr 的 forEach 回调函数中输出 this,指向的是什么呢?
var dx = {
arr: [1],
};
dx.arr.forEach(function() {
console.log(this);
}); // ?
关于 forEach,它有两个参数,第一个是回调函数,第二个是 this 指向的对象,这里只传入了回调函数,第二个参数没有传入,默认为 undefined,所以正确答案应该是输出全局对象。
类似的,需要传入 this 指向的函数还有:every()、find()、findIndex()、map()、some(),在使用的时候需要特别注意。
- 创建一个 fun 变量来引用实例 b 的 fn() 函数,当调用 fun() 的时候 this 会指向什么呢?
class B {
fn() {
console.log(this);
}
}
var b = new B();
var fun = b.fn;
fun(); // ?
fun 是在全局下调用的,所以 this 应该指向的是全局对象。这个思路没有没问题,但是 ES6 下的 class 内部默认采用的是严格模式,实际上面代码的类定义部分可以理解为下面的形式。
class B {
"use strict";
fn() {
console.log(this);
}
}
而严格模式下不会指定全局对象为默认调用对象,所以答案是 undefined。
- 前面提到 this 指向的要么是调用它的对象,要么是 undefined,那么如果将 this 指向一个基础类型的数据会发生什么呢
[0].forEach(function() {
console.log(this);
}, 0); //Number {0}
基础类型也可以转换成对应的引用对象。所以这里 this 指向的是一个值为 0 的 Number 类型对象