---
# System prepended metadata

title: JS-3

---

# JS-3


## 複習
> - JS 特色
>   - 編程語言
>     - 解釋執行: 
>       - 編譯一行, 執行一行, 
>       - 慢
>       - 靈活, 動態
>     - vs. 編譯執行( C, java ): 
>       - 編譯全部, 依序執行, 
>       - 快
>   - 頭等函數( first-class function )
>     - vs. C, java : 頭等類
>   - 執行環境: 宿主( host environment )
> - JS 組成 : ECMAScript, DOM, BOM
> - JS 執行過程: 
>   - hoisting 全局(變量, 函數) -> 執行全局 code
>   - 執行全局 code 中遇到執行函數的code :
>     - hoisting 函數(變量,形參, 函數) -> 執行函數
>     


## 瀏覽器
![](https://i.imgur.com/mHXRgvO.jpg)
> - 瀏覽器一開始會去找DNS要域名的IP, 然後才去該服務器要東西

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

> - User Interface : 就是看得到的介面
> - Browser engine : 查詢或操作 Rendering engine
> - ==Rendering engine== : 解析HTML, CSS, 並將其渲染成一個 DOM tree, 存放於內存中
> - NetWorking : 
>   - 接收回應, 交給 Rendering engine 處理
>   - 發送請求給 server
> - ==JS Interpreter== : 
>   - 執行 JS Code, 實際上就是操作 DOM tree, 
>   - 操作完成後 Rendering engine 會更新 DOM tree, 
>   - 更新完後經過 Browser engine 顯示到 User Interface
> - UI Backend : 
>   - 繪製 alert 等窗口
>   - 直接與 OS 交互, 調用 OS 的 API
> - Data Persistence : 存儲一些數據 sessionStorage
> 


## 物件導向程式設計(object oriented programming, oop)
> - 又稱 面向對象編程 ( 中國 )

### Object
> - 一個事物的抽象
> - 一個含有 property 和 method 的容器

> - 人是一個類別, 我就是一個對象, 我的名字就是 property, 我會睡覺就是 method

### object oriented

> - 把相關的物件封裝在一個對象中: 一包一包的對象
>   - 把問題拆成一個一個對象, 每個對象負責他們的功能
>   - vs. procedural programming 
>     - 程序式程式設計: 一條一條的指令
>     - 分析問題, 列出解決步驟, 一步步實現, 一個一個調用

> - procedural programming  就像一盤炒飯, 把一堆東西丟進去炒
> - object oriented 就像咖哩飯, 拿到飯淋上咖哩就成了咖哩飯

> - object oriented 特性
>   - 封裝 ( Encapsulation )
>   - 繼承 ( Inheritance )
>   - 多型 ( Polymorphis )

```javascript=
/* procedural programming
var apple = {price: 30, sweet: 5};
var banana = {price: 20, sweet: 3};
console.log(apple.price, apple.sweet);
console.log(banana.price, banana.price);
*/

// object oriented programming
function Fruit(name, price, sweet) {
  this.name = name;
  this.price = price;
  this.sweet = sweet;

  this.print = function () {
    console.log(this.name, this,price, this.sweet);
  }
}
var apple = new Fruit('apple', 30, 5);
var banana = new Fruit('banana', 20, 3);

apple.print();
banana.print(); 
```
> - 優點: 
>   - 分工明確: 每個人負責不能的對象物件或調用
>   - 容易維護: 哪個對象出錯就找哪個
>   - 物件可重複使用
>   - 高度模塊化

> - object oriented programming 就是 procedural programming 的封裝
> - JS 的看起來就跟 class 一樣
>   - create class -> Instance -> Instance()

### 創建對象

> #### 簡單創建(靜態對象)
> - 以下兩種差不多意思, 問題也差不多
> - 當要創建多個類似對象時, 重複的 code 會很多

> - `new object()`

```javascript=
var tmp = new Object();

tmp.name = 'GodJJ';
tmp.age = 20;
tmp.print = function () {
  console.log(this.name + ' ' + this.age + ' 歲')
}

tmp.print();
```
> - `{}`
```javascript=
var tmp = {

name: 'GodJJ',
age: 20,
print: function () {
  console.log(this.name + ' ' + this.age + ' 歲')
  },
}

tmp.print();
```

> #### 工廠函數
> - 基本上該有的功能封裝都可以裝起來了
> - 小缺點: 自定義對象返回都是 object 類型, 沒有其他類
>   - 因為都是自己在函數裡面用`new Object` 或 `{}` 創建對象後, 返回回來

> - `instanceof` : 判斷對象是否是某個實例


```javascript=
// 函數
function createLiver(name, age) {
  var o = {}
  o.name = name;
  o.age = age;
  o.print = function () {
    console.log(name + ' ' + age + ' 歲');
  }
  return o
}

// 調用函數, 返回一個 {}
var GodJJ = createLiver('Godjj', 20);
var Toyz = createLiver('Toyz', 20);

// 調用 {} 裡面某個屬性指向的的函數
GodJJ.print();
Toyz.print();


// 小缺點:

// 構造函數就可以用自己定義的類來創建
var a = []; // new Array()
console.log(a instanceof Object); // true
console.log(a instanceof Array); // true

// 函數無法自己定義自己的類, 自定義的對象都是用 Object 創建
console.log(GodJJ instanceof Object); // true
// console.log(GodJJ instanceof ?); ? 沒東西可以打
```

> #### 構造函數
> - new 做的事情:
>   - 在內存創建一個空對象
>   - 設置this, 指向創建的對象
>   - 執行構造函數的code
>   - 返回對象

```javascript=
// 函數
function Liver(name, age) {
  this.name = name;
  this.age = age;
  this.print = function () {
    console.log(this.name + ' ' + this.age + ' 歲');
  }
}

// 創建實例對象
var GodJJ = new Liver('GodJJ', 20);
var Toyz = new Liver('Toyz', 20);

// 調用實例對象中的方法
GodJJ.print();
Toyz.print();

// 構造函數就可以解決工廠函數的問題
console.log(GodJJ instanceof Liver); // true
console.log(GodJJ instanceof Object); // ture

var a = [];
console.log(a instanceof Liver); // false


// 補充:
  // constructor 屬性: 返回構造函數
console.log(GodJJ.constructor); // ƒ Liver(name, age) { [...] }
console.log(a.constructor); // ƒ Array() { [native code] }
```

> #### 靜態對象 vs 實例對象

> - 實例對象只能訪問實例屬性跟方法, 不能訪問靜態
> - 構造函數也只能訪問靜態, 不能訪問實例

```javascript=
// 靜態對象
var Math = {
  PI: 3.14, // 靜態屬性
  Max: function () {console.log(100)}, // 靜態方法
  Min: function () {},
}
// 調用靜態方法
Math.Max();

// ------------------------------------- //

// 構造函數
function Liver(name, age) {
  this.name = name; // 實例屬性
  this.age = age;
  this.print = function () { // 實例方法
    console.log(this.name + ' ' + this.age + ' 歲');
  }
}

// 實例對象
var GodJJ = new Liver('GodJJ', 20);

// 調用實例方法
GodJJ.print();



// ---------------------------------- //

// 添加『靜態』屬性
// 靜態屬性在構造函數本身添加
Liver.apple = 20;
// 添加實例屬性在實例對象添加
GodJJ.banana = 30;

// 實例對象只能訪問實例屬性跟方法, 不能訪問靜態
// 構造函數也只能訪問靜態, 不能訪問實例
console.log(Liver.apple); // 20
console.log(GodJJ.banana); // 30
console.log(Liver.banana); // undefined
console.log(GodJJ.apple); // undefined
// 即使從新實例也是
GodJJ = new Liver('GodJJ', 20);
console.log(GodJJ.apple);
```

### 構造函數的問題

> - 複雜數據類型會新開闢空間, 即使功能相同
> - 如此會造成空間浪費

```javascript=
function Liver(name, age) {
  this.name = name;
  this.age = age;
  this.print = function () {
    console.log(this.name + ' ' + this.age + ' 歲');
  }
}

var GodJJ = new Liver('GodJJ', 20);
var Toyz = new Liver('Toyz', 20);
GodJJ.print();
Toyz.print();


console.log(GodJJ.print === Toyz.print); // false

// 兩個實例對象都會各自創建 print 方法, 並將 print 指向各自創建的 function 中
// 但這明明是一樣的功能, 浪費空間與效能
```
> - 解法一: 把 function 拉到外面創建一個後, 讓構造函數直接指向
>   - 缺點: 為了要讓類屬性指向, 拉出來的函數要有名字, 取名真的痛苦
```javascript=
function Liver(name, age) {
  this.name = name;
  this.age = age;
  this.print = print; // 指向函數
}

// 拉到外面創建一個函數
function print() {
  console.log(this.name + ' ' + this.age + ' 歲');
}

var GodJJ = new Liver('GodJJ', 20);
var Toyz = new Liver('Toyz', 20);
GodJJ.print();
Toyz.print();


console.log(GodJJ.print === Toyz.print); // true

// 如此兩個實例對象的 print 屬性都指向同一個 function 空間了
```
> - 解法二: `prototype`( 原型 / 原型對象 )
>   - 每個構造函數都有一個 prototype 屬性
>   - `prototype` 是一個對象
>   - 把 function 塞進 prototype 對象裡

>   - 小結: 一般情況下, 
>     - 公共屬性放構造函數裡 (簡單數據類型)
>     - 公共方法放 `prototype` 裡 (複雜數據類型)

```javascript=
function Liver(name, age) {
  this.name = name;
  this.age = age;
}

// console.log(Liver.prototype); // prototype 是一個對象
Liver.prototype.print = function () { // 不用取名真開心
  console.log(this.name + ' ' + this.age + ' 歲');
}

var GodJJ = new Liver('GodJJ', 20);
var Toyz = new Liver('Toyz', 20);
GodJJ.print();
Toyz.print();


console.log(GodJJ.print === Toyz.print); // true
```

```python=
# 突然想寫一下 python
# class
class Liver(object): 
    def __init__(self, name, age): 
        self.name = name # 實例屬性
        self.age = age 
    def printLiver(self): # 實例方法
        print('%s %s 歲'%(self.name, self.age))

# 實例對象
godjj = Liver('godjj', 20)
toyz = Liver('toyz', 20)

# 調用實例方法
godjj.printLiver() # godjj 20 歲
toyz.printLiver() # toyz 20 歲

if True: 
    print(id(toyz.printLiver), id(godjj.printLiver))
    
# 4494304584 4494304584 
# python 沒有這個困擾, 兩個都是一樣的
```

### prototype 與 constructor

> - 任何函數都有一個 `prototype`, 而預設的 `prototype` 都有一個 `constructor`
> - 構造函數的實例對象都有一個__proto__
> - 對象中的 `__proto__` 其實就是引用 構造函數的`prototype`
>   - `console.log(godjj.__proto__ === Liver.prototype); // true`

> - 調用屬性或方法時, 
>   - 先從對象本身中找, 
>   - 後去對象的 \_\_proto__ 找, 找不到再繼續往上, 一直找到 Object 的 prototype
>   - 都沒有就報錯

> - `constructor` 在構造函數中的`prototype`引用構造函數
>   - `console.log(Liver.prototype.constructor  === Liver) // true`

> - 關係圖
>   <img src='https://i.imgur.com/7aZ1okT.jpg' style='width: 300px'/>  
>
```javascript=
function Liver(name, age) {
  this.name = name;
  this.age = age;
  this.print = function () {
    console.log(123);
  }
}

Liver.prototype.print = function () {
  console.log(this.name + ' ' + this.age + ' 歲');
}

var godjj = new Liver('GodJJ', 20);
godjj.print(); // 123
console.dir(godjj);
  // Liver
    // age: 20
    // name: "GodJJ"
    // print: ƒ ()
    // __proto__: Object -> 引用構造函數的 prototype
      // print: ƒ ()
      // constructor: ƒ Liver(name, age) -> 引用構造函數
      // __proto__: Object -> 註
      
// 實例屬性的 __proto__ 引用 構造函數的 prototype 的址
console.log(godjj.__proto__ === Liver.prototype); // true

// constructor 引用構造函數的址
console.log(godjj.constructor); // ƒ Liver(name, age) {[...]}
console.log(godjj.constructor === Liver); // true
  

// 註:
  // 構造函數也是 他的構造函數 的實例, 
  // 所以構造函數的 __proto__ 也引用的他的構造函數的 prototype
  // Python: type -> class -> 實例對象 的概念

// 整理:
console.dir(Liver); // 構造函數
console.dir(godjj); // 實例對象
console.log(godjj.__proto__.constructor); // ƒ Liver(name, age) {[...]}
                                          // constructor 指向構造函數


// 實例對象(構造函數)的原型對象
// 兩個指向同一個址
console.dir(godjj.__proto__); 
console.dir(Liver.prototype); 
console.log(godjj.__proto__ === Liver.prototype); // true

// 構造函數的上面是 Object
console.dir(godjj.__proto__.__proto__); // Object 的 prototype
console.log(godjj.__proto__.__proto__.constructor); // ƒ Object() { [native code] }
console.log(Object.prototype === godjj.__proto__.__proto__); // true

// Object的上面是 null
console.dir(godjj.__proto__.__proto__.__proto__); // null
```

> - 這也可以解釋為什麼每個函數都有一些方法, 例如 toString()
>   - 因為 toString 在 Object 的 prototype 裡
>   - 即使所有實例都沒有寫 toString, 最後還是會在 Object.prototype 找到來調用
```javascript=
// 呈上函數
console.log(godjj.toString) // "[object Object]"
```

> - 屬性查找與設置
>   - 承前, 查找是先找對象本身, 找不到就往上 \_\_proto__ 找到 Object.prototype 為止
>   - 惟, 設置不會往上找, 如果對象本身沒有, 他就直接新增一個

```javascript=
function Liver(name, age) {
  this.name = name;
  this.age = age;
}
Liver.prototype.sex = '男';
var godjj = new Liver('GodJJ', 20);
var toyz = new Liver('toyz', 20);

console.log(godjj.sex); // 男


godjj.sex = '女';
console.log(godjj.sex); // 女
console.log(toyz.sex); // 男

// 證明設置時沒有到 __proto__ 找到 sex 來改, 否則 toyz 的 sex 應該是女
console.dir(godjj); 
  // Liver
    // age: 20
    // name: "GodJJ"
    // sex: "女" <- 設置時直接塞進對象本身, 利用查找優先的特性來輸出剛設置的東西
    // __proto__: Object
      // sex: "男"
      // constructor: ƒ Liver(name, age)
      // __proto__: Object

console.dir(toyz);
  // Liver
    // age: 20
    // name: "toyz"
    // __proto__: Object
      // sex: "男"
      // constructor: ƒ Liver(name, age)
      // __proto__: Object
```

> #### 重新設置 `prototype`
> - 如果有多個東西想塞進 prototype, 原本的形式太麻煩了
> - 此時就可以考慮直接修改 prototype 的指向
> - 但修改後所指向的對象要記得把 constructor 指回原構造函數
> - 另外重新指向不是動態添加, 所以修改前所創建的實例會沒辦法用修改後的內容
```javascript=
function Liver(name, age) {
  this.name = name;
  this.age = age;
}

/* 太囉唆
Liver.prototype.haha = function () {
  console.log('haha');
}
Liver.prototype.wowo = function () {
  console.log('wowo');
}
*/

// 直接修改 prototype 對象
Liver.prototype = {
  haha: function () {
    console.log('haha');
  },
  wowo: function () {
    console.log('wowo');
  }
}

var godjj = new Liver('godjj', 20);
godjj.haha(); // haha

console.log(godjj.constructor); // ƒ Object() { [native code] }
console.dir(godjj);
  // Liver
    // age: 20
    // name: "godjj"
    // __proto__: -> Liver.prorotype
      // haha: ƒ ()
      // wowo: ƒ ()
      // __proto__: -> Object.prototype
        // constructor: ƒ Object()
        
// 由於設置 prototype 時沒有設置 constructor, 導致 prototype 指向的對象沒有
// 然後就繼續往上找, 所以輸出的結果是 Object

```
> - 解決方法就是修改 prototype 指向時, 要記得把手動設置 constructor 並指回去
```javascript=

function Liver(name, age) {
  this.name = name;
  this.age = age;
}
Liver.prototype = {
  constructor: Liver,
  haha: function () {
    console.log('haha');
  },
}
var godjj = new Liver('godjj', 20);
console.log(godjj.constructor); // Liver(name, age) { [...] }
```

> - 動態添加 與 重新指向

```javascript=
// 動態添加
function Liver(name, age) {
  this.name = name;
  this.age = age;
}

var godjj = new Liver('godjj', 20);
Liver.prototype.haha = function () {
  console.log('haha');
}
godjj.haha(); // haha
```
```javascript=
// 重新指向
function Liver(name, age) {
  this.name = name;
  this.age = age;
}

/* 報錯
var godjj = new Liver('godjj', 20);
Liver.prototype = {
  constructor: Liver,
  haha: function () {
    console.log('haha');
  },
}
godjj.haha(); // Error: godjj.haha is not a function
*/


Liver.prototype = {
  constructor: Liver,
  haha: function () {
    console.log('haha');
  },
}
var godjj = new Liver('godjj', 20); // 重新指向完再用
godjj.haha();  // haha
```

### prototype 應用
> - 為 Array, String 等新增方法

```javascript=
// 為 Array 新增偶數和的方法
Array.prototype.getEvenSum = function () {
  var sum = 0;
  for (var i = 0; i < this.length; i++) {
    if (this[i] % 2 === 0) {
      sum += this[i]
    }
  }
  console.log(sum);
}

var arr = [1,3,4,6];
arr.getEvenSum();
```

> - Array, String 等的prototype 是不可以重新指向的, 只能動態添加

```javascript=
console.dir(Array.prototype);
Array.prototype = {
  constructor: Array,
}
console.dir(Array.prototype); // 沒變
```

### 實作: 隨機方塊
<img src="https://i.imgur.com/2gTn5dE.png" style="width: 200px"/>

`$ vi index.html`
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <title></title>
  <link rel='stylesheet' href='css/style.css' />
</head>
<body>
  <div class='container'></div>
  <script src='js/tools.js'></script>
  <script src='js/box.js'></script>
  <script src='js/main.js'></script>
</body>
```

`vi css/style.css`

```css
.container {
  height: 600px;
  width: 600px;
  background: darkgray;
  position: relative;
}
```
`$ vi js/tools.js`
```javascript=
var Tools = {
  getRandom: function (min, max) {
    return Math.floor(Math.random() * (max-min+1) + min)
  },
}
```
`vi js/box.js`
```javascript=
function createBox(parent, option) {
  option = option || {};
  this.height = option.height || 30;
  this.width = option.height || 30;
  this.backgroundColor = option.backgroundColor || 'orange';
  this.top = option.top || 0;
  this.left = option.left || 0;

  this.div = document.createElement('div');
  parent.appendChild(this.div);
  this.parent = parent

  this.init();
}

createBox.prototype.init = function () {
  this.div.style.height = this.height + 'px';
  this.div.style.width = this.width + 'px';
  this.div.style.backgroundColor = this.backgroundColor;
  this.div.style.top = this.top + 'px';
  this.div.style.left = this.left + 'px';
  this.div.style.position = 'absolute';
}

createBox.prototype.randomPosition = function () {
  // 寫法一: 讓盒子不超出框框的前提下, 自由串動
  // var x = Tools.getRandom(0, this.parent.offsetWidth - this.div.offsetWidth);
  // var y = Tools.getRandom(0, this.parent.offsetHeight - this.div.offsetHeight);
  // 寫法二: 框框分隔很多格, 讓盒子移動到各個格子內
  var x = Tools.getRandom(0, this.parent.offsetWidth / this.div.offsetWidth - 1) * this.div.offsetWidth;
  var y = Tools.getRandom(0, this.parent.offsetHeight / this.div.offsetHeight - 1) * this.div.offsetHeight;

  // console.log(x,y);
  this.div.style.left = x + 'px';
  this.div.style.top = y + 'px';
}

createBox.prototype.randomColor = function () {
  var r = Tools.getRandom(0, 255);
  var g = Tools.getRandom(0, 255);
  var b = Tools.getRandom(0, 255);
  console.log(r,g,b);
  this.div.style.backgroundColor = 'rgb(' + r + ',' + g + ',' + b + ')';
}
```
`$ vi js/main.js`

```javascript=
var container = document.getElementsByClassName('container')[0];

var r,g,b,box, arr=[];
for (var i = 0; i < 10; i++) {
  r = Tools.getRandom(0, 255);
  g = Tools.getRandom(0, 255);
  b = Tools.getRandom(0, 255);
  box = new createBox(container, {
    backgroundColor: 'rgb(' + r + ',' + g + ',' + b + ')',
  })
  box.randomPosition();
  arr.push(box);
}


setInterval(function () {
  // 原本我用 container.children來找, 
  // 可是這找出來的對象DOM的, 跟 createBox 不同
  // 導致這個對象是沒有 createBox 塞的那些方法
  // 所以我就把 createBox 實例塞進陣列來遍歷
  for (var i = 0; i < arr.length; i++) {
    arr[i].randomPosition();
    arr[i].randomColor();
  }
}, 1000);

// console.log(container.children[0].constructor); 
  // ƒ HTMLDivElement() { [native code] }
// console.log(arr[0].constructor);
  // ƒ createBox(parent, option) { [...] }
```

### 自調用函數

> - 由於js直接命名時都是在全局作用域寫
> - 為了避免命名衝突以及各種資料保密等目的, 必須將所有個code包成一個局部作用域
>   否則會出現以下情形

`$ vi a.js`
```javascript=
var a = 100
```
`$ vi b.js`
```javascript=
var a = 200
```
`$ test.html`
```htmlmixed=
<body>
  <script src='a.js'></script>
  <script src='b.js'></script>
</body>
```
![](https://i.imgur.com/Pzf51xT.png)

> - 首先, 每個js檔可能是不同人做的, 難免會取道相同的名字, 導致資料被取代
> - 另外, 一般人怎麼可能可以直接在F12中看你的資料
> - 為了解決此問題, 應該將所以 code 包起來
> - 不過沒有完全解決命名衝突問題, 只能部分解決

`$ vi a.js`
```javascript=
(function () {
  var a = 100
  window.a = a; // 如果沒有給出去, 用戶就永遠訪問不到這個變量
                // 如此就能控制訪問權限
})()
```
`$ vi b.js`
```javascript=
(function () {
  var a = 200;
  window.a = a; // 不過給出去的變量名如果還是一樣, 那還是沒解決命名衝突
                // 不過解決了沒給出去的變量干擾其他人的變量問題
})()
```

### 自調用函數的問題

```javascript=
(function () {
	console.log(1);
})()

(function () {
	console.log(2);
})()

/* 運行結果:
   1
   Uncaught TypeError: (intermediate value)(...) is not a function
*/

/* 原因:

執行完第一個函數時, 會有一個返回值 undefined
接著繼續往下執行, 中間沒有任何結束, 對電腦來說要執行的東西變成

undefined(function () {console.log(2);})()
也就是報錯訊息說的 (intermediate value)(...) 不是一個 function

*/

/* 解決辦法就是加 ; 在自調用函數之間 */
(function () {
	console.log(1);
})();

(function () {
	console.log(2);
})();

// 輸出結果: 1 2

/* 但在後面加可能會有另外一個問題 */
var fn = function () {
	console.log(1);
}

(function () {
	console.log(2);
})();

/* 輸出結果:
 
   1
   Uncaught TypeError: (intermediate value)(...) is not a function

*/

/* 跟一開始一模一樣, 原因也是一樣的
   上面定義函數後面沒有結束, 對電腦來說整個語句變成
var fn = function () {
	console.log(1);
}(function () {
	console.log(2);
})();

  又, 不是每個人都會習慣在每句code的結尾都加 ; 
  所以規範上與實際看到別人寫的 code 的自調用都是在前面加 ; 來結束前面的語句
*/

// 實際的自調用寫法:
;(function () {
  console.log(1);
})()

;(function () {
  console.log(2);
})()
```




### 簡易貪吃蛇邏輯

> - 首先, 先判斷需要什麼對象: 蛇, 食物, 遊戲控制器
> - 接著陸續創建對象, 太多了我也不知道怎麼寫

`$ vi index.html`
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <title></title>
  <link href='css/style.css' rel='stylesheet'/>
</head>
<body>
  <div class='gameBox'></div>
  <script src='js/tools.js'></script>
  <script src='js/candy.js'></script>
  <script src='js/snake.js'></script>
  <script src='js/game.js'></script>
  <script src='js/main.js'></script>
</body>
```

`$ vi css/style.css`

```css=
.gameBox {
  height: 600px;
  width: 600px;
  background: gray;
  position: relative;
}
```

`$ vi js/tools.js`

```javascript=
;(function () {

  var Tools = {
    getRandom: function (min, max) {
      return Math.floor(Math.random() * (max-min+1) + min)
    },
  }
  window.Tools = Tools;
})()
```

`$ vi js/candy.js`

```javascript=
;(function () {
  var elem = []; // 存食物資料(位置)

  // 設置食物初始值
  function Candy(options) {
    // 避免如果調用時沒有傳參, 導致options = undefined
    // 而造成 undefined.x 等的報錯
    options = options || {};

    this.backgroundColor = options.backgroundColor || 'orange';
    this.x = options.x || 0;
    this.y = options.y || 0;
    this.height = options.height || 30;
    this.width = options.width || 30;
  }

  // 顯示食物
  Candy.prototype.render = function (parent) {
    // 生成下一個食物前, 需要把上一個食物砍了, 否則畫面上會有多個食物
    // 玩家不需要知道你怎麼刪的, 所以不需要把刪除方法放在Candy裡面
    // 因為 Candy之後會傳出去, 玩家不會去調用刪除, 否則就沒東西吃了
    // 這裡只需要調用刪除即可
    remove();

    // 生成食物
    var div = document.createElement('div');
    this.x = Tools.getRandom(0, parent.offsetWidth / this.width - 1) * this.width;
    this.y = Tools.getRandom(0, parent.offsetHeight / this.height - 1) * this.height;

    div.style.top = this.y + 'px';
    div.style.left = this.x + 'px';
    div.style.height = this.height + 'px';
    div.style.width = this.width + 'px';
    div.style.backgroundColor = this.backgroundColor;
    div.style.position = 'absolute';
    
    parent.appendChild(div); // 放到畫面上
    elem.push(div); // 把食物記錄起來
  }

  // 由於一般人不會自己去『調用』刪除, 否則貪吃蛇就沒食物吃了
  // 所以不用寫在Candy構造函數中
  // 一般來說貪吃蛇的食物會再被吃掉的時候生成新食物,
  // 也就是在生成之前調用刪除函數即可
  function remove() {
    // 循環刪除陣列資料時, 最好從後向前刪, 否則會有漏刪的麻煩
    // 因為前面刪掉後, 資料位置會往前補
      // arr = [0,1,2]
      // i = 0 -> 把 [0] 刪掉, 此時 arr = [1,2]
      // i = 1 -> 把 [1] 刪掉, 此時 arr=[1] -> 漏刪
    for (var i = elem.length - 1; i >= 0; i--) {
      elem[i].parentNode.removeChild(elem[i]);
      elem.splice(i,1);
    }
  }
  window.Candy = Candy; // 如果我沒給出去, 使用者就訪問不到
                        // 像 remove 跟 elem 就訪問不到
})()
```

`$ vi js/snake.js `

```javascript=
;(function () {

  var elem = [];
  // 設置蛇的基本資料
  function Snake(options) {
    options = options || {};

    this.height = options.height || 30;
    this.width = options.width || 30;
    this.direction = options.direction || 'right';
    // 蛇的身體有很幾格, 所以用陣列存放
    this.body = [
      {x:3, y:2, color: 'red'}, // 第一格是蛇頭
      {x:2, y:2, color: 'skyblue'}, // 預設兩格身體, x,y 是格數
      {x:1, y:2, color: 'skyblue'}, 
    ]
  }

  // 顯示蛇
  Snake.prototype.render = function (parent) {

    // 移動蛇時要把原本的資料刪掉, 否則會有一堆身體跟頭
    // 跟食物一樣, 不用寫給玩家知道怎麼刪的, 也不用讓他們自己刪
    remove();

    
    var obj, div; // 避免在循環裡定義, 這樣會一直開記憶體空間
    // 先定義 len 是因為避免每次 if 的時候都要去找一次 this.body.length 的值
    // 先把固定值拿出來, 效率比較高
    for (var i=0, len=this.body.length; i < len; i++) {
      obj = this.body[i];
      div = document.createElement('div');
      div.style.left = obj.x * this.width + 'px';
      div.style.top = obj.y * this.height + 'px';
      div.style.backgroundColor = obj.color;
      div.style.position = 'absolute';
      div.style.height = this.height + 'px';
      div.style.width = this.width + 'px';
      parent.appendChild(div);
      
      // 創建完要把資料存起來
      elem.push(div);
    }
  }

  // 蛇的移動
    // 蛇在移動時, 給格身體的下一步應該是前一格的資料
    // 蛇頭的移動方式應該是看方向來決定
  Snake.prototype.move = function (candy, parent) {

    var obj = this.body
    // 蛇身體移動移動, 就是前一格的 xy
    for (var i = obj.length - 1; i > 0; i--) {
      obj[i].x = obj[i-1].x;
      obj[i].y = obj[i-1].y;	
    }
    // 蛇頭的移動視方向而定
    switch (this.direction) {
      case 'right':
        obj[0].x += 1;
        break;
      case 'left':
        obj[0].x -= 1;
        break;
      case 'top':
        obj[0].y -= 1;
        break;
      case 'bottom':
        obj[0].y += 1;
        break;
    }
    
    // 判斷蛇有沒吃到食物, 吃到就生成食物與蛇變長
      // 蛇移動的過程才會吃到食物,
      // 所以在這裡判斷是否吃到食物     
      
    var headX = obj[0].x * this.width;
    var headY = obj[0].y * this.height;

    // 判斷吃到了沒
      // 要先拿到食物對象來判斷, 所以要把食物傳進來
    if (candy.x == headX && candy.y === headY) {
      // 多一格身體
        // 吃到食物時, 會增長蛇的長度一格
        // 長大後原本最後一格會往前移(原本的移動), 而最後一格的位置就是生出來的那格
        // 所以把最後一格的資料拿出來後, 添加一個對象到this.body裡, 把資料抄過去      
      var lastBox = obj[obj.length-1]; // 先取末格資料       
      obj.push({ // 身體的資料多一筆, 把末格資料塞進去
        x: lastBox.x,
        y: lastBox.y,
        color: lastBox.color,
      })

      // 生成食物
        // 需要把生成的食物添加到遊戲盒子中, 
        // Snake對象沒有存起來, 所以要馬上面要存起來, 要馬傳參進來
      candy.render(parent);
    }
  }

  function remove() {
    for (var i = elem.length -1; i >= 0; i--) {
      elem[i].parentNode.removeChild(elem[i]);
      elem.splice(i, 1);
    }
  }

  window.Snake = Snake;
})()
```

`$ vi js/game.js`

```javascript=
// 遊戲控制器
;(function () {

  var that;
  // 遊戲所需的材料都拿到
  function Game(parent) {
    this.candy = new Candy();
    this.snake = new Snake();
    this.parent = parent;
    that = this;
  }
  // 遊戲開始
  Game.prototype.start = function () {
  
    // 初始畫面開啟
    this.candy.render(this.parent);
    this.snake.render(this.parent);
    
    // 開始跑
      // 貪吃蛇遊戲不外乎就讓蛇跑與控制方向鍵
    run(); // 能解藕就盡量解
    keyDirection();
  }
  
  // 讓蛇跑
  function run() {
    var headX, headY, maxX, maxY;
    var timer = setInterval(function () {
      // this.snake.move() 
        // this.snake 在這裡是訪問不到的, 
        // 因為調用 setInterval 的是 window,
        // 也就是 this 指向的是 window,
        // 為了訪問到Game, 所以創了一個全局變量 that 存放 Game 對象
        // 此時就可以透過 that 來找到 Game
      
      // 讓蛇跑就是每次移動後顯示
      that.snake.move(that.candy, that.parent);
      that.snake.render(that.parent);	

      // GameOver的規則: 撞牆
      
        // 舌頭的位置
      headX = that.snake.body[0].x; 
      headY = that.snake.body[0].y;
      
        // 牆的右跟下邊界
      maxX = that.parent.offsetWidth / that.snake.width - 1; // 從0開始, 所以-1
      maxY = that.parent.offsetHeight / that.snake.height -1;
      
        // 超出去就停
        // 這裡判斷不太精確, 之後有空可以再改良
      if (headX < 0 || headX > maxX) {
        console.log(1);
        clearInterval(timer);
      }
      if (headY <0 || headY > maxY) {
        console.log(1);
        clearInterval(timer);
      }
    }, 100)
  }
  
  // 獲取方向鍵
  function keyDirection() {
    document.onkeydown = function (e) {
      // console.log(e.keyCode); // 先測試一下上下左右是多少
      switch (e.keyCode) {
        case 38:
          that.snake.direction = 'top';
          break;
        case 40:
          that.snake.direction = 'bottom';
          break;
        case 37:
          that.snake.direction = 'left';
          break;
        case 39:
          that.snake.direction = 'right';
          break;
      }
    } 	
  }
  window.Game = Game;
})()
```

`$ vi js/main.js `

```javascript=
// 遊戲執行
;(function () {

  var gameBox = document.getElementsByClassName('gameBox')[0];
  var game = new Game(gameBox);
  game.start();

})()
```

### 輸出優化
> - 輸出時必須減少使用者加載的時間, 所以可以做一些處理
> - 打包:
>   - html 檔中訪問太多 js 檔了, 每個 js 檔都要經歷收發請求的過程
>   - 解決辦法其實就是把所有 js 檔包成一包
>   - 注意事項:
>     - 記得加 ; 
>     - 順序要對
```javascript=
// tools
;(function () {})()
// candy
;(function () {})()
// snake
;(function () {})()
// game
;(function () {})()
// main
;(function () {})()
```
> - 壓縮:
>   - 就..Google 線上壓縮 js , 
>   - 然後把打包好的js檔丟上去拿到壓縮後的檔案來用
>   - 壓縮其實就是把那些註釋, 空格, 甚至改變變量的命名( 命名越長則佔用空間越大 )
>   - [在線工具](https://tool.oschina.net/jscompress)

### 自調用參數

```javascript=
;(function (window, undefined){
  var xx = 0;
  window.xx = xx;
})(window, undefined)
```
> - window
>   - 通常 自調用函數 在最後會有 `window.xx = xx` 來讓用戶訪問內容
>   - 而為了讓 `window.xxx` 的 window 能被壓縮, 所以會傳 window 實參進去
>     讓 window 形參指向 window, 此時形參名就可以被壓縮了
>     `;(function(w,u){var a=0;w.a=a;})(window,undefined)`
> - undefined
>   - 舊版本的 undefined 可以被賦值, 為了避免這種情形, 所以把 undefined 傳進去


### bind()

> #### 前情提要
```javascript=
function fn() {}
console.dir(fn); // function 也是對象, 所以他也有__proto__那套
```

> - ES5 後在function 的 \_\_proto__ 新增了一個 bind 方法
> - bind() 可以新建一個方法並返回址, 讓方法的 `this`  指向 bind() 的第一個參數
> - 如果要傳參數, 就從第二個參數接著寫即可

> - 這東西看起來跟 python 的 \_\_init__(self) 很像

```javascript=
var a = 100; // window.a = 100
function fn(x,y) { // window.fn = function(){console.log(this.a)} 
  console.log(this.a);
  console.log(this);
  console.log(x + y);
}
fn(1,2) 
  // 100 
  // window -> window.fn() -> this 當然指向 window
  // 3


var b = {a:200};
var fn = fn.bind(b, 1, 2); 
  // 創建一個對象, 
  // 讓新對象的 this 指向bind的第一個參數(b)
  // 返回新對象的址
fn(); 
  // 200 
  // {a: 200} -> b.fn() -> this 指向 b
  // 3
```

`$ vi test.py`
```python
class Test(): 
    def __init__(self, x, y): # self 指向 Test
        self.n1 = x 
        self.n2 = y 
    def add(self): 
        print(self.n1 + self.n2)

t = Test(1,2)  
t.add() # 3
```


> - 實例

```javascript=
// 這是貪吃蛇感應方向鍵的方法
function keyDirection() {
  document.onkeydown = function (e) {
    // console.log(e.keyCode); // 先測試一下上下左右是多少
    switch (e.keyCode) {
      case 38:
        this.snake.direction = 'top';
        break;
      case 40:
        this.snake.direction = 'bottom';
        break;
      case 37:
        this.snake.direction = 'left';
        break;
      case 39:
        this.snake.direction = 'right';
        break;
    }
  }.bind(that);  // 讓 this 指向 that	
}
```

### call()
`call([thisArg[, arg[, arg[, ...]]]])`

> - 調用函數且改變 this 指向
> - `call()`  的參數用法跟 `bind()` 相同
>   - 第一個參數為 this 的指向
>   - 後面開始為你要用的實參
> - vs. `bind()`
>   - `bind()` 只返回一個新函數, 而不調用函數

```javascript=
var a = 100; // window.a = 100
function fn(x,y) { // window.fn = function(){console.log(this.a)} 
  console.log(this.a);
  console.log(this);
  console.log(x + y);
}


var b = {a:200};

/* bind()
var fn = fn.bind(b, 1, 2);
fn(); // 200 -> b.fn() -> this 指向 b
*/
fn.call(b, 1,2); // this -> b
  // 200 // b.a
  // {a: 200} // b
  // 3
```

## ES6 類

### 創建類
```javascript=
class 類名 {}
```
### 實例類
```javascript=
var tmp = new 類名();
```
### constructor
> - 實例類時, 就會自動調用這個函數(即使沒寫也會)
> - constructor 可以接收 new 的參數, 並返回實例對象
> - 類裡面的函數沒有 `function`, 類也沒有`()`

> - 看起來跟 python 的 \_\_init__ 有點像 
```javascript=
class Name {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
var godjj = new Name('GodJJ', 20);
console.log(godjj);
```

### 類的方法

> - 方法間別加逗號

```javascript=
class Name {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  play(where) {
    console.log(this.name + '打' + where);
  }
}
var godjj = new Name('GodJJ', 20);
godjj.play('ad'); // GodJJ打ad
```

> - 創建時就調用方法

```javascript=
class Father {
  constructor(x,y) {
    this.x = x;
    this.y = y;
    this.sum();
      // 這句話做了兩件事,
      // 1. 將 sum 傳到 constructor 對象中
      // 2. 調用 sum
  }
  sum() {
    console.log(this.x + this.y);
  }
}

new Father(1,2);
```


### 類的繼承

> - `class a extends b` 即讓 a 繼承 b
> - `super()` 會調用父類的構造函數(constructor), 而讓 this 指向子類 ( 調用者 )
```javascript=
class Father {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
  sum() {
    console.log(this.x + this.y);
  }
}

class Son extends Father {
  constructor(x,y) {
    // 這裡的 this 是指向 Son.constructor
    // 所以傳值到 Son 的值是無法把值傳進 Father 用的
    // 導致 son.sum(); 時會沒有值而報錯
    // this.x = x;
    // this.y = y;
    
    // super 就相當於 Function.call() 
    // 調用父類的 constructor , 並將 this 指向 Son
    super(x,y); 
  }
}

var son = new Son(1,2);
son.sum();
```

> - 既然 super 調用後會讓 this 指向子類
> - 那麼當然 super 也能讓子類調用父類的方法

```javascript=
class Father {
  test() {
    console.log('爸爸測試一下');
  }
}

class Son extends Father {
  test() {
    // console.log('兒子測試一下')
    super.test()
  }
}

var son = new Son();
son.test(); // 爸爸測試一下
```

> - super 必須放到 this 之前

```javascript=

class Father {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
  sum() {
    console.log(this.x + this.y);
  }
}

class Son extends Father {
  constructor(x,y) {
    super(x,y);
    this.x = x;
    this.y = y;
    // super(x,y); -> 放這裡會報錯
  }
  sub() {
    console.log(this.x - this.y);
  }
}

var son = new Son(5,3);
son.sum();
son.sub();
```

### 注意事項

> - 類不會 hoisting
```javascript=
var f = new Father();
class Father {}

// Cannot access 'Father' before initialization
```
> - 類的 this 指向
>   - constructor 的 this 指向類實例對象
>   - 方法裡的 this 指向調用者

```javascript=
var that;
var thaat;

class Father {
  constructor() {
    console.log(this);
    that = this;
  }
  tmp() {
    console.log(this);
    thaat = this;
  }
} 

var fa = new Father(); // Father {}
  // constructor 的 this 指向實例對象
fa.tmp(); // Father {}
  // 方法裡面的 this 指向調用者
  // 實例對象調用, this 當然指向該對象

console.log(that === fa); // true
console.log(thaat === fa); // true
```

> - 陷阱題:

```htmlmixed=
<body>
  <button id='btn'></button>
  <script src='tmp.js'></script>
</body>
```
```javascript=
var that;
class Father {
  constructor(name) {
    that = this;
    this.name = name;
    this.btn = document.querySelector('#btn');
    this.btn.onclick = this.tmp;
  }
  tmp() {
    console.log(this);
    console.log(this.name);
    console.log(typeof this.name);
    console.log(that.name);
  }
} 

var fa = new Father('GodJJ');

fa.tmp();
// 輸出結果
  // Father {name: "GodJJ", btn: button#btn} -> fa 調用的
  // GodJJ -> 輸出 fa.name
  // string
  // GodJJ -> that 指向 constructor 創建的對象 ( fa的指向 )


// 打擊 button 後 輸出結果
  // <button id='btn'></button> -> 觸發事件的事 btn, 也就是調用tmp的是 btn
  //        -> 這個 this 是指向 DOM, 而 DOM 沒有 name 屬性, 所以是空的
  // string -> 但好像又不是 undefined, 是一個空 str
  // GodJJ -> 如果想要點擊後使用 constructor 的屬性, 那就可以用 that 存起來那招
  

// 實際上 fa 對象裡裝的東西
  // btn: button#btn
  // name: 'GodJJ'
  // __proto__: Father.prototype
```

### 實作: tab 欄
![](https://i.imgur.com/qrjwlEn.png)

> - 點擊tab 會切換
> - 點擊 x 會刪除
> - 點擊 + 會新增
> - 雙擊內容可以更新

> - 細節都在注釋, 刪除功能有三行非常精彩
```htmlmixed=
<head>
  <meta charset='utf-8'>
  <link rel='stylesheet' href='css/style.css'/>
</head>
<body>
  <div class='tabBox'>
    <div class='tabTop clearfl'>
      <ul class='clearfl'>
        <li class='curTab'><span>1</span><i>x</i></li>
        <li><span>2</span><i>x</i></li>
        <li><span>3</span><i>x</i></li>
      </ul>
      <div class='addBtn'>
        <span>+</span>
      </div>
    </div>
    <div class='tabBody'>
      <section class='curBody'><span>1</span></section>
      <section><span>2</span></section>
      <section><span>3</span></section>
    </div>
  </div>

  <script src='js/tab.js'></script>
</body>
```
```css=
* {
  margin: 0;
  padding: 0;
}
i {
  text-decoration: none;
  font-style: normal;
}
ul {
  list-style: none;
}

.tabBox {
  height: 600px;
  width: 800px;
  border: 1px solid orange;
  margin: 100px auto;
}

.clearfl:before,
.clearfl:after {
  content: '';
  display: table;
}
.clearfl:after {
  clear: both;
}

.tabTop ul {
  float: left;
}

.tabTop li {
  float: left;
  line-height: 30px;
  width: 100px;
  border-right: 1px solid gray;
  border-bottom: 1px solid gray;
  text-align: center;
  position: relative;
}
.tabTop li i {
  position: absolute;
  display: block;
  line-height: 10px;
  font-size: 8px;
  width: 10px;
  color: white;
  background: black;
  top: 0;
  right: 0;
  border-radius: 0 0 0 100%;
}
.tabTop .curTab {
  border-bottom: 0;
}

.addBtn {
  float: right;
  line-height: 20px;
  width: 20px;
  border: 1px;
  margin-right: 5px;
  margin-top: 8px;
  border: 1px solid gray;
  text-align: center;
}


.tabBody {
  height: 569px;
  width: 100%;
  padding: 10px;
  box-sizing: border-box;
  position: relative;
}

.tabBody section {
  height: 100%;
  width: 100%;
  position: absolute;
  display: none;
}
.tabBody .curBody {
  display: block;
}
```
```javascript=
var that; // 很多事件觸發, 如此要操作 constructor 就有困難, 所以開 that
class Tab {
  constructor(cls) {
    that = this;
    this.main = document.querySelector(cls);
    this.ul = this.main.querySelector('ul');
    this.addBtn = this.main.querySelector('.addBtn');
    this.tabBody = this.main.querySelector('.tabBody');
    this.init();
  }
  init() {
    // 獲取元素
    this.getLisSecElem(); 
    // 添加事件
    for (var i = 0; i < this.lis.length; i++) {
      this.lis[i].index = i; // 存位置, 等等比較好做事
      this.lis[i].onclick = this.switchTab;
      this.closes[i].onclick = this.delTab;
      this.spans[i].ondblclick = this.updateTab;
      this.sections[i].ondblclick = this.updateTab;
    }
    this.addBtn.onclick = this.addTab;
  }
  // 把為了每次動態添加都需要重新獲取的元素抽出來
  // 因為 querySelector 不是動態集合
  // 如果適用 get 系列好像就不用這樣搞剛
  getLisSecElem() { 
    this.lis = this.main.querySelectorAll('li');
    this.sections = this.main.querySelectorAll('section');	
    this.closes = this.main.querySelectorAll('i');
    this.spans = this.main.querySelectorAll('.tabTop li span');
  }
  
  // 點擊切換
  switchTab() {
    that.clearClsName(); // 排他, 因為很常用, 所以拿出去寫
    this.className = 'curTab';
    that.sections[this.index].className = 'curBody';
  }
  clearClsName() {
    for (var i = 0; i < that.lis.length; i++) {
      that.lis[i].className = '';
      that.sections[i].className = '';
    }
  }
  
  addTab() {
    /* 要改內容還要找span, 太麻煩了
     * 用 create 那個更麻煩, 要開超多東西
    var cloneLi = that.lis[0].cloneNode(true);
    var span = cloneLi.querySelector('span');
    span.innerText = 'tmp';
    */

    
    that.clearClsName(); // 排他, 這是事件方法(點擊+), this 是指向 +
    var random = Math.random(); // 為了看 span.innerText 內容有沒有改, 測試用的
    
    // elem.insertAdjacentHTML(position, text)
      // 用法補在WebAPI那
      // 直接寫 str 插到父元素就好了
    var li = '<li class="curTab"><span>tmp</span><i>x</i></li>'
    var section = '<section class="curBody"><span>'+ random + '</span></section>'
    that.ul.insertAdjacentHTML('beforeend', li);
    that.tabBody.insertAdjacentHTML('beforeend', section);
    that.init(); // 因為querySelector() 不是動態集合, 所以每次添加元素後都要重新獲取元素
  }
  
  // 點擊刪除
  delTab(e) {
    // 取消冒泡, 避免觸發 li 的切換事件
    e.stopPropagation(); 
    var index = this.parentNode.index; // 取得被點擊的編號
    
    // 刪除元素
    that.lis[index].remove(); // node.remove() 指定元素刪除
    that.sections[index].remove();
    that.init(); // 重整一下
    
    // 如果正在選取狀態的那個被刪掉了, 他的前一個變成選定狀態
    /* 太麻煩了, 刪除之後直接執行點擊前一個的操作即可
    that.clearClsName();
    that.lis[that.lis.length - 1].className = 'curTab';
    that.sections[that.sections.length - 1].className = 'curBody';
    */
    
    // 下面這幾步, 非常厲害
    
    // 1. 如果有 curTab 這個類, 表示不是刪掉現在選定的元素, 那就不用操作點擊
    if (document.querySelector('.curTab')) {return} 
    // 2. 點擊前一個, 所以編號 -1
    index--; 
    // 3. 如果前一個有東西, 就點擊(手動調用觸發)
    //    避免刪除第一個時, 選取 [-1] 的元素
    that.lis[index] && that.lis[index].click();
  }
  
  // 雙擊更新
  updateTab(){
    // 雙擊會選取文字, 這串是取消選取文字
    window.getSelection? window.getSelection().removeAllRanges(): document.selection.empty();

    // 把原本的內容存起來
    var str = this.innerHTML;

    // 把內容改成 input
    this.innerHTML = '<input type="text"/>';
    // 調整一下css
    this.children[0].style.width = '100%';
    this.children[0].style.boxSizing = 'border-box';
    
    // 把觸發前的內容寫進input
    this.children[0].value = str;
    // 觸發時選中文本框內的文字
    this.children[0].select();

    // 把內容改成輸入後的東西
    this.children[0].onblur = function () {
      this.parentNode.innerHTML = this.value;
    }
    
    // 案 ENTER 時, 取消焦點
    this.onkeyup = function (e) {
      // console.log(e.keyCode)
      // ENTER: 13
      if (e.keyCode === 13) {
        this.children[0].blur(); // 手動調用取消焦點
      }
    }
    // 這邊其實觸發的可以改成外層的 li 比較好, 但是這樣就要改裡面的標籤
    // 會比較麻煩, 所以先算了, 等我有空, I will be back
  }
  
}
var tab = new Tab('.tabBox');
```








## 繼承

> - 讓子類共同的東西拿到父類來, 重用父類
> - 不過因為 JS 繼承比較麻煩, 如果真的有很多共同的再使用即可

### 對象拷貝
> - 遍歷繼承目標
> - 查看被繼承目標有無
> - 複製
```javascript=
var lol1 = {
  name: 'GodJJ',
  age: 20,
  play: function () {
    console.log('AD');
  },
}

var lol2 = {
  : 'bebe',
}


function extend(parent, child) {
  // 遍歷所有父元素
  for (key in parent) {
    // 如果子元素有值, 就不繼承
    if (child[key]) {
      continue;
    }
    child[key] = parent[key]; // 複製
  }
}

console.log(lol2); // {name: "bebe"}
extend(lol1, lol2);
console.log(lol2); // {name: "bebe", age: 20, play: ƒ}
```

> - 貪食蛇改

```javascript=
if (candy.x == headX && candy.y === headY) {
  var lastBox = obj[obj.length-1];
  /* 原本的
  obj.push({
    x: lastBox.x,
    y: lastBox.y,
    color: lastBox.color,
  })
  */
  
  // 對象拷貝
  var tmp = {}
  extend(lastBox, tmp);
  obj.push(tmp);


  candy.render(parent);
}

```

### 原型繼承

> - 改變 prototype 到父類實例, (注意! 不是父類原型)
![](https://i.imgur.com/kFTQcWX.jpg)

> - 缺點: 無法從實例中改變父類的值

```javascript=
function Fruit(store, origin) {
  this.store = store;
  this.origin = origin;
}


function Mango(price) {
  this.price = price;
}


// 把原型改成指向父類實例
Mango.prototype = new Fruit('全聯', '台南');
Mango.prototype.constructor = Mango; // 改變原型指向時, 記得 Who you are
var mango = new Mango(20); // 無法從這裡改變父類的值

console.dir(mango);
console.log(mango.store) // 全聯
```
> - 別直接把子類的原型直接指向父類原型
> - 因為後面再更改時是直接改父類原型的值

```javascript=
function Fruit(store, origin) {
  this.store = store;
  this.origin = origin;
}

function Mango(price) {
  this.price = price;
}

Mango.prototype = Fruit.prototype // 把 Mango 原型指向 Fruit原型
Mango.prototype.constructor = Mango; // 動態修改
var mango = new Mango(20); 

function Apple(price) {
  this.price = price;
}

Apple.prototype = Fruit.prototype // Apple 原型也指向 Fruit 原型
Apple.prototype.constructor = Apple; // 動態修改
var apple = new Apple(20); 


// 發生悲劇
console.log(mango.constructor); // ƒ Apple(price) {this.price = price;}


// 因為Fruit.prototype, Mango.prototype, Apple.prototype 都指向同個對象
```

### 借用構造函數

> - 調用父元素, 並利用 `call()` 來改變 this 指向
> - 缺點: 無法借用到 prototype 裡的屬性方法

```javascript=
function Fruit(store, origin) {
  this.store = store;
  this.origin = origin;
}

Fruit.prototype.shop = function () {
  console.log('去' + this.store + '買');
}

function Mango(store, origin, price) {
  // new Mango()進來時
    // store = '全聯'
    // origin = '台南'
    // price = 20
    // this = Mango
  Fruit.call(this, store, origin) // -> 原本調用時 this 指向 window, 
                                  // -> 用 call 改變 this 指向 new Mango()
    // Mango.store = '全聯';
    // Mango.origin = '台南';
  this.price = price;
    // Mango.price = 20
    // return Mango
}


var mango = new Mango('全聯', '台南', 20);


console.dir(mango);
console.log(mango.store) // 全聯

mango.shop(); 
  // ERROR
  // 缺點: 只能借用function本身有的東西, 沒辦法借用 prototype 裡的東西
```

### 組合繼承
> - 其實就是借用 + 原型


```javascript=
function Fruit(store, origin) {
  this.store = store;
  this.origin = origin;
}

Fruit.prototype.shop = function () {
  console.log('去'+ this.store + '買');
}



function Mango(store, origin, price) {
  Fruit.call(this, store, origin); // 借用
  this.price = price;
}
Mango.prototype = new Fruit(); // 原型
Mango.prototype.constructor = Mango;



var mango = new Mango('全聯', '台南', 20);
mango.shop(); // 去全聯買
console.log(mango.origin); // 台南
```






## 函數

### 函數定義方式
> - 函數聲明
> - 函數表達式
> - `new Function`

> #### 函數聲明
```javascript=
function fn() {}
```

> #### 函數表達式
```javascript=
var fn = function () {}
```

> #### 區別: hoisting 方式不同
> - 函數聲明是整個都 hoistion上去
> - 函數表達式會拆成聲明與賦值
```javascript=
fn(); // 1
function fn() {
  console.log(1);
}

fn2(); // ERROR
var fn2 = function () {
  console.log(2);
}
```
```javascript=
// hoisting 後

function fn() {
  console.log(1);
}
var fn2;
fn(); // 1

fn2(); // ERROR -> 此時 fn2 是 undefined
fn2 = function () {
  console.log(2);
}
```

### hoisting 補充

> #### 舊版本瀏覽器的 if 也會 hoisting

```javascript=
if (true) {
  function fn() {console.log(1)};
} else {
  function fn() {console.log(2)};
}

fn();
```
> - 以上這段 code 在瀏覽器的結果是 2

```javascript=
// 推測他的執行過程應該是
function fn() {console.log(1)};
function fn() {console.log(2)}; // 後面把 fn 重新指向了
if (true) {} else {}; // 跟這判斷式無關
fn();
```
> - 解決辦法
```javascript=
if (true) {
  var fn = function () { // hoisition 變量而已
    console.log(1)
  };
} else {
  var fn = function () {
    console.log(2)
  };
};
fn();
```

> #### `new Function()`

> - 執行速度相較於其他調用方法慢
> - 因為解析次數較多

```javascript=
function fn2() {
  var a = 100;
  console.log(a);
}
  // 上述全部都是 js 語言, 直接解析成 os 語言即可

var fn = new Function('var a = 100; console.log(a);');
  // 首先要先將參數的 str 解析成 js 語言, 然後實例後又要再解析成 os 語言 
  // 所以執行速度比較慢
fn2(); // 100
fn(); // 100
```

> - 傳參數的方法: 形參在前面, 表達式在後面

```javascript=
function fn2(x,y) {
  console.log(x,y);
	console.log(typeof x, typeof y);
}


var fn = new Function('x', 'y', 'console.log(x,y);console.log(typeof x, typeof y);');


fn2(1,2);
  // 1 2
  // num num
fn(1,2);
  // 1 2
  // num num
```

> - 不論函數聲明還是函數表達式, 都是 Function 的實例

```javascript=
var fn = function () {};
function fn2() {};

console.log(fn.__proto__ === Function.prototype); // true
console.log(fn2.__proto__ === Function.prototype); // true
```


### 函數調用與 this 指向

```javascript=

// 普通函數調用
  // this 指向 window
function fn() {
	console.log(this);
}
fn(); // window -> window.fn()

// 方法調用
  // this 指向調用方法的對象
var obj = {
	fn1: function () {
		console.log(this);
	},
}
obj.fn1(); // {fn1: ƒ}

// 構造函數調用
  // this 指向實例對象
function Cls() {
	this.fn = function () {console.log(this);};
}
var cls = new Cls();
cls.fn(); // Cls {fn: ƒ}

// 事件綁定
  // 觸發事件的對象
var btn = document.getElementById('btn');
btn.onclick = function () {
	console.log(this); // <button id='btn'></button>
}

// 定時器函數
  // this 指向 window
setInterval(function () {
	console.log(this); // window -> window.setInterval
}, 1000)

// 自調用函數
;(function (){
  console.log(this) // Window
})()
```

> - 老話一句, 誰調用, this 指向誰

```javascript=
function fn () {
  console.log(this);
}
fn(); // Window

var obj = {
	fn: fn,
}
obj.fn(); // {fn: ƒ}

// ----------------------------------- //

var obj2 = {
	fn2: function () {
		console.log(this);
	}
}
obj2.fn2(); // {fn2: ƒ}

var fn = obj2.fn2;
fn(); // Window


function Cls() {
  this.fn = function () {console.log(this);};
}
Cls.prototype.tmp = function () {console.log(this);};

var cls = new Cls();
cls.fn(); // cls
Cls.prototype.tmp(); // Cls.prototype
```

### call(), bind(), apply()
> - 三者主要皆用來改變 this 指向
> - `call()` 跟 `apply()` 都是用來立即調用函數
> - `call()` 的參數為參數列表(,)
> - `apply()` 的參數為陣列([,])
> - `bind()` 與其他兩個最大的區別在於, bind()不會調用函數, 而是創建一個拷貝函數

> #### `call()`
> - `fn.call(thisArg[, arg1[, arg2[, ...]]])`
> - 調用函數
> - 改變調用函數的this指向為第一個參數
>   - 如果傳 `null` 表示不改變 this 指向
>   - 不過最好不要傳 `null` 否則可能會產生問題,
>     如果不改指向, 就傳原本的指向即可
> - 後面參數就一般實參
> - 返回值就是函數的返回值
> - 主要常用於繼承, 調用父構造函數, 並改變指向為子構造函數 (window -> son)

```javascript=
function Father(x,y) {
  this.x = x;
  this.y = y;
}

function Son(x,y) {
  Father.call(this, x, y);
  this.sum = this.x + this.y;
}

var son = new Son(1,2)
console.log(son.sum); // 3

// ------不改變指向------------- //
function fn() {
  console.log(this);
}
// fn.call(null); // Window
fn.call(window);

// 應用: 非陣列調用陣列方法
var obj = { // 偽陣列
  0: 100,
  1: 80,
  length: 2,
}

// obj['2'] = 30;
// obj.length++;
// console.log(obj); // {0: 100, 1: 80, 2: 30, length: 3}


Array.prototype.push.call(obj, 30);
console.log(obj); // {0: 100, 1: 80, 2: 30, length: 3}
```

> #### `apply()`
> - `fn.apply(thisArg, [argsArray])`
> - 第一個參數為 this 指向
> - 第二個參數為一個陣列實參
>   - 被調用的函數會將實參裡的值拆成一個一個, 分散到每個形參中
> - 會直接調用函數
> - 返回值就是函數的返回值
> - 應用: 常用於補充陣列沒有的方法, 類似借用繼承的概念

```javascript=
function fn(a,b,c) {
  console.log(this);
  console.log(a);
  console.log(typeof a);
  console.log(b);
  console.log(typeof b);
  console.log(c);
}
var arr = ['haha', 1]
var v = {};
fn.apply(v, arr);
// {}         -> this 指向
// haha       -> a
// string     -> a 的類型
// 1          -> b
// number     -> b 的類型
// undefined  -> c


// 應用
var arr2 = [22,33,64,99];
var max = Math.max.apply(Math, arr2); // 沒有用到 this 就只回去就好了
console.log(max); // 99

// 應用
var arr = [1,2,4,6];
console.log(arr); // (4) [1, 2, 4, 6]
console.log.apply(console, arr); // 1 2 4 6
```

> #### `bind()`
> - `fun.bind(thisArg[, arg1[, arg2[, ...]]])`
>   - 參數跟 `call()` 一模一樣
> - `bind()` ==不會調用函數==, 
> - `bind()` 返回一個用 bind 參數改造的==原拷貝函數==

> - 需求: 按鈕後關閉, 三秒後打開
```htmlmixed=
<body>
  <button></button>
  <button></button>
  <button></button>
  <script src='test.js'></script>
</body>
```
```javascript=
var btns = document.querySelectorAll('button');
console.log(btns);

for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    this.disabled = true;
    // var that = this
    setTimeout(function () {
      // btns[i].disabled = false; 
         // Cannot set property 'disabled' of undefined
         // 因為這個事件是後來觸發的, i = 4了, 所以 btns[4] undefined
      // that.disabled = false; // 用 that 可以, 只是還要另外在開記憶體
      this.disabled = false; // 如果沒有 bind(), 
                             // 那 this 指向的是 window, 按鈕永遠都不會開
    }.bind(this), 3000)
  }
}

// 另外想法
// 這題用 forEach 也能解, 因為第一個參數就一直指向那個對象不會變
btns.forEach(function (v) {
  v.onclick = function () {
    this.disabled = true;
    setTimeout(function () {
      v.disabled = false; // v 就一直指著那個按鈕, 所以不會有 undefined 的問題
    }, 3000)
  }
})
```
> - 應用2: 如果一個函數同時需要用到兩種 this
```htmlmixed=
<body>
  <button></button>
  <script src='test.js'></script>
</body>
```
```javascript=
// var that;
function Fn(a) {
  // that = this;
  this.a = a;
  this.btn = document.querySelector('button');
  this.tmp = function (that) {
    console.log(this); // 同時需要用到點擊的 DOM this
    console.log(that); // 跟實例對象的this
                       // 有個做法是在外面開一個變量後, 在裡面存實例對象
                       // 但是這樣這個函數就一直需要外面的變量才能用
                       // bind()就可以解決這個問題
  }
  this.btn.onclick = this.tmp.bind(this.btn, this); 
    // 綁定不能變, 否則弄不到被點擊的 DOM 對象,
    // 實例對象的 this 就直接當實參傳進去, 對那個 function 使用即可 
}

var f = new Fn(1);
```
### 函數其他屬性
```javascript=
function fn(x,y) {
  console.log(fn.arguments); // 偽陣列, 獲取調用的實參
  console.log(fn.caller); // 函數調用者, 全局調用時為 null
  console.log(fn.length); // 形參個數
  console.log(fn.name); // 函數名, string
}
function tmp() {
  fn(1,2,3)
}
console.dir(fn);
tmp();
  // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  // ƒ tmp() {fn(1,2,3)}
  // 2
  // fn
```
```javascript=
function fn() {
  console.log(fn.arguments); // 偽陣列, 獲取調用的實參
  console.log(arguments); // 函數內部有個私有變量, 看起來跟上面那個一樣功能
}
fn(1,2,3);
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]


// arguments 應用
function getMax() {
  let max = arguments[0];
  for (let i = 1; i< arguments.length; i++) {
    if (max < arguments[i]) {
      max = arguments[i];
    }
  }
  return max
}

let max = getMax(10,2,6,99,10);
console.log(max); // 99

// 延伸: ES6 剩餘參數
function getMax2(...args) {
  let max = args[0];
  for (let i = 1; i< args.length; i++) {
    if (max < args[i]) {
      max = args[i];
    }
  }
  return max
}

let max2 = getMax2(10,2,6,99,10);
console.log(max2); // 99
```




### 高階函數
> - 其實就是指函數可以當球踢
>   - 把函數當實參傳進去, 例如 callback
>   - 把函數 return 出來

> #### callback
```javascript=
function fn() {
  return function () {console.log(2)}
}

function fn2(func) {
  func && func();
}

fn()(); // 2 
fn2(function () {console.log(1);}) // 1
```
```javascript=
// sort()
let arr = [3,6,11,8,33,10, 99, 1];
arr.sort((a,b)=>a-b); // callback
console.log(arr); // (8) [1, 3, 6, 8, 10, 11, 33, 99]

// 複習冒泡
Array.prototype.bubbleSort = function (fn) {
  for (let i = 0; i < this.length - 1; i++) {
    // console.log(i);
    let isSort = true;
    for (let j = 0; j < this.length - i - 1; j++) {
      // console.log(this);
      if (fn(this[j], this[j+1]) > 0) {
        isSort = false;
        let tmp = this[j];
        this[j] = this[j+1];
        this[j+1] = tmp;
      }
    }
    if (isSort) {
      break
    }
  }
}

let arr = [3,6,11,8,33,10, 99, 1];
arr.bubbleSort((a,b) => a - b) // callback
console.log(arr); // (8) [1, 3, 6, 8, 10, 11, 33, 99]
```
> #### return
```javascript=
// 生成 [1~10] 隨機數
function getRandom() {
	return Math.floor(Math.random() * 10 + 1)
}
console.log(getRandom()); 
console.log(getRandom());
console.log(getRandom());

// 使用第一次生成的隨機數
function useRandom() { // 這個函數只未了生成隨機數並保存
  let num = Math.floor(Math.random() * 10 + 1);
  return function () { // 這個函數只為了輸出 num
    return num
  }
}

let fn = useRandom();
console.log(fn());
console.log(fn());
console.log(fn());


// 求 n + m
  // n 變動小, m 變動大
function func(n) { // 變動小的存起來
  return function (m) {
    return n + m
  }
}

let sum100 = func(100);
let sum1000 = func(1000);

console.log(sum100(10))
console.log(sum100(40))
console.log(sum1000(20))
console.log(sum1000(30))
```



### 閉包 (closure)
> - 形式: 函數嵌套函數, 內函數訪問了外函數的變量或函數
> - 函數有權訪問另外一個函數作用域的變量
> - 主要作用: 延伸函數裡變量作用範圍 ( 本來只有局部作用域能用 )
> - 缺點: 由於沒有釋放記憶體, 所以比較肥


> - 子函數訪問父函數
```javascript=
function fn() {
  var a = 10;
  function fn2() {
    console.log(a); // fn2 本身沒有 a, 他拿的 a 是 fn 的
  }
  fn2(); 
}

fn(); // 10
```
> - 全局訪局部

```javascript=
function fn() {
  var a = 10;
  // function fn2() {
  // 	console.log(a);
  // }
  // return fn2;
  return function () { // 簡化
    console.log(a);
  }
    
}

var f = fn();
  // 返回的函數還指向著父函數裡的變量, 所以 fn() 開起的作用域沒有被回收
  // 也就延展了變量的作用範圍
f();

// 比較
function fn2() {
  var b = 10;
  return b
} 
fn2() // 執行完即銷毀
```

> - 點擊輸出索引值

```htmlmixed=
<body>
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <script src='test.js'>
  </script>
</body>
```
```javascript=
var divs = document.querySelectorAll('div'); 

/* 一般的循環註冊
for (var i = 0 ; i < divs.length; i++) {
  divs[i].index = i;
  divs[i].onclick = function () {
    console.log(this.index);
  }
}
*/
/*
divs.forEach(function (v,i) {
  v.index = i;
  v.onclick = function () {
    console.log(this.index);
  }
})
*/

// 閉包寫法
for (var i = 0; i < divs.length; i++) {
  (function (i) {
    divs[i].onclick = function () {
      console.log(i); // 這個 fn 沒有 i, 但是父函數的形參有 -> 用別人的: 閉包
    }
  })(i) // i 剛好等於索引值, 把 i 傳進去使用
}

// 三秒後輸出各自的內容
for (var i = 0; i< divs.length; i++) {
  (function (elem) {
    setTimeout(function () {
      console.log(elem.innerHTML) // 使用父函數的形參
    }, 3000)
  })(divs[i]) // 把元素傳進去用
}
```
> - 兩個函數的指向思考
```javascript=
var name = 'window';
var obj = {
  name: 'obj',
  fn: function () {
    return function tmp() {
      return this.name
    }
  },
  fn2: function () {
    var that = this;
    return function tmp2() {
      return that.name
    }
  }
}

console.log(obj.fn()()); // window

  // obj.fn() 返回一個函數到全局 tmp()
  // 這個 tmp() 的父函數沒有任何變量聲明的操作, 
  // 且, this 指向的是全局, 而沒有使用任何父函數的變量, 故沒有閉包產生
  // 普通模式下直接調用 tmp() -> window.tmp()

console.log(obj.fn2()()); // obj

  // obj.fn2() 執行了一個宣告附值 this 的操作,
  // 此時的 this 指向調用者的 obj, 附值給了 that
  // 然後返回了一個函數 tmp2 到全局
  // tmp2() 調用時, 雖然 this 一樣指向 window, 
  // 可是使用的是剛剛產生的閉包變量 that
```

### 遞歸
> - 在函數內部調用自己
> - 效果跟 while 很像, 
> - 當然注意點也差不多, 就是避免造成死循環(stack overflow), 在條件達成時要退出
```javascript=
var num = 1
function fn() {
  console.log(1);
  num++
  if (num>3) {
    return
  }
  fn() // 在裡面調自己
}
fn()
```
> - 求 1+2+...+n

```javascript=
/*
var num = 1
var sum = null;
function fn(n) {
  sum+=num;
  num++
  if (num>n) {
    console.log(sum);
    return
  }
  fn(n)
}
*/ 
function fn(n) {
  if (n == 1) {
    return 1
  }
  return n + fn(n-1) // -> n-1 參數傳進去就是每圈 n--了
}
console.log(fn(10)); // 55
```
> - 求 1\*2\*3...\*n
```javascript=
function fn(n) {
  if (n == 1) {
    return 1
  }
  return n * fn(n-1)
}
console.log(fn(3)); // 6
// 假設 n = 3
// fn(3) -> return 3 * fn(3-1)
//                     fn(2) -> return 2 * fn (2-1)
//                                         fn(1) -> 1
// fn(3) -> return 3 * 2 * 1
```

> - 求 Fibonacci
```javascript=
// fibonacci: 第三個數等於前兩個和
// 1 1 2 3 5 8 13
/*
  function getFb(n) {
    var fir = 1;
    var sec = 1;
    var fb;
    for (var i = 3; i<=n; i++)  {
      fb = fir + sec;
      fir = sec;
      sec = fb;
    }
    return fb
  }
*/

function getFb(n) {
  if (n == 1 || n == 2) {
    return 1
  }
  return getFb(n-1) + getFb(n-2)
}
console.log(getFb(3)); // 2
  // getFb(2) + getFb(1) = 1 + 1 = 2
console.log(getFb(5)) // 5
  // getFb(4) + getFb(3) 
  // getFb(3) + getFb(2) + getFb(2) + getFb(1)
  // getFb(2) + getRb(1) + 1 + 1 + 1 = 5
```
> - 輸入 id 查資料

```javascript=
function getData(json, n) {
  var o = {};
  json.forEach(function (v) {
    if (v.id == n) {
      o = v;
      o.flag = 1; // 為了避免後面圈數進去elif後返回一個 {} 出來讓o 被重新指向
                  // 暫時還沒想到更好的解法
                  // I will Be back
    } else if (v.team && v.team.length > 0) {
      if (getData(v.team, n).flag == 1) {
        o = getData(v.team, n);
      }
    }
  })	
  return o
}

console.log(getData(datas, 1));
console.log(getData(datas, 2));
console.log(getData(datas, 11));
console.log(getData(datas, 22));
```

## 淺拷貝與深拷貝
### 淺拷貝
> - 淺拷貝只拷貝一層, 第一層資料如果是對象, 拷貝的是該對象的引用
> - 所以如果操作資料是對象的對象, 那麼原本被拷貝的資料也會跟著變, 
>   因為大家都指向同一個對象

> <img src='https://i.imgur.com/wiAzFu3.jpg' style='width: 300px'/>

```javascript=
var obj = {
  id: 1,
  name: 'GodJJ',
  play: {
    po: 'AD',
    lev: 'GE',
  }
}

var o = {};
for (k in obj) {
  o[k] = obj[k];
}

// console.log(o);
  // id: 1
  // name: "GodJJ"
  // play: {po: "AD", lev: "GE"}
  // __proto__: Object
o.id = 2;
console.log(obj.id, o.id); // 1 2

o.play.po = 'mid';
console.log(obj.play.po, o.play.po); // mid mid
```
### ES6 的淺拷貝
`Object.assign(target, ...sources)`
```javascript=
var obj = {
  id: 1,
  name: 'GodJJ',
  play: {
    po: 'AD',
    lev: 'GE',
  }
}

var o = {};
Object.assign(o, obj);
console.log(o);
  // id: 1
  // name: "GodJJ"
  // play: {po: "AD", lev: "GE"}
  // __proto__: Object
```
### 深拷貝
```javascript=
var obj = {
  id: 1,
  name: 'GodJJ',
  tmp: [2,3,4],
  play: {
    po: 'AD',
    lev: 'GE',
  }
}

var o = {};

function deepCopy(target, source) {
  for (k in source) { 
    var item = source[k]; // 把資料拿出來
    if (item instanceof Array) { // 如果資料是 Array (註1)
      target[k] = [];  // 創一個新的陣列對象
      deepCopy(target[k], item); // 再進去掃一次 (註2)
      // item = [2,3,4]
      // for (k in item) 
        // {0: 2, 1: 3, 2: 4}
    } else if (item instanceof Object) { // 如果資料是物件
      target[k] = {}; // 創一個新的物件對象
      deepCopy(target[k], item); // 進去掃
    } else {
      target[k] = item; // 掃到不是物件類型就存進去
    }

  }
}

deepCopy(o, obj);
// console.log(o);
  // id: 1
  // name: "GodJJ"
  // play: {po: "AD", lev: "GE"}
  // tmp: (3) [2, 3, 4]
  // __proto__: Object


// 修改拷貝數據不影響原資料
  // 因為拷貝的對象數據指向的是自己新創建的對象, 不是原本的
o.tmp[0] = 100;
o.play.po = 'Mid';

console.log(obj.tmp, o.tmp); // (3)[2, 3, 4] (3) [100, 3, 4]
console.log(obj.play.po, o.play.po); // AD Mid

```
```javascript=
// 註1: 陣列也是物件對象, 故先判斷是不是陣列, 再判斷是不是物件
var a = [];
console.log(a instanceof Object); // true

// 註2: 陣列複習
for (k in a) {
  console.log(k, a[k]);
  // 0 2
  // 1 3
  // 2 4
}

a[4] = 100;
console.log(a); // (5) [1, 2, 3, empty, 100]
```

### 遍歷 DOM 樹
> - 找到所有子元素, 並對子元素做callback
```htmlmixed=
<body>
  <div>HaHa</div>
  <ul id="tmp">
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <button>GO</button>
  <script src='test.js'></script>
</body>
```
```javascript=
function getElem(parent, callback) {
  // 遍歷子元素
  for (var i = 0; i < parent.children.length; i++) {
    var child = parent.children[i];
    // console.log(child);
    callback && callback(child); // 對子元素作 callback
    getElem(child); // 遍歷子元素的子元素
  }
}

getElem(document.body.querySelector('ul'), function (elem) {
  elem.onclick = function () {
    console.log(this.innerText);
  }
});
```


## 嚴格模式(strict mode)

> - 簡單說就是讓 JS 執行code嚴謹一點
> - IE10 + 才支援

### 嚴格模式的開啟
> - 在所有code之前寫上 `'use strict';`
> - 沒有支援的瀏覽器就會當成 str 來略過

```htmlmixed=
<body>

  <!-- 為整個 script標籤 開啟-->
  <script>
    'use strict';
  </script>
  
  <!-- 為 自調用函數 開啟 -->
  <script>
    ;(function () {
      'use strict';
    })()
  </script>
  
  <!-- 為 某個函數 開啟 -->
  <script>
    function fn() {
      'use strict';
      // 這個函數執行嚴格模式
    }
    
    function fn2() {
      // 這個函數沒有影響
      // 執行普通模式
    }
  </script>
</body>
```

### 嚴格模式的變化

> #### 變量
> - 未聲明就使用, Error
> - 嚴禁刪除已聲明變量 `delete x;`
```javascript=
// 普通模式
a = 10;
console.log(a); // 10 -> 未聲明, 可以用

delete a; // -> 可以刪
console.log(a); // Error: a is not defined
```
```javascript=
// 嚴格模式
'use strict';

// a = 10; 
  // a is not defined
var a = 10;
console.log(a);

// delete a;
  // Delete of an unqualified identifier in strict mode.
console.log(a);
```

> #### this
> - 全局作用域的函數 this 指向 window, 嚴格模式指向 undefined
> - 基於這個原理的應用當然也會掛掉, 其他則沒有影響
> - 不變的定理: 誰調用就指誰, 鬼調用就會掛

```javascript=
// 普通模式
function fn() {
  console.log(this); 
}
fn(); // window.fn() -> Window {...}

function Fn2() {
  this.a = 1; 
}
Fn2() // window.a = 1;
console.log(window.a); // 1

setInterval(function () {
  console.log(this); // Window {...}
}, 1000)
```
```javascript=
// 嚴格模式
'use strict';
function fn() {
  console.log(this);
}
fn(); // undefined

function Fn2() {
  this.a = 1;
}
// Fn2() // undefined.a = 1
  // Cannot set property 'a' of undefined at Fn2
  // 由於全局函數指向 undefined 當然會報錯
console.log(window.a); // 1

setInterval(function () {
  console.log(this); // Window {...}
                     // 這本來就是window的方法了, 所以沒有影響
}, 1000)
```

> #### 函數
> - 不允許形參重名
> - 不建議在==非==函數 code 裡聲明函數(沒有強制, 因為我測試沒有掛)

```javascript=
'use strict';

// function fn(a,a) { 
  // Duplicate parameter name not allowed in this context
function fn(a,b) {
	console.log(a+a);
}
fn(1,2); // 4

// 在非函數裡宣告函式
// 下面都沒掛, 可是 MDN 有寫不要這樣寫
if (true) {
	function fn2() {}
}
while (true) {
	function fn3() {}
	break
}
for (var i = 0; i<2; i++) {
	function fn4() {}
}

// 在函數裡宣告函式
// 這本來就是合法的
function fn5() {
	function fn6() {}
}
```



## ES5 新增的方法

### 陣列
> #### forEach()
> - `array.forEach(function([curValue, index, arr]))`
> - 用來遍歷陣列
> - 參數是一個 callback
>   - callback 第一個參數是當時的值
>   - callback 第二個參數是當時的位置
>   - callback 第三個參數是陣列本身
> - 沒有返回值
```javascript=
var a = [1,2,3];

// 以前遍歷方法
for (var i = 0; i < a.length; i++) {
  console.log(a[i]);
  console.log(i);
  console.log(a);
}

// forEach
var b = a.forEach(function (value, index, array) {
  console.log(value);
  console.log(index);
  console.log(array);
})

console.log(b) // undefined
```

> #### filter()
> - `array.filter(function(curValue, index, arr))`
> - 參數跟 `forEach(callback)` 都一樣
> - `filter()` 主要用於篩選陣列
> - `filter()` 會返回一個新陣列, 篩選條件在 return 寫即可

```javascript=
var a = [11,22,33];

var c = a.filter(function (value) {
	return value >= 20
})

console.log(c); // (2) [22, 33]
```
> #### some()
> - `array.some(function(curValue, index, arr))`
> - 參數也跟上面一樣
> - `some()` 返回的是 Boolean
> - 只要符合即馬上停止循環, 返回 true

```javascript=
var arr = [11,22,33];
var arr2 = ['G', 'O', 'D']

var a = arr.some(function (value){
  return value < 10
})
var b = arr2.some(function (value){
  return value === 'G'
})


console.log(a); // false
console.log(b); // true
```

> #### map(), every()
> - 暫略

### 練習: 查詢商品

```htmlmixed=
<head>
  <meta charset='utf-8'>
  <link rel='stylesheet' href='css/style.css'/>
</head>
<body>
  <div class='box'>
    <head>
      <div id='top'>
        <span>按照價格查詢:</span>
        <input type='text'/>
        <span>-</span>
        <input type='text'/>
        <input type='button' value='搜索'/>
      </div>
      <div id='buttom'>
        <span>按照商品名稱查詢:</span>
        <input type='text'/>
        <input type='button' value='查詢'/>
      </div>
    </head>
    <table>
      <thead>
        <tr>
          <th>id</th>
          <th>品名</th>
          <th>價格</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
  <script src='js/main.js'></script>
</body>
```

```css=
* {
  margin: 0;
  padding: 0;
}

.box {
  margin: 100px auto;
  width: 600px;
}

table {
  text-align: center;
}


th, td {
  height: 50px;
  width: 100px;
  border: 1px solid red;
}
```

```javascript=

// 資料
var json = [
	{
		id: 1,
		name: 'apple',
		price: 20,
	},
	{
		id: 2,
		name: 'banana',
		price: 25,
	},
	{
		id: 3,
		name: 'grova',
		price: 30,
	}
]


// 拿到元素們
var tbody = document.querySelector('tbody');
var price = document.querySelectorAll('#top input[type=text]');
var btn = document.querySelectorAll('input[type=button]');
var n = document.querySelector('#buttom input[type=text]');

// 顯示資料
function render(j) {
  tbody.innerHTML = '';
  j.forEach(function (v) {
    var tr = document.createElement('tr');
    tr.innerHTML = '<td>' + v['id'] + '</td><td>' + v['name'] + '</td><td>' + v['price'] + '</td>';
    tbody.appendChild(tr);
  });
}

// 價格區間
btn[0].onclick = function () {
  var newJson = json.filter(function (v) {
    return v['price'] >= price[0].value && v['price'] <= price[1].value
  })
  render(newJson);
}

// 搜尋名字
btn[1].onclick = function (v) {
  var arr = []
  // some 的效率高, 找到就馬上跳出
  // 如果只找陣列中唯一元素, some 非常適合
  json.some(function (v) {
    if (v['name'] === n.value) {
      arr.push(v);
      return true
    }
  })
  render(arr);
}


// 一開始直接調用一次, 把所有資料顯示出來
render(json);
```


### some 與其他人的效率差別

```javascript=
var arr = [1,2,3,4,5]

arr.some(function (v) {
  if (v === 2){
    return true
  }
  console.log(99); // 99
})

arr.forEach(function (v) {
  if (v === 2){
    return true
  }
  console.log(99); // 四次99
})

arr.filter(function (v) {
  if (v === 2){
    return true
  }
  console.log(99); // 四次99
})

// 只有 some 在 return 後跳出, 其他都跑完全部
```


### string

> #### trim()
> - `str.trim()`
> - 去除前後空格, 返回新 str
> - 中間的空格不會被刪掉

> #### 應用

```htmlmixed=
<body>
  <input type='text'/>
  <button></button>
  <div></div>
  <script src='test.js'></script>
</body>
```
```javascript=
var txt = document.querySelector('input');
var btn = document.querySelector('button');
var div = document.querySelector('div');

/* 問題
btn.onclick = function () {
  if (txt.value) { // 如果只有空格也會進去
    div.innerText = txt.value;
    console.log(txt.value.length); // 有時想判斷長度可能也會有困擾
  }
}
*/

btn.onclick = function () {
  var newStr = txt.value.trim();
  if (newStr) {
    div.innerText = newStr;
    console.log(newStr.length);
  }
}
```


### 對象方法

> #### `Object.keys(obj)`
> - 遍歷出與 for in 相同順序的陣列
```javascript=
var obj = {
  id: 1,
  name: 'GodJJ'
}

console.log(Object.keys(obj)); // (2) ["id", "name"]
```

