# [JS201] 進階 JavaScript:那些你一直搞不懂的地方
###### tags:`JavaScript 觀念`
[TOC]
## 變數
prmitive type is immutable,例如:
```javascript=
var a = 'abc'
a.toUpperCase()
console.log(a) //abc
// 如果是 obj 的話則會改變
```
object type is reference type ,會指向相同的記憶體,例如:
```javascript=
var obj1 = { number: 10 }
var obj2 = obj1
obj2.number = 20
console.log(obj1, obj2) //20, 20
```
讓你摸不透的 = 賦值, = 賦值會指向不同的記憶體,例如:
```javascript=
var obj = { number: 10 }
var obj2 = obj1
obj2 = []
console.log(obj, obj2) //{ number: 10 } []
```
== 與 === 的差別
有套規則再走,[轉換表](JavaScript-Equality-Table)
== 會做型態轉換,規則有點複雜
=== 在 objtect type 之下,比較的是記憶體位置,例外`NaN 不等於任何東西`
```javascript=
var obj = { number: 10 }
var obj2 = obj1
obj2 = []
console.log(obj, obj2) //false
```
變數的生存範圍,函式A裡面再一個函式B的話,scope chain 會是 B => A => global
## 從 Hoisting 理解底層運作機制
> 甚麼時候會有 hosting? > hosting 發生在宣告變數的時候,當 EC 裡面使用到一個變數,但是這個變數在該 EC 裡面卻找不到宣告,就會往上層 EC 找(hoisting),一職都找不到的話就會把宣告提升到全域
> 執行函式在上面,宣告函式在下面
> 還有個原因是不提升的話,沒有辦法達成 function 互相呼叫。
* 先尋找存不存在,不存在再 hoisting? 內層使用外層的已宣告變數也是一種 hosting
* 「變數宣告」、「參數」、「函式」會被提升
```javascript=
console.log(a) // undefined
var a = 10
// equals to
var a // hoisting
console.log(a) // undefined
a = 10
```
```javascript=
test(10) // 10
function test (a) { // hoisting
console.log(a)
}
```
```javascript=
test(10) // undefined
var test = function (a) { // hoisting
console.log(a)
}
// equals to
var test // hoisting
test(10) // undefined
test = function (a) {
console.log(a)
}
```
Hoisting 會提升到哪裡?提升到變數有被宣告或是 global
```javascript=
var a = 1;
function test(){
var a = 7;
inner()
function inner(){
// var a 不會發生
console.log('3.', a);
a = 30;
b = 200;
}
}
test()
```
### 理解 Execution Context 與 Variable Object
* 發生 Hoisting 的權重: 函式 > arguments > 變數宣告
* 初始化完之後才會做賦值(不是絕對,下面 closure 段落的變數 obj1 獲得匿名函式是因為函式執行的結果回傳該匿名函式),也就是說變數一開始會是 undefined,等初始化完之後再給值
* 當我們在進入一個 EC 的時候,會按照順序做以下三件事
1. 把參數放到 VO 裡面並設定好值,傳什麼進來就是什麼,沒有值的設成 undefined
1. 把 function 宣告放到 VO 裡,如果已經有同名的就覆蓋掉
1. 把變數宣告放到 VO 裡,如果已經有同名的則忽略
每 call 一個函式,都會產生一組 EC/VO (包括參數也會初始化):


適用 var 宣告的變數,let const 不適用這規則


### let 與 const 的詭異行為
==變數宣告 let , const 到給變數賦值以前,都會存在於 TDZ==:Temporal Dead Zone,在 TDZ 裡面的時候,變數是不能被存取的
```javascript=
function test() {
yo() // c 的 TDZ 開始
// ReferenceError: Cannot access 'c' before initialization
let c = 10 // c 的 TDZ 結束
function yo(){
console.log(c)
}
}
test()
```
執行流程大概是這樣

