JavaScript全解析——this指向

Posted qian-fen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript全解析——this指向相关的知识,希望对你有一定的参考价值。

本系列内容为JS全解析,为千锋教育资深前端老师独家创作

致力于为大家讲解清晰JavaScript相关知识点,含有丰富的代码案例及讲解。如果感觉对大家有帮助的话,可以【点个关注】持续追更~

this指向(掌握)

this 是一个关键字,是一个使用在作用域内的关键字

作用域分为全局作用域和局部作用域(私有作用域或者函数作用域)

全局作用域

全局作用域中this指向window

局部作用域

函数内的 this, 和 函数如何定义没有关系, 和 函数在哪定义也没有关系,只看函数是如何被调用的(箭头函数除外)

可分为以下场景:

普通函数中调用

普通函数中的this和全局调用一样,this指向window

语法:函数名()

<script>
    // 全局使用 this 
    console.log(this) //window 
    console.log(window) //window 
    console.log(window === this) //true 
    //普通函数调用 
    function fn()  
        console.log(\'我是全局 fn 函数\') 
        console.log(this) //window 
     
    fn() 
</script>

对象(包含数组)中调用

该函数内的 this 指向 前面的内容,也就是那个对象或者数组

语法:

对象名.函数名()*

对象名

<script> 
    //对象函数调用 
    function fn()  
        console.log(this) //fun: ƒ 
     
    var obj =  
        fun: fn 
     
    obj.fun() 
    obj[\'fun\']() 
</script>

定时器处理函数中调用

定时器中的this同样也是指向window

<script> 
    // 定时器处理函数 
    setTimeout(function()  
        console.log(this); //window 
    , 1000) 
</script>

事件处理程序中调用

事件处理程序中的this指向的是事件源

<!DOCTYPE html> 
<html lang="en"> 

<head> 
   <meta charset="UTF-8"> 
   <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
   <meta name="viewport" content="width=device-width, initial-scale=1.0">        <title>Document</title> 
   <style> 
       div  
           width: 200px; 
           height: 200px; 
           background-color: red; 
         
    </style> 
</head> 

<body> 
   <div>hello world</div> 
   <script> 
       var res = document.querySelector(\'div\') 
       
       res.onclick = function()  
           console.log(this); //<div></div>
        
   </script> 
</body> 

</html>

自执行函数中调用

●自执行函数中的this也指向window

<script> 
    (function()  
        console.log(this); //window 
    )() 
</script>

强行改变this指向

通过上面的学习大家知道,this在不同的情况指向是不同的,但是有时候需要指向一个制定的对象,这就需要改变this的指向

可以理解成不管之前指向哪里,现在我让你指向哪里你就要指向哪里

强行改变this指向的方式可以通过call、apply和bind来改变

call调用

作用:改变函数内部this的指向

语法:

函数名.call()

对象名.函数名.call(参数1,参数2,参数3...)

参数:

○第一个参数是this要指向的对象

○从第二个参数开始,依次给函数传递实参

特点: 会立即调用函数或者说立即执行

<script> 
    function fn(a, b)  
        console.group(\'fn 函数内的 打印\') 
        console.log(\'this : \', this) 
        console.log(\'a : \', a) 
        console.log(\'b : \', b) 
        console.groupEnd() 
     
    var obj =  
        name: \'我是 obj 对象\' 
     
    var arr = [100, 200, 300, 400, 500] 
    // 用 call 调用 
    fn.call(obj, 100, 200) 
    fn.call(arr, 1000, 2000) 
    /* 
    this : name: \'我是 obj 对象\' 
    a : 100 
    b : 200 
    fn 函数内的 打印 
    this : (5) [100, 200, 300, 400, 500] 
    a : 1000 
    b : 2000 
    */ 
</script>

apply调用

作用: 改变函数内部this的指向

语法:

○函数名.apply()
○对象名.函数名.apply(参数1,[参数2,参数3...])

参数:

○第一个参数是this要指向的对象

○第二参数的一个数组,要传递的实参要放到数组里面,就是有一个实参也要放到数组里面

特点: 会立即调用函数或者说立即执行

<script> 
    function fn(a, b)  
        console.group(\'fn 函数内的 打印\') 
        console.log(\'this : \', this) 
        console.log(\'a : \', a) 
        console.log(\'b : \', b) 
        console.groupEnd() 
      
     var obj =  
         name: \'我是 obj 对象\' 
      
     var arr = [100, 200, 300, 400, 500] 
     // 用 apply 调用 
     fn.apply(obj, [100, 200]) 
     fn.apply(arr, [1000, 2000]) 
     /* 
     fn 函数内的 打印 
      this : name: \'我是 obj 对象\' 
      a : 100 
      b : 200 
      fn 函数内的 打印 
      this : (5) [100, 200, 300, 400, 500] 
      a : 1000 
      b : 2000 
      */ 
</script>

bind调用

作用: 改变函数内部this的指向

语法:

函数名.bind()

对象名.函数名.bind(参数1,参数2,参数3...)

参数:

第一个参数是this要指向的对象

从第二个参数开始,依次给函数传递实参

特点: 函数不会立即调用,会返回一个改变this指向以后的函数,使用的时候需要调用

<script> 
    function fn(a, b)  
        console.group(\'fn 函数内的 打印\') 
        console.log(\'this : \', this) 
        console.log(\'a : \', a) 
        console.log(\'b : \', b) 
        console.groupEnd() 
      
     var obj =  
         name: \'我是 obj 对象\' 
      
     var arr = [100, 200, 300, 400, 500] 
     // 用 bind 调用 
     // 注意: 因为是 bind, 不会把 fn 函数执行, 而是把 fn 
     // res 接受的就是 bind 方法复制出来的 fn 函数, 和 fn 
     var res = fn.bind(obj, 100, 200) 
     var res1 = fn.bind(arr, 1000, 2000) 
     res() 
     res1() 
     /* 
     fn 函数内的 打印 
     this : name: \'我是 obj 对象\' 
     a : 100 
     b : 200 
     fn 函数内的 打印 
     this : (5) [100, 200, 300, 400, 500] 
     a : 1000 
     b : 2000 
     */ 
</script>

以上即为JS中this指向的一些基础知识点,更多技术干货、知识技巧可以关注我们!有不清楚的问题也可以在评论区交流讨论,也可以私信小千~

深入解析JavaScript中的this关键字

如果问初学者js中什么东西比较难懂,很多回答会是this关键字。this在不同场景下所指向的对象不同,这就有一种扑朔迷离的感觉,放佛魔法一般神秘:this到底是什么?这里有四种绑定规则。

 

1. 默认绑定

默认绑定是无法应用其他调用规则时的绑定方式,看如下代码:

1 var a = 1;
2 
3 function foo(){
4     console.log(this.a);
5 }
6 
7 foo(); // 1
1 "use strict"; 
2 
3 var a = 1;
4 
5 function foo(){
6     console.log(this.a);
7 }
8 
9 foo(); // TypeError: Cannot read property ‘a‘ of undefined

这是最基本的一个函数调用,在第一张图中,非严格模式下,this绑定到全局对象,因此this.a指向全局变量a。第二张图中,严格模式下,全局对象无法使用默认绑定,因此this会绑定到undefined。

那么如何判断是默认绑定呢,其实很简单,我们观察foo的调用位置,这里foo是直接被调用的,foo没有被引用到任何其他对象或着被显式绑定到指定对象(显式绑定稍候会说明),因此只能使用默认绑定规则。

 

 2. 隐式绑定

 隐式绑定需要考虑调用位置是否有上下文对象,或者说是够被某个对象包含或拥有,比如以下代码:

 1 function foo(){
 2     console.log(this.a);
 3 }
 4 
 5 var obj = {
 6     a: 1,
 7     foo: foo
 8 };
 9 
10 obj.foo(); // 1

在声明obj时,包含了foo,因此调用obj.foo()时,this绑定到obj,this.a就是obj.a。如果有多个层级的包含关系,this会绑定到最后一层的上下文对象上,比如以下代码:

 1 function foo(){
 2     console.log(this.a);
 3 }
 4 
 5 var obj2 = {
 6     a: 2,
 7     foo: foo
 8 };
 9 
10 var obj1 = {
11     a: 1,
12     obj2: obj2
13 };
14 
15 obj1.obj2.foo(); // 2

此时this会绑定到obj2上。

 还有一种情况,叫隐式绑定丢失,看如下代码:

 1 function foo(){
 2     console.log(this.a);
 3 }
 4 
 5 var obj = {
 6     a: 1,
 7     foo: foo
 8 };
 9 
10 var a = ‘global‘;
11 
12 var bar = obj.foo;
13 
14 bar(); // 严格模式下是undefined,非严格模式下是global

这里指定了bar为obj.foo的一个别名(或者说引用),但是很重要的是,这里bar实际上引用的是foo本身,所以这里调用bar()相当于一个默认绑定,适用于上面讲到的默认绑定规则。如果确实需要函数别名并且把this绑定到指定的对象上,可以使用显式绑定,比如bind、call、apply之类的,后面会陆续谈到。

隐式绑定丢失还会出现在传入回调函数的时候:

 1 function foo(){
 2     console.log(this.a);
 3 }
 4 
 5 function caller(func){
 6     func();
 7 }
 8 
 9 var obj = {
10     a: 1,
11     foo: foo
12 };
13 
14 var a = ‘global‘;
15 
16 caller(obj.foo); // 严格模式下是undefined,非严格模式下是global

在前端的js编程中,由于是事件驱动的,调用回调函数经常发生在用户交互之后,由于绑定丢失,我们经常需要手动把this绑定到某个对象上。

 

3. 显式绑定

如果我们想在某个对象上强制调用函数,可以是用显式绑定。js中的函数的原型是Function对象,它提供了一些通用方法。就显式绑定来说,我们可以使用apply和call这两个方法,具体用法是:

1 func.apply(obj, [arg1, arg2,...]);
2 func.call(obj, arg1, arg2,...);

先不用纠结后面参数的格式(其实apply和call只是在传参格式上不一样而已),apply和call都是将func的this绑定到第一个参数obj上。看以下代码:

 1 function foo(){
 2     console.log(this.a);
 3 }
 4 
 5 var obj = {
 6     a: 1
 7 };
 8 
 9 var a = ‘global‘;
10 
11 foo.call(obj); // 1

此时foo的this绑定到了obj上面。 

apply和call的第一个参数也可以是null,即不绑定到任何对象,但实际上这样会绑定到全局对象:

1 function foo(){
2     console.log(this.a);
3 }
4 var obj = {
5     a: 1
6 };
7 var a = ‘global‘;
8 foo.call(null); // 严格模式下是undefined,非严格模式下是global

虽然apply和call可以把this绑定到指定对象,但是还是没有解决回调函数的问题,因为apply和call都是在当下立刻执行,而回调函数的执行时间是不确定的。而且回调函数的上下文也是不确定的,在回调函数的上下文中可能很难获得我们想要的那个this绑定对象。

为了解决回调函数绑定丢失的问题,我们可以使用硬绑定bind。bind很有用,它可以对this强制绑定一个对象,而且绑定后无法修改。这对我们事件驱动的编程模型很有帮助,可以大量运用在回调函数中。另外bind在js的函数式编程中也是一项利器。看以下代码:

 1 function foo(){
 2     console.log(this.a);
 3 }
 4 var obj1 = {
 5     a: 1
 6 };
 7 var obj2 = {
 8     a: 2
 9 };
10 var bar = foo.bind(obj1); // bar的this永远只会指向obj1
11 bar(); // 1
12 bar.call(obj2); // 1 因为无法改变bind后的this绑定,所以还是1

 

4. new绑定

new绑定是使用new操作符对函数进行调用产生的绑定。js中的new和其他面向对象编程语言的new不同。一般的OOP中new操作符会调用类的构造函数,生成一个全新的类实例。js中没有类,也没有构造函数,使用new操作符调用函数实际上做了以下4件事情: 

    (1) 创建一个全新的对象。

    (2) 这个新对象会和它的原型对象进行连接。

    (3) 这个新对象会被绑定到函数调用的this。

    (4) 如果函数没有返回其他对象,那这个new表达式将自动返回这个新对象。

代码如下:

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

 

总结

本文介绍了this的四种情况,需要再强调的是,判断this的绑定,不要看函数被定义的地方,而要看函数被调用的地方,或者说上下文。

以上是关于JavaScript全解析——this指向的主要内容,如果未能解决你的问题,请参考以下文章

Javascript方法callapplybind的解析与实现

Javascript方法callapplybind的解析与实现

状态模式全解析--JavaScript

关于JavaScript中this指向问题

对JavaScript中的this的理解

深入解析JavaScript中的this关键字