---
# System prepended metadata

title: 第四節：物件與函數
tags: [JavaScript]

---

第四節：物件與函數
==
###### tags: `JavaScript`

## <font color="#3733FF">Objects And The Dot</font>
物件本身會有一個記憶體位址，它可以參考到其他屬性、物件或方法的所在位址

了解JavaScript如何找出物件 屬性 和 方法 的記憶體位置。

```javascript!
let person = new Object()
person["firstname"] = " Tony" //創造屬性firstname 
let firstNameProperty = "firstname"

console.log(person[firstNameProperty])  //Tony
```

物件本身和屬性方法都待在記憶體內，`.` 點(dot notation)、`[ ]` 中括號(bracket notation)都只是函數，它們是運算子，一個取出資訊的方式

new Object()不是建立物件的好方法，只是範例

```javascript!
person.address = new Object() // 在person物件中新增另一個物件address
person.address.street = "111 Main St."
person.address.city = "New York"

console.log(person)  
// {
//  firstname: ' Tony',
//  address: { street: '111 Main St.', city: 'New York' }
//}
console.log(person.address.street) //111 Main St.
console.log(person["address"]["city"]) //New York
```

雖然我們可以用 dot notation也可以用bracket notation 中括號去取用屬性和方法 最好還是用dot notation運算子就好，簡潔且容易除錯，除非真的需要用動態字串取用屬性(一些可能會改變的字串)，我在寫weather-cast時有遇到


## <font color="#3733FF">Object and Object literal</font>
剛剛的new Object()，可以改寫成下面這樣，直接透過`{}`建立物件

物件也可以被傳進函數當作參數
```javascript
const person = { firstname: 'Tony', lastname: 'Alicea'}

function greet(person) {
    console.log("Hi" + person.firstname)
}

greet(person)

// 也可以同時建立函數和物件
greet({ firstname: 'Mary', lastname: 'Doe' })
```

## <font color="#3733FF">JSON and Object literal</font>
JSON : JavaScript Object Notation
JSON 是被 JavaScript的物件實體語法啟發的，並不是JavaScript的一部分

JSON對於名稱的使用，括號的需求比較嚴格，外層要用`{}`花括號包住，屬性都要加上`""`

用==JSON.stringify== 把物件轉換成JSON語法的字串
```javascript!
const objectLiteral = {
    firstname: "Mary",
    isAProgrammer: true
}

console.log(JSON.stringify(objectLiteral))
//{"firstname":"Mary", "isAProgrammer":true}
```
用==JSON.parse== 把字串轉為物件
```javascript!
const jsonValue = JSON.parse('{"firstname": "Mary", 
    "isAProgrammer": true}')

console.log(jsonValue)
//object{ firstname :"Mary",isAProgrammer:true}
```
## <font color="#3733FF">Function are Objects</font>
在 JavaScript 中函數就是物件

First Class Functions，一級函數可以被傳入別的地方，可以被創造、使用，把函數給另一個函數，

所以當我們說JavaScript的函數就是物件時，函數物件長的什麼樣子？
其實就像 JavaScript 的其他物件一樣，在記憶體裡，它是一個特殊型態的物件，它有所有物件的特色 還有一些其他屬性

函數可以有屬性和方法，在 JavaScript, 函數物件有一些隱藏版的特殊屬性(紅色線)

``` graphviz
digraph graphname{

T [label="Function(a special type of object)"]
P [label="Primitive
(property)"]
A [label="Object
(property)"]
B [label="Function
(method)"]
C [label="Name", color=red,fontcolor=red ]
D [label="CODE", color=red,fontcolor=red ]

T->P
T->A
T->B
T->C[label="optional,
can be anonymous", color=red,fontcolor=red]
T->D[label="invocable", color=red,fontcolor=red]
}
```
Code : 我們寫的程式會成為函數物件的特殊屬性，並非是函數本身，這個函數是有其他屬性的物件，我們寫的程式只是其中一種屬性

這個屬性特別的是，它是可以呼叫的，代表你可以執行這個程式碼，這是當整個執行環境的創造和執行時會發生的

必須把函數想像成物件，而它的程式碼是那個物件的屬性之一，還有許多其他東西函數能夠包含，還有許多其他事情函數可以做，他可以被移動、複製、傳入另一個東西，就像是任何物件或值一樣， 就像是字串或數字一樣

