ES6 小整理

ES6

咱們直奔主題html

var、let和const

var是以前就有的了,在這裏提出來主要是爲了比較其和let與const。react

區別編程

  1. 塊級做用域
for(var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 輸出3個3
  }, 0)
}
// 解析:變量i是var聲明的,在全局範圍內是都有效,全局只有一個變量i。
//每次循環,變量的值會發生改變。循環內的i是指向全局的i。
複製代碼
for(let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 輸出0, 1, 2
  }, 0)
}
//解析:變量i是let聲明的,當前的i只在本輪循環有效,因此每次循環的i其實都是一個新變量。
//JavaScript引擎內部會記住上一輪的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。


// 也可使用閉包吧變量生命週期延長
for(let i = 0; i < 3; i++) {
  (function(i){
    setTimeout(() => {
    console.log(i); // 輸出0, 1, 2
  }, 0)
  })(i)
}
複製代碼
  1. 不存在變量提高
console.log(a); // undefined
var a = 100;
// 等同於
var a
console.log(a)
a = 100


// var命令會發生變量提高現象,即變量能夠在聲明以前使用,值爲undefined;而let糾正了這種行爲,不能產生變量提高。
console.log(a); // 報錯
let a = 100;
複製代碼
  1. 暫時性死區
// 只要塊級做用域內,存在let命令,它所聲明的變量就綁定(binding)在這個區域,再也不受外部影響。
var temp = 123;
if(true) {
  temp = 'abc'; // 引入錯誤
  let temp;
}
// 在上面中,if後面的大括號內容就造成了一個區域。而temp此時是找不到外層的,由於內部有個temp且你在內部let temp聲明前賦值了。
複製代碼
//在看一個隱晦的例子:
function bar(x = y, y = 2) {
  return [x, y]
}
bar(); // 報錯

//上面的例子中bar裏面進行賦值操做的時候,就產生了一個封閉的區域了,能夠認爲x 和 y經過let聲明,但是上面的問題是,x = y的引用在y = 2的聲明以前。
//能夠修正以下:
function bar(y = 2, x = y) {
  return [x, y];
}
bar(); // [2, 2]
複製代碼
  1. 不可重複聲明
var a = 100;
var a = 1000;
console.log(a); // 1000
let a = 100;
let a = 1000; // 報重複聲明錯誤
複製代碼
  1. ES6聲明的變量不會掛在頂層對象json

    嗯~ES6變量的聲明是指哪些聲明呢? 指let, const, import, class聲明。 而var, function聲明是ES6以前的。 因此目前JavaScript有六種聲明變量的方式了~windows

var job = 'teacher';
console.log(window.job); // teacher
let job = 'teacher';
console.log(window.job); // undefined
複製代碼

const命令注意點數組

1.let能夠先聲明稍後賦值;而const聲明以後必須立馬賦值,不然會報錯promise

let a;
a = 100; // this is ok
const a; // 報沒初始化數據的錯誤
複製代碼

2.const聲明瞭簡單的數據類型就不能更改了;聲明瞭引用類型(數組,對象等),指針指向的地址不能更改,可是內部的數據能夠更改的瀏覽器

const str = 'this is a string';
str = 'this is another string'; // 報了個「給不變的變量分配值」的錯誤
const obj = {
  name: 'jia'
}
obj.name = 'ming'; // this is ok
obj = {}; // 報了個「給不變的變量分配值」的錯誤
複製代碼

let和const的使用場景bash

1.let使用場景:變量,用以代替var服務器

2.const使用場景:常量、聲明匿名函數、箭頭函數的時候。

// 常量
const PI = 3.14;

// 匿名函數
const fn1 = function() {
  // do something
}

// 箭頭函數
const fn2 = () => {
  // do something
}
複製代碼

變量的解構賦值

解構能夠理解就是一個做用:簡化你變量賦值的操做。

1.數組場景

