美文网首页
如何判断JS中this的绑定

如何判断JS中this的绑定

作者: 1海内无双1 | 来源:发表于2018-09-10 23:04 被阅读0次

    this的绑定或者说指向一直是一个令人困惑的地方,在使用VUE开发中方便合适的this带来了诸多便利也带来了更多的不解,如果能从根本上理解了这个问题对工作和语言的理解将产生极大的帮助,下文将结合《你不知道的js》和 ECMAScript文档 来分析这个问题。

    我们都知道作用域的概念,作为必会知识一直活跃在面试题中,Js的作用域属于静态作用域或者说词法作用域,而非动态作用域,也就是说在书写的时候作用域就已经定义好了(其实是在词法解析阶段,但这样说并没有错),举个例子:

    var a = 0;
    function sayA(){  
      console.log(a);
    }
    sayA();
    function otherEnv(){
      var a = 1;
      sayA();
    }
    otherEnv();
    

    两次输出都是0印证了我们的观点,确实是静态作用域,因为动态作用域将输出0,1;但是静态作用域并非总能满足我们的需求,有些变量是执行时需要的但是并不能通过静态作用域获取到,this就扮演了这样一个角色提供合适的上下文。

    可执行代码分为三种:全局代码,evel中的代码,函数中的代码;

    跟我们关系最密切的自然是函数代码的执行,尤其是作为回调函数。This从何而来呢?我们慢慢分析。
    当控制流进入到一个函数内时需要创建一个环境用以执行代码,我们称之为执行上下文(不同于上下文),执行上下文包含词法环境、变量环境和this。前两项可以看为同一个标识符环境,仅在少有的情况会产生差异。根据ES5.1 10.4.3文档中的描述在严格模式下将直接指向thisArg;非严格模式下有如下判断:

    • 1.如果为null或undefined,this指向window;
    • 2.否则对thisArg进行类型检测,不是object类型时this指向toObject(thisArg)的返回值;
    • 3.否则this指向thisArg。
      这段描述略显晦涩,用伪代码来演示如下:
    if(hasStrict){
      this = thisArg;
    }else if(thisArg === null || thisArg === undefined){
      this = window;
    }else if(type(thisArg) !== object){
      this = toObject(thisArg);
    }else{
      this = thisArg;
    }
    

    (这里的thisArg是什么将在下文叙述,暂时可理解为call方法中传递的第一个参数)

    上述的概念主要是讲的进入执行环境之后发生的事情,那执行之前呢?是否还发生了别的事情?书中分为四种情况进行了讨论:一.正常的函数调用(默认绑定);二.作为对象属性的函数调用(隐式绑定);三.利用函数自身的方法调用,apply,call和bind(显式绑定);四.new操作符调用。但是分析的时候分为三种情况来讲:第一种情况和第二种情况一块,原因是他们经过同一个判断过程。下图是我画的一个函数执行过程的流程图:



    一 、正常的函数调用下会有一个调用环境的创建,这个环境保存了此时的上下文,这个过程是通过对调用括号左边的部分进行解析得到的,默认绑定不存在左值,所以thisArg为undefined,通过上面的分析我们可以判断在严格模式下指向undefined,非严格模式指向window;隐式绑定因为会提供一个上下文对象,所以会将thisArg指向thisArg。

    function sayThis(){
      console.log(this);
    }
    sayThis();    // Window
    
    function sayThis(){
      "use strict";
      console.log(this);
    }
    sayThis();    // undefined
    
    var obj = {
       sayThis: function(){
         console.log(this);
       }
    }
    obj.sayThis();  // obj
    

    二、 显式绑定调用以call为例,会将第一个参数作为this执行call左边的函数(call是Function.prototype的方法,理论上所有函数都可以调用),所以thisArg指向第一个参数。

    function sayThis(){
      console.log(this);
    }
    sayThis.call('a');    // String {"a"}
    

    三、 new操作符执行时,函数作为构造器存在的,整个过程较为复杂,过滤掉掉一些不必要逻辑,关于this部分如下:创建一个对象,将this指向新创建的对象,所以thisArg指向新创建的对象。

    function Construct(){
     this.sayThis = function(){
       console.log(this);
     }
    }
    var obj = new Construct();
    obj.sayThis();  // Construct {sayThis: ƒ}
    

    用法讲完了那vue中使用的哪种方式呢?看起来bind的很实用,可能作者考虑到其他的原因并没有使用bind来绑定而是使用了闭包+call的方式保证this指向vue实例。

    补充:

    书中还有一部分写的是关于优先级的叙述,这部分对我们判断优先级有这很重要的作用,写的较为“详细”,其实主要是调用的先后顺序问题,比如花了很多代码来分析显示绑定bind和new的优先级问题,一般情况下通过bind返回的函数被调用时会传入this和参数,然而通过bind返回的函数被当做构造器函数时最终会使用通用的内置方法construct,在该方法中this为新创建的原生对象,完整的优先级如下:

    new > 显示绑定(bind) > 显示绑定(call,apply)> 隐式绑定 > 默认绑定

    其实每个类型的判断还可以细分很多情况,尤其是默认绑定涉及到with和catch语句时,但是这些并不常用但语法规范叙述的很清楚,想了解的可以自己去查看规则。另外还有ES6的箭头函数绑定了父级函数调用时的this值。

    相关文章

      网友评论

          本文标题:如何判断JS中this的绑定

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