第四節:物件與函數
==
###### 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')
}
```

<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')
}
```

<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
```

<font color="#999999">圖片來源:udemy:JavaScript 全攻略:克服 JS 的奇怪部分
</font>
物件都是屬於按址拷貝 by reference(記憶體的位置)
建立一個變數b,寫上 b = a,這時已經a的記憶體位置已經被創造, 而b = a 代表b也等於同樣的記憶體位址,所以沒有新的物件被創造,此時a和 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' }
```

<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')
```

我們也可以給參數預設值,`language='es'`

或是`language = language || 'en'`

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

### 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'`被單獨印出來

## <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裡面

---
2. makeGreeting() 離開執行堆,接著執行`var greetSpanish = makeGreeting('es')`,記得每次呼叫一個函數,都會得到新的執行環境,有自己的變數環境,所以裡面的language是es,執行後回傳函數, 離開執行堆

---
3. 現在我們有兩個記憶體中的位置,是兩個不同著執行環境,呼叫`greetEnglish('John','Doe')`時,這創造一個新的執行環境,firstname 是 John, lastname 是 Doe,JS引擎知道第一個language,是在第一個執行環境時被創造的,執行環境回傳的就是閉包所在地,greetEnglish回傳了 Hello John Doe

---
4. 呼叫`greetSpanish('John','Doe')`時,產生自己的執行環境,因為函數物件是在第二次呼叫被創造的,所以它的外部參考會指向,第二次呼叫產生的第二個執行環境,它有自己的閉包,會找到language是es,greetSpanish回傳了 Hola John Doe 
---
要知道每當你呼叫一個函數,它會得到自己的執行環境,然後在裡面被創造的函數會指向那個執行環境,做他該做的事,指向記憶體空間,就好像其他執行環境沒有消失一樣,他知道該指向哪個,內部函數在哪裡在何時被創造
## <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)
---
當你開始用函數程式設計,最好是能夠在層級高的函數,或盡量不要更動他們,而是直接回傳一個新的東西,像是這邊的新陣列,沒有動到原來的陣列,這是函數程式設計的小提醒