test EC scopeChain 忘記放到圖片了...。
## 從 Closure 更進一步理解 JS 運作
總結,Closure,在一個函式裡面回傳另一個函式。
> 下面的範例要分清楚什麼是"宣告"函式,什麼是"執行"函式
> 小括號和大括號一起出現 > 宣告,只有小括號 >執行。用變數來代替函式 > 宣告,用變數和小括號 > 執行。
假設有一個函式,
```javascript=
function complex(num) {
return num * num * num
}
```
我們不希望每次傳入相同的 num 時都重複做一次計算然後回傳,曾經傳入的 num 就不要再做一次計算,直接回傳 + 印出結果就好,例如這樣,
```javascript=
obj1(1) // 1
obj1(2) // 8
```
該怎麼做?
我們寫另外一個函式來做這個判斷,
```javascript=
function storeComplex(fn) {
let result = {}
if (result[num]) {
return result[num]
}
result[num] = fn(num)
return result[num]
}
```
但這樣做還是沒辦法達到目的,因為每次呼叫函式 `storeComplex` 都會宣告一個新的 `let result = {}`,而且我們該怎麼把參數 `num` 傳進去?所以我們需要再宣告一個變數存放 `result` 並且修改原先 `storeComplex` 的邏輯,最後變成這樣,
```javascript=
function complex(num) {
return num * num * num
}
function storeComplex(fn) {
let result = {}
return function (num) {
if (result[num]) { // 這裡不能用這種寫法 result.num
return result[num]
}
result[num] = fn(num)
return result[num]
}
}
const obj1 = storeComplex(complex)
// obj1 用來宣告一個匿名函式
// function (num) {
// if (result[num]) {
// return result[num]
// }
// result[num] = fn(num)
// return result[num]
// }
console.log(obj1(1))
// obj1(1) 相當於把 1 丟給那個匿名函式
console.log(obj1(2))
console.log(obj1(2)) // 這裡不會再做一次運算
```
再延伸,上面程式碼的 EC 會怎麼跑?
> 畫在紙本筆記裡
> 算是影片 再次 cosplay JS 引擎 的複雜化版本
### 初始化的時候,會產生下列物件(?),注意,是初始化,不是執行:
* 每個 EC 都有一個 scope chain 會被建立,scope chain 的內容是 自己的 AO + 自己的`.[[Scope]]`內容
* 函式的 EC 裡面是 AO,非函式的 EC 裡面是 VO
* 在 EC 裡面,如果有函式被宣告,就會有額外的 `.[[Scope]]` 屬性,它的內容會跟宣告時產生的 EC 的 scopeChain 內容相同

> 上面這張圖片的程式碼寫得不完整

**注意**
宣告和執行的差異之一是:
宣告函式 => 有`.[[Scope]]`
執行函式 => 沒有產生`.[[Scope]]`,而是從它開始尋找需要的變數
### 日常生活中的作用域陷阱
* IIFE,立刻執行匿名函式的方法



### Closure 可以應用在哪裡?


(OOP...!!)
## 物件導向基礎與 prototype
* class name 需要是大寫開頭
* 屬於 class 的函式不需要加上 function prefix
* ==constructor 是一個特殊函式,它在 instance 產生(new)的時候會自動執行==,例如
```javascript=
class Dog {
constructor (name) {
this.name = name
}
getName() {
return this.name
}
sayHello() {
console.log('hi i am ', this.name)
}
}
const dog1 = new Dog('bunny')
dog1.sayHello() // 我們沒有執行 this.name = name 但還是可以取得 this.name,因為在 instance 產生的時候就已經做了這件事了
```
### class 在 ES6才出現,==ES5==之前要用 ==prototype, new== 來達成 OOP
以上面段落的 ES6 class 範例為例,ES5 裡面想做到同樣的事情需要這樣做↓
* ES5 裡面的 constructor 是一個函式,使用 new 來指定該函式是一個 contructor,
```javascript=
function dog (name) {
this.name = name
}
const dog1 = new dog('bunny')
console.log(dog1)
const dog2 = new dog('bunny2')
console.log(dog2)
```
* 其餘的 class 函式則放到 protorype 裡面

### instance 使用`.__proto__` 這個屬性來串接 instance 媽媽的`.prototype`
* 變數都會擁有 .__proto__ 這個屬性(非絕對,一直往上層找的話最後會是 null)
```javascript=
let a = 1
// undefined
a.__proto__
// Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
a.protorype
// undefined
a.__proto__.__proto__
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
a.__proto__.__proto__.__proto__ // 這個就是原型鍊
// null
```
* `.__proto__` 和`.prototype`的關係


* 繼承間的關係會是:Object 生出 Instance,Function 生出 funtcion。所以下面的程式碼的結果會是這樣。

```javascript=
function Dog(name) {
this.name = name
}
var d = new Dog('nick')
console.log(d.__proto__.__proto__ === Object.prototype) // true,因為`Dog.prototype`是個物件
console.log(Dog.__proto__ === Function.prototype) // true,因為 Dog 其實就是個 Function 的 instance
console.log(Function.prototype.__proto__ === Object.prototype) // true
```
再舉個影片的例子,
通過 Dog 這個函數所 new 出來的 d 會具有`.__proto__` 屬性,它指向`Dog.prototype`。
`Dog.prototype.__proto__` 則指向 `Object.prototype`,因為`Dog.prototype`是個物件

### new 所做的事情
這裡不使用 new 這個關鍵字,而是自己寫一個函式來達成它的工作

