美文网首页
JavaScript中的this指向总结

JavaScript中的this指向总结

作者: 阿羡吖 | 来源:发表于2021-03-19 16:19 被阅读0次

    前置知识:作用域与this

    要想彻底明白 \color{#ff0000}{this}的行为,就要明白作用域和 \color{#ff0000}{this}的关系。
    \color{#ff0000}{JavaScript}中采用的词法作用域,即在函数声明时,就已经确定了其作用域,而与函数在任何处调用没有关系;而 \color{#ff0000}{this}是在运行时确定的,因此对于同一个函数而言,不同的调用方式会导致不同的 \color{#ff0000}{this}绑定(暂时不考虑箭头函数,下文会单独讲)。这一点对于this很重要。

    四种绑定方式

    在各种书籍和博客上都会提到 \color{#ff0000}{this}的绑定方式有四种,分别如下(长不了考虑箭头函数)

    • 默认绑定
    • 隐式绑定
    • 显示绑定
    • \color{#ff0000}{new}绑定

    1、默认绑定

    默认绑定是指,当函数独立调用时(即使没有调用者),\color{#ff0000}{this} 指向\color{#ff0000}{window}

    // 代码1.1
    function foo(){
      console.log(this)
    }
    foo() // window
    

    而在严格模式下 \color{#ff0000}{this}\color{#ff0000}{undefined}

    // 代码1.2
    'use strict'
    function foo(){
      console.log(this)
    }
    foo() // undefined
    

    这种\color{#ff0000}{this}绑定形式很好理解,此外,值得注意的是,立即执行函数表达式 也是函数独立调用的一种,其中\color{#ff0000}{this}也会指向window

    // 代码1.3
    (function(){
      console.log(this) //window
    })()
    

    下面这种形式也是函数独立调用,\color{#ff0000}{this}指向\color{#ff0000}{window}

    代码1.4
    function bar(){
      console.log(this)
    }
    let obj = {
      foo:function(){
        bar();
      }
    }
    obj.foo() // window
    
    2、隐式绑定

    隐式绑定与默认绑定相对应,隐式绑定 可以大致理解为谁调用,\color{#ff0000}{this}就指向谁,即\color{#ff0000}{this}指向当前执行上下文。

    // 代码2.1
    let obj  ={
      foo:function(){
        console.log(this)
      }
    }
    obj.foo() // obj
    

    上面的代码中,先在全局声明了\color{#ff0000}{foo}函数,之后将其赋值给\color{#ff0000}{obj}的foo方法。再次强调,\color{#ff0000}{this}是执行的时候确定,与作用域无关。因此,虽然函数\color{#ff0000}{foo}是在全局声明的,但是调用者是obj,也就是执行时上下文\color{#ff0000}{tobj}对象,因此\color{#ff0000}{this}指向\color{#ff0000}{obj}对象。

    是不是感觉自己懂了?别急,再看看下边的代码

    // 代码2.3
    let obj ={
      foo:function(){
        console.log(this) // window
      }
    };
    let bar = obj.foo
    bar();
    
    obj.foo() 
    

    解析如下:

    • 首先不要一看到函数,就开始在脑海中运行,只要把它看作是一个变量就好,不要考虑函数体里面的代码,等到执行时再去进行解析。
    • obj.off方法声明部分,只需要理解为在堆内存中开辟了一块空间,并由obj.foo持有这块内存空间的作用,由于函数尚未执行,因此还没有确定\color{#ff0000}{this}
    • \color{#ff0000}{obj.foo}赋值给\color{#ff0000}{bar},也就是将函数的引用拷贝一份给bar
    • \color{#ff0000}{bar}独立调用,因此this指向window

    这也就是很多文章中提到的隐式丢失
    代码2.3中的this隐式丢失是由于变量赋值导致的,此外,间接引用也会造成隐式丢失。

    // 代码2.4
    function logThis(){
      console.log(this)
    }
    let obj1 = { log: logThis }
    let obj2 ={ }
    
    obj1.log(); //obj1
    (obj2.log = obj1.log)() // window
    

    这是由于赋值表达式(obj2.log = obj1.log)的返回值是函数logThis的引用,上面的代码可以理解为

    let retFn = (obj2.log = obj1.log)
    retFn();
    

    可以看出,这相当于函数独立调用 符合默认绑定的规则。
    还有一种比较隐蔽的导致隐式丢失的情况:

    //代码2.5
    let obj ={
      foo:function(){
        console.log(this)
      }
    }
    function bar(cb){
      cb();
    }
    bar(obj.foo)  // window
    

    答案同样是window,这就是由于参数传递导致的this隐式丢失,分析如下。

    • 声明\color{#ff0000}{obj.foo},在堆内存中开辟一块空间,并由\color{#ff0000}{obj.foo}持有对内存的引用。
    • \color{#ff0000}{obj.foo}作为函数\color{#ff0000}{bar}的参数,\color{#ff0000}{bar}独立调用,函数执行过程如下所示;
      1、在函数作用域内声明cb,并将实参\color{#ff0000}{obj.foo}赋值给形参cb
      2、cb独立调用。
      从以上的分析可以看出,参数传递导致的隐式丢失与变量赋值导致的隐式丢失,从本质上来说是一样的。

    参数传递导致的this隐式丢失在JavaScript内置API中也十分常见,比如:Array.prototype.mapsetTimeout,ES6的Promise构造函数,node中的readFile等等。这些函数接受一个回调函数作为参数,并在某一时刻执行它,执行模式与下面的伪代码类似

    // 代码2.5
    function fn(cb) {
      // {...代码...}
      cb()
      // {...代码...}
    }
    

    参数传递导致的隐式丢失比较隐蔽,不容易发现,需要额外留意。

    this隐式绑定是指谁调用,this就指向谁
    经常与隐式绑定一同出现的是隐式丢失,隐式丢失常见于三种情况,变量赋值,简介引用和参数传递(常见于回调函数)

    3、显示绑定

    最常见的显示绑定就是\color{#ff0000}{Function.prototype}上的三个方法:\color{#ff0000}{call}\color{#ff0000}{apply}\color{#ff0000}{bind}
    相信这几个方法日常用的就比较多了吧,也就不做过多的赘述。但是有一点还是要注意,就是通过以上三个方法绑定后的this之后,就无法通过这三个方法继续改变this指向了,也就是说,只有一次绑定有效,后续绑定就会失败。

    \color{#ff0000}{bind}有一个点需要注意,就是返回的bound没有prototype属性,但是可以用let ins = new bound()来创建来创建对象。并且ins instanceof boundtrue,这一点规范中有提到,如果instanceof的对象是一个bind之后的函数,就会找原来的函数进行操作。

    image.png
    除了上面提到的三个显式绑定的方法,还有另外两种绑定方式,我也不知道该怎么分类,姑且放到显式绑定里吧:事件的绑定和部分API提供的绑定方式。

    第一种是事件处理函数,在事件处理函数中,this指向绑定事件的元素。第二种是例如Array.prototype.map支持第二个参数来指定回调函数中的this指向。值得一提的是,通过bind可以改变这两种情况中函数的this指向。

    4、new绑定

    当使用new 来实例化一个构造函数时,this指向实例。

    优先级

    绑定的优先级就比较简单了,这四种方式从上到下权重依次增加:new > 显式绑定 > 隐式绑定 > 默认绑定

    箭头函数

    前面说了这么多,都是针对普通函数,也就是用function关键字声明的函数。ES6中引入了箭头函数,都说箭头函数没有this,但是又能在箭头函数中使用this,这可能会给很多人造成困惑。其实,弄清楚箭头函数的this很简单,只需要把箭头函数的this看做一个普通的变量,由于箭头函数自身没有this,所以需要沿着作用域链向上进行查找,直到找到this(普通函数或者window)。也就是说,箭头函数的this由两方面决定:词法作用域和父级函数的this。因此,理解了普通函数的this指向,箭头函数的this也就很简单了。

    那么箭头函数和call一起使用会发生什么呢?由于箭头函数自身是没有this的,而call是改变目标函数的this指向,因此对箭头函数使用call是无效的,箭头函数依然会根据词法作用域来查找this

    转自:https://juejin.cn/post/6935652420265771044

    相关文章

      网友评论

          本文标题:JavaScript中的this指向总结

          本文链接:https://www.haomeiwen.com/subject/ejdvcltx.html