一、介绍
与其它语言相比,函数的this
关键字在JavaScript
中的表现略有不同。此外,在严格模式和非严格模式之间也会有一些差别。
在绝大多数情况下,函数的调用方式决定了this
的值(运行时绑定)。this
不能在执行期间被赋值,并且在每次函数被调用时this
的值也可能会不同
this
关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象
常见的this
指向:
- 全局作用域中或者普通函数中 => this指向全局对象
window
- 立即执行函数 => this指向
window
- 定时器 => this指向
window
- 事件 => this指向事件源对象
- 方法 => this指向调用者
- 构造函数 => this指向对象实例
二、绑定规则
根据不同的使用场合,this
有不同的值,主要分为下面几种情况:
- 默认绑定
- 隐式绑定
- new 绑定
- 显示绑定
2.1 默认绑定
-
默认绑定全局对象window
jsconsole.log(this === window); //true
-
全局作用域下独立调用函数,this指向window
jsvar name = 'Jenny'; function person() { return this.name; } console.log(person());
上面代码中,全局环境中定义
person
函数,内部使用this
关键字。输出结果是Jenny
。原因是:调用函数的对象在浏览器中位于window
,因此this
指向window
,所以输出Jenny
。我们可以看看window中是否存在
name
这个变量:如上图所示,window中是存在name变量的。
【注意】严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined
,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象
2.2 隐式绑定
简单理解:谁调用指向谁
-
函数还可以作为某个对象的方法调用,这时
this
就指这个上级对象jsfunction test() { console.log(this.x); } var name = "张三" var obj = {}; obj.x = 1; obj.m = test; obj.m(); // 1
上述代码,
x
是挂在obj
下的,所以this.x
的值是1
。在控制台中,name是挂在
window
下,所以我们可以尝试在test函数中打印this.name
,看看是什么:从上图,我们可以发现,
this.name
的结果是undefined
。这说明this
的指向不是window
,所以找不到name
这个字段,所以是undefined
-
这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,
this
指向的也只是它上一级的对象jsvar o = { a:10, b:{ c: 'zhangsan', fn:function(){ console.log(this.a); // undefined console.log(this.c); // zhangsan } } } o.b.fn();
上述代码中,
this
的上一级对象为b
,b
内部并没有a
变量的定义,所以输出undefined
;b
内部有c
的变量定义,所以输出zhangsan
-
this
永远 指向的是最后调用它的对象jsvar o = { a:10, b:{ c: 'zhangsan', fn:function(){ console.log(this.a); // undefined console.log(this.c); // undefined console.log(this); // window } } } var func = o.b.fn; func()
从上述结果,我们可知,此时
this
指向的是window
。因为 this永远指向的是最后调用它的对象 。虽然fn
是对象b
的方法,但是fn
赋值给j
时候并没有执行,所以最终指向window
2.3 new 绑定
-
通过构建函数
new
关键字生成一个实例对象,此时this
指向这个实例对象jsfunction test() { this.x = 1; } var obj = new test(); obj.x // 1
上述代码,之所以能够输入1,是因为
new
关键字改变了this
的指向 -
new
过程遇到return
一个对象,此时this
指向为返回的对象jsfunction fn() { this.user = 'xxx'; return { name: "张三" }; } var a = new fn(); console.log(a.user); //undefined
-
new
过程遇到return
一个简单类型的对象,此时this
指向为实例对象jsfunction fn() { this.user = 'xxx'; return "张三"; } var a = new fn(); console.log(a.user); //xxx
【注意】如果是
return
的是null
,此时this
指向仍为实例对象 ,因为null
是简单类型
2.4 显示绑定
apply()、call()、bind()
是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this
指的就是这第一个参数。
js
var x = 0;
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(obj) // 1
三、 箭头函数
3.1 箭头函数的this指向
在ES6的语法中提供了箭头函数语法,让我们在代码书写时就能确定this
的指向(编译时绑定)。
举个栗子:
js
const obj = {
sayThis: () => {
console.log(this);
}
};
obj.sayThis(); // window。因为 JavaScript 没有块作用域,所以在定义 sayThis 的时候,里面的 this 就绑到 window 上去了
const globalSay = obj.sayThis;
globalSay(); // window 浏览器中的 global 对象
3.2 this指向的潜在坑
虽然箭头函数的this
能够在编译的时候就确定了this
的指向,但也需要注意一些潜在的坑:
-
绑定监听事件
jsconst button = document.getElementById('btn_wrap_id'); button.addEventListener('click', ()=> { console.log(this === window) // true this.innerHTML = 'clicked button' })
通过上述代码和截图,我们可得:此时
this
指向了window
,但是实际上,我们想要的是this
为点击的button
。我们直接打印this,查看this的值,可以更直观:
-
构建函数
jsfunction Cat() {} Cat.prototype.sayName = () => { console.log(this === window) //true return this.name } const cat = new Cat('mm'); cat.sayName()
3.3 箭头函数this
不遵循上述的四种规则:
-
独立调用对箭头函数无效:
jsvar a = 0 function foo() { let test = () => { console.log(this) } return test } let obj = { a: 1, foo: foo } obj.foo()() // obj.foo()返回test obj.foo()()调用test 而且是独立调用 但是this还是指向obj
-
隐式绑定对箭头函数无效:
jslet a = 0 let obj1 = { a: 1, foo: () => { console.log(this); } } obj1.foo() //指向window
-
显式绑定对箭头函数无效:
jsvar a = 0 function foo() { let test = () => { console.log(this) } return test } let obj1 = { a: 1, foo: foo } let obj2 = { a: 2, foo: foo } obj1.foo().call(obj2) //obj1.foo()返回test obj1.foo.call(obj2)把test的指向绑定到obj2上,无效,this依然指向obj1
3.4 总结
综上所述,我们可以总结出:
- 创建箭头函数时,就已经确定了它的 this 指向。
- 箭头函数没有子级的
this
指向,它的this
指向外层的 this。 - 箭头函数没有子级的作用域,绑定的是父级作用域的上下文
四、优先级
this
绑定的优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
4.1 隐式绑定 VS 显示绑定
js
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
4.2 new绑定 VS 隐式绑定
js
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
可以看到,new绑定的优先级>
隐式绑定
4.3 new 绑定 VS 显示绑定
因为new
和apply、call
无法一起使用,但硬绑定也是显式绑定的一种,可以替换测试
ini
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
bar
被绑定到obj1上,但是new bar(3)
并没有像我们预计的那样把obj1.a
修改为3。但是,new
修改了绑定调用bar()
中的this
我们可认为new
绑定优先级>
显式绑定