String
Template Strings
連接String更方便直觀。使用反引號包括要生成的String,使用${}
包括具體變量。
var name = "Yixuan";
var email = "[email protected]";
var title = "Student";
//以前
var msg = "Welcome! Your " +
title + " is " + name + ", contact: " +
email + ".";
//現在
var msg = `Welcome! Your ${title} is ${name}, contact: ${email}.`
//Welcome! Your name is Yixuan, contact: [email protected].
Tagged Templates
在模版字符串前調用自定函數,來定製模版字符串的解析方式。這個函數的第一個函數包含一個字符串值的數組,其餘的參數是一系列的$表達式的引用變量,可以用...操作來把這些參數當成一個數組。
這個例子中我們的自定函數formatCurrency將字符串中的數字加上了$符號並保留兩位小數。
var amount = 4.2;
var msg = formatCurrency`The total for your order is ${amount}.`
function formatCurrency(strings, ...values) {
var str = "";
//console.log(strings);
for (let i = 0; i < strings.length; i++) {
str += strings[i];
if( i < values.length){
console.log(typeof values[i]);
if(typeof values[i] == "number") {
str += `$${values[i].toFixed(2)}`;
}else {
str += values[i];
}
}
}
return str;
}
console.log(msg);
//The total for your order is $12.30.
String Padding & String Trimming
JS標準庫中現在自帶給字符串兩邊加內容和刪減空格的方法。
padStart給字符串左邊加內容,padEnd給字符串右邊加內容。
var str = "Hello";
str.padStart(5); // "Hello"
str.padStart(8) // " Hello"
str.padStart(8, "*"); // "***Hello"
str.padStart(8, "12345"); // "123Hello"
str.padStart(8, "ab"); // abaHello"
str.padEnd(8) // "Hello "
str.padEnd(8, "*") // "Hello***"
str.padEnd(8, "ab"); // "Helloaba"
trimStart去除左邊空格, trimEnd去除右邊空格。
var str = " some stuff \t\t";
str.trim(); // "some stuff"
str.trimStart(); // "some stuff "
str.trimEnd(); // " some stuff"
Destructuring
有時我們get到了一個很大的JSON對象,我們需要把裏面的一些值分配到變量當中。解構讓這個操作更方便。
看一個例子。
var tmp = getSomeRecords();
//tmp是一個擁有兩個對象的數組。
//以前
var first = tmp[0];
var second = tmp[1];
var firstName = first.name;
var firstEmail = first.email !== undefined ? first.email : "no email";
var secondName = second.name;
var secondEmail = second.email !== undefined ? first.email : "no email";
//現在
var [
{// 創建一個叫做firstName的變量,值是數組中第一個對象中name鍵的值。
name: firstName,
// 創建一個叫做firstEmail的變量,值是數組中第一個對象中email鍵的值,如果沒有這個鍵則使用默認值“no email"。
email: firstEmail = "no email"
},
{
name: secondName,
email: secondEmail = "no email"
}
] = tmp;
注意在這個例子中,賦值等號左邊的[],這個中括號不代表數組,而代表一種解構賦值的pattern模式。還有要注意的是像上面這個例子的email的默認值只會在檢測到undefined的時候纔會被使用,如果值是null,不會觸發使用默認值。
看一個更簡單的例子。
function data(){
return [1,2,3];
}
// 以前
var tmp = data();
var first = tmp[0];
var second = tmp[1];
var third = tmp[2];
//現在
var [
first,
second,
third
] = data();
如果左邊的變量比右邊的值多,多餘的變量的值就會是undefined。如果左邊的變量比右邊的值少,多餘的值會被忽略。
如果我們要在一個變量裏多賦幾個值呢?
data = [1,2,3,4,5];
var [
first,
second,
third,
...fourth
] = data;
//這裏fourth是[4,5];
如果值不夠
data =[1,2,3];
var [
first,
second,
third,
...fourth
] = data;
//這裏fourth是空數組[];
有的時候我們需要交換變量的值,用解構也更方便。
var x = 10;
var y = 20;
//以前
var tmp = x;
x = y;
y = tmp;
//現在
]
[y,x] = [x,y];
有的時候我們將一個數組傳入函數的時候,我們只需要它的前三個元素,我們也可以使用解構,在函數參數聲明的時候就做到這步。
function data([
first,
second,
third
]) {
}
解構賦值如果出現賦值錯誤,和普通賦值一樣,也會報錯。
var data = null;
var [first, second] = data;
//TypeError
function foo([first,second])
} {
...
}
foo(data);//傳入null
//TypeError
這個時候我們需要Graceful Fallback(降級,向下兼容)
var data = null;
var [first, second] = data || [];
//不報錯
function foo([first,second] = [])
} {
...
}
//不報錯
嵌套解構
var data = [1,[2,3],4];
var [
first,
[
second,
third
],
fourth
] = data;
Object Destructuring
有的時候我們有一個默認的對象,但是我們需要根據一個新傳過來的對象,來創建一個新對象。舉個例子,我們知道的表單信心有name,wechat,phone,gender等屬性(鍵名),但是有時表單會有新的屬性。這是我們可以用解構賦值來很好的創建出一個新對象,來傳到後端或傳到數據庫。
function makeObject({
name = "default name",
wechat = "default wechat",
phone = "default phone",
gender = "none",
...otherProps
} = {}) {
return {
name,
wechat,
phone,
gender,
...otherProps
}
}
const obj = {
name: "wyx",
wechat: "weixin",
gender: "male",
age:"22",
year:"2019"
}
const newObj = makeObject(obj);
console.log(newObj);
/*
{ name: 'wyx',
wechat: 'weixin',
phone: 'default phone',//沒有的屬性使用默認值
gender: 'male',
age: '22',
year: '2019' }
*/
Array
Array.find()
找到數組中相應的值,如果有就返回這個值,沒有就undefined。
var arr = [{a:1},{a:2}];
var res = arr.find(v => v && v.a > 1);
console.log(res);
// {a:2}
res = arr.find(v => v && v.a > 10);
console.log(res);
// undefined
Array.findIndex()
找到相應值的index
var arr = ["a","b","c","d"];
var res = arr.findIndex(v => v && v == "c");
console.log(res);
// 2
res = arr.findIndex(v => v && v == "x");
console.log(res);
// -1
Array.includes()
用來代替以前的Array.indexOf(xxx) != -1
var arr = [10,20,30,40];
//以前
if(arr.indexOf(30) != -1){
console.log("exist!");
}
//現在
if(arr.includes(30)) {
console.log("exist!");
}
//exist!
Array.flat()
攤開數組,可以根據傳入參數改變具體攤開的層數。默認攤開一層。
var nestedArray = [1,2,[3,4],[5,[6,7]]];
nestedValues.flat(0);
//[1,2,[3,4],[5,[6,7]]]
nestedValues.flat(); //default 1
//[1,2,3,4,5,[6,7]]
nestedValues.flat(2);
//[1,2,3,4,5,6,7];
Array.flatMap()
flatMap只能攤開一層,如果需要更多層,需要分開使用map()和flat().
[1,2,3,4,5,6].flatMap(v => {
if (v % 2 == 0) {
return [v, v * 2];
}
else {
return [];
}
})
//[2,4,4,8,6,12]
Iterator
Built-in Iterable
Iterator簡單來說,就是我們用next()來遍歷一個集合。
在ES6中,String, Array, TypedArray,Map, Set是默認iterable的。
var str = "Hi"
//在string上使用iterator
var it1 = str[Symbol.iterator]();
//這裏Symbol.iterator是str對象的一個屬性,我們通過[Symbol.iterator]獲取到這個對象的iterator然後把它賦給it1。
it1.next(); // { value: "H", done: false}
it2.next(); // { value: "i", done: false}
it3.next(); // { valye: undefinedn done:true}
var arr = ["H","i"];
//在Array上使用iterator
var it2 = arr[Symbol.iterator]();
it2.next(); // {value : "H", done: false}
it2.next(); // {value : "i", done: false}
it2.next(); // {value : undefined, done: true}
Declarative Iterators
for of 循環其實就是使用了iterator
var str = "Hello";
var it = str[Symbol.iterator]();
//在for loop中使用iterator
for(let v of it){
console.log(v);
}
//效果和直接使用for loop是一樣的
for (let v of str) {
console.log(v);
}
...符號也使用iterator
var str ="Hello";
var letters = [...str];
console.log(letters);
// ["H","e","l","l","o"]
Object類型沒有iterator
我們在js中最常使用的對象類型,沒有默認的iterator,我們需要自己定義一個。
var obj = {
a : 1,
b : 2,
c : 3
}
for (let v of obj) {
console.log(v);
}
// TypeError!
//想要使用for of循環的時候出現了錯誤
//定義iterator屬性
obj[Symbol.iterator] = function(){
let keys = Object.keys(this);
let index = 0;
return {
next: () => {
if(index < keys.length){
return {
done : false,
value : keys[index++]
}
}else {
return {
done : true,
value : undefined
}
}
}
};
}
console.log([...obj]);
//['a','b','c']
Generators
Generators
function *generate() {
yield 1;
yield 2;
yield 3;
return 4;
}
var it = generate();
it.next(); // {value: 1, done : false}
it.next(); // {value: 2, done : false}
it.next(); // {value: 3, done : false}
it.next(); // {value: 4, done : true}
console.log([...generate()]);
// [1,2,3]
一個使用生成器的例子,這裏我們給luckyNumbers對象一個我們自定義的生成器函數叫lucky,然後在調用這個生成器的時候使用...操作符來iterate這個生成器,打印1-30之間6的倍數。
var luckyNumbers = {
*lucky({
start = 0,
end = 100,
step = 1
} = {}) {
for (let i = start; i <= end; i+= step) {
yield i;
}
}
};
console.log(`My lucky numbers are: ${
[...luckyNumbers.lucky({
start: 6,
end: 30,
step: 4
})]
}`)
//My lucky numbers are: 6,10,14,18,22,26,30
Async Await
Async出現的歷史
這裏有一個例子,我們先請求當前當前用戶,獲得用戶數據以後,又請求當前用戶所下過訂單和進行中的訂單的例子。
以前用Promise的時候,使用鏈式then()來處理這種連續請求。
fetchCurrentUser()
.then(function onUser(user) {
//獲得當前用戶
return Promise.all([
//用Promise.all來做兩個請求,返回的還是一個promise,如果有一個請求中有一個reject則都reject
fetchArchivedOrders( user.id ),
fetchCurrentOrders (user.id)
]);
})
後來我們有人不再用鏈式then()來處理多個請求。而是用generator來獲得多個請求的response。一個generator可以yield一個promise,並且等待yield結果以後再進行下一步。但是這個方法一般要使用第三方庫的一種runner函數進行,像Co,Koa都有。runner函數的作用就是在yield的時候等待結果resolve,然後再往下iterate,往下yield。
runner(function *main() {
var user = yield fetchCurrentUser();
var [ archivedOrders, currentOrders ] = yield Promise.all([
fetchArchivedOrders( user.id ),
fetchArchivedOrders( user.id )
])
});
其實上面這個例子裏的yield關鍵詞,已經很像await關鍵詞了,所以後來JS官方就推出了Async Await關鍵詞,不再需要用第三方庫的runner函數。
async function main() {
var user = await fectchCurrentUser();
var [archiveOrders, currentOrders] = await Promise.all([
fetchArchiveOrders(user.id),
fetchCurrentOrders(user.id)
]);
return archiveOrders + currentOrders;
}
這裏有個例子,我們同時請求三個file,但是保證打印結果是按順序打印,也沒有undefined。並且一請求到就立即打印結果,並不等待後續的請求完成。
function getFile(file) {
return new Promise(function(resolve){
fakeAjax(file,resolve);
});
}
async function loadFiles(files) {
var prs = files.map(getFile);
//用map同時做三個請求
for (let pr of prs) {
console.log(await pr);
}
//在for loop中加入await關鍵詞,來確保按順序打印,也不會undefined。
}
loadFiles(["files","file2","file3"]);
async await problems
async await也有一些問題
- await只能應對Promise
- Starvation
promise會在時間循環中排進microtask,會造成飢餓陷阱,這裏不多寫。
- cancelation
Async函數是沒有辦法被手動取消的,比如一個request要下載巨大的文件,async函數過程就會一直進行