複習ES6以後的一些特性

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函數過程就會一直進行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章