狠狠撸

狠狠撸Share a Scribd company logo
JavaScript?快速複習?2017Q1
by?Aysh?Su
2017/02/08
講這堂的目的在哪?
看看現在一堆新東西在解決什麼問題
認識新語法
看新一點的教學都不太可避免看到新語法orz
不要去記,看過有印象看久就習慣
好用的話就記下來用
今天的主題們
1. 宣告式編程
2. 點來點去的鏈式調用如[].map(). lter().sort()
3. ECMAScript 6 和7 新出現的語法(只講常用)
4. 非同步的基本概念複習
5. callback、ES6 Promise、async/await 之間的關係
主題一:宣告式編程是什麼?
可以吃嗎^o^
先看看傳統的命令式編程
你想把所有陣列元素乘以2,指派到新陣列
var arr = [1,2,3];
var arr2 = [];
for (var i = 0; i < arr.length; i++) {
arr2.push(arr[i] * 2);
}
console.log(arr2); //[ 2, 4, 6 ]
所謂的「命令」在哪?
要for 要push 要指派變數,全都要一一命令清楚
太多操作細節
code一長就沒辦法一眼看出在幹嘛
怎麼改善?
宣告式編程的解法
var arr = [1, 2, 3];
var arr2 = arr.map(function (value) { return value*2;});
// with ES6
// let arr2 = arr.map(value => value * 2);
console.log(arr2); //[ 2, 4, 6 ]
登愣!完全沒有命令?
只有宣告要對陣列中所有元素做什麼處理
底層怎麼for 怎麼push ?不重要
讓程式碼只表達你的目的
將不重要的操作細節隱藏
常用的內建宣告式編程方法
[].map()
回傳依函數轉換過的元素的新陣列
[]. lter()
回傳將原陣列篩選過的新陣列
[].sort()
將原陣列按指定規則做排序
[].reduce()
將所有陣列元素合併成單一個值
文件去哪查?
假設我要查?[].map()?怎麼用:
1. .map() 是[] 陣列(Array)類型物件的方法
2. 記住:?MDN?好棒棒
JavaScript 最方便參考資源沒有之一
3. Google 搜尋「MDN array map」
或「MDN Array.prototype.map」
跟?贬罢惭尝?顿翱惭?互动也可以
jQuery?或原生的命令式編程
<div>
<input type="text" id="todo-input">
<button id="new-todo">Submit</button>
<ul id="todo-list"></ul>
</div>
<script>
$('#new-todo').click(function () {
var newTodo = $('#todo-input').val();
$('#todo-list').append('<li>' + newTodo+ '</li>');
$('#todo-input').val('');
});
</script>
問題在哪?
操作誰都要有清楚的?selector
加id 很常見
HTML 跟JS 嵌合太緊密
HTML?版型一改,selector?要重寫的機率很高
怎麼操作也都要個別講清楚
明明做的事情都差不多
人生浪費在'<li>' + value + '</li>' 之上
宣告式編程帶來什麼不同?
h ps://goo.gl/dwyQQE
以?Vue.js?為例
宣告式編程:?HTML部份
<div id="app">
<input type="text" v-model="todoInput">
<button v-on:click="newTodo()">Submit</button>
<ul>
<li v-for="todo in todos">{{ todo }}</li>
</ul>
</div>
Vue.js 的 v- 來 v- 去在幹嘛?
幫你全自動同步 <input> 的內容到JS 裏的變數
定義 <li> 的數量和內容是按照哪個JS 變數
宣告式編程:?只操作資料的?JS
var app = new Vue({
el: '#app',
data: {
todoInput: '',
todos: []
},
methods: {
newTodo: function () {
this.todos.push(this.todoInput);
this.todoInput = '';
}
});
完全不用寫任何操作DOM 元素的細節!
主題二:怎麼理解鏈式調用?
點來點去點點點
沒有鏈式調用的時候
挑戰你的想像力:幫各種暫存變數想名字
var arr = [1, 3, 5, 4, 2];
var tempArr1 = arr.map(function (value) { return value * 2; } );
var tempArr2 = tempArr1.filter(function (value) { return value >
var tempArr3 = tempArr2.sort(function (a, b) { return a - b; });
var arr2 = tempArr3;
有沒有更簡潔的寫法?
[].map().?lter().sort()
這在幹嘛囧rz?
要訣:從左到右一個一個解讀
鏈式調用(chaining)例子
var arr = [1, 3, 5, 4, 2];
var arr2 = arr
.map(function (value) { return value * 2; } )
.filter(function (value) { return value > 5; })
.sort(function (a, b) { return a - b; });
1. 算出arr.map() 的结果
2. 拿去. lter()
3. 再拿去.sort()
鏈式調用(chaining)例子
var arr = [1, 3, 5, 4, 2];
var arr2 = arr
.map(function (value) { return value * 2; } )
.filter(function (value) { return value > 5; })
.sort(function (a, b) { return a - b; });
1. 算出arr.map() 的结果// [2, 6, 10, 8, 4]
2. 拿去. lter()
3. 再拿去.sort()
鏈式調用(chaining)例子
var arr = [1, 3, 5, 4, 2];
var arr2 =
[2, 6, 10, 8, 4]
.filter(function (value) { return value > 5; })
.sort(function (a, b) { return a - b; });
1. 算出arr.map() 的结果// [2, 6, 10, 8, 4]
2. 拿去. lter() // [6, 10, 8]
3. 再拿去.sort()
鏈式調用(chaining)例子
var arr = [1, 3, 5, 4, 2];
var arr2 =
[6, 10, 8]
.sort(function (a, b) { return a - b; });
1. 算出arr.map() 的结果// [2, 6, 10, 8, 4]
2. 拿去. lter() // [6, 10, 8]
3. 再拿去.sort() // [6, 8, 10]
return?出什麼東西
接下來就可以點什麼
到底?return?出什麼東西?
1.?查文件
2.?console.log()?出來看
3.?開發者工具
主题叁:较常用的?贰厂6?功能
什麼是?ES6
JavaScript 是ECMAScript (ES) 的實現
ECMAScript 不斷更新版本,加入新功能和語法糖
連IE9 都可以跑的那個叫ECMAScript 5 (ES5)
[].indexOf() 也是ES5 才有!
各家瀏覽器漸漸相容ES6 的新功能
真的只能學不能寫嗎?相容性
當代環境已支援超過95% 的ES6 功能
Firefox
Chrome
Safari
Node.js
針對舊版瀏覽器,可將ES6 編譯成相容ES5
Babel
ES6?‐?Block‐Scoped?Variable
拿來給你取代var 的
用const 宣告常數
用let 宣告一般變數
const PI = 3.14; // 常數宣告時必須指派值
PI = 3; //注意這裡馬上會拋錯!不能重新指派值給已宣告的常數
let id = 123;
let name; // let 跟 var 比較像,宣告時要不要指派值都沒關係。
原則上優先使用?const
ES6?‐?Block‐Scoped?Variable
const 和let 的變數域(scope) 只作用到{}
相對於var 是function-scoped
// 比較差異,想想為什麼?
for (var i = 0; i < 5; i++) {
setTimeout(function () {console.log('i = ' + i)}, 0);
}
for (let j = 0; j < 5; j++) {
setTimeout(function () {console.log('j = ' + j)}, 0);
}
在for 的() 中的let 的scope 是for 的{}
ES6?‐?Arrow?Func on
長這樣() => {}
可以拿來取代匿名函數function () {}
那個() 和{} 在兩種用法都是一樣用意
setTimeout(function () {
console.log('yay')
});
setTimeout(() => {
console.log('yay')
});
ES6?‐?Arrow?Func on
只有一個參數時可省略括弧
只有一行且直接作為return 值時
可省略{} 和return 不寫
[1,2,3].map( (val) => {
return val * 2;
});
[1,2,3].map( (val) => val * 2 );
[1,2,3].map( val => val * 2 );
會自動?bind?this,要留意
通常比較方便,但偶爾會讓踩到this 的坑
ES6?‐?樣板字串
樣板字串是用「反撇」!!不是單引號!!
就是你平常打「~」的那顆鍵,不要壓shift
`` 裏面可以用${變數名稱} 存取變數
const name = 'John';
`<li>${name}</li>` === '<li>' + name + '</li>' //true
ES6?‐?參數預設值
直接在function 定義的參數名稱後面加= 預設值
// 註: 一樣可在()=>的參數括弧中用此寫法
function hello(name = 'world') {
console.log(`Hello ${name}!`);
}
hello(); // Hello world!
就不用再自己判定unde ned 再另外餵值了
ES6‐?參數解構
直接拆解參數傳入的物件並指派到同名變數
// 註: 一樣可在()=>的參數括弧中用此寫法
const f = function ({ id }) {
console.log(id);
};
const obj = {
id: 123,
name: 'John Doe',
};
f(obj); // 123
若寫{ id: idOfObj }
則可把傳入物件的.id 指派給idOfObj
ES6?‐?變數解構指派
直接拆解參數傳入的物件並指派到同名變數
const obj = {
id: 123,
name: 'John Doe',
};
const { id, name } = obj;
// const id = obj.id;
// const name = obj.name;
const { id: idOfObj } = obj; // const idOfObj = obj.id;
另參數/變數解構也都可以拆陣列
const [a, b] = [1, 2]; // a === 1 && b === 2
ES6?‐?import/export
瀏覽器中無法直接使用
需搭配如webpack 等工具編譯
在js 檔中透過export 設定要匯出什麼
在另個js 檔能import 匯入剛才的js 檔內容來使用
模組化
效果同require() 和module.exports
ES6?‐?物件方法簡寫
const obj = {
method1: function () {
// 傳統的的方法宣告
},
method2() {
// 效果等同上面那種宣告
},
prop1: 'value'
};
ES6?‐?物件淺合併
Object.assign(目標物件, 來源物件1, ...)
把來源物件的key 和value 對應複製到目標物件上
一個一個來源依序處理
後來的來源與目標有key 重複時,
由後來的value 覆蓋該key
如果value 是物件,不會遞迴合併
淺合併shallow merge 只合併最外層物件
深合併deep merge 才會把每層物件都合併
沒有原生deep merge
ES6?‐?剩餘參數
在function 的參數最後用... 將剩餘參數存成陣列
const f = function (...args) {
console.log(args);
};
f(1,2,3); // [1, 2, 3]
ES6?‐?Class?和?extends
只是語法糖,底層跟以前的物件導向效果一樣
建議直接看es6-feature.org ,此處不贅述
http://es6-features.org/#ClassDe nition
主题四:非同步的处理原理
簡單來說,什麼是非同步?
只有我一個人可以做正事
可是一個人只有一雙手,
同時間只能做一件事
我做A 做到一半,
可以先把B 委託給別人幫忙,
他做完會提醒我說他做好了,
等我處理完A,
再來處理別人已經幫我處理好的B 的结果
簡單來說,什麼是非同步?
只有一個JS引擎可以做正事
可是一個JS引擎只有一個執行緒,
同時間只能執行一段程式碼
JS引擎做A 做到一半,
可以先把B 委託給瀏覽器幫忙,
瀏覽器做完會提醒JS引擎說他做好了
等JS引擎處理完A,
再來處理瀏覽器已經幫JS引擎處理好的B 的结果
也就是執行B的callback(B的结果)
記住:
JS引擎永遠只有一個執行緒
就是只有那一雙手
非同步的三個角色
背景環境(如瀏覽器)
背景環境把程式碼塞入Event Loop 隊列
Event Loop 隊列
Event Loop 會把隊列上第一塊程式碼
推給JS 引擎執行
等JS 引擎執行完才再推下一塊程式碼給JS 引擎
JS 引擎
執行JS 程式碼
可以呼叫背景環境的原生非同步API
瀏覽器的常用原生非同步?API
XMLHttpRequest
jQuery 的$.ajax() 底層也是呼叫這個
setTimeout()、setInterval()
等等你就會知道為什麼這兩個時間「不準」
簡單的非同步例子:
console.log(1);
$.get(url, function (res) {
console.log(2)
});
console.log(3);
執行結果一定是:
1
3
2
為什麼?
簡單的非同步例子:?步驟?1
瀏覽器: 塞程式碼區塊1給隊列
隊列: 從瀏覽器取得程式碼區塊1
======區塊1 開始======
console.log(1);
$.get(url, callback);
console.log(3);
======區塊1 結束======
JS引擎: 目前手上沒在做事
簡單的非同步例子:?步驟?2
瀏覽器: 沒事做
隊列: 把區塊1推給沒在做事的JS引擎
JS引擎: 執行隊列塞進來的區塊1
console.log(1);
$.get(url, callback);
console.log(3);
執行第1行console.log(1);
簡單的非同步例子:?步驟?3
瀏覽器: 沒事做
隊列: 無
JS引擎: 執行隊列塞進來的區塊1
console.log(1);
$.get(url, callback);
console.log(3);
執行到第2行時,JS引擎請瀏覽器幫他做AJAX請求
並請瀏覽器在做好AJAX之後
把呼叫callback的區塊塞回隊列
簡單的非同步例子:?步驟?4
瀏覽器: 沒事做
隊列: 無
JS引擎: 執行隊列塞進來的區塊1
console.log(1);
$.get(url, callback);
console.log(3);
$.get() 就是「把AJAX請求交給瀏覽器」這件事,
委託完瀏覽器,對JS 引擎就算是做完第2行了。
JS引擎繼續執行第3行console.log(3);
簡單的非同步例子:?步驟?5
瀏覽器: 剛做完ajax 請求,把程式碼區塊2塞給隊列
隊列: 手上拿到區塊2,可是?JS?引擎還在忙
======區塊2 開始======
callback(ajaxResponse);
======區塊2 結束======
JS引擎: 還在執行區塊1
console.log(1);
$.get(url, callback);
console.log(3);
簡單的非同步例子:?步驟?6
瀏覽器: 沒事做
隊列: 發現JS 引擎沒事做,把隊列上第一個區塊推過去
======區塊2 開始======
callback(ajaxResponse);
======區塊2 結束======
JS引擎: 跑完了,沒事做
簡單的非同步例子:?步驟?7
瀏覽器: 沒事做
隊列:
JS引擎: 執行區塊2,也就是對callback 的呼叫
callback(ajaxResponse);
非同步處理:?哪裡有坑?
現在你看得懂這個邏輯錯誤在哪了
var answer;
$.ajax(url, function (res) {
answer = res;
});
console.log(answer); // undefined
非同步處理:?哪裡有坑?
想想看,幾秒之後才會執行setTimout 的callback
function sleepFor(sleepDuration) {
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration) {}
}
setTimeout(function () { // 預定1秒後執行
console.log('說好的1秒呢?');
}, 1000);
sleepFor(5000); //讓JS引擎執行區塊時卡在while卡滿整整5秒
注意?JS?引擎一定會把手上的程式碼區塊跑到完,
Event?Loop才會再把隊列上的下個區塊推給?JS?引擎
主題五:callback,?Promise,
和?async/await?間的關係
Callback?Hell?a.k.a.?龜派氣功
只有callback 時,
唯一確保各非同步區塊執行順序的方法
$.get(url1, (res1) => {
const param2 = res1.returnValue;
$.get(url2, { param2 }, (res2) => {
const param3 = res1.returnValue;
$.get(url3, (res3) => {
const param4 = res1.returnValue;
// ...
});
});
});
ES6?Promise?來拯救咧
把callback 深度變淺了!結構清楚不少
fetch(url1)
.then((res1) => {
const param2 = res1.returnValue;
return $.get(url2, { param2 });
})
.then((res2) => {
const param3 = res2.returnValue;
return $.get(url3, { param3 });
})
.then((res3) => {
const param4 = res3.returnValue;
return $.get(url4, { param4 });
})
// ...
怎麼用?Promise?拯救?callback
1. 呼叫會回傳Promise 的方法或library
fetch():?瀏覽器原生的新標準AJAX請求API
fetch(url)
.then((res)=>res.json())
.then((res)=>console.log(res));
axios 等AJAX library
jQuery 3.0 以上也有相容Promise/A+ 標準
怎麼用?Promise?拯救?callback
2. 用new Promise((resolve, reject)=>{}) 包裝
const p = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('yay');
}, 1000);
});
p.then((res)=>console.log(res)); // yay
注意當你?new?Promise?的同時
該?func on?內容已經開始執行
用?ES7?async/await?假裝同步
單純Promise 的場合
fetch(url)
.then((res)=>res.json())
.then((res) => {
console.log(res);
}); // 一樣只能在 callback 內存取 Promise 的 resolve 值
用?ES7?async/await?假裝同步
用async/await 幫Promise 加持
async function f() {
const res = await fetch(url).then((res)=>res.json());
console.log(res);
}
f(); // 把值從 Promise 裏面拔出來,magic!
async/await?語法說明
宣告async function () {}
在async function 裏面,
可以await 一個Promise。
把Promise 的resolve 值直接穿越callback 取出
語法寫起來像傳統的同步操作,
執行上卻一樣是基於Promise 非同步
當然可以IIFE
(async () => { })();
async function 本身return 值會被Promise 包裝
Ques ons?
可趁機會提問平常看到但看不懂的JS
延伸壓泡麵閱讀推薦:
You?Don't?Know?JS
中文版:你所不知道的JS
英文版在?GitHub?上可免費閱讀

More Related Content

JavaScript 快速複習 2017Q1