js中this指向全面解析——四种绑定规则

this

this指向什么?不了解this时,一看到函数中有this,就以为this指的是函数自身,这是错的!!
首先要明确:
this既不指向函数自身也不指向函数的词法作用域。
this是运行时进行绑定的,而不是在编写时绑定,它的上下文取决于函数调用的各种条件。
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式,完全取决于函数在哪里被调用
好吧,还是好迷??不要急,我们往下看,慢慢理解,看完这篇文章会豁然开朗。
本文将会讲解三个内容:1.调用位置、2.绑定规则、3.优先级

1.调用位置

调用位置:函数被调用的位置 (但是有时候会隐藏真正的调用位置,所以要学会利用调用栈进行分析)
什么是调用栈? 用下面这个代码分析调用栈

function baz() {
        // 当前调用栈是:baz
        // 因此,当前调用位置是全局作用域
        console.log( "baz" );
        bar(); // <-- bar 的调用位置
}
function bar() {
       // 当前调用栈是 baz -> bar
       // 因此,当前调用位置在 baz 中
       console.log( "bar" );
       foo(); // <-- foo 的调用位置
}
function foo() {
       // 当前调用栈是 baz -> bar -> foo
       // 因此,当前调用位置在 bar 中
       console.log( "foo" );
 }
baz(); // <-- baz 的调用位置

还可以用开发者工具得到调用栈
要判断被调用函数的调用栈,就在被调用函数第一行代码设置个断点,栈中的第二个元素就是真正的调用位置,例如:foo函数的调用栈是?在foo函数中第一行代码打个断点
在这里插入图片描述
调用栈的第二个元素就是真正的调用位置

想要知道函数this绑定的对象,那么要先找出这个函数直接调用位置,再判断用什么规则来判断绑定对象。也就是:
this绑定的对象?调用位置=>规则=>绑定对象
绑定规则有:默认绑定、隐式绑定、显示绑定、new绑定

2.绑定规则

先简单介绍一下绑定规则:
【new绑定】由new调用?绑定到新创建的对象
【显示绑定】由call或者apply(或者bind)调用?绑定到制定的对象
【隐式绑定】由上下文对象调用?绑定到那个上下文对象
【默认绑定】{严格模式下}绑定到undefined;{非严格模式下}绑定到全局对象

【默认绑定】

最常用的函数调用模型,无法使用其他规则时的默认规则
独立函数调用,可能有些读者会不懂什么叫“独立”,下面会解释
非严格模式下可以使用默认绑定,严格模式下不能使用

一、非【严格模式】下: this指向全局对象
首先,先思考一下以下代码会输出什么

//代码一
function foo() {
           var  a = 4;
            console.log( a );
        }
 var a = 2; 
foo(); 

再思考一下以下代码

//代码二
function foo() {
    var  a = 4; //这里是foo函数里的a
    console.log( this.a );
}
var a = 2;    //这里是全局对象的a
foo(); 

答案:
代码一:输出4
代码二:输出2
代码一的输出很容易知道,代码二多了个this输出就不同了??
因为代码二中,输出的是全局对象a,因为调用foo()没有任何修饰进行调用,使用了默认绑定,此时的this指向了全局对象

此时产生了疑惑,如果函数不在全局调用在另一个函数里面调用并且这个函数是有a的,那此时的this指向谁呢?

function foo() {
            var  a=4;
            console.log('foo函数:'+this.a );
            lo();   ====》没有任何修饰调用
 }
function lo() {
             var  a=6;
            console.log('lo函数:'+this.a );
            go();  ====》没有任何修饰调用

 }
function go() {
            var  a=8;
            console.log('go函数:'+this.a );
 }
 var a = 2;
foo();   ====》没有任何修饰调用

输出结果:
在这里插入图片描述
从结果看出,被调用的函数foo()、lo()、go()函数的this指向的都是全局对象a=2,所以就算函数不在全局调用,this也会指向全局对象

二、【严格模式】this会绑定到undefined
!!这里要注意所说的严格模式下,不是指在严格模式下调用函数,this会绑定到undefined,说的是this的调用位置
看下面代码

function foo() {
    "use strict";  
    console.log( this.a ); //调用this.a的函数体是严格模式
}
var a = 2;
foo(); // TypeError: this is undefined
function foo() {
    console.log( this.a );  //调用this.a的函数体是非严格模式
}
var a = 2;
(function(){
    "use strict";
    foo(); // 2  调用foo()的位置是严格模式
})();

从上面两个代码可以看出,【代码一】在foo()函数体内采用严格模式并使用了this.a,this会绑定到undefined;【代码二】foo()在严格模式下调用this不会绑定到undefined

决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式
如果函数体处于严格模式,this 会被绑定到 undefined,否则this 会被绑定到全局对象。

【隐式绑定】

调用位置是否有上下文对象
一个对象,这个对象有指向函数的属性,通过这个属性间接引用函数,从而把函数里的this(间接)(隐式)绑定到这个对象上

function foo() {
        console.log( this.a );
        }
 var obj = {
        a: 2,
        foo: foo
};
var a=4;
obj.foo(); // 2
 foo();//4

obj.foo()调用函数时,this指向的是obj这个上下文对象,this被绑定到了obj,此时this.a和obj.a是一样的。——这就是隐式绑定

现在可以解释什么叫独立函数调用
obj.foo() 、foo() 前者是有 obj. 后者是直接 foo()没有任何修饰,也就是独立函数调用
所以foo()使用的是默认绑定,此时的this指向了全局对象a

