踩碎js中的this

winter

一、前言

image

this是前端开发人员绕不过去的一个点,而this的指向有总是让我们很头疼,今天就让我们一起来彻底踩碎this相关问题吧。

二、this的作用

很多人会说this这么讨厌,为什么我们需要它呢,其实this在代码里面是起到了非常大的作用的,简单来说,this提供了一种更优雅的方式来隐式“传递”一个对象引用,因此就可以将API设计得更加简洁并且易于复用。随着你的使用模式越来越复杂,显示传递上下文对象会让代码变得越来越混乱,使用this则不会这样。

三、this到底指向谁

总体来说,this有四条绑定规则,当你遇到一个this,你只需要看看它符合那条规则,然后就能找到对应的指向,听起来是不是很简单呢?
四条规则如下:

1. 默认绑定:非严格模式下指向全局对象,严格模式下指向undefined。

独立函数调用无法适用其他规则,所有它被认为是默认绑定,比如:

function foo(){
    console.log(this.a)
}
var a = 1;
foo();   //1
严格模式下,独立函数调用的this是指向undefined的。需要注意的是,决定this绑定对象的并不是函数的调用位置是否处于严格模式中,而是函数体是否处于严格模式中。

2. 隐式绑定:当函数调用位置拥有上下文对象的时候,隐式绑定规则会将函数调用中的this绑定到这个对象上(注意有隐式丢失的情况)

隐式绑定应该是最常见也最令人头疼的了,不过现在我们知道了规则,只要找打了上下文对象,就可以轻松的找到this到底指向谁啦。

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

在这个例子中,我们可以说函数被调用时,obj对象“拥有”或者“包含”它,此时obj就是函数的上下文,this.a也就相当于obj.a。

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。(可以理解为就近原则)
举例:

 function foo(){ 
    console.log(this.a) 
 } 
 var obj2 = { 
     a:2, 
     foo:foo 
};
var obj1 = { 
     a:1, 
     obj2:obj2 
};
obj1.obj2.foo();  //2
结合第1、2两条规则,我们可以简单的总结为,函数执行时首先看函数名前面是否有".",有的话,"."前面是谁,this就是谁;没有的话this就是window
值得注意的是隐式丢失的情况:

隐式丢失是指被隐式绑定的函数会丢失绑定对象,从而应用默认绑定规则的情况。
最常见的例子就是当你把一个函数传入语言内置的函数的时候,就会发生隐式丢失。比如常见的setTimeout:

function foo(){ 
    console.log(this.a) 
} 
var obj = { 
    a:2, 
    foo:foo 
} 
var a = "global" 
setTimeout(obj.foo,100);//global

此时foo函数内的this并不是执行obj的,而是应用了默认规则,指向了全局对象,是不是很意外?这是为什么呢?让我们来看一看内置setTimeout函数实现的伪代码吧:

 function setTimeout(fn,delay){ 
    fn(); 
 }

看到没,其实传入的函数其实是独立调用的,所以使用了默认绑定规则。
隐式丢失最常见的就是发生在回调函数中,大家要特别注意哦

3. 显示绑定:通过call(..)和apply(..)等方法自己显示的指定this的指向。

4. new绑定:this指向新构造出来的对象。

使用new来调用函数,或者说发生构造函数调用时,发生了哪几步?

1.创建一个新对象

2.将构造函数的对象赋给新对象(因此this就指向了这个新对象)

3.执行构造函数中的代码(为新对象添加属性)

4.返回新对象

--《javascript高级程序设计》

从上面的步骤来看,有一步就是:将构造函数的对象赋给新对象(因此this就指向了这个新对象)
举个例子:

function foo(a){ 
    this.a = a; 
} 
var bar = new foo(2); 
console.log(bar.a);//2

使用new来调用foo()时,我们会构造一个新对象bar,并把它绑定到foo()调用中的this上。

四、上述四条规则的优先级

1.函数是否在new中调用(new绑定) ? 如果是的话this绑定的是新创建的对象

2.函数是否通过call、apply(显式绑定)或者硬绑定调用 ? 如果是的话,this绑定的是指定的对象

3.函数是否在某个上下文对象中调用(隐式绑定) ? 如果是的话,this绑定的是那个上下文对象

4.如果都不是的话,使用默认绑定,严格模式下,绑定到undefined,非严格模式下,绑定到全局对象

每看到一个this我们只需要通过上述步骤一步一步的走下来,便能很轻松的找到它的指向了,是不是很轻松呢?

五、特殊情况

1.箭头函数:

箭头函数没有自己的this,箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this。
换句话说,箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window

2.间接引用:

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

赋值表达式p.foo=o.foo的返回值是目标函数的引用。

image

六、this总结

1.this既不指向自身,也不指向函数的词法作用域。

2.this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里调用
因为确定this的指向是在执行上下文的创建阶段完成的,而执行上下文的创建阶段位于函数被调用但未执行任何内部函数的时候,也就是说在函数调用的时候才决定了this的指向---箭头函数是例外

七、参考

《你不知道的JavaScript》(上卷)
《javascript高级程序设计》

阅读 391
1 声望
1 粉丝
0 条评论
你知道吗?

1 声望
1 粉丝
文章目录
宣传栏