Javascript - 更進一步

Pmroise , async_await …

  1. call back
    補充:https://cythilya.github.io/2018/10/30/callback/
  2. Synchronous vs Asynchronous
    顧客:阻塞(憨厚) vs 非阻塞(變通) polling
    老闆:同步(臭跩) vs 非同步(親切)
    ex:公司報到+銀行開戶
    避免過度阻塞

polling
longpolling
streaming (websocket)
補充:
https://blog.niclin.tw/2017/10/28/%E7%8D%B2%E5%BE%97%E5%AF%A6%E6%99%82%E6%9B%B4%E6%96%B0%E7%9A%84%E6%96%B9%E6%B3%95polling-comet-long-polling-websocket/

https://blog.gtwang.org/web-development/websocket-protocol/

補充:
https://johlmike.wordpress.com/2016/07/08/%E5%90%8C%E6%AD%A5synchronous%E3%80%81%E7%95%B0%E6%AD%A5asynchronous%E3%80%81%E9%98%BB%E5%A1%9Eblock%E3%80%81%E9%9D%9E%E9%98%BB%E5%A1%9Enon-block/
阻塞與非阻塞主要是描述請求在等待結果時的狀態
同步與異步主要是描述回傳的聯絡方式

非同步有
1.setInterval setTimeout 2.一般的 eventhandler
3.server 的呼叫

  1. 佇列、堆疊、事件循環
    佇列(Queue):先進先出 ex:排隊、jQuery 動畫
    堆疊(Stack):後進先出 ex:河內塔
    事件循環(Event Loop):從佇列提取到堆疊執行
    補充:
    https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html

非同步處理:主流程處理完後處理
Stack 不清空,Queue 是無法往下做的
補充:
https://ithelp.ithome.com.tw/articles/10200054?sc=iThelpR
loupe:
http://latentflip.com/loupe/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function A(){
setTimeout(() => {
console.log('A')
}, 1000)
}