let [name, job] = ['jiaming', 'teacher'];
console.log(name); // jiaming
// 本質上,這種寫法屬於模式匹配,只要等號兩邊的模式相同(重點),左邊的變量就會被賦予對應的值。再好比:
let [ , , third] = ["foo", "bar", "baz"];
console.log(third); // "baz"

let [head, body, ...tail] = [1, 2, 3, 4, 5];
console.log(tail); // [3, 4, 5]
複製代碼
//也可使用默認值。可是默認值生效的前提是:ES6內部使用嚴格相等運算符(===),判斷一個位置是否有值。
//因此,只有當一個數組成員嚴格等於undefined,默認值纔會生效。null也至關於賦值,
let [x, y = 'b'] = ['a']; // x='a', y='b'

// 後面的值爲undefined,因此默認值生效
let [z = 1] = [undefined];
console.log(z); // 1
// 後面的值爲null,默認值不生效。使用null值
let [k = 1] = [null];
console.log(k); // null
複製代碼

2.對象場景

const state = {
  name: 'jiaming',
  job: 'teacher'
};
let {
  name,
  job
} = state;
// 上面的場景很熟悉吧
console.log(job); // teacher

// 上面的例子若是寫具體的話,是這樣的:
const state = {
  name: 'jiaming',
  job: 'teacher'
};
let {
  name: name, // 第一個name是匹配模式,第二個name纔是變量,二者同名簡化成一個便可
  job: job
} = state;

// 咱們來改寫下:
const state = {
  name: 'jiaming',
  job: 'teacher'
};
let {
  name: job,
  job: name
} = state;
console.log(job); // jiaming
複製代碼
// 對象也可使用默認值,可是前提是:對象的屬性值嚴格等於undefined。
var {x = 3} = {x: undefined};
console.log(x); // 3

var {y = 3} = {y: null};
console.log(y); // null

複製代碼

3.字符串場景

字符串之因此可以被解構賦值,是由於此時字符串被轉換成了一個相似數組的對象。

const [a, b, ...arr] = 'hello';
console.log(arr); // ["l", "l", "o"]
複製代碼

兩種使用場景

//交換兩變量值
let [a, b] = ['reng', 'jia'];
[a, b] = [b, a];
console.log(b); // 'reng'
複製代碼
//將字符串轉換爲數組
let [...arr] = 'reng';
console.log(arr); // ["r", "e", "n", "g"]
console.log(arr.splice(0, 2)); // ["r", "e"] 返回刪除的數組(能使用數組的方法了)
複製代碼

字符串擴展

針對字符串擴展這個,我的感受模版字符串使用的頻率比較高。模版字符串解放了拼接字符串帶來的繁瑣操做的體力勞動。

let name = 'jiaming';
let str = 'Hello! My name is '+ name + '. Nice to meet you!';
let strTemp = `Hello! My name is ${ name }. Nice to meet you!`
複製代碼

對於新增的字符串方法,能夠記下下面這幾個:

includes(): 返回布爾值,表示是否找到了參數字符串
startWith(): 返回布爾值,表示參數字符串是否在原字符串的頭部
endWith(): 返回布爾值,表示參數字符串是否在原字符串的尾部
trimStart(): 返回字符串,表示消除參數字符串開頭的空格
trimEnd(): 返回字符串,表示消除參數字符串結尾的空格
複製代碼

數值擴展

留意下在Number對象上提供的新方法:

Number.isFinite(): 返回布爾值,表示參數值是否有限的
Number.isNaN(): 返回布爾值,用來檢查一個值是否爲NaN
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isInteger(): 返回布爾值,用來判斷一個數值是否爲整數
關於Math對象上的方法,遇到要用到時候,查API吧,否則記太多,腦瓜子會疼~
複製代碼

函數擴展

rest參數

ES6引入rest參數(形式是...變量名),用於獲取多餘的參數,這樣就不須要使用arguments對象了。rest參數搭配的變量是一個數組(arguments是一個類數組來的),該變量將多餘的參數放入數組中。

