4

参考链接:
一道JS面试题所引发的"血案",透过现象寻本质,再从本质看现象
JavaScript This 之谜

在 JavaScript 中,this 是指当前函数中正在执行的上下文环境:

  • 函数调用
  • 方法调用
  • 构造函数调用
  • 间接调用
  • 绑定函数调用
  • 箭头函数
  • 易错场景

函数调用

函数调用 代表了该函数接收以成对的引号包含,用逗号分隔的不同参数组成的表达式。

函数调用中的this

    function sum(a, b) {  
      console.log(this === window); // => true
      this.myNumber = 20; // 在全局对象中添加 'myNumber' 属性
      return a + b;
    }
    // sum() 为函数调用
    // this 在 sum() 中是全局对象 (window)
    sum(15, 16);     // => 31  
    window.myNumber; // => 20

当 sum(15, 16) 被调用时,JavaScript 自动将 this 设置为全局对象,即 window。

严格模式下,函数调用中的 this

严格模式由 ECMAScript 5.1 引进,用来限制 JavaScript 的一些异常处理,提供更好的安全性和更强壮的错误检查机制。使用严格模式,只需要将 'use strict' 置于函数体的顶部。这样就可以将上下文环境中的 this 转为 undefined。这样执行上下文环境不再是全局对象,与非严格模式刚好相反。

    function multiply(a, b) {  
      'use strict'; // 开启严格模式
      console.log(this === undefined); // => true
      return a * b;
    }
    // 严格模式下的函数调用 multiply() 
    // this 在 multiply() 中为 undefined
    multiply(2, 5); // => 10

严格模式不仅在当前作用域起到作用,它还会影响内部作用域,即内部声明的一切内部函数的作用域。

    function execute() {  
      'use strict'; // 开启严格模式
      function concat(str1, str2) {
        // 内部函数也是严格模式
        console.log(this === undefined); // => true
        return str1 + str2;
      }
      // 在严格模式下调用 concat()
      // this 在 concat() 下是 undefined
      concat('Hello', ' World!'); // => "Hello World!"
    }
    execute();  

方法调用

当在一个对象里调用方法时,this 代表的是对象它自身。
严格模式不仅在当前作用域起到作用,它还会影响内部作用域,即内部声明的一切内部函数的作用域。

    var calc = {  
      num: 0,
      increment: function() {
        console.log(this === calc); // => true
        this.num += 1;
        return this.num;
      }
    };
    // 方法调用,this 指向 calc
    calc.increment(); // => 1  
    calc.increment(); // => 2       

构造函数调用

    function Foo () {  
      console.log(this instanceof Foo); // => true
      this.property = 'Default Value';
    }
    // 构造函数调用
    var fooInstance = new Foo();  
    fooInstance.property; // => 'Default Value'    

间接调用

间接调用表现为当一个函数使用了 .call() 或者 .apply() 方法。.call() 和 .apply() 被用来配置当前调用的上下文环境。

    var rabbit = { name: 'White Rabbit' };  
    function concatName(string) {  
      console.log(this === rabbit); // => true
      return string + this.name;
    }
    // 间接调用
    concatName.call(rabbit, 'Hello ');  // => 'Hello White Rabbit'  
    concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'     
两个方法最主要的区别为 .call() 接收一组参数,而 .apply() 接收一串参数作为类数组对象传递。

绑定函数调用

绑定函数调用是将函数绑定一个对象,它是一个原始函数使用了 .bind() 方法。

方法 .bind(thisArg[, arg1[, arg2[, ...]]]) 接收第一个参数 thisArg 作为绑定函数在执行时的上下文环境,以及一组参数 arg1, arg2, ... 作为传参传入函数中。 它返回一个新的函数,绑定了 thisArg。
    function multiply(number) {  
      'use strict';
      return this * number;
    }
    // 创建绑定函数,绑定上下文2
    var double = multiply.bind(2);  
    // 调用间接调用
    double(3);  // => 6  
    double(10); // => 20   
    var numbers = {  
      array: [3, 5, 10],
      getNumbers: function() {
        return this.array;    
      }
    };
    // 创建一个绑定函数
    var boundGetNumbers = numbers.getNumbers.bind(numbers);  
    boundGetNumbers(); // => [3, 5, 10]  
    // 从对象中抽取方法
    var simpleGetNumbers = numbers.getNumbers;  
    simpleGetNumbers(); // => undefined 或者严格模式下抛出错误  
.bind() 创建了一个永恒的上下文链并不可修改。一个绑定函数即使使用 .call() 或者 .apply()传入其他不同的上下文环境,也不会更改它之前连接的上下文环境,重新绑定也不会起任何作用。
    function getThis() {  
      'use strict';
      return this;
    }
    var one = getThis.bind(1);  
    // 绑定函数调用
    one(); // => 1  
    // 使用 .apply() 和 .call() 绑定函数
    one.call(2);  // => 1  
    one.apply(2); // => 1  
    // 重新绑定
    one.bind(2)(); // => 1  
    // 利用构造器方式调用绑定函数
    new one(); // => Object  
补充bind用法
bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。这些参数(如果有的话)作为bind()的第二个参数跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。
    function list() {
      return Array.prototype.slice.call(arguments);
    }
    
    var list1 = list(1, 2, 3); // [1, 2, 3]
    
    // Create a function with a preset leading argument
    var leadingThirtysevenList = list.bind(undefined, 37);
    
    var list2 = leadingThirtysevenList(); // [37]
    var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]  

箭头函数

箭头函数的设计意图是以精简的方式创建函数,并绑定定义时的上下文环境。

箭头函数并不创建它自身执行的上下文,使得 this 取决于它在定义时的外部函数。
    class Point {  
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
      log() {
        console.log(this === myPoint); // => true
        setTimeout(()=> {
          console.log(this === myPoint);      // => true
          console.log(this.x + ':' + this.y); // => '95:165'
        }, 1000);
      }
    }
    var myPoint = new Point(95, 165);  
    myPoint.log(); 
箭头函数一次绑定上下文后便不可更改,即使使用了上下文更改的方法.
    var numbers = [1, 2];  
    (function() {  
      var get = () => {
        console.log(this === numbers); // => true
        return this;
      };
      console.log(this === numbers); // => true
      get(); // => [1, 2]
      // 箭头函数使用 .apply() 和 .call()
      get.call([0]);  // => [1, 2]
      get.apply([0]); // => [1, 2]
      // Bind
      get.bind([0])(); // => [1, 2]
    }).call(numbers);

易错场景

this 在内部函数中

    var numbers = {  
      numberA: 5,
      numberB: 10,
      sum: function() {
        console.log(this === numbers);        // => true
        function calculate() {
          console.log(this === numbers);      // => false
          return this.numberA + this.numberB;
        }
        return calculate();
      }
    };
    numbers.sum();

解决办法:return calculate.call(this);

this在回调函数中

    function Animal(type, legs) {  
      this.type = type;
      this.legs = legs;  
      this.logInfo = function() {
        console.log(this === myCat);       // => false
        console.log('The ' + this.type + ' has ' + this.legs + ' legs');
      }
    }
    var myCat = new Animal('Cat', 4);  
    setTimeout(myCat.logInfo, 1000);  

解决办法:setTimeout(myCat.logInfo.bind(myCat), 1000);

把包含this的方法赋给一个变量

    var name = "aa";
    var user = {
       name: 'hhh',
       sayName: function(){
           console.log(this.name);
       }
   }
   var test = user.sayName;
   test();   // aa 

解决办法:var test = user.sayName.bind(user);


zhouzhou
1.5k 声望76 粉丝

web前端


引用和评论

0 条评论