Example:
```javascript!
function greet() {
    console.log('hi')
}
greet.language = 'english' // 用 . 創造屬性
console.log(greet) // function greet() {console.log('hi')}
console.log(greet.language)  //english，存在記憶體中
```
當 greet 函數被創造，這個函數物件會被放進記憶體，是全域物件，名稱是我們命名的 greet，然後有名稱屬性和程式屬性(寫在裡面的程式碼)，如果用greet()呼叫函數，會執行函數，此時執行環境會被創造
```javascript
function greet() {
    console.log('hi')
}

greet()
```
``` graphviz
digraph graphname{

T [label="Function(a special type of object)"]
C [label="Name
greet"]
D [label="CODE 
console.log('hi')" ]

T->C
T->D[label=""]
}
```

## <font color="#3733FF">Function Statements and Function Expressions</font>
函數陳述句與函數表示式 用法差異
- Expression：a unit of code that results in a value (it doesn't hanve to save to a variable)

表示式是程式碼的單位，會形成一個值，所以當我們說函數陳述句會做某件事，但函數表示式或任何表示式，最終會創造一個值，而這個值不一定要儲存在某個變數

簡單的兩種表示式，都會回傳一個值
```javascript!
a = 3  //3
1 + 2  //3
```

來看看兩者的差別

譬如：if 條件是就是陳述句，不會回傳任何值
- 函數陳述句
```javascript!
function greet() {
    console.log('hi')
}
```
當函數被執行，它不會回傳值，這個函數只會被放進記憶體中，但它只是陳述句，不會回傳值，直到函數被執行，但它會被提升（hoist）
在執行環境的創造階段，它是全域執行環境 會放進記憶體中，所以可以被取用，我們可以在宣告greet之前、在建立函數陳述句之前 呼叫greet。

```javascript!
greet()
function greet() {
    console.log('hi')
}
```
![](https://i.imgur.com/1IPOTUe.png)
<font color="#999999">圖片來源：udemy：JavaScript 全攻略：克服 JS 的奇怪部分
</font>

---

- 函數表示式 

寫一個變數名稱 命名它為 anonymousGreet.
這個function 是匿名的，匿名函數就是沒有名稱屬性的函數
```javascript!
var anonymousGreet = function() {
    console.log('hi')
}
anonymousGreet()
```
在 JavaScript 中，函數就像是物件一樣，可以在記憶體中被建立並且傳遞，我們要建立一個物件，設定它等於anonymousGreet這個變數，因為我們可以利用指向物件位址的變數名稱來參照到函數，寫`anonymousGreet()`就可以觸發函數

當等號運算子被執行，它把這個陳述句、這個新的函數物件的值，放到這個變數中，這個變數就指向了記憶體中的一個點

使用表達式的會在執行階段就被執行，他的值會是一個物件（由匿名函數產生的）

而陳述句的函數會被放在記憶體，當執行階段在執行時，只會看到這邊有一個函數不會做任何事

```javascript!
anonymousGreet()
// 匿名函數
var anonymousGreet = function() {
    console.log('hi')
}
```
![](https://i.imgur.com/8MD89YI.png)
<font color="#999999">圖片來源：udemy：JavaScript 全攻略：克服 JS 的奇怪部分
</font>

但不能像陳述句一樣，把呼叫寫在上面，
會出現錯誤: ==Uncaught TypeError: undefined is not a function==
JS執行到anonymousGreet() 這行時，會知道他在記憶體中是undefinded，undefinded是純值不是function，直到執行到下一行，anonymousGreet才被創造出來，匿名函數沒有被提升的特性，必須先將變數賦值給函數物件，才能在程式中呼叫函數，所以一定要先設定值，再呼叫


從下面範例可以看到我們可以透過一級函數做一些事，譬如將物件或函數傳到log當作參數
```javascript
function log(a) {
    console.log(a)
}

log({ greeting: "hi"})

log(function () {
    console.log("hi")
})
```
如果想要執行傳進來的function，可以寫成`function log(a) { a() }`這樣，

## <font color="#3733FF">By Value VS By Reference</font>
傳值 還是 傳址 ？

在一些程式語言中，可以用語法決定 要傳值（pass by value）還是傳參考(pass by reference)，在JavaScript裡面是沒有選擇的，所有純值都是 by value, 而所有的物件都是 by reference.

在第三節有提到純值的type（數字/布林/字串），都是屬於按值拷貝 by value
建立一個變數b，寫上 b = a ，指的是記憶體的不同位置，所以改變b的值不會影響到a
```javascript!
var a = 3
var b = 3
b = a

a = 2
console.log('a', a   //2
console.log('b', b)  //3
```
![](https://i.imgur.com/gC2mi1k.png =60%x)
<font color="#999999">圖片來源：udemy：JavaScript 全攻略：克服 JS 的奇怪部分
</font>

物件都是屬於按址拷貝 by reference（記憶體的位置）
建立一個變數b，寫上 b = a，這時已經a的記憶體位置已經被創造， 而b = a 代表b也等於同樣的記憶體位址，所以沒有新的物件被創造，此時ａ和 b都指向同一個位址，所以改變其中一個值的內容，另一個的值也會被改變
```javascript!
//by reference (all objects(including functions))
var a = { greeting: 'hi'}
var b

b = a
console.log(a)  //{ greeting: 'hi' }
console.log(b)  //{ greeting: 'hi' }

a.greeting = 'hello'
console.log(a)  //{ greeting: 'hello' }
console.log(b)  //{ greeting: 'hello' }

//寫成新的function也一樣，都是mutate greeting
function changeGreeting(obj) {
    obj.greeting = 'hola' 
}
changeGreeting(b)

console.log(a)  //{ greeting: 'hola' }
console.log(b)  //{ greeting: 'hola' }
```
![](https://i.imgur.com/htSJjUX.png =60%x)
<font color="#999999">圖片來源：udemy：JavaScript 全攻略：克服 JS 的奇怪部分
</font>

:::info
補充
b.greeting = 'hello'
改變原本c物件 greeting的值，稱為Mutate，代表的是Change Something，可能是新增、移除屬性或修改屬性值（這個觀念在React中也很重要）
另一個詞是Immutate: mean it can't be changed
:::

但如果我們設定一個新的值給a，使用等號運算子，會設定一個新的記憶體空間給a，此時a 和 b就不再指向同一個記憶體位置
equals operator sets up new memory space(new address)
```javascript!
a = { greeting: 'howdy'}
console.log(a)  //{ greeting: 'howdy' }
console.log(b)  //{ greeting: 'hola' }
```
這是個特殊例子，這不是by reference，因為 等號運算子 看到`a = { greeting: 'howdy'}`還不存在於記憶體，這是新的創造物件的方法 藉由這個物件實體語法
因為他看到第二個參數不是已經存在的物件，他必須建立另一個記憶體空間給物件

[replit練習檔](https://replit.com/@Tsai-WeiWei/value-and-reference#index.js)


## <font color="#3733FF">[物件、函數與「this」](https://hackmd.io/@weii/rksaogww3)</font>
this 獨立出來一個章節


##  <font color="#3733FF">'arguments' 與 spread</font>

JS執行環境除了會有 Variable Environment、Outer Environment、this之外，還有一個特殊的關鍵字: argument（也可以稱為parameters）
argument: The parameters you pass to a function，它包含了所有傳入函數的參數

範例：觀察console.log變化，沒有提供參數時，會印出undefined，
```javascript!
function greet(firstname, lastname, language) {
  console.log(firstname)
  console.log(lastname)
  console.log(language)
  console.log('-------------')
}

greet()
greet('John')
greet('John','Doe')
greet('John','Doe','es')
```
![](https://i.imgur.com/dRUUIpZ.png)

我們也可以給參數預設值，`language='es'`
![](https://i.imgur.com/2TX7HXE.png)

或是`language = language || 'en'` 
![](https://i.imgur.com/ffnFw28.png)

### augument
不用經過宣告，已經內建好的，使用`console.log(arguments)`就會看到我們傳入的所有參數，但他不是陣列，只是array-like，有一部分的陣列功能而已，但augument目前已經逐漸少在使用
![](https://i.imgur.com/AvsE3fs.png)

### spread
ES6 新用法，spread parameter 表示傳入函數的參數，可以用 ... 增加一個參數

例如: 新增address的street name和city name = '111 main st', 'new york', 
他就會被歸類在...other陣列裡面 （`console.log('other',other)`）
```javascript!
greet('John','Doe','es','111 main st','new york')
```
可以看到`'111 main st','new york'`被單獨印出來
![](https://i.imgur.com/XbxcEOi.png)

## <font color="#3733FF">[立即呼叫的函數表示式（IIFEs）](https://hackmd.io/@weii/SkjQHNPw2)</font>
獨立出來一個章節

## <font color="#3733FF">[Understanding Closures 了解閉包](https://hackmd.io/@weii/SJANBmdv3)</font>
獨立出來一個章節

## <font color="#3733FF">框架小叮嚀：Function Factories</font>
了解閉包可以怎麼應用，可以利用這個特性，創造新的函數，用閉包製造預設的參數
來看一個範例： 
建立兩個變數 greetEnglish 和 greetSpanish 來給 `makeGreeting()`不同的參數，而這兩個變數都是函數物件，雖然是呼叫一樣的的函數`makeGreeting()`，但也代表著不同的執行環境，可以藉由這兩個函數再給參數

所以` makeGreeting()` 函數 就像 factory function，我們利用閉包，去設定裡面這個被回傳的函數，所需要的參數值


```javascript!
function makeGreeting(language) {
    
  return function (firstname, lastname) {
    if (language === 'en') {
      console.log('Hello ' + firstname + ' ' + lastname)
    }
    if (language === 'es') {
      console.log('Hola ' + firstname + ' ' + lastname)
    }
  }
}

const greetEnglish = makeGreeting('en')
const greetSpanish = makeGreeting('es')

console.log(greetEnglish)  // [Function (anonymous)]
console.log(greetSpanish)  // [Function (anonymous)]

greetEnglish('John','Doe')  // Hello John Doe
greetSpanish('John','Doe')  // Hola John Doe
```

### <font color="#3733FF">過程發生了什麼</font>
1. 執行程式碼，產生全域執行環境，執行 `var greetEnglish = makeGreeting('en')`，makeGreeting()有自己的執行環境，裡面的language是en ，執行後會回傳函數，存入在greetEnglish裡面
![](https://i.imgur.com/jy5CKGv.png)
---
2. makeGreeting() 離開執行堆，接著執行`var greetSpanish = makeGreeting('es')`，記得每次呼叫一個函數，都會得到新的執行環境，有自己的變數環境，所以裡面的language是es，執行後回傳函數， 離開執行堆
![](https://i.imgur.com/SxN8QUx.png)
---
3. 現在我們有兩個記憶體中的位置，是兩個不同著執行環境，呼叫`greetEnglish('John','Doe')`時，這創造一個新的執行環境，firstname 是 John， lastname 是 Doe，JS引擎知道第一個language，是在第一個執行環境時被創造的，執行環境回傳的就是閉包所在地，greetEnglish回傳了 Hello John Doe
![](https://i.imgur.com/zwJumrF.png)
---
4. 呼叫`greetSpanish('John','Doe')`時，產生自己的執行環境，因為函數物件是在第二次呼叫被創造的，所以它的外部參考會指向，第二次呼叫產生的第二個執行環境，它有自己的閉包，會找到language是es，greetSpanish回傳了 Hola John Doe ![](https://i.imgur.com/nhqR6ms.png)
---

要知道每當你呼叫一個函數，它會得到自己的執行環境，然後在裡面被創造的函數會指向那個執行環境，做他該做的事，指向記憶體空間，就好像其他執行環境沒有消失一樣，他知道該指向哪個，內部函數在哪裡在何時被創造

## <font color="#3733FF">Closures and Callbacks</font>
setTimeout ，其實這也是在使用函數表示式和閉包

執行setTimeout時，三秒後，JS engine會觀察到還要執行`function() {console.log(greeting)}`，目的是要印出console.log(greeting)，但greeting不在這個函數裡面，sayHiLater這個函數也已經執行完了，所以透過scope chain，在閉包內找greeting這個變數
```javascript!
function sayHiLater() {
  const greeting = 'Hi'
  
  setTimeout(function() {
    console.log(greeting)
  },3000)
}

sayHiLater()  // Hi (after 3 sec)
```

### <font color="#3733FF">callback function 回呼函數</font>
A function you give to another function, to be run when the other function is finished
我呼叫函數a，然後給它函數b，當a結束，a呼叫函數b，這就是回呼函數

簡單範例：
我呼叫tellMeWhenDone，並給他一個函數當作參數，執行完一些事情後，執行callback()，呼叫我提供的function
```javascript!
function tellMeWhenDone(callback) {
  const a = 1000
  const b = 2000

  callback() // the callback, it runs the function I gave it
}

tellMeWhenDone(function() {
  console.log('I am done!')
})

tellMeWhenDone(function() {
  console.log('All done!')
})
```
## <font color="#3733FF">[Call()、Apply() and Bind()](https://hackmd.io/@weii/HJ8NwI_v3)</font>
獨立出來一個章節


## <font color="#3733FF">Functional Programming 程式設計</font>
從範例了解程式設計

陣列計算，建立兩個陣列 arr1、arr2，arr2存放把arr1裡面的值，透過for迴圈乘以2後的值
```javascript!
const arr1 = [ 1,2,3 ]
console.log(arr1)       //  [ 1,2,3 ]

const arr2 = []

for ( let i = 0; i < arr1.length; i++) {
  arr2.push(arr1[i] *2)
  console.log(arr2)
}

console.log(arr2)       //  [ 2,4,6 ]
```
### 練習 1
身為工程師都希望可以減少重複做的事情，或者將要做的事情放在函數裡面，在JS中一級函數可以被當作參數傳遞、賦值給變數或儲存在數據結構中，並且能夠作為return value

我們創造一個 mapForEach函數，可以接受兩個參數(arr, func)，在裡面建立一個陣列newArr和for迴圈

在newArr.push裡面做程式設計，`func(arr[i])`，呼叫函數func，傳入參數`arr[i]`，函數執行結束後，回傳newArr

函數表示式，建立arr3，呼叫`mapForEach`函數，將arr1陣列做為參數，並提供一個function 負責return item * 2的值

因為傳入的參數是arr1，會對arr1進行for迴圈遍歷，item 會是 arr1的內容」[1,2,3]，遍歷後得到的結果push給newArr，得到[2,4,6]

```javascript!
function mapForEach(arr, func) {
  const newArr = []
  for (let i = 0; i < arr.length; i++) {
    newArr.push(
      func(arr[i])
    )
  }
  return newArr
}

const arr3 = mapForEach(arr1, function(item) {
  return item * 2
})

console.log(arr3)   // [2,4,6]
```
### 練習 2
我們可以重複利用mapForEach做不同的任務 只要傳入函數，傳入要他做的運算，這是函數程式設計的經典例子

這樣就可以透過不同的需求，傳入不同的function，對陣列做不同的處裡，
譬如要判斷item 是否 > 2 
```javascript!
const arr4 = mapForEach(arr1, function(item) {
  console.log(item)
  return item < 2
})

console.log(arr4)  // [ true, false, false ]
```
### 練習 3
檢查陣列中數字是否有超過限制自定義的數值

建立一個新函數checkLimit，return item > limiter ， 只是用limiter 變數取代固定的數字
但這個函數接受兩個參數，而mapForEach需要接受一個參數的函數，要如何呼叫這個函數？

可以透過bind，我們可以讓函數當中的參數變成預設值，因此等於只需要填入另一個參數就可以了


```javascript!
const checkLimit = function(limiter, item) {
  return item > limiter
}

const  arr5 = mapForEach(arr1, checkLimit.bind(this,1))
console.log(arr5)    // [ false, true, true ]
```
`mapForEach(arr1, checkLimit.bind(this,1))`使用mapForEach函數對arr1陣列進行迭代，並傳入一個函數`checkLimit.bind(this,1)`作為第二個參數。

使用`bind()`方法將checkLimit 函數中的第一個參數limiter綁定為1，因為使用`bind()`會複製一個新函數，所以checkLimit.bind(this,1)實際上是第一個參數limiter綁定1的新函數

當mapForEach函數對arr1陣列進行迭代時，它會呼叫新函數`checkLimit.bind(this,1)`，
並將arr1中的每個元素作為第二個參數傳入，在新函數中，limiter已被綁定為1，所以新函數將檢查傳入的每個元素(item)是否大於1


### 練習4
有沒有辦法只代入Limiter這個參數，所以不用每次都用.bind來達到一樣的效果呢？

建立一個新函數checkLimitSimplified，它會回傳一個函數，並接受兩個參數 limitNumber 和 item
一樣使用 bind 將 limiter 參數綁定到回傳的函數中，這樣當調用函數時， limiter 參數的值就會被固定下來


執行 checkPastLimitSimplified時,它會給我一個已經用bind處理的函數
limitNumber的值是透過checkLimitSimplified(2)裡面的參數2給的
```javascript!
const checkLimitSimplified = function(limiter) {
  return function(limitNumber, item ) {
    return item > limitNumber
  }.bind(this, limiter)
}

const arr6 = mapForEach(arr1, checkLimitSimplified(2))
console.log(arr6)  // [ false, false, true ]
```

我更喜歡下面這個寫法，使用閉包的寫法
```javascript!
const checkLimiterSimplified = function(limiter) {

    return function(item) {
        return item > limiter;
    }
}

const arr6 = mapForEach(arr1, checkLimiterSimplified(2));
console.log(arr6);   // [ false, false, true ]
```


參考[[筆記] 了解JavaScript中functional programming的概念](https://pjchender.blogspot.com/2016/06/javascriptfunctional-programming.html)

---

當你開始用函數程式設計，最好是能夠在層級高的函數，或盡量不要更動他們，而是直接回傳一個新的東西，像是這邊的新陣列，沒有動到原來的陣列，這是函數程式設計的小提醒