iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0

今天我想來點 this !

當時在學習 this 概念時,覺得有點複雜,但他又是 JavaScript 中滿重要的概念之一,
而且之前在看 JavaScript 相關面試題目 - javascript-questions 也發現 this 是 JavaScript 面試經典題,最近剛好在整理筆記時,想透過本篇文章再次整理一次,讓自己能輕易掌握概念。

this 是什麼?

在 JavaScript 中,this 的值是動態的,this 跟怎麼被呼叫有關,與放在哪無關!

來透過以下幾種情境與例子來看看 What is this ?

  • 情境一:無明確的呼叫者時,this 指向全局物件 window / global
    在瀏覽器中,當你在全域作用域(也就是没有在任何函式内部)使用 this 時,
    他通常會指向全域物件 window

    console.log(this); // 在瀏覽器中運行,通常輸出全域物件 window
    

    在全域作用域中使用 console.log(this),會輸出全域物件 window
    因為在瀏覽器環境中,全域作用域下的程式碼就是在全域物件 window 下執行的。

    在全域作用域中,如果沒有明確的呼叫者,this 會默認指向全域物件~!


  • 情境二:誰呼叫,誰就是 this
    指在 JavaScript 中,this 的值取決於函式被誰呼叫。
    宣告一個常數 person,其中包含函式 sayHello
    當這個函式被呼叫時,this 將指向呼叫他的對象,也就是 person

    const person = {
      name: "Viii",
      sayHello: function () {
        console.log(`Hello,${this.name}!`);
      },
    };
    
    person.sayHello(); // Hello, Viii!
    

    誰呼叫 sayHellothis 就指向谁,這裡就會輸出 Hello, Viii!


  • 情境三:當使用 new 關鍵字時,this 指向新建立的物件 {}

    // 定義一個名為 Person 的建構函式
    function Person(name, age) {
      // 在建構函式中使用 this 來設定物件的屬性
      this.name = name;
      this.age = age;
    }
    
    // 使用 new 關鍵字來創建一個新的 Person 物件
    const person1 = new Person('Viii', 18);
    
    // 現在 person1 是一個新的物件,this 指向 person1
    console.log(person1.name); // 輸出 "Viii"
    console.log(person1.age);  // 輸出 18
    

    我們定義了一個名為 Person 的建構函式,然後使用 new 關鍵字來創建一個新的 Person 物件 person1。當我們在建構函式中使用 this 設定 nameage 屬性時,this 正確地指向了新建立的 person1 物件。因此,person1.name 等於 Viii,person1.age 等於 18。

    使用 new 關鍵字時,this 指向新建立的物件 {},這裡 this 指向 person1


  • 情境四:Arrow Function (箭頭函式) 没有自己的 this,會指向全域物件 window / global

    Arrow Function (箭頭函式) 在 JavaScript 中沒有自己的 this 綁定,而是捕獲了其所在上下文的this 值。也就是箭頭函式內部的 this 值與包含他的函數或作用域的 this 值是相同的,而不是在常規函數中,this 的值可以根據呼叫方式而變化。

    // 在全局上下文中定義一個物件
    const person = {
      firstName: "Viii",
      lastName: "Chang",
    
      // 使用箭頭函數定義一個方法
      getFullName: () => {
        return `${this.firstName} ${this.lastName}`;
      }
    };
    
    console.log(person.getFullName()); // 輸出 "undefined undefined"
    

    在上面的例子中,getFullName 方法是一個箭頭函數,但他嘗試訪問 this.firstNamethis.lastName,由於箭頭函數沒有自己的 this 綁定,他會捕獲包含他的上下文,這裡是全域上下文。因此,他嘗試訪問全域上下文中的 firstNamelastName,這些值在全域上下文中未定義,所以返回了 "undefined undefined"。

    相比之下,如果我們使用常規函數定義 getFullName,它將能夠正確訪問 person 物件的屬性:

    const person = {
      firstName: "Viii",
      lastName: "Chang",
    
      // 使用常規函數定義一個方法
      getFullName: function() {
        return `${this.firstName} ${this.lastName}`;
      }
    };
    
    console.log(person.getFullName()); // 輸出 "Viii Chang"
    

    在這個例子中,getFullName 方法是一個常規函數,它可以正確地訪問person物件的屬性,因為它有自己的this綁定,並且該綁定指向person物件。


  • 情境五:使用 .call() .apply() .bind()

    以下分別說明這三種情境,明確指定函式內的 this 值:

    情境五之一:使用 .call()

    call() 方法允許在呼叫函式時明確指定函式內的 this 值,並且可以將參數傳遞給函式。
    第一個參數是要指定的 this 值,後續參數是傳遞給函式的引數。

    function greet(message) {
      console.log(`${message}, ${this.name}!`);
    }
    
    const person = {
      name: "Viii"
    };
    
    greet.call(person, "你好"); // 輸出 "你好, Viii!"
    

    call() 方法的第一個參數 person 取代了 greet 函式內的 this,所以在函式內部 this.name 指向了 person 物件的 name 屬性。


    情境五之二:使用 .apply()

    apply() 方法與 call() 方法類似,明確指定函式內的 this 值,並且傳遞參數給函式。
    不同之處在於,參數是以陣列形式傳遞的。

    function greet(message) {
      console.log(`${message}, ${this.name}!`);
    }
    
    const person = {
      name: "Bob"
    };
    
    greet.apply(person, ["嗨"]); // 輸出 "嗨, Bob!"
    

    apply() 方法的第一個參數 person 取代了 greet 函式內的 this,並且參數以陣列形式傳遞給函式。


    情境五之三:使用 .bind()

    bind() 方法不會立即呼叫函式,而是回傳一個新的函式,該函式內的 this 值已經綁定到指定的值。這個新的函式可以稍後呼叫,它將始終具有綁定的 this 值。

    function greet(message) {
      console.log(`${message}, ${this.name}!`);
    }
    
    const person = {
      name: "Charlie"
    };
    
    const greetPerson = greet.bind(person);
    
    greetPerson("嘿"); // 輸出 "嘿, Charlie!"
    

    bind() 方法將 greet 函式內的 this 綁定到 person 物件,並回傳一個新的函式 greetPerson。當呼叫 greetPerson 時,仍然具有與 person 物件相關聯的 this 值。


  • 情境六:是否有用 strict mode (嚴格模式)

    自從 ECMAScript 5(ES5)中引入了 strict mode (嚴格模式),嚴格模式已經成為 JavaScript 中的一個重要特性,旨在提高代碼的安全性,發現並防止一些常見的錯誤。嚴格模式對於 this 的行為也產生了一些影響,特別是在以下情況:

    1. 全域範圍中的this 在非 "嚴格模式" 下,全域範圍中的 this 通常指向全域物件(在瀏覽器中是window)。但在 "嚴格模式" 下,全域範圍中的 this 將是 undefined,而不是全域物件。這有助於防止意外地修改全域物件上的屬性。

    2. 建構函式中的this 在非 "嚴格模式" 下,如果在沒有使用 new 關鍵字的情況下調用建構函式,this 會自動綁定到全域物件。但在 "嚴格模式" 下,如果未明確指定建構函式的執行上下文,this 會保持為undefined,而不會自動綁定到全域物件。

    用簡單的例子說明 "使用嚴格模式" 對 this 的影響:

    "use strict";
    
    function sayHello() {
      console.log(this); // 在嚴格模式下,this 是 undefined
    }
    
    sayHello(); // 在嚴格模式下,不會將 this 綁定到全域物件
    