### 物件導向的繼承:Inheritance

所以 new 做的事情就是:
1. 創造一個 obj, O
1. call() constructor 函式,把 constructot 的屬性存放到 O
1. 把O`.__proto__` 串接 O 媽媽的`.prototype`
1. 回傳 O
## 先學完物件導向,學 this 才有意義
:::warning
this 這邊我寫得不好,直接看HW和專文比較好
:::
this 跟函式怎麼被呼叫有關(arrow function 是例外)
* 非 OOP 呼叫函式,this 就是 global (在沒有意義的地方呼叫 this,預設值會是什麼?)
* OOP 的方式呼叫函式,this 就是 instance 本身(call 與 apply)
* 用物件來呼叫函式,this 可以用 call 來判斷(用另一種角度來看 this 的值)
### 在沒有意義的地方呼叫 this,預設值會是什麼?
在寬鬆模式下,global this => Window(瀏覽器)/global(Nodejs)
在嚴格模式下(`'use strict'`),global this => undefined
### 另外兩種呼叫 function 的方法:call 與 apply
`.call()` 和 `.apply()` 用處是一樣的,都是用來呼叫函式並設定函式的 this 的值和傳參數進去,只是前者參數是一個一個傳,後者參數是用==陣列==的形式傳的

> 為什麼用額外用 call/apply 來呼叫函式,直接呼叫函式不好嗎?
詳見下面程式碼,因為 this 所指向的內容是會因應它被呼叫的方式或者是 arrow function 而改變
```javascript=
const obj = { '0': 1, '1': 2 }
Array.prototype.first = function() {
return this
}
console.log(Array.prototype === Array.prototype.first()) // true
console.log(Array.prototype.first()) // Object(0) [ first: [Function (anonymous)] ]
console.log(Array.prototype.first.call(obj)) // { '0': 1, '1': 2 }
```
```javascript=
function log() {
console.log(this);
}
var a = { a: 1, log: log };
var b = { a: 2, log: log };
log();
// instrument.ts:129 Window {0: Window, 1: global, window: Window, self: Window, document: document, name: "", location: Location, …}
a.log();
// instrument.ts:129 {a: 1, log: ƒ}
b.log.apply(a)
// instrument.ts:129 {a: 1, log: ƒ}
```
### 用另一種角度來看 this 的值
快速判斷 this 的內容的方法
```javascript=
const obj = {
a:123,
b: {
fn: function () {
console.log(this)
}
}
}
const a = obj.b.fn
console.log(a())
// a() 可以理解成 umdefined.a.call(undefined)
// 同理 obj.b.fn() 可以理解成 obj.b.fn.call(obj.b)
// 上面兩個註解同樣是使用同一個函式,但是 this 卻會不一樣,因為 this 的值跟函式怎麼被呼叫有關
```

### 強制指定 this:bind()
```javascript=
const obj = {
a:123,
b: {
fn: function () {
console.log(this)
}
}
}
const a = obj.b.fn.bind('ffdaf') // 用 bind 來綁定 fn 的 this
a()
// .bind() 跟 call, apply 不同,它會回傳一個函式
```

### arrow function 的 this
arrow function 的 this 跟函式怎麼被呼叫沒關系而是跟它的 function scope 有關
> 在宣告它的地方的 this 是什麼,它的 this 就是什麼
```javascript=
const obj = {
a:123,
b: {
test: console.log('this ', this), // 不好的寫法(?),他會馬上執行
fn: function () {
console.log('fn', this)
},
fn2: () => {
console.log('fn2', this)
}
}
}
// obj.b.fn()
obj.b.fn2()
// arrow function 的 this 是個例外, this 的值跟函式是怎樣被呼叫的無關,而是跟宣告函式的位置有管(scope)
// 所以 fn2 和 test 的 this 其實是一樣的
```

## event loop


## 練習題
https://github.com/Lidemy/mentor-program-4th/issues/16
### ANS
```javascript=
// 題目說明請參考:
// https://github.com/Lidemy/mentor-program-4th/issues/16
export class Robot {
constructor (x, y) {
this.x = x
this.y = y
}
getCurrentPosition () {
return {'x': this.x, 'y': this.y}
}
go (direction) {
if (direction === 'E') return this.x += 1
if (direction === 'W') return this.x -= 1
if (direction === 'S') return this.y -= 1
if (direction === 'N') return this.y += 1
}
}
export function debounce(fn, delay) {
let timer = null
return function (...args) {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => { fn(...args) }, 250)
}
}
export function memoize(fn) {
const storage = {}
return function (num) {
if(storage[num]) {
return storage[num]
}
storage[num] = fn(num)
return storage[num]
}
}
```