arguments對象是一個類數組,還得經過Array.prototype.slice.call(arguments)將其轉換爲真數組;而rest參數直接就可使用數組的方法了。

function add(...arr) {
  console.log(arr); // [2, 5, 3]
  let sum = 0;
  for(var val of arr) {
    sum += val;
  }
  return sum;
}
console.log(add(2, 5, 3)); // 10
複製代碼

箭頭函數

ES6容許使用「箭頭」(=>)定義函數。

const f = v => v; // 注意是有返回值return的啊

// 等同於
const f = function (v) {
  return v;
}
複製代碼

若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回結果。

const sum = (num1, num2) => num1 + num2;

// 等價於,使用了大括號,那箭頭函數裏面就要使用return了
const sum = (num1, num2) => { return num1 + num2 }

// 等價於
const sum = function(num1, num2) {
  return num1 + num2
}
複製代碼

使用箭頭函數注意點:

函數體內的this對象,就是定義所在的對象,而不是使用時所在的對象
不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。
不可使用arguments對象,該對象在函數體內不存在的,若是要用,能夠用rest參數代替。
不可使用yield命令,所以箭頭函數不能用做Generator函數。
複製代碼

箭頭函數

數組擴展

數組擴展運算符

數組擴展運算符(spread)是三個點(...)。它比如rest參數的逆運算,將一個數組轉爲用空格分隔的參數序列。

console.log(...[1, 2, 3]); // 1 2 3
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
複製代碼

rest參數是運用在函數參數上的,將函數參數轉換爲數組的形式,以下:

function fn(...values) {
  console.log(values); // ['jia', 'ming']
}
fn('jia', 'ming');
複製代碼

下面咱們結合數組擴展運算符和rest參數來實現一個相似call的方法call2操做:

Function.prototype.call2 = function(context=windows){ // 這裏使用到rest參數
  context.fn = this; // 讓fn的上下文爲context
  let args = [...arguments].slice(1)  
  const result = context.fn(...args); // 這裏使用了數組擴展運算符
  delete context.fn;
  return result; // 由於有可能this函數會有返回值return
}
var job = 'outter teacher';
var obj = {
  job: 'inner teacher'
};
function showJob() {
  console.log(this.job);
}
showJob(); // outter teacher
showJob.call2(obj); // inner teacher

//複習一下,咱們把var job = 'outter teacher'改成let job = 'outter teacher'後,showJob()會輸出什麼?
//答案是undefined。在前一篇中也提到過,ES6語法聲明的變量是不會掛載在全局對象上的~
複製代碼

Array.from()

Array.from方法用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(對象包括ES6新增的數據結構Set和Map)。

// 類數組轉化成數組
let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
}

// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
複製代碼

Array.of()

Array.of() // 方法用於將一組值,轉換爲數組。
let arr = Array.of(2, 3, 'reng');
console.log(arr); // [2, 3, 'reng']
console.log(arr.pop()); // reng
Array.of基本上能夠彌補Array()或new Array()帶來的由於參數個數致使的不一樣行爲。Array.of基本上能夠替代它們兩個了。
Array.of(); // []
Array.of('reng'); // ['reng']
Array.of(2, 'reng'); // [2, 'reng']
複製代碼

數組中還有其它有用的方法:

copyWithin(target, start = 0, end = this.length): 拷貝指定數組的範圍值
find(fn): 用於查找第一個符合條件的數組成員,沒有返回undefined
findIndex(fn): 用於查找第一個符合條件的數組成員的位置,沒有返回-1
entries(): 對鍵值對的遍歷
keys(): 對鍵的遍歷
values(): 對值的遍歷
includes(el): 返回一個布爾值,表示某個數組是否包含給定的值,與字符串的include(el)方法類似
flat(num): 將嵌套的數組拉平,num是遍歷的深度 數組扁平化
[1, [2, [3]]].flat(Infinity);
// [1, 2, 3]
複製代碼
//有這麼一個需求:將數組[[2, 8], [2], [[4, 6], 7, 6]]轉成一維且元素不重複的數組。
// 咱們的實現方案以下:
let arr = [[2, 8], [2], [[4, 6], 7, 6]];
console.log([...new Set(arr.flat(Infinity))]); // [2, 8, 4, 6, 7]
複製代碼

