---
# System prepended metadata

title: 'L4: 物件、函數與「this」'
tags: [JavaScript]

---

L4: 物件、函數與「this」
==
###### tags: `JavaScript`

![](https://i.imgur.com/sl7PtSw.jpg =80%x)
<font color="#999999">圖片來源：udemy：JavaScript 全攻略：克服 JS 的奇怪部分
</font>

每次程式碼被呼叫時，執行環境被創造，每個執行環境的有自己的環境變數（variable Environment），他可以參考外部環境（outer Environment），隨著範圍練去找我們要的變數或函數，也就是說如果我要找一個變數，但不在環境變數裡面，JS就會到外部去找這個變數，直到找到全域環境為止

每當函數被執行，JavaScript引擎會給我們一個不曾宣告的東西，this 變數，this會指向不同的物件，一個不同的東西，依據函數如何被呼叫的

全域環境下
```javascript!
console.log(this)   // windiw object
```

```javascript!
function a() {
  console.log(this)
  this.newvariable = 'hello'
}
var b = function() {
  console.log(this)
}

a()
b()
console.log(newvariable) // hello
```
可以觀察到當我們在==全域==創造一個函數，不論是==函數表示式或函數陳述句，this關鍵字都會指向全域物件 windiw object==

甚至是我們可以直接在function a裡面建立一個屬性newvariable，這會連結它到全域物件，呼叫a()後，就可以印出newvariable，他會存在全域物件裡的變數，直接`console.log(newvariable)`就可以印出

![](https://i.imgur.com/n6mTspO.png)

### 了解物件的this
如果是建立純值，稱為屬性，如果建立的值是一個函數，稱為方法
執行c.log() 會印出`{name: 'the c object', log: ƒ}`，==當函數式連結到物件裡的方法時，this關鍵字成為裡面有方法的物件==，也就是會指向C物件
```javascript!
var c = {
  name: 'the c object',
  log: function(){
    console.log(this) // object{name: 'the c object', log: ƒ}
    this.name = "Updated c object"
  }
}

c.log()  //{name: 'the c object', log: ƒ}
```
由於this會指向C物件，也可以在裡面透過`this.name` 改變C物件的name值，C物件就變成`{name: 'Updated c object', log: ƒ}`
```javascript!
var c = {
  name: 'the c object',
  log: function(){
    this.name = "Updated c object"
    console.log(this)  
  }
}

c.log()  //{name: 'Updated c object', log: ƒ}
```


### JS 的bug ?
在log方法裡面創造一個匿名函數，存在變數setname中，在函數中設定`this.name = newName`，我們在呼叫setname時傳入一個參數到函數裡，命名為newName，我們正在試著用newName改變物件

這時呼叫`setname('Updated again! The c object')`，觀察this會印出什麼？
```javascript!
var c = {
  name: 'the c object',
  log: function(){
    this.name = "Updated c object"
    console.log(this)     //{name: 'Updated c object', log: ƒ}

    var setname = function(newName) {
      this.name = newName
    }
    setname('Updated again! The c object')
    console.log(this)     //{name: 'Updated c object', log: ƒ}
  }
}

c.log()
```

結果兩個console.log都是`name: 'Updated c object'` ， setname並沒有發揮作用
讓我們回到window全域物件看一下，可以找到有一個name值，
這裡的名稱屬性`this.name = newName`，==被等號運算子創造並新增到**全域物件**==，表示當裡面的函數`function(newName) {
      this.name = newName
    }`，它的執行環境被創造時，this指向全域物件，即使他在我創造的物件裡面？

![](https://i.imgur.com/0fxhZHz.png)

所以很多人會覺得這是JS的 bug，應該怎麼做來解決這個情形呢？
有一個常用的模式來應付這個情況，**物件是用 by reference 設定**，我可以設定變數，命名為self（有些人會命名為 that），`var self = this`
==這樣 self 會指向和 this 一樣的記憶體位置==，把程式碼的this 都改為 self 

執行程式碼時，self 雖然沒有在function(newName)函數裡面被宣告，JavaScript 引擎會往範圍鏈裡面找，往外一層級看到外部環境，繼續尋找一個叫作self的變數 然後找到它，等於我讓self物件等同this物件，然後mutation
```javascript!
var c = {
  name: 'the c object',
  log: function(){
    var self = this
    
    self.name = "Updated c object"
    console.log(self)

    var setname = function(newName) {
      self.name = newName
    }
    setname('Updated again! The c object')
    console.log(self)
  }
}

c.log() 
```
就可以看到第二個self 成功變成 `Updated again! The c object`
![](https://hackmd.io/_uploads/rJ2auedDh.png)

改成用let來測試this和self，還是用self才可以改變
![](https://hackmd.io/_uploads/HyQwceuwn.png)


---
### this 題目：
#### Q1: 下列程式碼會在 console 輸出什麼？為什麼？
```javascript
const myObject = {
  foo: 'bar',
  func: function () {
    const self = this
    console.log('outer func: this.foo = ', this.foo)
    console.log('outer func: self.foo = ', self.foo);
    (function () {
      console.log('inner func: this.foo = ', this.foo)
      console.log('inner func: self.foo = ', self.foo)
    }())
  }
}

myObject.func()
```

輸出結果
```javascript
outer func: this.foo =  bar
outer func: self.foo =  bar
inner func: this.foo =  undefined
inner func: self.foo =  bar
```
呼叫 myObject.func() 時，this是在物件下調用，那麼 this 指向 myObject 物件

在 func 函式中定義了一個 self 變數，賦值為 this，這樣self 也是指向 myObject 物件，所以第一個和第二個 console.log會顯示
 outer func ：this.foo = "bar" 和 self.foo = "bar"。

inner func : 為立即執行函式
執行內部函式中的第一個 console.log ：因為內部函式的 this 物件是指向全域物件window，而全域中沒有定義foo，因此 this.foo 的值為 undefined。

執行內部函式中的第二個 console.log ：selfㄕ.foo 參考了外層函式中的 self，，而 self 指向的是 myObject 物件，因此 self.foo 等於 myObject.foo，也就是 'bar'。

#### Q2: 下列程式的 console.log 結果會是什麼？在四種作法裡的 this 指向了誰？

```javascript
var person = {
  firstName  : "Ellen",
  lastName   : "Lee",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
}

function fullName() {
  return `${this.firstName} ${this.lastName}`
}

console.log(person.fullName())
console.log(fullName())
console.log(fullName.bind(person)());
console.log(fullName.call(person));
```

輸出結果
```javascript
person.fullName() = Ellen Lee 
fullName() = undefined undefined
fullName.bind(person)() = Ellen Lee
fullName.call(person) = Ellen Lee
```
第一個：呼叫person裡面的fullName方法，this 指向 person 物件，所以輸出 "Ellen Lee"。

第二個：直接呼叫 fullName() 時，由於 fullName() 函式並不是作為物件被呼叫的，因此this 會指向全域物件，也就是 window 物件，所以輸出"undefined undefined"

不想讓程式自動設定 this 的綁定對象，想要自行明確定義 this 要綁定在誰身上，這就是顯式綁定 。
使用bind, call, apply 的方法，在後面的括號中，傳入想要this變數指向的物件person，這樣把 fullName這個函式中的 this 指稱為Person物件

所以第三個作法和第四個做法：在呼叫該函式時，this 都會指向 person 物件，所以輸出 "Ellen Lee"。

要注意的是使用`.bind`，不會立刻執行函數，所要題目是使用`.bind(person)()`來立刻執行


#### Q3: 頁面上有 3 個按鈕，按下時都會觸發 hello()，請問當三個按鈕被點擊時，console.log 的結果是什麼，hello 函式中的 this 分別指向了誰？
```htmlembedded
<!DOCTYPE html>
<html>
<head>
  <title>Page Title</title>
  <style></style>
</head>
<body>
  <button type="button" id="btn0" onclick='hello()'>Click #00</button>
  <button type="button" id="btn1" onclick='hello.bind(this)()'>Click #01</button>
  <button type="button" id="btn2">Click #02</button>
  <script>
    function hello() {
      console.log(this.id)
    }
    document.querySelector('#btn2').onclick = hello
  </script>
</body>
</html>
```

輸出結果：undefined、 btn1、 btn2

1. 當按下 Click #00 按鈕時，此時的 this 指向的是全域物件。我們並沒有在 global context 中定義 id 屬性，所以 `this.id` 所印出的結果為 undefined。 
2. 當按下 Click #01 按鈕時，console.log 會輸出 btn1，透過 bind() 方法，this 被綁定為按鈕 #btn1， `this.id` 就會是按鈕中的id:btn1。
3. 當按下 Click #02 按鈕時，console.log 會輸出 btn2，因為透過 document.querySelector() 設定，按鈕 #btn2 的 onclick 事件被綁定為 hello 函式，所以 this 指向的是按鈕 #btn2，因此 `this.id` = btn2。