隐式绑定常见的问题: 隐式丢失
隐式丢失:被隐式绑定的函数会丢失绑定对象,也就是它会应用默认绑定,从而吧this绑定到全局对象或者undefined上(取决于是否是严格模式)

下面看看这段代码理解一下“被隐式绑定的函数会丢失绑定对象”这句话

function foo() {
        console.log( this.a );
}
 var obj = {
        a: 2,
        foo: foo
 };
 var bar = obj.foo; // 函数别名!
 var a = "oops, global"; // a 是全局对象的属性
 obj.foo(); //2
 bar(); // "oops, global"

bar = obj.foo 看上去bar()函数的this跟obj.foo的this是一样的,也就是看上去bar函数的this绑定的是obj对象
调用bar()了之后输出的是“oops, global”,实际上bar的this被绑定到了全局对象
分析一下
obj.foo:foo函数的this绑定obj(隐式),
bar = obj.foo:被bar引用了之后丢失了obj对象,实际上是bar=foo ——这就是“被隐式绑定的函数会丢失绑定对象”
所以 bar(); 其实是一个不带任何修饰的函数调用,用了默认绑定

下面的情况更加常见,发生在传入回调函数时:

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    // fn 其实引用的是 foo
    fn(); // <-- 调用位置!
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global
doFoo( foo );// "oops, global

看上面的结果,发现doFoo( obj.foo );和doFoo( foo );结果是一样的,说明obj.foo作为参数传入给函数时也会丢失obj对象

那如果传的函数不是自己声明的,而是内置的函数(如:setTimeout( )内置函数)结果会如何呢?
看以下代码

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
setTimeout( obj.foo, 100 ); // "oops, global"

发现结果也是"oops, global",obj.foo参数传入给内置函数时也丢失了obj(即只剩下foo传给setTimeout)

【显示绑定】

js提供的绝大多数函数以及我们自己创建的所有函数都可以使用call(…)和apply(…)方法来进行显示绑定

在这里简单提一下call和apply
call和apply的作用完全一样,可以用来改变this 的对象,接受参数的方式不太一样,第一个参数都是一个对象(this绑定的对象),call可以依次传入,apply要放进数组里传进去
如:通过call和apply传参数给function foo(arg1,arg2){ … }
foo.call(obj, arg1, arg2);
foo.apply(obj, [arg1, arg2]);

function foo() {
    console.log( this.a );
}
var obj = {
    a:2  //obj中的a
};
var a=4;  //window中的a
foo.call( obj ); // 2  输出obj中的a
foo(); //4 输出window中的a

显式地把obj绑定到foo上

显式绑定无法解决丢失绑定的问题,但是可以用显示绑定的一个变种来解决

硬绑定
function foo() {
    console.log( this.a );
}
var obj = {
    a:2  //obj中的a
}; 
var a=4;   //window的a
var bar = function() {
    foo.call( obj ); //硬绑定,将obj绑定到foo上,
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2    输出的还是obj中的a,说明无法修改已绑定的对象

用一个函数bar,将foo的this绑定到obj上,以后不论怎么调用都不会改变this已经绑定到obj的事实,每次调用时都会在obj上调用foo。
这种显式的强制绑定——硬绑定

典型应用场景,创建一个包裹函数,传入所有的参数并返回接收到的所有值

function foo(something) {
    console.log( this.a, something );  //输出接收到的全部参数
    return this.a + something;   //将接收到的全部参数加起来,并返回
}
var obj = {
    a:2
};
var bar = function() {
    return foo.apply( obj, arguments );  //将foo的this绑定到obj上
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5

还可以用bind绑定,在ES5中提供的内置的方法Function.prototype.bind

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}

var obj = {
    a:2  //obj中的a
};
var  a=4;  //window的a
var bar = foo.bind( obj );  //返回一个foo中this绑定到obj上的新函数,并没有改变原函数
foo();//4   发现果然没有改变原来的foo
var b = bar( 3 ); // 2 3
console.log( b ); // 5

注意,bind和call()、apply()虽然都可以用来将this绑定到某个对象上,但是不太相同

【new绑定】

function foo(a) {
    this.a = a; //把a赋值给对象中的a属性
}

var bar = new foo(2);
console.log(typeof(bar)); //object     bar的类型是object
console.log( bar.a ); // 2

var tree = new foo(4);
console.log( tree.a ); // 4

console.log( bar.a ); // 2

分析上述代码
使用new调用foo(…)时,会构造一个新对象并绑到foo(…)调用的this上。
var bar = new foo(2);将bar绑定到foo(…)的this上 ,将2赋值给bar中的a属性
var tree = new foo(4);将tree绑定到foo(…)的this上 ,将4赋值给bar中的a属性

3.优先级

四种规则的优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

this绑定的对象?调用位置=》规则=》绑定对象
可以按照这个顺序判断:
1、函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象
var bar = new foo()
foo中的this绑定在bar对象上
2、函数是否通过call、apply(显式绑定)?如果是this绑定的是指定的对象
var bar = foo.call(obj2)
foo中的this绑定在obj2上
3、函数是否在某个上下文对象红调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象
var bar = obj1.foo()
foo的this绑定到obj1上
4、如果都不是的话,使用默认绑定。在严格模式下,绑定到undefined;否则。绑定全局对象

上面讲述了四种绑定规则,当然也有例外的情况,可以看看下一篇文章js中this指向的绑定例外

这篇文章是在看《你不知道的JavaScript》(上卷) this这部分内容之后再加上自己的一些尝试和理解写下的读书笔记。
参考:《你不知道的JavaScript》(上卷)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值