一個可以肯定 this 值的算法

每一個 JavaScript 程序猿,包括我本身,都一直在努力瞭解 this 關鍵字在代碼中的真正身份。javascript

我設計了一個通用算法,能夠幫你在任何狀況下肯定 this 關鍵字的值。雖然我儘量的使算法容易看懂,但仍是建議你多看幾遍並理解相關術語。前端

另外還用了幾個例子展現怎樣用這個算法一步一步的對 this 進行評估,最後你本身親自試一試。java

1. this 算法

把算法定義爲 ThisValueOfFunction(func, invocationType) ,返回值爲在以 invocationtype 方式調用函數 func 時的 this 值:程序員

ThisValueOfFunction(func, invocationType):面試

  1. 若是 func 是一個箭頭函數,那麼算法

    1. 若是 func 是在最外面的做用域 中定義的,那麼返回 globalObject
    2. 不然segmentfault

      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


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索