對象擴展

屬性名錶達式

ES6容許字面量定義對象時,把表達式放在方括號內:

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world',
  ['end'+'symbol']: '!' 
};

a['first word'] // 'hello'
a[lastWord] // 'world'
a['last word'] // 'world'
a['endsymbol'] // '!'
複製代碼

對象的擴展運算符 上面整理數組擴展內容的時候,提到了數組的擴展運算符。ES2018將這個運算符引入了對象~

let z = { a: 3, b: 4 };
let n = { ...z }; // 關鍵點
n // { a: 3, b: 4 }
複製代碼

對象中某些新增的方法

Object.is(arg1, arg2): 比較兩個值是否嚴格相等,與===行爲基本一致
Object.assign(target, source1, ...): 用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。屬於淺拷貝
Object.keys(obj): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名
Object.values(obj): 方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。
Object.entries(obj): 方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
複製代碼

Set和Map數據結構

Set

Set翻譯出來就是集合,有元素惟一性的特色。

在數組去重的場景上頗有用處:

// 去除數組的重複成員
[...new Set(array)]
// 如
console.log([...new Set([2, 2, 3, 2])]); // [2, 3]
複製代碼

須要留意的Set屬性和方法有如下:

size: 返回實例成員的總數
add(value): 添加某個值,返回Set結構自己
delete(value): 刪除某個值,返回一個布爾值,表示刪除是否成功。
has(value): 返回一個布爾值,表示該值是否爲Set的成員
clear(): 清除全部成員,沒有返回值。
key():返回鍵名的遍歷器。
values(): 返回鍵值的遍歷器。
entries(): 返回鍵值對的遍歷器。
forEach(): 使用回調函數遍歷每一個成員
複製代碼

WeakSet WeakSet結構與Set相似,也是有不重複元素的集合。可是它和Set有兩個區別:

WeakSet對象中只能存放對象引用, 不能存放值, 而Set對象均可以.

WeakSet中對象中存儲的對象值都是被弱引用的, 若是沒有其餘的變量或屬性引用這個對象值, 則這個對象值會被當成垃圾回收掉. 正由於這樣, WeakSet 對象是沒法被枚舉的, 沒有辦法拿到它包含的全部元素。

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false, 對象 foo 並無被添加進 ws 中

ws.delete(window); // 從集合中刪除 window 對象
ws.has(window);    // false, window 對象已經被刪除了

ws.clear(); // 清空整個 WeakSet 對象

WeakSet 沒有size屬性,沒有辦法遍歷它的成員。
複製代碼

Map

Map對象保持鍵值對。任何值(對象或者原始值)均可以做爲一個鍵或一個值。