最後,想透過一個簡單的小題目,來複習一下今天的內容!

Q: How to make User0 do not return "undefined undefined"?

```javascript
const User0 = {
  firstName: "who",
  lastName: "whowho",
  getName: function () {
    const hello = function () {
      return `${this.firstName} ${this.lastName}`;
    };
    return hello();
  },
};
console.log(User0.getName());
```

  • Answer 1: 使用 .call() 方法明確地將 this 綁定到 User0 物件,並呼叫 hello 函式。
const User0 = {
  firstName: "Viii",
  lastName: "Chang",
  getName: function () {
    const hello = function () {
      return `${this.firstName} ${this.lastName}`;
    };
    return hello.call(this);
  },
};
console.log(User0.getName());
  • Answer 2: 使用 .apply() 方法明確地將 this 綁定到 User0 物件,並呼叫 hello 函式。
const User0 = {
  firstName: "Viii",
  lastName: "Chang",
  getName: function () {
    const hello = function () {
      return `${this.firstName} ${this.lastName}`;
    };
    return hello.apply(this);
  },
};
console.log(User0.getName());
  • Answer 3: 使用 .bind() 方法將 this 綁定到 User0 物件,然後立即呼叫 hello 函式(IIFE - 立即調用的函式表達式)。
const User0 = {
  firstName: "Viii",
  lastName: "Chang",
  getName: function () {
    const hello = function () {
      return `${this.firstName} ${this.lastName}`;
    };
    return hello.bind(this)();
  },
};
console.log(User0.getName());
  • Answer 4: 將 hello 函式改為箭頭函式,箭頭函式不會建立自己的 this 綁定,而是捕獲外部上下文的 this,因此可以正確地訪問 User0 物件的屬性。
const User0 = {
  firstName: "Viii",
  lastName: "Chang",
  getName: function () {
    const hello = () => {
      return `${this.firstName} ${this.lastName}`;
    };
    return hello();
  },
};
console.log(User0.getName());

呼~ this 真的是不簡單,以上是今天分享的內容,希望大家都能理解~!
我們下篇見!

參考資料:

文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


上一篇
Day 08 - 理解 JavaScript ,為什麼要知道 Event Loop 事件循環?
下一篇
Day 10 - 理解 JavaScript,為什麼要知道如何建立物件?
系列文
從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言