this的绑定规则
js中函数的this指向是在调用时绑定的,所以this指向就是取决于函数调用的位置。
主要以下几条绑定规则
默认绑定
最常见就是独立函数调用,就是不带任何修饰的函数引用进行调用,结果就是会将this绑定到window上面。这个规则就是其他规则无法应用时使用的规则。
var a=1;
function foo() {
console.log(this); // window
}
foo();
function bar(){
console.log(this.a);
}
bar();//调用后会打印1
但是如果是严格模式下,this就会绑定到undefined,这里注意是函数体处于严格模式,而不是调用位置处于严格模式。
function foo() {
"use strict";
console.log(this); // undefined
}
foo();
函数体处于严格模式,此时注意this是window,不是undefined
function foo(){
console.log(this.a);
};
var a=1;
function bar(){
"use strict";
foo();
};
bar();//1
在看下面的例子
function bar(){
console.log(this);//window
}
function foo(){
bar();
}
foo();
这里bar还是独立函数调用,没有被绑定到任何对象上。
隐式绑定
隐式绑定最常见的就是某个对象发起函数调用,那么函数的this就会被隐式绑定到这个对象上。
var a=2;
function foo(){
console.log(this.a);//输出1
}
var obj={
a:1,
foo:foo
}
obj.foo();
绑定的对象是离函数调用最近的那一个对象,例如
function foo() {
console.log(this.a); // obj对象
}
var obj1 = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
obj1: obj1
}
obj2.obj1.foo();//输出1
隐式丢失
隐式丢失就是被隐式绑定的函数会丢失绑定的对象,会使用默认绑定。例如
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo
}
var a=2;
var bar = obj.foo;
bar();//输出2
虽然bar引用了obj.foo函数,但是其实只是引用了foo函数,所以此时bar函数的调用就是不带任何修饰的函数调用,相当于foo()调用,因此就是使用了默认绑定,绑定到window对象。
还有另一种形式,如下:
function foo(){
console.log(this.a);
};
function bar(fn){
//这里跟上面一样,fn=obj.foo
fn();
}
var obj={
a:1,
foo:foo
};
var a=2;
bar(obj.foo);//打印2
参数传递的时候是一种赋值过程,就如fn=obj.foo,所以这种情况也是跟上面的一样,使用默认绑定
还有当把函数传入内置的函数或第三方库的一些函数时,例如setTimeout,此时结果跟上面一样。
function foo(){
console.log(this.a);
};
var obj={
a:1,
foo:foo
};
var a=2;
setTimeout(obj.foo,200);//2
因为内部都是跟上面的bar函数一样,做独立函数调用。
显式绑定
显示绑定就是通过call,apply,bind将this绑定到传入的对象上
function foo(){
console.log(this.a);
}
var obj={
a:1
};
var a=3;
foo.call(obj);//1
foo.call(window)//3;
在js中还有一些内置函数,提供一个可选的参数,用于指定你传入的回调函数的this。例如forEach
function foo(){
console.log(this.a);
};
var obj={
a:1
}
var a=2;
[1,2,3].forEach(foo,obj);//1 1 1
函数内部就是通过call或apply进行显示绑定。
new绑定
new绑定就是使用new关键字来调用函数,new的过程中会发生如下操作。
- 创建一个新的对象。
- 新对象的__proto__属性会指向构造函数的prototype。
- 新对象绑定为函数的this。
- 如果函数没有返回其他对象,那么就会返回该新对象。
new的模拟过程,可以看这篇,new操作符原理
function foo(a){
this.a=a;
};
var bar=new foo(1);
console.log(bar.a);//1
规则优先级
当调用的位置应用了多条规则,它会怎么绑定?此时这些规则就需要有优先级。当然,毫无疑问就是默认绑定的优先级是最低的。
1.隐式绑定和显示绑定的优先级
function foo(){
console.log(this.a);
};
var obj={
a:1,
foo:foo
};
var obj2={
a:2,
}
obj.foo.call(obj2);//2
很明显看到显示绑定的优先级更高。
2.new绑定和隐式绑定的优先级比较
function foo(a){
this.a=a;
};
var obj={
a:1,
foo:foo
};
var bar=new obj.foo(2);
console.log(bar.a)//2
可以看到new绑定比隐式绑定优先级更高。
3.new绑定和显示绑定的优先级 new和call,apply无法一起使用,如,无法通过new foo.call(obj),但可以先通过bind后再new调用。
function foo(a){
this.a=a;
};
var obj={
a:1
};
const bar=foo.bind(obj);
const c=new bar(4);
console.log(c.a);//4
因此new绑定比显示绑定优先级更高。
所以优先级从高到低为:new绑定,显示绑定,隐式绑定,默认绑定。
规则之外
被忽略的this
当你把null或undefined作为显示绑定的this值时,这个值会被忽略,而使用默认绑定规则
function foo(){
console.log(this.a);
};
var a=1;
foo.call(null);//1
间接引用
这种情况就是有时候会创建严格函数的间接引用,此时调用应用的是默认绑定规则。主要发生再赋值的时候。
function foo(){
console.log(this.a);
};
var a=2;
var obj1={
a:1,
foo:foo
};
var obj2={
a:3
};
(obj2.foo=obj1.foo)();//2
这个obj2.foo=obj1.foo赋值表达式会返回foo函数的引用,实际就是(foo)();所以这里会使用默认绑定。
箭头函数
箭头函数不使用四种规则,而是根据外层作用域来决定this。
var obj={
a:1,
foo:()=>{
console.log(this.a);
}
};
var a=2;
obj.foo();//2
注意是寻找外层(函数或者全局)作用域,这里的obj没有作用域,所以只能是最外层的window,如果obj包含在一个函数中,那么箭头函数就会找到这个函数的this。
箭头函数并不会绑定this,而是会往外层作用域寻找this。而且箭头函数的调用call,apply来修改this是不行的。
function foo(){
return ()=>{//this会是foo这个函数的this
console.log(this.a);
};
};
var obj={
a:1
};
var obj2={
a:2
};
var bar=foo.call(obj);//foo函数的this指向改成obj对象
bar.call(obj2);//输出的是1,而不是2
总结
总之,要判断this指向,需要找到这个函数的调用位置,然后按四条规则进行匹配,除了一些不满足规则的需要另外查看。