9

每个 JavaScript 程序猿,包括我自己,都一直在努力了解 this 关键字在代码中的真正身份。

我设计了一个通用算法,可以帮你在任何情况下确定 this 关键字的值。虽然我尽可能的使算法容易看懂,但还是建议你多看几遍并理解相关术语。

另外还用了几个例子展示怎样用这个算法一步一步的对 this 进行评估,最后你自己亲自试一试。

1. this 算法

把算法定义为 ThisValueOfFunction(func, invocationType) ,返回值为在以 invocationtype 方式调用函数 func 时的 this 值:

ThisValueOfFunction(func, invocationType):

  1. 如果 func 是一个箭头函数,那么

    1. 如果 func 是在最外面的作用域 中定义的,那么返回 globalObject
    2. 否则

      1. SuffeFuncFunc外部函数
      2. 返回 ThisValueOfFunction(outerFunc, outerInvocationType)
  2. 如果 funcoriginFunc 函数的绑定函数,那么

    1. thisArgFunc = OriginFunc.bind(thisarg) 的参数
    2. 返回 thisArg
  3. 如果 funcsomeclass 中的 constructor() 方法,那么

    1. instanceinstance = new SomeClass() 的实例
    2. 返回 instance
  4. 如果 func 是一个常规函数,那么

    1. 如果 invocationtype作为构造函数,那么

      1. newObject 是新构造的对象 newObject = new func()
      2. 返回 newObject
    2. 如果 invocationtype 是间接调用的,那么

      1. thisArgfunc.call(thisArg)func.apply(thisArg) 的参数
      2. 返回 thisArg
    3. 如果 invocationtype方法,那么

      1. object 是在 object.func() 上调用 func 的对象
      2. 返回 object
    4. 如果 invocationtype常规的,那么

      1. 如果启用了严格的模式,那么返回 undefined
      2. 否则返回 globalObject

1.1 算法中使用的术语

这个算法使用了大量的 JavaScript 术语。如果你不熟悉某些东西,先看下面的解释。

  • 箭头函数

    箭头函数是使用粗箭头语法 => 定义的函数。 箭头函数示例:

    const sum = (number1, number2) => {
      return number1 + number2;
    }
  • 绑定函数

    绑定函数是通过在函数上调用方法 myFunc.bind(thisArg, arg1, ..., argN)创建的函数。 绑定函数的示例:

    function originalFunction() {
      // ...
    }
    
    const boundFunction = originalFunction.bind({ prop: 'Value' });
  • 常规函数

    常规函数是用 function 关键字或在对象上定义的简单 JavaScript 函数。 常规函数的示例:

    function regularFunction(who) {
      return `Hello, ${who}!`;
    }
    
    const object = {
      anotherRegularFunction(who) {
        return `Good bye, ${who}!`
      }
    };
  • constructor()

    constructor()class 内部的一种特殊方法,用于初始化类实例。

    class SomeClass() {
      constructor(prop) {
        this.prop = prop;
      }
    }
  • 最外部的作用域

    最外部的作用域是没有外部作用域的最顶级作用域。

    // 最外部的作用域
    let a = 1;
    
    function someFunction() {
      // someFunction() 的作用域
      // 这里不是最外部的作用域
      let b = 1;
    }
  • 外部函数
    外部函数在其作用域内包含另一个函数。

    // outerFunction() 是 myFunction() 的外部函数
    function outerFunction() {
      function myFunction() {
        //...
      }
    }
  • 全局对象

    全局对象是在全局作用域内始终存在的对象。 window 是浏览器环境中的全局对象,在 Node 环境中是 global

  • 调用
    函数的调用只是使用一些参数来调用该函数。

    function sum(number1, number2) {
      return number1 + number2;
    }
    sum(1, 3);           // 调用
    sum.call({}, 3, 4);  // 调用
    sum.apply({}, 5, 9); // 调用
    
    const obj = {
      method() {
        return 'Some method';
      }
    };
    obj.method(); // 调用
    
    class SomeClass {
      constructor(prop) {
        this.prop = prop;
      } 
    }
    const instance = new SomeClass('Value'); // 调用
  • 构造函数调用
    使用 new 关键字调用函数或类时,将发生构造函数调用。

    function MyCat(name) {
      this.name = name;
    }
    const fluffy = new MyCat('Fluffy'); // 构造函数调用
    
    class MyDog {
      constructor(name) {
        this.name = name;
      }
    }
    const rex = new MyDog('Rex'); // 构造函数调用
  • 间接调用
    使用 func.call(thisArg, ...)func.apply(thisArg, ...) 方法调用函数时,会发生间接调用。

    function sum(number1, number2) {
      return number1 + number2;
    }
    
    sum.call({}, 1, 2);  // 间接调用
    sum.apply({}, 3, 5); // 间接调用
  • 方法调用
    当在属性访问器表达式 object.method() 中调用函数时,将发生方法调用。

    const object = {
      greeting(who) {
        return `Hello, ${who}!`
      }
    };
    
    object.greeting('World');    // 方法调用
    object['greeting']('World'); // 方法调用
  • 常规调用
    只用函数参数变量调用 func(...) 时,会发生常规调用。

    function sum(number1, number2) {
      return number1 + number2;
    }
    
    sum(1, 4); // 常规调用
  • 严格模式
    严格模式是对运行 JavaScript 代码有特殊限制的一种特殊模式。 通过在脚本的开头或函数作用域的顶部添加 use strict 指令来启用严格模式。

