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

> - 瀏覽器一開始會去找DNS要域名的IP, 然後才去該服務器要東西

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

> - 首先, 每個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 欄

> - 點擊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 到父類實例, (注意! 不是父類原型)

> - 缺點: 無法從實例中改變父類的值
```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"]
```