Object和Map的比較: ::: tip 一個Object的鍵只能是字符串或者Symbols,但一個Map的鍵能夠是任意值,包括函數、對象、基本類型。 Map中的鍵值是有序的,而添加到對象中的鍵則不是。所以,當對它進行遍歷時,Map對象是按插入的順序返回鍵值。 Map在涉及頻繁增刪鍵值對的場景下會有些性能優點`。 :::

//若是你須要「鍵值對」的數據結構,Map比Object更合適。
const set = new Set([ // 數組轉換爲map
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
複製代碼

Map擁有的屬性和方法和Set類似,多出了些: set(key, value):set方法設置鍵名key對應的鍵值爲value,而後返回整個 Map 結構。若是key已經有值,則鍵值會被更新,不然就新生成該鍵。 get(key):get方法讀取key對應的鍵值,若是找不到key,返回undefined

WeakMap

WeakMap結構與Map結構相似,也是用於生成鍵值對的集合。可是有兩點區別:
WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。
WeakMap的鍵名所指向的對象,不計入垃圾回收機制。和WeakSet類似啦。
屬性方法啥的跟Map差很少,就是沒有了size和forEach,由於其是不可枚舉的。
複製代碼

Promise對象

Promise是異步編程的一種解決方案,比傳統的解決方案「回調函數和事件」更合理和更強大。

Promise對象有如下兩個特色:

對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。

一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種狀況:從pending變成fulfilled(fulfilled也稱 resolved)和從pending變成rejected。

用法

const promise = new Promise(function(resolve, reject) {
  // ...some code
  
  if(/* 異步操做成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
})
參數resolve和reject是兩個函數,由JavaScript引擎提供,不用本身部署。

Promise實例生成以後,可使用then方法分別指定resolved狀態和rejected狀態的回調函數。
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
複製代碼

咱們來粘貼個簡單例子:

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value); // 100ms後輸出'done'
});
複製代碼

嗯~咱們順道來複習下setTimeout的第四個參數。 var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]); function 是你想要在到期時間(delay毫秒)以後執行的函數。 delay 是可選語法,表示延遲的毫秒數。 param1, ..., paramN 是可選的附加參數,一旦定時器到期,它們會做爲參數傳遞給function 那麼,到這裏你理解了上面的例子爲何在100ms後輸出done了嘛💨

簡單的例子看完了,看下咱們在工做中使用得比較多的請求接口的例子:

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if(this.readyState !== 4) {
        return;
      }
      if(this.status === 200) {
        resolve(this.response); // this.response做爲參數傳給then中的json
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = handler;
    client.responseType = 'json';
    client.setRequestHeader('Accept', 'application.json');
    client.send();
  });
  return promise;
};
getJSON('/post.json').then(function(json) {
  console.log('Contents: '+ json);
}, function(error) {
  console.log('error happen ', error);
});
複製代碼

catch方法

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err)); // promise中任何一個拋出錯誤,都會被最後一個catch捕獲
  
// 等同於
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log('rejected:', err));
複製代碼

finally方法

Promise.prototype.finally()方法(其不接受任何參數)用於指定無論Promise對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。
語法:promise
  .then(result => {···})
  .catch(error => {···})
  .finally(() => {···});
複製代碼

Promise.all

構造函數方法Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。
const p = Promise.all([p1, p2, p3]);
上面代碼中,Promise.all方法接受一個數組做爲參數,p1, p2, p3都是Promise實例。若是不是會調用Promise.resolve方法。
// 生成一個Promise對象的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});
複製代碼

上面代碼中,promises是包含 6 個 Promise 實例的數組,只有這6個實例的狀態都變成fulfilled,或者其中有一個變爲rejected,纔會調用Promise.all方法後面的回調函數。

⚠️注意,若是做爲參數的Promise實例,本身定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()的catch方法。因此使用Promise.all()別手癢在每一個實例promise內添加錯誤捕獲。

一道練手題

需求:使用promise改寫下面的代碼,使得輸出的指望結果是每隔一秒輸出0, 1, 2, 3, 4, 5,其中i < 5條件不能變
for(var i = 0 ; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    },1000)
}
console.log(i);
咱們直接上使用promise改寫的代碼吧~
const tasks = []; // 存放promise對象
for(let i = 0; i < 5; i++){
  tasks.push(new Promise((resolve) => {
    setTimeout(() => {
      console.log(i);
      resolve();
    }, 1000 * i);
  }));
}
Promise.all(tasks).then(() => {
  setTimeout(() => {
    console.log(tasks.length);
  }, 1000);
});
// 每隔一秒輸出 0, 1, 2, 3, 4, 5
複製代碼

async函數

ES2017標準引入了async函數,使得異步操做更加方便。async函數是Generator函數的語法糖。不打算寫Generator函數,感興趣的話能夠看文檔。與Generator返回值(Iterator對象)不一樣,async返回的是一個Promise對象。

用法 async函數返回一個Promise對象,可使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}
getStockPriceByName('goog').then(function(result) {
  console.log(result);
})
複製代碼

再來看幾種狀況加深下印象:

function fun1() {
  console.log('fun1');
  return 'fun1 result';
}
async function test() {
  const result1 = await fun1();
  console.log(result1);
  console.log('end');
}
test();
// 輸出
// 'fun1'
// 'fun1 result'
// 'end'
複製代碼
async function fun2() {
  console.log('fun2');
  return 'fun2 result';
}
async function test() {
  const result2 = await fun2();
  console.log(result2);
  console.log('end');
}
test();
// 輸出
// 'fun2'
// 'fun2 result'
// 'end'
複製代碼

正常狀況下,await命令後面是一個Promise對象,返回該對象的結果。若是不是Promise對象,就直接返回對應的值。

async function fun3() {
  console.log('fun3');
  setTimeout(function() {
    console.log('fun3 async');
    return 'fun3 result';
  }, 1000)
}
async function test() {
  const result3 = await fun3();
  console.log(result3);
  console.log('end');
}
test();
// 輸出
// 'fun3'
// undefined
// 'end'
// 'fun3 async'
複製代碼
async function fun4() {
  console.log('fun4');
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('fun4 async');
      resolve('fun4 result');
    }, 1000);
  })
}
async function test() {
  console.log(result4);
  console.log('fun4 sync');
  console.log('end');
}
test();
// 輸出
// 'fun4'
// 'fun4 async'
// 'fun4 result'
// 'fun4 sync'
// 'end'
複製代碼

模擬sleep

JavaScript一直沒有休眠的語法,可是藉助await命令就可讓程序停頓指定的時間。【await要配合async來實現】

function sleep(interval) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  })
}
// use
async function one2FiveInAsync() {
  for(let i = 1; i <= 5; i++) {
    console.log(i);
    await sleep(1000);
  }
}
one2FiveInAsync();
// 1, 2, 3, 4, 5 每隔一秒輸出數字
複製代碼

一道題

需求:使用async await改寫下面的代碼,使得輸出的指望結果是每隔一秒輸出0, 1, 2, 3, 4, 5,其中i < 5條件不能變。

for(var i = 0 ; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    },1000)
}
console.log(i);

咱們用async await方式來實現:
const sleep = (time) => new Promise((resolve) => {
  setTimeout(resolve, time);
});

(async () => {
  for(var i = 0; i < 5; i++){
    console.log(i);
    await sleep(1000);
  }
  console.log(i);
})();
// 符合條件的輸出 0, 1, 2, 3, 4, 5
複製代碼

比較promise和async

爲何只比較promise和async呢?由於最近用得頻繁,實在的纔是須要的,並且async語法是generator的語法糖。

二者上,async語法寫法上代碼量少,錯誤處理能力佳,並且更有邏輯語義化。

假定某個 DOM 元素上面,部署了一系列的動畫,前一個動畫結束,才能開始後一個。若是當中有一個動畫出錯,就再也不往下執行,返回上一個成功執行的動畫的返回值。

// promise
function chainAnimationsPromise(elem, animations) {

  // 變量ret用來保存上一個動畫的返回值
  let ret = null;

  // 新建一個空的Promise
  let p = Promise.resolve();

  // 使用then方法,添加全部動畫
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // 返回一個部署了錯誤捕捉機制的Promise
  return p.catch(function(e) {
    /* 忽略錯誤,繼續執行 */
  }).then(function() {
    return ret;
  });

}
複製代碼
// async await
async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略錯誤,繼續執行 */
  }
  return ret;
}
複製代碼

類class

在ES6以前,是使用構造函數來模擬類的,如今有了關鍵字class了,甚是開心😄

// 之前
function Person() {}
Person.prototype.sayHello = function(){
  console.log('Hi');
};
// 如今
class Person{
  sayHello(){
    console.log('Hi!');
  }
}
複製代碼

constructor方法

constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法,一個類中必須有construtor方法,若是沒有顯式定義,一個空的constructor方法會默認添加。

class Person{}
// 等同於
class Person{
  constructor(){}
}
複製代碼

construtor方法也就相似構造函數,在執行new的時候,先跑構造函數,再跑到原型對象上。

取值函數(getter)和存值函數(setter) 與ES5同樣,在類的內部可使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。

class MyClass {
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log(`setter: ${ value }`)
  }
}

let inst = new MyClass();

// 設置屬性了。set攔截
inst.prop = 123;
// 'setter: 123'

// 直接輸出,就走get攔截
console.log(inst.prop);
// 'getter'
複製代碼

this的指向

類的方法內部若是含有this,它默認是指向類的實例。可是,必須很是當心,一旦單獨使用該方法,極可能報錯。

class Person{
  constructor(job) {
    this.job = job;
  }
  printJob() {
    console.log(`My job is ${ this.job }`);
  }
  sayHi() {
    console.log(`I love my job -- ${ this.job }.`)
  }
}
const person = new Person('teacher');
person.printJob(); // 'My job is teacher'
const { sayHi } = person;
sayHi(); // 報錯: Uncaught TypeError: Cannot read property 'job' of undefined


//上面的代碼中,sayHi方法單獨使用,this會指向該方法運行時所在的環境(因爲class內部是嚴格模式,因此this實際上指向undefined)。

// 修正上面的錯誤也很簡單,也是咱們在react開發中常用的一種手段:在調用構造函數實例化的時候直接綁定實例(this),修改以下:
class Person{
  constructor(job) {
    this.job = job;
    this.sayHi = this.sayHi.bind(this);
  }
}
複製代碼

繼承

ES6中的繼承經過extends關鍵字實現,比ES5的實現繼承更加清晰和方便了。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color;
  }
}

let cp = new ColorPoint(25, 8, 'green'); // 報錯:Must call super constructor in derived class before accessing 'this' or returning from derived constructor
複製代碼

上面這樣寫,不能繼承構造函數裏面的屬性值和方法。須要在子類的構造函數中加上super關鍵字。改爲下面這樣便可:

// react 就是這麼寫的啊
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調用父類的construtor(x, y),至關於ES5中的call。注意的是,super要放在子類構造函數的第一行
    this.color = color;
  }
}

let cp = new ColorPoint(25, 8, 'green');
複製代碼

module模塊

在ES6以前,社區制定了一些模塊的加載的方案,最主要的有CommonJS和AMD兩種。前者用於服務器,後者用於瀏覽器。

// CommonJS
let { stat, exists, readFile } = require('fs');
複製代碼

ES6在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS和AMD規範,成爲瀏覽器和服務器通用的模塊解決方案。

// ES6模塊
import { stat, exists, readFile } from 'fs';
複製代碼

export命令

export命令用於規定模塊的對外接口 。

**一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。**你能夠理解爲一個命名空間~

想要獲取模塊裏面的變量,你就須要導出export:

// profile.js
const name = 'jia ming';
const sayHi = function() {
  console.log('Hi!');
}

export { name, sayHi };
複製代碼

還有一個export default命令,方便用戶(開發者啦)不用閱讀文檔就能加載模塊(實際上就是輸出一個default變量,而這個變量在import的時候是能夠更改的):

// export-default.js
export default function () {
  console.log('foo');
}
其餘模塊加載該模塊時,import命令能夠爲該匿名函數指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
複製代碼

export 和 export default的區別

export 導出的時候,其餘模塊要引入就得 import {x,x,x,} from xx,一個模塊能夠有多個 export

export default導出的時候,其餘模塊要引入就得 import x from xx,一個模塊只能一個 export default

相關文章
相關標籤/搜索