美文网首页Web前端之路
this-动态作用域?

this-动态作用域?

作者: zhaochengqi | 来源:发表于2018-06-15 17:25 被阅读15次

    JavaScript中的作用域是词法作用域,但它的另一个重要机制 this 是动态作用域的表亲。this提供了一种更优雅的方式来隐式传递一个对象的引用(自动引用合适的上下文对象),使得我们可以将API设计得更加简洁和易于复用。
    函数执行环境包含函数的调用栈、调用方法、传入参数等信息,this就是其中一个属性。

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

    1 this 绑定规则

    在严格模式下,this = undefined

    this 是在运行时确定的,并不是在编写时确定。它的上下文取决于函数调用时的各种条件。 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

    分析函数的调用位置最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
    调用位置决定 this 的绑定对象遵循四条规则:

    1.1 隐式绑定

    对象的一个属性指向函数,并通过这个属性调用函数,从而把 this 绑定到这个对象上。

    var a = {
      f: function(){console.log(this)}
    }
    a.f() // this指向a
    
    function bf(){console.log(this)} 
    var b = {f: bf}
    b.f() // this指向b
    

    各种嵌套情况:

    //对象的对象的方法
    var a = {
     b: {
      f: function(){console.log(this)}
     }   
    }
    a.b.f() // this指向b{f: ƒ}
    
    //对象的方法的内部函数
    var a = {
     b: function(){
      (function(){console.log(this)})()
     }   
    }
    undefined
    a.b() //this指向window;因为在调用栈中匿名函数前一个是b函数
    

    1.2 call()/apply()/bind()

    ES5 中提供了内置的方法 Function.prototype.
    bind ,用来显式绑定this:

    function foo(something) {
      console.log( this);
    }
    foo.bind({})() //this 指向 {}
    
    //实现原理如下
    Function.prototype.bind(obj, arguments ) {
      var foo = this
      return function() {
        return foo.apply( obj, arguments );
      };
    }
    

    如果你把 null 或者 undefined 作为 this 的绑定对象传入 call 、 apply 或者 bind ,这些值
    在调用时会被忽略,实际应用的是默认绑定规则

    bind()函数在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行。你可以部份地在脚本开头加入以下代码,就能使它运作,让不支持的浏览器也能使用 bind() 功能。

    if (!Function.prototype.bind) {
      Function.prototype.bind = function(oThis) {
        if (typeof this !== 'function') {
          // closest thing possible to the ECMAScript 5
          // internal IsCallable function
          throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }
    
        var aArgs   = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP    = function() {},
            fBound  = function() {
              return fToBind.apply(
                      //判断是否使用new操作符调用此函数
                      this instanceof fNOP ? this : oThis,
                     // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                     aArgs.concat(Array.prototype.slice.call(arguments)));
            };
    
        // 维护原型关系
        if (this.prototype) {
          // Function.prototype doesn't have a prototype property
          fNOP.prototype = this.prototype; 
        }
        fBound.prototype = new fNOP();
    
        return fBound;
      };
    }
    

    上述算法和实际的实现算法还有许多其他的不同 (尽管可能还有其他不同之处,却没有那个必要去穷尽):

    • 这部分实现依赖于Array.prototype.slice()Array.prototype.concat()Function.prototype.call()这些原生方法。
    • 这部分实现创建的函数的实现并没有caller 以及会在 get,set或者deletion上抛出TypeError错误的 arguments 属性这两个不可改变的“毒药” 。(假如环境支持{jsxref("Object.defineProperty")}}, 或者实现支持__defineGetter__ and __defineSetter__ 扩展)
    • 这部分实现创建的函数有 prototype 属性。(正确的绑定函数没有的)
    • 这部分实现创建的绑定函数所有的 length 属性并不是同ECMA-262标准一致的:它的 length 是0,而在实际的实现中根据目标函数的 length 和预先指定的参数个数可能会返回非零的 length。

    1.3 new绑定

    使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行 [[ 原型 ]] 连接。
    3. 这个新对象会绑定到函数调用的 this 。
    4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

    1.4 默认规则
    无法使用上述各项规则时;this 指向全局对象

    相关文章

      网友评论

        本文标题:this-动态作用域?

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