ES6系列目錄
1 let 和 const命令
2 變量的解構賦值
3 字符串的拓展
4 正則的拓展
5 數值的拓展
6 函數的拓展
7 數組的拓展
8 對象的拓展
9 Symbol
10 Set和Map數據結構
11 Proxy
12 Promise對象
13 Iterator和 for...of循環
14 Generator函數和應用
15 Class語法和繼承
16 Module語法和加載實現
所有整理的文章都收錄到我《Cute-JavaScript》系列文章中,訪問地址:http://js.pingan8787.com
14 Generator函數和應用
14.1 基本概念
Generator
函數是一種異步編程解決方案。原理:Genenrator
函數會返回一個遍歷器對象,依次遍歷 Generator
函數內部的每一個狀態。Generator
函數是一個普通函數,有以下兩個特徵:
function
關鍵字與函數名之間有個星號;函數體內使用
yield
表達式,定義不同狀態;
通過調用 next
方法,將指針移向下一個狀態,直到遇到下一個 yield
表達式(或 return
語句)爲止。簡單理解, Generator
函數分段執行, yield
表達式是暫停執行的標記,而 next
恢復執行。
function * f (){
yield 'hi';
yield 'leo';
return 'ending';
}
let a = f();
a.next(); // {value: 'hi', done : false}
a.next(); // {value: 'leo', done : false}
a.next(); // {value: 'ending', done : true}
a.next(); // {value: undefined, done : false}
14.2 yield表達式
yield
表達式是暫停標誌,遍歷器對象的 next
方法的運行邏輯如下:
遇到
yield
就暫停執行,將這個yield
後的表達式的值,作爲返回對象的value
屬性值。下次調用
next
往下執行,直到遇到下一個yield
。直到函數結束或者
return
爲止,並返回return
語句後面表達式的值,作爲返回對象的value
屬性值。如果該函數沒有
return
語句,則返回對象的value
爲undefined
。
注意:
yield
只能用在Generator
函數裏使用,其他地方使用會報錯。
// 錯誤1
(function(){
yiled 1; // SyntaxError: Unexpected number
})()
// 錯誤2 forEach參數是個普通函數
let a = [1, [[2, 3], 4], [5, 6]];
let f = function * (i){
i.forEach(function(m){
if(typeof m !== 'number'){
yield * f (m);
}else{
yield m;
}
})
}
for (let k of f(a)){
console.log(k)
}
yield
表達式如果用於另一個表達式之中,必須放在圓括號內。
function * a (){
console.log('a' + yield); // SyntaxErro
console.log('a' + yield 123); // SyntaxErro
console.log('a' + (yield)); // ok
console.log('a' + (yield 123)); // ok
}
yield
表達式用做函數參數或放在表達式右邊,可以不加括號。
function * a (){
f(yield 'a', yield 'b'); // ok
lei i = yield; // ok
}
14.3 next方法
yield
本身沒有返回值,或者是總返回 undefined
, next
方法可帶一個參數,作爲上一個 yield
表達式的返回值。
function * f (){
for (let k = 0; true; k++){
let a = yield k;
if(a){k = -1};
}
}
let g =f();
g.next(); // {value: 0, done: false}
g.next(); // {value: 1, done: false}
g.next(true); // {value: 0, done: false}
這一特點,可以讓 Generator
函數開始執行之後,可以從外部向內部注入不同值,從而調整函數行爲。
function * f(x){
let y = 2 * (yield (x+1));
let z = yield (y/3);
return (x + y + z);
}
let a = f(5);
a.next(); // {value : 6 ,done : false}
a.next(); // {value : NaN ,done : false}
a.next(); // {value : NaN ,done : true}
// NaN因爲yeild返回的是對象 和數字計算會NaN
let b = f(5);
b.next(); // {value : 6 ,done : false}
b.next(12); // {value : 8 ,done : false}
b.next(13); // {value : 42 ,done : false}
// x 5 y 24 z 13
14.4 for...of循環
for...of
循環會自動遍歷,不用調用 next
方法,需要注意的是, for...of
遇到 next
返回值的 done
屬性爲 true
就會終止, return
返回的不包括在 for...of
循環中。
function * f(){
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for (let k of f()){
console.log(k);
}
// 1 2 3 4 沒有 5
14.5 Generator.prototype.throw()
throw
方法用來向函數外拋出錯誤,並且在Generator函數體內捕獲。
let f = function * (){
try { yield }
catch (e) { console.log('內部捕獲', e) }
}
let a = f();
a.next();
try{
a.throw('a');
a.throw('b');
}catch(e){
console.log('外部捕獲',e);
}
// 內部捕獲 a
// 外部捕獲 b
14.6 Generator.prototype.return()
return
方法用來返回給定的值,並結束遍歷Generator函數,如果 return
方法沒有參數,則返回值的 value
屬性爲 undefined
。
function * f(){
yield 1;
yield 2;
yield 3;
}
let g = f();
g.next(); // {value : 1, done : false}
g.return('leo'); // {value : 'leo', done " true}
g.next(); // {value : undefined, done : true}
14.7 next()/throw()/return()共同點
相同點就是都是用來恢復Generator函數的執行,並且使用不同語句替換 yield
表達式。
next()
將yield
表達式替換成一個值。
let f = function * (x,y){
let r = yield x + y;
return r;
}
let g = f(1, 2);
g.next(); // {value : 3, done : false}
g.next(1); // {value : 1, done : true}
// 相當於把 let r = yield x + y;
// 替換成 let r = 1;
throw()
將yield
表達式替換成一個throw
語句。
g.throw(new Error('報錯')); // Uncaught Error:報錯
// 相當於將 let r = yield x + y
// 替換成 let r = throw(new Error('報錯'));
next()
將yield
表達式替換成一個return
語句。
g.return(2); // {value: 2, done: true}
// 相當於將 let r = yield x + y
// 替換成 let r = return 2;
14.8 yield* 表達式
用於在一個Generator中執行另一個Generator函數,如果沒有使用 yield*
會沒有效果。
function * a(){
yield 1;
yield 2;
}
function * b(){
yield 3;
yield * a();
yield 4;
}
// 等同於
function * b(){
yield 3;
yield 1;
yield 2;
yield 4;
}
for(let k of b()){console.log(k)}
// 3
// 1
// 2
// 4
14.9 應用場景
控制流管理
// 使用前
f1(function(v1){
f2(function(v2){
f3(function(v3){
// ... more and more
})
})
})
// 使用Promise
Promise.resolve(f1)
.then(f2)
.then(f3)
.then(function(v4){
// ...
},function (err){
// ...
}).done();
// 使用Generator
function * f (v1){
try{
let v2 = yield f1(v1);
let v3 = yield f1(v2);
let v4 = yield f1(v3);
// ...
}catch(err){
// console.log(err)
}
}
function g (task){
let obj = task.next(task.value);
// 如果Generator函數未結束,就繼續調用
if(!obj.done){
task.value = obj.value;
g(task);
}
}
g( f(initValue) );
異步編程的使用 在真實的異步任務封裝的情況:
let fetch = require('node-fetch');
function * f(){
let url = 'http://www.baidu.com';
let res = yield fetch(url);
console.log(res.bio);
}
// 執行該函數
let g = f();
let result = g.next();
// 由於fetch返回的是Promise對象,所以用then
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
15 Class語法和繼承
15.1 介紹
ES6中的 class
可以看作只是一個語法糖,絕大部分功能都可以用ES5實現,並且,類和模塊的內部,默認就是嚴格模式,所以不需要使用use strict指定運行模式。
// ES5
function P (x,y){
this.x = x;
this.y = y;
}
P.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var a = new P(1, 2);
// ES6
class P {
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return '(' + this.x + ', ' + this.y + ')';
}
}
let a = new P(1, 2);
值得注意:
ES6的類的所有方法都是定義在 prototype
屬性上,調用類的實例的方法,其實就是調用原型上的方法。
class P {
constructor(){ ... }
toString(){ ... }
toNumber(){ ... }
}
// 等同於
P.prototyoe = {
constructor(){ ... },
toString(){ ... },
toNumber(){ ... }
}
let a = new P();
a.constructor === P.prototype.constructor; // true
類的屬性名可以使用表達式:
let name = 'leo';
class P {
constructor (){ ... }
[name](){ ... }
}
Class不存在變量提升: ES6中的類不存在變量提升,與ES5完全不同:
new P (); // ReferenceError
class P{...};
Class的name屬性:name
屬性總是返回緊跟在 class
後的類名。
class P {}
P.name; // 'P'
15.2 constructor()方法
constructor()
是類的默認方法,通過 new
實例化時自動調用執行,一個類必須有 constructor()
方法,否則一個空的 constructor()
會默認添加。constructor()
方法默認返回實例對象(即 this
)。
class P { ... }
// 等同於
class P {
constructor(){ ... }
}
15.3 類的實例對象
與ES5一樣,ES6的類必須使用 new
命令實例化,否則報錯。
class P { ... }
let a = P (1,2); // 報錯
let b = new P(1, 2); // 正確
與 ES5 一樣,實例的屬性除非顯式定義在其本身(即定義在 this
對象上),否則都是定義在原型上(即定義在 class
上)。
class P {
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
// toString是原型對象的屬性(因爲定義在Point類上)
15.4 Class表達式
與函數一樣,類也可以使用表達式來定義,使用表達式來作爲類的名字,而 class
後跟的名字,用來指代當前類,只能再Class內部使用。
let a = class P{
get(){
return P.name;
}
}
let b = new a();
b.get(); // P
P.name; // ReferenceError: P is not defined
如果類的內部沒用到的話,可以省略 P
,也就是可以寫成下面的形式。
let a = class { ... }
15.5 私有方法和私有屬性
由於ES6不提供,只能變通來實現:
1.使用命名加以區別,如變量名前添加
_
,但是不保險,外面也可以調用到。
class P {
// 公有方法
f1 (x) {
this._x(x);
}
// 私有方法
_x (x){
return this.y = x;
}
}
2.將私有方法移除模塊,再在類內部調用
call
方法。
class P {
f1 (x){
f2.call(this, x);
}
}
function f2 (x){
return this.y = x;
}
3.使用
Symbol
爲私有方法命名。
const a1 = Symbol('a1');
const a2 = Symbol('a2');
export default class P{
// 公有方法
f1 (x){
this[a1](x);
}
// 私有方法
[a1](x){
return this[a2] = x;
}
}
15.6 this指向問題
類內部方法的 this
默認指向類的實例,但單獨使用該方法可能報錯,因爲 this
指向的問題。
class P{
leoDo(thing = 'any'){
this.print(`Leo do ${thing}`)
}
print(text){
console.log(text);
}
}
let a = new P();
let { leoDo } = a;
leoDo(); // TypeError: Cannot read property 'print' of undefined
// 問題出在 單獨使用leoDo時,this指向調用的環境,
// 但是leoDo中的this是指向P類的實例,所以報錯
解決方法:
1.在類裏面綁定
this
class P {
constructor(){
this.name = this.name.bind(this);
}
}
2.使用箭頭函數
class P{
constructor(){
this.name = (name = 'leo' )=>{
this.print(`my name is ${name}`)
}
}
}
15.7 Class的getter和setter
使用 get
和 set
關鍵詞對屬性設置取值函數和存值函數,攔截屬性的存取行爲。
class P {
constructor (){ ... }
get f (){
return 'getter';
}
set f (val) {
console.log('setter: ' + val);
}
}
let a = new P();
a.f = 100; // setter : 100
a.f; // getter
15.8 Class的generator方法
只要在方法之前加個( *
)即可。
class P {
constructor (...args){
this.args = args;
}
*[Symbol.iterator](){
for (let arg of this.args){
yield arg;
}
}
}
for (let k of new P('aa', 'bb')){
console.log(k);
}
// 'aa'
// 'bb'
15.9 Class的靜態方法
由於類相當於實例的原型,所有類中定義的方法都會被實例繼承,若不想被繼承,只要加上 static
關鍵字,只能通過類來調用,即“靜態方法”。
class P (){
static f1 (){ return 'aaa' };
}
P.f1(); // 'aa'
let a = new P();
a.f1(); // TypeError: a.f1 is not a function
如果靜態方法包含 this
關鍵字,則 this
指向類,而不是實例。
class P {
static f1 (){
this.f2();
}
static f2 (){
console.log('aaa');
}
f2(){
console.log('bbb');
}
}
P.f2(); // 'aaa'
並且靜態方法可以被子類繼承,或者 super
對象中調用。
class P{
static f1(){ return 'leo' };
}
class Q extends P { ... };
Q.f1(); // 'leo'
class R extends P {
static f2(){
return super.f1() + ',too';
}
}
R.f2(); // 'leo , too'
15.10 Class的靜態屬性和實例屬性
ES6中明確規定,Class內部只有靜態方法沒有靜態屬性,所以只能通過下面實現。
// 正確寫法
class P {}
P.a1 = 1;
P.a1; // 1
// 無效寫法
class P {
a1: 2, // 無效
static a1 : 2, // 無效
}
P.a1; // undefined
新提案來規定實例屬性和靜態屬性的新寫法
1.類的實例屬性
class P {
prop = 100; // prop爲P的實例屬性 可直接讀取
constructor(){
console.log(this.prop); // 100
}
}
有了新寫法後,就可以不再 contructor
方法裏定義。constructor
裏面已經定義的實例屬性,新寫法允許直接列出。
// 之前寫法:
class RouctCounter extends React.Component {
constructor(prop){
super(prop);
this.state = {
count : 0
}
}
}
// 新寫法
class RouctCounter extends React.Component {
state;
constructor(prop){
super(prop);
this.state = {
count : 0
}
}
}
2.類的靜態屬性
static
關鍵字就可以。
class P {
static prop = 100;
constructor(){console.log(this.prop)}; // 100
}
新寫法方便靜態屬性的表達。
// old
class P { .... }
P.a = 1;
// new
class P {
static a = 1;
}
15.11 Class的繼承
主要通過 extends
關鍵字實現,繼承父類的所有屬性和方法,通過 super
關鍵字來新建父類構造函數的 this
對象。
class P { ... }
class Q extends P { ... }
class P {
constructor(x, y){
// ...
}
f1 (){ ... }
}
class Q extends P {
constructor(a, b, c){
super(x, y); // 調用父類 constructor(x, y)
this.color = color ;
}
f2 (){
return this.color + ' ' + super.f1();
// 調用父類的f1()方法
}
}
子類必須在 constructor()
調用 super()
否則報錯,並且只有 super
方法才能調用父類實例,還有就是,父類的靜態方法,子類也可以繼承到。
class P {
constructor(x, y){
this.x = x;
this.y = y;
}
static fun(){
console.log('hello leo')
}
}
// 關鍵點1 調用super
class Q extends P {
constructor(){ ... }
}
let a = new Q(); // ReferenceError 因爲Q沒有調用super
// 關鍵點2 調用super
class R extends P {
constructor (x, y. z){
this.z = z; // ReferenceError 沒調用super不能使用
super(x, y);
this.z = z; // 正確
}
}
// 關鍵點3 子類繼承父類靜態方法
R.hello(); // 'hello leo'
super關鍵字:
1.當函數調用,代表父類的構造函數,但必須執行一次。
class P {... };
class R extends P {
constructor(){
super();
}
}
2.當對象調用,指向原型對象,在靜態方法中指向父類。
class P {
f (){ return 2 };
}
class R extends P {
constructor (){
super();
console.log(super.f()); // 2
}
}
let a = new R()
注意: super
指向父類原型對象,所以定義在父類實例的方法和屬性,是無法通過 super
調用的,但是通過調用 super
方法可以把內部 this
指向當前實例,就可以訪問到。
class P {
constructor(){
this.a = 1;
}
print(){
console.log(this.a);
}
}
class R extends P {
get f (){
return super.a;
}
}
let b = new R();
b.a; // undefined 因爲a是父類P實例的屬性
// 先調用super就可以訪問
class Q extends P {
constructor(){
super(); // 將內部this指向當前實例
return super.a;
}
}
let c = new Q();
c.a; // 1
// 情況3
class J extends P {
constructor(){
super();
this.a = 3;
}
g(){
super.print();
}
}
let c = new J();
c.g(); // 3 由於執行了super()後 this指向當前實例
16 Module語法和加載實現
16.1 介紹
ES6之前用於JavaScript的模塊加載方案,是一些社區提供的,主要有 CommonJS
和 AMD
兩種,前者用於服務器,後者用於瀏覽器。export
命令對外暴露接口,使用 import
命令輸入其他模塊暴露的接口。
// CommonJS模塊
let { stat, exists, readFire } = require('fs');
// ES6模塊
import { stat, exists, readFire } = from 'fs';
16.2 嚴格模式
ES6模塊自動採用嚴格模式,無論模塊頭部是否有 "use strict"
。嚴格模式有以下限制:
變量必須聲明後再使用
函數的參數不能有同名屬性,否則報錯
不能使用
with
語句不能對只讀屬性賦值,否則報錯
不能使用前綴 0 表示八進制數,否則報錯
不能刪除不可刪除的屬性,否則報錯
不能刪除變量
deleteprop
,會報錯,只能刪除屬性delete*global[prop]
eval
不會在它的外層作用域引入變量eval
和arguments
不能被重新賦值arguments
不會自動反映函數參數的變化不能使用
arguments.callee
不能使用
arguments.caller
禁止
this
指向全局對象不能使用
fn.caller
和fn.arguments
獲取函數調用的堆棧增加了保留字(比如
protected
、static
和interface
)
特別是,ES6中頂層 this
指向 undefined
,即不應該在頂層代碼使用 this
。
16.3 export命令
使用 export
向模塊外暴露接口,可以是方法,也可以是變量。
// 1. 變量
export let a = 'leo';
export let b = 100;
// 還可以
let a = 'leo';
let b = 100;
export {a, b};
// 2. 方法
export function f(a,b){
return a*b;
}
// 還可以
function f1 (){ ... }
function f2 (){ ... }
export {
a1 as f1,
a2 as f2
}
可以使用 as
重命名函數的對外接口。特別注意:export
暴露的必須是接口,不能是值。
// 錯誤
export 1; // 報錯
let a = 1;
export a; // 報錯
// 正確
export let a = 1; // 正確
let a = 1;
export {a}; // 正確
let a = 1;
export { a as b}; // 正確
暴露方法也是一樣:
// 錯誤
function f(){...};
export f;
// 正確
export function f () {...};
function f(){...};
export {f};
16.4 import命令
加載 export
暴露的接口,輸出爲變量。
import { a, b } from '/a.js';
function f(){
return a + b;
}
import
後大括號指定變量名,需要與 export
的模塊暴露的名稱一致。as
爲輸入的變量重命名。
import { a as leo } from './a.js';
import
不能直接修改輸入變量的值,因爲輸入變量只讀只是個接口,但是如果是個對象,可以修改它的屬性。
// 錯誤
import {a} from './f.js';
a = {}; // 報錯
// 正確
a.foo = 'leo'; // 不報錯
import
命令具有提升效果,會提升到整個模塊頭部最先執行,且多次執行相同 import
只會執行一次。
16.5 模塊的整體加載
當一個模塊暴露多個方法和變量,引用時可以用 *
整體加載。
// a.js
export function f(){...}
export function g(){...}
// b.js
import * as obj from '/a.js';
console.log(obj.f());
console.log(obj.g());
但是,不允許運行時改變:
import * as obj from '/a.js';
// 不允許
obj.a = 'leo';
obj.b = function(){...};
16.6 export default 命令
使用 exportdefault
命令,爲模塊指定默認輸出,引用的時候直接指定任意名稱即可。
// a.js
export default function(){console.log('leo')};
// b.js
import leo from './a.js';
leo(); // 'leo'
exportdefault
暴露有函數名的函數時,在調用時相當於匿名函數。
// a.js
export default function f(){console.log('leo')};
// 或者
function f(){console.log('leo')};
export default f;
// b.js
import leo from './a.js';
exportdefault
其實是輸出一個名字叫 default
的變量,所以後面不能跟變量賦值語句。
// 正確
export let a= 1;
let a = 1;
export default a;
// 錯誤
export default let a = 1;
exportdefault
命令的本質是將後面的值,賦給 default
變量,所以可以直接將一個值寫在 exportdefault
之後。
// 正確
export detault 1;
// 錯誤
export 1;
16.7 export 和 import 複合寫法
常常在先輸入後輸出同一個模塊使用,即轉發接口,將兩者寫在一起。
export {a, b} from './leo.js';
// 理解爲
import {a, b} from './leo.js';
export {a, b}
常見的寫法還有:
// 接口改名
export { a as b} from './leo.js';
// 整體輸出
export * from './leo.js';
// 默認接口改名
export { default as a } from './leo.js';
常常用在模塊繼承。
16.8 瀏覽器中的加載規則
ES6中,可以在瀏覽器使用 <script>
標籤,需要加入 type="module"
屬性,並且這些都是異步加載,避免瀏覽器阻塞,即等到整個頁面渲染完,再執行模塊腳本,等同於打開了 <script>
標籤的 defer
屬性。
<script type="module" src="./a.js"></script>
另外,ES6模塊也可以內嵌到網頁,語法與外部加載腳本一致:
<script type="module">
import a from './a.js';
</script>
注意點:
代碼是在模塊作用域之中運行,而不是在全局作用域運行。模塊內部的頂層變量,外部不可見。
模塊腳本自動採用嚴格模式,不管有沒有聲明
usestrict
。模塊之中,可以使用
import
命令加載其他模塊(.js
後綴不可省略,需要提供絕對UR
L 或相對UR
L),也可以使用export
命令輸出對外接口。模塊之中,頂層的
this
關鍵字返回undefined
,而不是指向window
。也就是說,在模塊頂層使用this
關鍵字,是無意義的。同一個模塊如果加載多次,將只執行一次。