function B(){
setTimeout(() => {
console.log(B')
}, 500)
}

function C(){
setTimeout(() => {
console.log('C')
}, 2000)
}

// B A C
A()
B()
C()

// 不好的寫法
A()
setTimeout(B,1000)
setTimeout(C,1500)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
log('=====start=====')
log('a');
log('b');
while (pass() < 5000) {}
log('c');
``log('====`=end=====')



const timerHandler = () => {
pass() < 5000 ? setTimeout(timerHandler, 0) : log('timer ok');
};

log('=====start=====')
log('a');
log('b');
while (pass() < 5000) {}
setTimeout(timerHandler, 0);
log('c');
log('=====end=====')

+new Date() 等同於 new Date().getTime()
不要用 Timer 控流程,時間不準

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"MY_ROOM": {
"prefix": "room",
"body": [
"const time = +new Date();",
"const pass = () => +new Date() - time;",
"const log = msg => {",
"console.log(msg, `${pass()}ms`);",
"};",
"",
"log('===== Start =====');",
"$1",
"log('===== End =====');"
],
"description": "MY_ROOM"
}
  1. Promise
    (1) 可以做同步組合
    (2) Queue 在不同瀏覽器可能會有不同表現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const promise = (time, msg) => {
return new Promise((resolve, reject) => {
log(`Promise Create: ${msg}`);
if (time <= 0) {
resolve(`Promise OK: ${msg}`);
} else {
setTimeout(() => {
resolve(`Promise OK: ${msg}`);
}, time);
}
});
};
// 觀戰重點1: Promise: 同步 then:非同步
log("==== A ====");
promise(0, "A").then(result => log(result));
log("==== B ====");
promise(1000, "B").then(result => log(result));
log("==== B ====");
promise(500, "C").then(result => log(result));
// 觀戰重點2: stack 阻塞
while (pass() < 2000) {}

// 觀戰重點3: timeout人權議題
setTimeout(() => {
log("timer");
}, 0);
log("==== A ====");
promise(0, "A").then(result => log(result));

promise:等到允許授權才能往下走
then 須等到 resolve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 觀戰重點4: 執行順序,將同步非同步變成依序執行
// 如果再then裡catch會往下走,如果沒有就不會再往下走
promise(0, "A")
.then(
result => log(result),
error => log(`error:${error}`)
)
.then(result => promise(3000, "B"))
.then(result => log(result))
.then(result => promise(0, "C"))
.then(result => log(result))
.then(result => promise(2000, "D"))
.then(result => log(result))
.catch(error => log(`error:${error}`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
log("===== Start =====");

// if timer ?

promise(0, "A")
.then(
result => log(result),
error => log(`error:${error}`)
)
// .catch(error => log(`error:${error}`))
.then(result => promise(3000, "B"))
.then(result => log(result))
.then(result => promise(0, "C"))
.then(result => log(result))
.then(result => promise(2000, "D"))
.then(result => log(result))
.catch(error => log(`error:${error}`))
.then(result => log(result)); // final

log("===== End =====");

// if blocking ?

// example 讀取API資料
// 1.使用者觸發 -> 2.loading(true) -> 3.發出請求 -> 4.結果回應 -> 5.loading(false)
1
2
3
4
5
6
7
8
9
10
setTimeout(() => {
log("timer");
}, 0);
log("==== A ====");
promise(0, "A").then(result => log(result));
// log("==== B ====");
// promise(1000, "B").then(result => log(result));
// log("==== C ====");
// promise(500, "C").then(result => log(result));
// while (pass() < 2000) {}

finally
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Control_flow_and_error_handling
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function f() {
try {
log(1);
return 1;
} catch (e) {
log(2);
return 2;
} finally {
log(3);
return 3;
}
log(4);
return 4;
}
f();

後進先出範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
後進先出;
function A() {
console.log("A");
}

function B() {
A();
console.log("B");
}

function C() {
B();
console.log("C");
}

C();
  1. Pmroise.all vs Promise.race
    all:全部一起做,時間最長的
    race:只取最快回來的資料
1
2
3
4
5
6
7
8
9
10
11
Promise.all([
promise(0, "A")
.then(result => result)
.catch(result => result),
promise(3000, "B")
.then(result => result)
.catch(result => result),
promise(2000, "C")
.then(result => result)
.catch(result => result)
]).then(result => log(`all ${result}`));
  1. Generator
    function*()
    會自動產生 return
    建議為每個 function 做適當的 return 值

Generaor+Promise 的範例(async await 的前身)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let gen = genFn();
gen
.next()
.value.then(result => gen.next(result))
.then(result => result.value)
.then(result => gen.next(result))
.then(result => result.value)
.then(result => gen.next(result))
.then(result => log(result));

//每個next都是做一個 create promise

//1~2 {value:Promise A, done:false}
//4 {value:Promise B, done:false}
//6 {value:Promise C, done:false}
//8 {value:{}, done:true}

補充:所有 function 都應該有 return,沒有 return 他就是 undefined
如果是事件觸發,那 return 值就會沒人會使用到

  1. async_await
    async => 非同步 , await => 等
    async await => 等非同步

補充:
7.1 await 只能存在 async function 裡面
7.2 await 必須等非同步(必須等非同步(promise))

重要:先讓 promise 做完(同步),再一起 await,不要一邊 promise,一邊 await,因為會讓同步的 promise 的同步做成非同步的

1
2
3
4
5
6
7
8
(async function() {
let result = {};
resule["A"] = await promise(0, "A");
resule["B"] = await promise(3000, "B");
resule["C"] = await promise(2000, "C");

console.log(result);
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(async function() {
let A = promise(0, "A"); //promise
let B = promise(3000, "B");
let C = promise(2000, "C");

//1.
await A;
await B;
await C;
let result = { A, B, C };

//or

//2.
let result = {
A: await A, //result
B: await B,
C: await C
};

log(result);
})();

async function 裡面有 await

await 等 async function
await 等 promise

  1. async_array
    同一個作用域裡,await 才會互等
1
2
3
4
5
6
7
8
9
10
11
(async function() {
Array.prototype.asyncEach() = anync function(handler) {
for(let i = 0; i < this.length; i++) {
await handler(this[i], i, this);
}
}
data.asyncEach(async item => {
log(await promise(Math.random() * 10, item));
//同步指令只有第一次,之後都是一個一個等(非同步)
});
})();

補充:
大部分有用的 this 時候都會用 function,大部分第二層就會用箭頭函式,setTimeout,就會往外找,找到上一層的對象(document)

1
2
3
4
5
document.addEventListener("xxx", function() {
setTimeout(() => {
this;
}, 0);
});
  1. API
    (1) 販賣機
    NG-1 吃錢
    NG-2 給錯東西
    => 給正確的東西

(2) 拉麵販賣機
打對的網址(按對的按鈕)
給正確的查詢
回正確的資料(固定有規範的輸入與輸出)

  1. JSON
    JavaScript Object Notation
    最上層建議:物件

Restful API : Representational State Transfer

Read:GET 讀取紀錄
Create:POST 新增紀錄
Update:PUT/PATCH 修改紀錄
Delete:DELETE 刪除紀錄

Http Status Code:

//ok => then
2xx:這是您要的東西
3xx:請稍後為您轉接
//error => catch
4xx:請查明後再撥
5xx:嘟~嘟~嘟~

TCP 三次握手與四次揮手

C:Client S:Server
傳輸前:
三次握手(確認雙方都在)
1 C:安安,你好!住哪?給虧嗎? => 尋找目標 Server
2 S:台北,28 => Server 回應
3 C:好巧喔!我也住台北 => Client 也回應

雙方連上線開始傳輸

離開前:
四次揮手
1 C:我要去洗澡了 => 我要結束了(C)
2 S:等一下 => 確認是否還有未傳完(S)
3 S:我去洗澡 (雙重攻擊) => 傳完我要結束了(S)
4 C:你就去吧 => 你可以結束了(C)

此時 Server 斷線,等待兩秒確定斷線,Client 也斷線

DoS 攻擊:大量請求,使 Server 被打到癱瘓,無法正常服務

SQL VS NOSQL

SQL:關聯,查詢資料需要透過多個資料表查詢,較費時
資料分散,資料較小 ( 時間換空間 )
NOSQL:需要資料直接拿整包,不需要組合,較快
資料較多份,資料較大 ( 空間換時間 )
( Not Only SQL )

Token:令牌/通行證
Storage:都存在本機
Local Storage:沒有時效性,連線不會傳,不刪他不會被刪除 (長時間暫存)
Session Storage:跟著 Tag(分頁),關閉就刪除(session 連線結束 會被清掉),存在 SERVER 端會占用 SERVER 端的資源,單次連線的暫存 儲存在 server 端(暫存檔)

cookie:存在本機,使用者可以刪除,有期限的(可設計過期時間),SERVER 在每次連線的時候會自動綁定和傳輸(目的:隨使想取用都可以做檢查),不適手動自己加的東西 儲存在 user 端 (短期),不建議前端操作 cookie
補充:TOKEN 都會放在 cookie 為主
Http Only:程式不能刪
session:連線階段的操作,存在 server 端,使用者不能刪除

PUT vs PATCH

PUT:傳整包,實務上後端某些欄位會鎖住(例如:ID),相信你傳
PATCH:傳部分修改資料
理論上如此,但實作還是要看後端作法

範例 9

  1. 讀取資料 (讀取 api 是非同步,所以呼叫時,必須加 await,因為必須等非同步完成才 render,不然 render 時回沒有資料 render,ex:await xxx()),await 是一種等的概念,render(同步)必須等 await loadData()的概念
  2. 渲染畫面
  3. 使用者功能
    reduce:組合技
1
2
3
4
5
6
7
8
9
10
11
12
const input = $(this)
.serializeArray()
.reduce((prev, now) => {
prev[now.name] = now.value;
//prev[userId]="1";
//prev[body] = "aa";
return prev;
}, {});
// 1=> prev = {},now = {name: "userId", value: "1"}
// 2=> prev = {userId:"1"}, now = {name: "body, value: "aa"}
input.userId *= 1;
console.log(input);

location.reload():刷新畫面

讀取資料 => 渲染畫面 => 使用者功能
可以考慮重構成下面:
讀取表單資料(users) => 渲染表單 => 表單功能
讀取列表資料(posts) => 渲染列表 => 列表功能

補充:
form 的好工具

1
const input = $(this).serializeArray();

JSONP

JSON with Padding
解決跨域問題

物件導向

封裝:分類,然後包起來(資料 attribute+功能 Methods)
繼承:爸爸有房子,我就有房子

SOLLID

S:做好分類 (單一職責(Single Responsibility Principle
O:開放擴充 封閉修改 (開放封閉(Open-Closed Principle
L:小孩可以代替爸爸 爸爸不能代替小孩 (里氏替換(Liskov Substitution Principle
L:只給部分功能 (最小知識(Least Knowledge Principle
I:不受其他介面影響 (介面隔離(Interface Segregation Principle
D:避免小孩影響到爸爸 (依賴反轉(Dependency Inversion Principle

物件建議

  1. 判斷 this 再使用(有 new 就是你創物件本身,不然就是指向 window(全域))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const Creature = function(name, sex){
if(this instanceof Creature){
this.name = name || "NoName";
this.sex= sex || 0;
this.life = 100;
// this.intro = function(){
// console.log(
// `My name is ${this. name}, I'am a ${this.sex ? "man" : "woman"`
// );
}
}else{
return new Creature(name, sex);
}
};

Creature.prototype.intro = function

let C1 = new Creature("Alex", 1);
let C2 = Creature("Sara", 0);

console.log(C1);
console.log(C2);

//執行結果: Creature {name: "Alex", sex: 1, life: 100}
//自製的Creature物件


console.log(C1.intro === C2.into);
//執行結果:false
每產生一個Creature就會多產生一個function,所以用Creatureprototype,已讓產生的Creatue物件能共用


console.log(C1, C1._proto_,C1._proto_._proto_);
//{name: "Alex"m sex:1, life: 200} Creatur{intro:[Function]} {}

//原型練的定義:假設這層沒有,就會找尋上一層,直到找到盡頭找不到,就會回undefined

//原型鍊的每一層皆是object

  1. function 如果是做一樣事情,建議拉到 proto 做,不然在 new 的時候會多產生
  2. 公有與私有

4.defineProperty 非常重要!! 與陣列 reduce 一樣重要
defineProperty 的功能是把一個屬性轉變成 function(並分別有讀和取的功能)
改成 function 的好處,他可以回傳 callback,ex: 假設有人改你的資料,就會回傳,但變數卻無法

1
2
3
4
5
6
7
8
9
10
11
12
let _age = 30;
let a = {
name: "alex"
};

Object.defineProperty(a, "age", {
get(){
return _age;
});
a.age++
console.log(a.age);
//還是是30,因為他只開get,所以不能寫入
1
2
3
4
5
6
7
8
9
10
11
12
13
//defineProperties的寫法
Object.defineProperties(this, {
name: {
get() {
return _name;
}
},
sex: {
get() {
return _sex;
}
}
});

prototype 與proto的分別
就是物件生成前後(ex: new Creature),之前
Creature.prototype.intro = function(){

}
new 出來之後只能讀到proto,讀 prototype 會有一些問題

X 全域變數
X 浮點數計算
X with 與 eval
X 強制轉型的雙等號
X 無所不在的宣告
X 無區塊的敘述式
X 函式敘述與運算
X 類型的包裝與 New
X 隨意換行與分號
X 加號運算子
X continue
X switch 穿越
X 分散的回傳值
X 太早做過度縮寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let a = "A";
let b = "B";
let c = "C";

let obj = {
a: 1,
b: 2
};

with (obj) {
console.log(a); // 1
console.log(b); // 2
console.log(c); // C (obj.c => C => not defiend
}

let script = "console.log(123)";
eval(script);

閉包

咖哩化

模式

工廠模式 (Factory)

主力:產東西
EX: Object

策略模式 (Strategy)

主力:對應東西
策略表(Map 表)

外觀模式 (Facade)

一個 function 包很多功能
EX:點青椒炒牛肉 => 處理青椒/處理牛肉/炒都交給廚師處理
點餐的人不需要知道這些

觀察者模式

觀察者模式 : 透過第三者溝通 EX:事件

MV*

Model 資料邏輯
View 畫面層

C:畫面對 Model 監聽
P:畫面透過 Present 偵聽?

第三天

prototype

  1. People.prototype = Creature.prototype
    直接賦值 prototype
    問題: 原本方法被取代
  2. People.prototype = new Creature()
    問題: 有自己及父親的屬性,本身屬性移除就變成父親的屬性
    方法也會不見(多額外屬性)
  3. 繼承方法
1
2
3
Function.prototype.extend = function(parent) {
this.prototype.__proto__ = parent.prototype;
};

protp : 實體化使用
prototype : 未實體化

1
2
3
4
5
6
7
8
9
10
11
12
13
const Alex = function() {};
Alex.prototype.eat = function() {};

let eatShit = function() {};

let a1 = new Alex();
// a1.__proto__ = Object.assign({},a1.__proto__, {eat:eatShit})
a1.__proto__ = { eat: eatShit };
let a2 = new Alex();

console.log(a1.eat === a2.eat); // true
console.log(a1.eat === eatShit); // false => true
console.log(a2.eat === eatShit); // false

作業 : alex.lvUp(‘People’)

1
2
3
4
5
6
const People = function(name) {
// Creature.apply(this, arguments)
People.prototype.__proto__.constructor.apply(this, arguments);
};

People.extend(Creature);

與下面差異相同

1
2
3
4
5
6
7
8
9
function A() {
console.log("HI");
}

let msg = "HI";

function B() {
console.log(msg);
}

class

  1. class 關鍵字
  2. construtor 初始化
  3. static function

//TODO 待補

複製 obj

  1. obj1 = {…obj}
  2. obj2 = Object.assign({},obj)
  3. obj3 = JSON.parse(JSON.stringigy(obj))
  4. obj4 = {}
  5. Object.create()

複製 function

閉包

1
2
3
4
5
function copyFunction(fun) {
return function() {
fun();
};
}

MVC VS MVP

  1. MVC
    V -event-> C -> M -> V
  2. MVP
    V <-> P <-> M
  3. MVVM
    V <-> VM <-> M

MVC

  1. 主程式(拉近三個架構 App
  2. 另外三隻檔案( View/Mode/Controller
  3. Observer

Alex 開發順序(參考)

  1. 先做基礎架構(App 主程式)
    1-1 import MVC
    1-2 開三支檔案
    1-3 做關係(new 出來

View 需要認識 Model
Controller 要認識 Model 跟 View

1
2
3
const M = new Model();
const V = new View(M);
const C = new Controller(M, V);
  1. Model (資料邏輯)
    增加/讀取/選擇/刪除(事件)
    做完後打事件出去
    盡量不要動畫面

  2. View (畫面邏輯:畫面偵聽/資料變動)
    3-1 記資料
    3-2 匯入 jQuery (import \$ from ‘jquery’

盡量不要動資料

補充 call/apply/bind
與 function 的 this 操作有關

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let A = {
name: 'A'
fun: function(a,b) {
console.log(this.name,a,b)
}
}

let B = {
name: 'B'
}

A.fun(1,2) // A,1,2
// B.fun(3,4) // B.fun is not a function
// B.fun(x,y) // x is not defined

A.fun.call(B,3,4) // B,3,4
A.fun.apply(B,[3,4]) // B,3,4

// bind會產生新function
let fun = A.fun.bind(B,3)
// 類似咖哩化 (傳滿就比較沒有意義)
fun(5) // B,3,5
fun(6) // B,3,6

// [].map.call
// Math.xxx.call

show 改變畫面
MVC 架構中,View 不能自己改畫面,要通知讓後面去改
這邊採用畫面全部重劃較耗效能,可以嘗試修改不要整個畫面重畫

  1. Controller (牽手=>控制 Model)

TODO:可以練習 switchHandler

Singleton

單例模式 : 閉包
判斷如果產生過,就用之前產生的,沒產生過再產生新的
概念類似

1
2
3
let a = { count: 0 };
let b = a;
let c = a;

練習:

  1. 繼承練習
  2. MVC 架構修正 View 的 switch 事件

第三天下午

問題討論

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var out = 25,
inner = {
out: 20,
func: function() {
var out = 30; // 沒有用到
return this.out;
},
func1: function() {
var out = 30;
return out;
}
};
console.log((inner.func, inner.func1)()); // 30
console.log(inner.func()); // 20
console.log(inner.func()); //20
console.log((inner.func1 = inner.func)()); // undefined => 25
1
2
3
4
5
6
7
let a = (1, 2);
console.log(a); // 2

let q1 = 1;
let q2 = 2;
let b = (q1 = q2);
console.log(b); // 2

DL DT DD
DL=>Definition List
DT=>Definition Term
DD=>Definition Description

Observer 觀察者模式

電視/收音機無時無刻都在播放,但你有沒有去看/聽 (觀察)
決定會部會有回應
結構:1 個持續播放,1 個監聽者
功能:解耦

耦合

我在你也在,我死你也死

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Observer = function(host) {
this.b1b2 = function() {
this.btn1.log();
this.btn2.log();
};
};

const btn1 = function() {
this.log = function() {
console.log("b1 log");
};
};

const btn2 = function() {
this.log = function() {
console.log("b1 log");
};
};

const ob = new Observer(this);

改成下面,不容易在 ob 內部發生 b1/b2 錯誤
//TODO 待補

1
2
3
4
5
6
7
8
9
10
11
12
```

```javascript=
let timestamp = function({ year, month, date, hour, min, sec }) {
year = addZero(year, 4);
month = addZero(month, 2);
day = addZero(date, 2);
hour = addZero(hour, 2);
min = addZero(min, 2);
sec = addZero(sec, 2);
return `${year}-${month}-${day} ${hour}:${min}:${sec}`;
};
1
2
3
4
5
6
7
8
loading = true;

this.XXX = XXX();
this.ooo = OOO();
await this.XXX;
await this.ooo;

loding = false;