2.例子

例 1

const myFunc = () => {
  console.log(this); // logs `window`};

myFunc();

ThisValueOfFunction(myFunc, “常规的”)

myfunc 是箭头函数:从而在算法中匹配情况 1。同时 myFunc 在最外面的作用域内定义,匹配情况 1.1

算法 1.1 中返回 globalObject 意思是 myFunc 中的 this 值为全局对象 window(在浏览器环境中)。

例 2

const object = {
  method() {
    console.log(this); // logs { method() {...} }  } 
};

object.method();

ThisValueOfFunction(object.method, “作为方法调用”)

method() 同时是 object 的属性,是常规函数。与算法的情况 4 匹配。

object.method() 是一种方法调用,因为是属性访问的,送一因此与 4.3 匹配。

然后,根据 4.3method() 方法中的 this 等于方法的拥有者 (object.method()) — object

例 3

function MyCat(name) {
  this.name = name;

  const getName = () => {
    console.log(this); // logs { name: 'Fluffy', getName() {...} }    return this.name;
  }

  this.getName = getName;
}

const fluffy = new MyCat('Fluffy');
fluffy.getName();

ThisValueOfFunction(getName, “作为方法调用”)

getName() 是一个箭头函数,所以符合算法的情况 1;因为 mycatgetName()的外部函数,然后与 1.2 匹配。

分支 1.2.2thisgetName() 箭头函数内部的值等于外部函数的值 MyCat

所以让我们在 MyCat 函数上运行算法 ThisValueOfFunction(MyCat, "做为构造函数")

ThisValueOfFunction(MyCat, “作为构造函数”)

MyCat 是常规函数,所以跳转到算法的分支 4

因为 MyCat 做为构造函数调用 new MyCat('Fluffy'),符合分支 4.1。最后根据 4.1.14.1.2thisMyCat 中等于构造的对象:fluffy

然后,返回箭头函数后符合 1.2.2,在 getname() 中的 this 等于 mycatthis,最终结果为 fluffy

3. 练习

要理解这个算法,最好自己亲自试试。下面是 3 个练习。

练习 1

const myRegularFunc = function() {
  console.log(this); // logs ???};

myRegularFunc();

如何确定 myRegularFunc() 中的 this 值?写出你的判断步骤。

练习 2

class MyCat {
  constructor(name) {
    this.name = name;
    console.log(this); // logs ???  }
}

const myCat = new MyCat('Lucy');

如何确定 new MyCat('Lucy') 中的 this 值?写出你的判断步骤。

练习3

const object = {
  name: 'Batman',

  getName() {
    const arrow = () => {
      console.log(this); // logs ???      return this.name;
    };

    return arrow();
  };
}

object.getName();

如何确定 arrow() 中的 this 值?写出你的判断步骤。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:



疯狂的技术宅
44.4k 声望39.2k 粉丝