你不知道的Javascript上卷 this部分学习笔记

摘自第二部分的第一章和第二章

  • 为什么要用this?
    • 显示传递上下文对象会让代码变得越来越混乱,使用this就不会这样,不用针对每个对象编写不同版本的函数。
  • this辟谣:
    • this不指向函数自身

    • this在任何情况下都不指向函数的词法作用域。在Javascript内部,作用域确实和对象类似,可见的标识符都是它的属性,但是作用域“对象”无法通过Javascript代码访问,它存在于Javascript引擎内部。

    function foo() {
        var a = 2;
        bar();
    }
    
    function bar() {
        console.log(this.a);
    }
    
    foo(); //undefined
    
  • this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。当一个函数被调用的时候,会创建一个活动记录(有时也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数调用的方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

  • 调用位置是啥?如何找到调用位置?

    • 调用位置就是函数在代码中被调用的位置(而不是声明的位置)。
    • 最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
  • 绑定规则(先找到调用位置):
    • 默认绑定:在代码中,函数是直接使用不带任何修饰的函数引用进行调用的,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。

    • function foo() {
        console.log(this.a);
      }
      
      var a = 2;
      foo(); // 2
      
    • function foo() {
        "use strict";
        console.log(this.a);
      }
      
      var a = 2;
      foo(); // TypeError: Cannot read property 'a' of undefined
      
    • function foo() {
        console.log(this.a);
      }
      
      var a = 2;
      (function() {
        "use strict";
      
        foo(); // 2
      })();
      
    • 隐式绑定:当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

    function foo() {
        console.log(this.a);
    }
    
    var obj = {
        a: 2,
        foo: foo
    };
    
    obj.foo();
    
    • 显示绑定:call(),apply(),第一个参数是一个对象,是给this准备的,在调用函数时将其绑定到this。
    function foo() {
        console.log(this.a);
    }
    
    var obj = {
        a: 22
    };
    
    foo.call(obj); // 22
    
    • new绑定:用new来调用函数的时候,我们会构造一个新对象并把它绑定到该函数调用中的this上(可以理解为this===新对象)。
    function foo(a) {
        this.a = a;
    }
    
    var bar = new foo(3);
    console.log(bar.a); // 3
    
  • 根据优先级判断函数在调用位置应用的是哪条规则(从上到下):

    • 函数是否在new中调用(new绑定)?如果是的话,this绑定的是新创建的对象。
    var bar = new foo();
    
    • 函数是否通过call、apply(显示绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
    var bar = foo.call(obj2);
    
    • 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是指定的是那个上下文对象。
    var bar = obj1.foo();
    
    • 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
    var bar = foo();
    
  • 关于箭头函数:
    • 箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
      // foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法修改(new也不行)
      function foo() {
       return (a) => {
           console.log(this.a);
       }
      };
      
      var obj1 = {
       a: 2
      };
      
      var obj2 = {
       a: 3
      };
      
      var bar = foo.call(obj1);
      bar.call(obj2); // 2, 不是3
      
    • 箭头函数最常用于回调函数中,例如事件处理器或者定时器。

    • 箭头函数用更常见的词法作用域取代了传统的this机制。

      • ES5:
      function foo() {
          var self = this; // 根据词法作用域捕获到了this
          setTimeout(function() {
              console.log(self.a);
          }, 100);
      }
      
      var obj = {
          a: 2
      }
      
      foo.call(obj);
      
      • ES6:
      function foo() {
          setTimeout(() => {
              console.log(this.a);
          }, 100);
      }
      
      var obj = {
          a: 2
      }
      
      foo.call(obj);
      
  • 隐式丢失:关于隐式绑定,会出现被隐式绑定的函数丢失绑定对象的问题,也就是说它会应用默认绑定,这其中的原因一般是直接把对象的函数标识符赋值给某个变量或者当作参数传进某个函数,这其实相当于是引用了函数本身,所以自然会出现丢失绑定对象的问题。还有一种情况是调用回调函数的函数可能会修改this,在一些流行的Javascript库中事件处理器会把回调函数的this强制绑定到触发事件的DOM元素上。
    • function foo() {
        console.log(this.a);
      }
      
      function doFoo(fn) {
        fn();
      }
      
      var obj = {
        a: 2,
        foo: foo
      }
      
      var a = "oops,global";
      
      doFoo(obj.foo); // "oops,global"
      
    • Solution:硬绑定,即创建一个包裹函数,负责接收参数并返回值,ES5也提供了内置的方法Function.prototype.bind,bind()会返回一个硬绑定后的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。
      function foo(something) {
        console.log(this.a, something);
        return this.a + something;
      }
      //简单的辅助绑定函数
      function bind(fn, obj) {
        return function() {
            return fn.apply(obj, arguments);
        };
      }
      
      var obj = {
        a: 2
      }
      
      var bar = bind(foo, obj);
      
      var b = bar(3);
      console.log(b);
      
  • 软绑定:硬绑定会大大降低函数的灵活性,因为使用硬绑定以后就无法使用隐式绑定或者显示绑定来修改this,所以我们有时需要软绑定,即给默认绑定制定一个全局对象和undefined以外的值,可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。
    if (!Function.prototype.softBind) {
        Function.prototype.softBind = function(obj) {
            var fn = this;
            console.log(fn);
            var curried = [].slice.call(arguments, 1);
            var bound = function() {
                return fn.apply(
                    (!this || this === (window || global)) ?
                    obj : this,
                    curried.concat.apply(curried, arguments)
                );
            }
            bound.prototype = Object.create(fn.prototype);
            return bound;
        }
    }
    
    function foo() {
        console.log("name: " + this.name);
    }
    
    var obj = { name: "obj" },
        obj2 = { name: "obj2" },
        obj3 = { name: "obj3" };
    
    var fooOBJ = foo.softBind(obj);
    
    fooOBJ(); // name:obj
    
    obj2.foo = foo.softBind(obj);
    obj2.foo(); // name: obj2
    
    fooOBJ.call(obj3); // name: obj3
    
    setTimeout(obj2.foo, 10); // name: obj
    

发表评论

电子邮件地址不会被公开。 必填项已用*标注