ES2015學習筆記

ECMA6學習筆記

參考資料javascript

ECMAScript6入門:http://es6.ruanyifeng.com/html

官方文檔:https://babeljs.io/learn-es2015/java

開發軟件:WebStorm 開源地址:https://coding.net/u/chenxygx/p/CodeSave/git/tree/master/ECMAScript2015node

npm installreact

Settings - Keymap : Main menu - Code - Reformat Code (配置格式化文件)webpack

babel軟件須要WebStorm配置一下git

須要全局安裝 babel,es6

npm install babel-preset-env --save-dev
npm install --save-dev babel-cli
npm install -g babel-cli
npm install --save-dev babel-plugin-transform-es2015-modules-commonjs

而後須要添加.babelrc文件,用來控制生成es2015github

{
"presets": ["env"]
}

而後package.json添加build,script用來控制編譯目錄web

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel Script --watch --out-dir lib"
},

ES6聲明變量的6種方式

ES5只有兩種聲明變量方式:var命令 和 function 命令。

ES6還有四種聲明變量方式:let命令 、Const命令、import命令、class命令

let命令

用來聲明變量,相似於var,聲明的變量只在代碼快({}表示代碼塊)內有效。而且不會受到變量提高的影響。

若是區塊中存在let和const命令,就會造成封閉做用域,在聲明以前使用變量就會報錯。這種行爲稱爲:暫時性死區

var tmp = 123;
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

let不容許重複聲明,不能在函數內部從新聲明參數。有塊級做用域,就再也不須要 當即執行函數表達式(IIFE)了

Const命令

const聲明一個只讀的常量,一旦聲明,常量就不能改變。必須在聲明的同時,進行初始化。

const做用域和let命令相同。只在聲明所在塊級做用域內有效。

const變量也不會提高,一樣存在 暫時性死區

const本質上是指變量指向的內存地址不得改動。但對象和數組是能夠進行變更的。

const x = {};
x.prop = 1;
x = {};

上面代碼,能夠對x進行添加屬性,但不能從新進行賦值改變地址。

若是想讓對象或數組徹底凍結,可使用object.freeze方法。

const x = Object.freeze({ prop : 2 });
x.prop = 1;
console.log(x.prop); // 2

頂級對象

頂級對象在瀏覽器環境指 window對象,在node指的是global對象。

由於頂級對象在各類實現中不統一,通常使用this變量,可是會有一些侷限性。

全局環境中,this會返回頂層對象。可是node模塊和ES2015模塊中,this返回的是當前模塊。

函數裏的this,若是不是做爲對象運行,而是單純的函數,this會指向頂層對象。

針對this指向,能夠查看javascript知識點記錄

綜上所述,能夠在兩種狀況下都獲取頂層對象的方法有兩種

// 方法一
!(function () {
    (typeof window !== 'undefined'
        ? window
        : (typeof process === 'object' &&
        typeof require === 'function' &&
        typeof global === 'object')
            ? global
            : this);
    this.a = 1;
})()
console.log(a);

// 方法二
var getGlobal = function () {
    if (typeof self !== 'undefined') { return self; }
    if (typeof window !== 'undefined') { return window; }
    if (typeof global !== 'undefined') { return global; }
    throw new Error('unable to locate global object');
};
getGlobal().b = 2;
console.log(b);

數組的解構賦值 

ES6容許按照必定模式,從數組和對象中提取值,對變量進行賦值,被稱爲解構。

let [a,b,c] = [1,2,3]; console.log(a+b+c); // 6

上面代碼表示,能夠從數組中提取值,按照對應次序位置,對變量進行賦值。

若是解構不成功,變量的值就等於 undefined

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

對象的解構賦值 

對象的解構變量必須與屬性同名,才能夠取到值。次序不一致是沒有影響的,如取不到值返回undefined

let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

若是但願變量名與屬性名不一致,必須寫成下面這樣。此時foo 和 bar是匹配模式,f是變量。

let { foo: f, bar: b } = { foo: "aaa", bar: "bbb" };

採用解構的寫法,變量不能從新聲明,因此若是有賦值的變量從新聲明就會報錯。

解構也能夠用於嵌套結構的對象。此時p是模式不會賦值

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

對於結構嵌套對象,也是一樣的操做。

var node = {
    loc: {
        start: {
            line: 1,
            column: 5
        }
    }
};
let { loc:{start:{line:l,column:c}} } = node;
console.log(l + c);
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
obj // {prop:123}
arr // [true]

對象的解構也能夠設定默認值,使用 = 能夠在目標值的屬性等於undefined的時候,進行賦初始化值

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x:y = 3} = {};
y // 3

var {x:y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

若是要使用一個已經聲明的變量,須要將內容嵌套在一個大括號裏,告訴js不當作代碼段處理

let x;
({x} = {x: 1});

字符串的解構賦值

字符串也能夠進行解構,將字符串拆分紅數組對象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
let {length : len} = 'hello';
len // 5

數值和布爾值的解構賦值

若是等號右邊是數值或布爾值,則會轉換爲對象。也就是說賦值的變量與等號右邊類型相同。

賦值的規則是隻要右邊不是對象和數組,就會轉換成對象。undefined和null是沒法進行賦值的。

let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

函數參數的解構賦值

函數的參數也能夠進行解構賦值,而且可使用默認值。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

可是注意,若是

move({x, y} = { x: 0, y: 0 })

則不會給參數賦默認值,由於上面代碼是給函數的參數給默認值,而不是給變量賦默認值。

move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]

undefined也會觸發函數參數的默認值。

[1, undefined, 3].map((x = 'yes') => x); // [ 1, 'yes', 3 ]

圓括號問題

ES6的規則是,只要有可能致使解構的歧義,就不得使用圓括號。

可使用圓括號的只有一種場景:賦值語句的非模式部分。

[(b)] = [3]; // 正確 ({ p: (d) } = {}); // 正確 [(parseInt.prop)] = [3]; // 正確

實際用途

1. 交換兩個變量的值

let [x,y] = [1,2];
[x,y] = [y,x];
console.log(x+","+y); // 2,1

2. 從函數返回多個值

// 返回數組
function example() {
    return [1,2,3];
}
let [a,b,c] = example(); // 1,2,3

// 返回對象
function exampleObj(){
    return{
        foo:1,
        bar:2
    }
}
let{foo,bar} = exampleObj(); // 1,2

3. 函數參數的定義,能夠將一組參數與變量名對應起來。

// 參數數組
function  f([a,b,c]) {
    console.log(a+","+b+","+c);
}
f([1,2,3]);

// 參數對象
function ff({a,b,c}){
    console.log(a+","+b+","+c);
}
ff({a:1,b:2,c:3});

4. 提取JSON數據

let jsonData = {
    "Name":"A",
    "Old":12
}
let {Name,Old} = jsonData;
console.log(Name+Old);

5. 函數參數的默認值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

6. 遍歷Map結構

var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
//僅獲取鍵 let[key]
//僅獲取值 let[,value]

7. 輸入模塊的指定方法

const { SourceMapConsumer, SourceNode } = require("source-map");

字符串的Unicode表示法

Js容許使用\uxxxxx形式表示一個字符,其中xxx表示字符的Unicode碼點

單一碼點只能在\u0000-\uffff之間。若是超過這個範圍須要使用兩個字符。

ES6對此進行了改進,只要將碼點放進大括號,就能正確解讀該字符。

"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

String.codePointAt() 會返回字符串的碼點。

String.fromCodePoint() 會從碼點返回字符。

字符串的遍歷器接口

ES6爲字符串添加了for...of循環遍歷

for(let codePoint of 'hello')
{
console.log(codePoint);
}

for...of循環,能夠識別大於0xFFFF的碼點。也就是能夠識別漢字

includes('') //返回布爾值,表示是否找到了參數字符串

startsWith('') //返回布爾值,表示是否在字符串開頭

endsWith('')  //返回布爾值,表示是否在字符串結尾

'hello'.repeat(2) //返回一個新字符串,表示將原字符串重複n次,若是是小數則取整,負數會報錯

'x'.padStart(2,'x') //若是某個字符串不足兩位長度,就在頭補全

'x'.padEnd() //尾部補全

模板字符串

模板字符串是加強版的字符串,使用 ` 反引號標識。用來定義多行字符串,或者在字符串中嵌入變量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入變量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());

若是使用模板字符串表示多行字符串,全部的空格和縮進都會被保留在輸出中。若是不想保留,能夠在末尾使用trim()刪除

模板字符串中嵌套變量,可使用 ${變量名},括號內能夠聽任何計算和表達式,還能夠調用函數

正則的擴展

http://es6.ruanyifeng.com/#docs/regex

數值的擴展

http://es6.ruanyifeng.com/#docs/number

函數的擴展

ES6容許爲函數的參數設置默認值,直接寫在參數定義的後面使用=號

function log(x, y = 'World') { console.log(x, y); }

參數的變量都是默認聲明的,因此不能使用let 或 const再次聲明。

若是參數默認值是變量,則每次都從新計算默認表達式的值。

函數默認值必須在函數的尾部,非尾部定義是沒有辦法省略的。

做用域

一旦設置了參數的默認值,參數會造成一個單獨的做用域。

等初始化結束之後,這個做用域就會消失。

var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

rest參數

ES6引用rest參數,用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。

rest參數是一個數組變量,該變量將多餘的數組存放進去。

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10

擴展運算符

擴展運算符是三個點(...),將一個數組轉爲用逗號分隔的參數序列。

console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5

擴展運算符能夠展開數組,也就是說不須要apply方法了。

// ES5的寫法 Math.max.apply(null, [14, 3, 77])  // ES6的寫法 Math.max(...[14, 3, 77])

在看一個把一個數組添加到另外一個數組尾部的例子

// ES5的寫法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; Array.prototype.push.apply(arr1, arr2);  // ES6的寫法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; arr1.push(...arr2);

擴展運算符的應用

1. 合併數組

// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]

var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

// ES5的合併數組
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6的合併數組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

2. 與解構賦值結合。若是擴展運算符用於給數組賦值,只能放在參數最後一位

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list

3. 函數的返回值

Javascript函數只能返回一個值,若是須要返回多個,就只能使用數組或對象。

擴展運算符提供瞭解決這個問題的一個變通方法。

var dateFields = readDateFields(database); var d = new Date(...dateFields);

4. 擴展運算符能夠將字符串轉爲數組

[...'hello'] // [ "h", "e", "l", "l", "o" ]

name屬性

函數的 name 屬性,返回該函數的函數名。

function foo() {} foo.name // "foo"

箭頭函數

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

var a = v => console.log(v);
// 等同於
var b = function(v){
    console.log(v);
}
var f = () => 5;
// 等同於
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
  return num1 + num2;
};

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

var sum = (num1, num2) => { return num1 + num2; }

大括號會被解析爲代碼塊,若是箭頭函數直接返回一個對象,則必須在對象外面加上括號

var getTempItem = id => ({ id: id, name: "Temp" });

箭頭函數能夠與變量解構一塊兒使用

const full = ({ first, last }) => first + ' ' + last;  // 等同於 function full(person) { return person.first + ' ' + person.last; }

也可使得表達式更加簡潔一些

const isEven = n => n % 2 == 0; const square = n => n * n;

箭頭函數的一個做用就是簡化回調函數

// 正常函數寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數寫法
[1,2,3].map(x => x * x);

// 正常函數寫法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭頭函數寫法
var result = values.sort((a, b) => a - b);

箭頭函數還能夠與rest結合使用

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]

箭頭函數注意點

1. 函數內的this對象,就是定義時所在的對象,而不是運行時所在的對象

2. 不能夠當作構造函數,不可使用new

3. 不可使用 arguments對象,若是須要可使用rest替代

4. 不可使用 yield 命令

箭頭函數能夠嵌套使用

const plus1 = a => a + 1;
const mult2 = a => a * 2;

mult2(plus1(5))
// 12

箭頭函數可讓this固定化,由於箭頭函數根本沒有本身的this,致使內部的this就是外層代碼塊的this。

也正是由於沒有this,因此不能作爲構造函數。

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代碼中,只有一個this,就是函數foo的this。由於全部的內層函數都是箭頭函數,都沒有本身的this。

屬性簡潔表達式

ES6容許直接寫入變量和函數,做爲對象的屬性和方法

var foo = 'bar'; var baz = {foo}; baz // {foo: "bar"}  // 等同於 var baz = {foo: foo};

屬性表達式

ES6容許字面量定義對象,將變量用做對象的屬性名、方法名。兩個表達式不能同時使用

let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };

方法的name屬性

函數的name屬性,返回函數名。對象方法也是函數,也有name屬性。

Object.is()

Object.is用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)一致。

Object.assign() 

Object.assign 用於將對象合併,將源對象全部可枚舉的屬性,複製到目標對象中。

object.assign 使用的是淺拷貝。

常見途徑

1. 爲對象添加屬性(將x和y拷貝到對象實例上)

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

2. 爲對象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// 等同於下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};

3. 克隆對象

function clone(origin) {
  return Object.assign({}, origin);
}

上面的克隆代碼,只能克隆原始對象值,不能克隆他的繼承值。若是想保持繼承鏈,能夠用下面代碼

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

4. 合併多個對象

const merge = (target, ...sources) => Object.assign(target, ...sources);

若是但願合併成一個空的對象,能夠用下面的代碼

const merge = (...sources) => Object.assign({}, ...sources);

屬性的遍歷

ES6一共有5中方式能夠遍歷對象的屬性

for ... in:循環遍歷自身和繼承的可枚舉屬性(不含Symbol屬性

Object.keys(obj):返回一個數組,包括對象自身的全部可枚舉屬性。(不含繼承、不含Symbol屬性)

Object.getOwnPropertyNames(obj):返回一個數組,包括對象自身的全部屬性,包括不可枚舉的屬性。(不含繼承、不含Symbol屬性)

Object.getOwnPropertySymbols(obj):返回一個數組,包含對象自身的Symbol屬性。

Reflect.ownKeys(obj):返回一個數組,包含對象自身的屬性。(不含繼承、含Symbol屬性)

以上5中遍歷方式,都會按照如下順序。

1. 遍歷全部屬性名爲數值的屬性,按照數字排序

2. 遍歷全部屬性名爲字符串的屬性,按照生成時間排序

3. 遍歷全部屬性名爲Symbol值的屬性,按照生成時間排序

__proto__屬性

用來讀取或設置當前對象的 prototype 對象。

它其實是一個內部屬性,不對外開放。因此儘可能不要使用這個屬性。

__proto__ 和 prototype的區別

prototype是函數的一個屬性。注意是函數,對象中是沒有的。這個屬性指向對象的原型的屬性。

__proto__是一個對象擁有的內置屬性,是JS內部使用尋找原型鏈的屬性。

當咱們須要使用prototype的時候,下面例子進行new時分爲三步。

var Person = function(){};
var p = new Person();

1. var p = {}; 初始化一個對象p

2. p.__proto__ = Person.prototype;

3. Person.call(p) 構造p,初始化p

Object.setPrototypeOf()

與__proto__屬性相同,用來設置一個對象的prototype對象,返回參數對象自己。

這是ES6推薦的設置對象的方法。左側是待設置對象,右側是設置繼承對象。

var obj1 = {
    Name: "C"
};
var obj2 = {
    Old : 12
};
Object.setPrototypeOf(obj2,obj1);
console.log(obj2.Old); //12
console.log(obj2.Name); //C

若是第一個參數不是對象,則會給他自動轉爲對象,但因爲返回的仍是第一個參數,因此不會產生效果。

因爲undefined 和 null 沒法轉爲對象,因此若是當作第一個參數,則會報異常。

Object.getPrototypeOf()

該方法與Object.setPrototypeOf 方法配套,用於讀取一個對象的原型對象。

Object.getPrototypeOf(obj);
let obj1 = { Name: "C" };
let obj2 = { Old: 12 };
let obj3 = () => ({Name: "CC"});
let obj4 = function () {};
Object.setPrototypeOf(obj2, obj1);
Object.setPrototypeOf(obj4, obj3);
console.log(Object.getPrototypeOf(obj4) == obj4.__proto__); //true
console.log(Object.getPrototypeOf(obj2) === obj2.__proto__); //true
console.log(Object.getPrototypeOf(new obj4()) == obj4.prototype); //true

若是參數不是對象,會被自動轉爲對象。若是參數是undefined或null,沒法轉爲對象,會報錯。

Object.keys()

返回一個數組,成員是參數對象自身的鍵名(不含繼承)

let obj1 = {Name: "C"};
let obj2 = {Old: 12, Old2 : 13};
Object.setPrototypeOf(obj2, obj1);
console.log(Object.keys(obj2)); // [ 'Old', 'Old2' ]

Object.values()

返回一個數組,成員是參數對象自身的鍵值(不含繼承)

let obj1 = {Name: "C"};
let obj2 = {Old: 12, Old2 : 13};
Object.setPrototypeOf(obj2, obj1);
console.log(Object.values(obj2)); // [ 12, 13 ]

Object.entries()

返回一個數組,成員是參數對象自身的鍵值對(不含繼承)

let obj1 = {Name: "C"};
let obj2 = {Old: 12, Old2 : 13};
Object.setPrototypeOf(obj2, obj1);
console.log(Object.entries(obj2)); // [ [ 'Old', 12 ], [ 'Old2', 13 ] ]

Object.getOwnPropertyDescriptors()

返回某個對象屬性的描述對象(不含繼承)

let obj1 = {Name: "C"};
let obj2 = {Old: 12, Old2 : 13};
Object.setPrototypeOf(obj2, obj1);
console.log(Object.getOwnPropertyDescriptor(obj2,"Old"));
//{ value: 12,
// writable: true,
//    enumerable: true,
//    configurable: true }

Symbol屬性

ES6引入了一種原始數據類型Symbol,表示獨一無二的值。

Symbol值經過Symbol函數生成。凡是屬性名屬於Symbol類型,都是獨一無二的,能夠保證不會產生衝突。

let s = Symbol(); typeof s // "symbol"

Symbol函數前不能使用new命令,由於生成Symbol是一個原始類型的值,不是對象。

Symbol能夠接受一個字符串做爲參數,用來表示描述或區分。若是Symbol參數是一個對象,則會調用該對象的toString方法。

Symbol不能與其餘類型的值進行運算,可是能夠顯示轉換爲字符串,也能夠轉換Boolean類型,但不能轉爲數值。

做爲屬性名Symbol

因爲每一個Symbol值都是不相等的,用於對象的屬性名,就能保證不會出現同名的屬性。

var mySymbol = Symbol();
// 第一種
var obj = {};
obj[mySymbol] = "C";
//第二種
var obj2 = {
    [mySymbol]:"C2"
}
//第三種
var obj3 = {};
Object.defineProperty(obj3,mySymbol,{value:"C3"});
console.log(obj[mySymbol]);

Symbol做爲屬性名的時候,不能用點運算符。由於點運算符後面老是字符串,不會讀取標示; 

同理,在對象內部使用Symbol定義屬性時,必須放在方括號[]內。若是不放在方括號內,則當作字符串處理。

Symbol還能夠定義一組常量,保證這組常量的值都是不相等的。

log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') }; log(log.levels.DEBUG, 'debug message'); log(log.levels.INFO, 'info message');
const COLOR_RED    = Symbol(); const COLOR_GREEN = Symbol(); ---------------------------------------------- function getComplement(color) { switch (color) { case COLOR_RED: return COLOR_GREEN; case COLOR_GREEN: return COLOR_RED; default: throw new Error('Undefined color'); } }

常量使用Symbol值最大好處是,其餘任何值都不可能有相同的值了。

Symbol值做爲屬性名時,該屬性仍是公開屬性,不是私有屬性。

經常使用實例:消除魔法數

魔法數指,在代碼中屢次出現的字符串或數值。會與代碼進行強耦合,應該儘可能消除魔法數。

若是魔法數的值不重要,只是爲了區分,就可使用Symbol來進行修改。

function getAreaBefore(shape,options)
{
    var area = 0;
    switch(shape)
    {
        case 'Triangle':
            area = .5 * options.width * options.height;
            break;
    }
    return area;
}
console.log(getAreaBefore('Triangle',{width:100,height:100}));

var shareType = {
    Triangle:Symbol()
}
function getAreaAfter(shape,options)
{
    var area = 0;
    switch(shape){
        case shareType.Triangle:
            area = .5 * options.width * options.height;
            break;
    }
    return area;
}
console.log(getAreaAfter(shareType.Triangle,{width:100,height:100}));

屬性名的遍歷

Symbol做爲屬性名,不會出如今 for...in、for...of循環中,也不會在Object.keys、Object.getOwnPropertyNames、Json.stringify中

有一個Object.getOwnPropertySymbols方法,能夠獲取指定對象全部Symbol屬性名。

Reflect.ownKeys方法也能夠返回全部類型的鍵名,包括常規的和Symbol的。

Symbol.for

能夠接受一個字符串做爲參數,而後搜索有沒有以該參數做爲名稱的Symbol值。若是有就返回,沒有就新建。

var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true

Symbol.keyFor

能夠返回一個已登記的Symbol類型值的key。

var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined

經常使用實例:單例模式

單例模式指一個類,任什麼時候候都是返回同一個實例。咱們爲了防止這個類的全局變量修改,可使用Symbol

// mod.js
const FOO_KEY = Symbol.for('foo');
function A() {
    this.foo = 'hello';
}
if (!global[FOO_KEY]) {
    global[FOO_KEY] = new A();
}
module.exports = global[FOO_KEY];
require('./mod.js');
console.log(global[Symbol.for('foo')].foo); //hello

Symbol.hasInstance

當其餘對象使用instanceof運算符時,判斷是否爲該對象的實例時,會調用此方法。

class MyClass{
    [Symbol.hasInstance](foo){
        return foo instanceof Array;
    }
}
var result = [1,2,3] instanceof new MyClass();
console.log(result); //true

Symbol.isConcatSpreadable

布爾值屬性,表示該對象使用Array.prototype.concat() 時,是否能夠展開。默認爲能夠展開

let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']

相似數組的對象也能夠展開,但他的屬性默認值爲false,必須手動打開。

let obj = {length: 2, 0: 'c', 1: 'd'}; ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e'] obj[Symbol.isConcatSpreadable] = true; ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']

對於一個類來講,此屬性必須寫成實例的屬性。

class A1 extends Array {
  constructor(args) {
    super(args);
    this[Symbol.isConcatSpreadable] = true;
  }
}
class A2 extends Array {
  constructor(args) {
    super(args);
    this[Symbol.isConcatSpreadable] = false;
  }
}

Symbol.species

建立實例時,默認會調用這個方法,使用這個屬性返回的函數當作構造函數,來建立新的實例對象。

class MyArray extends Array {
  static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

mapped instanceof MyArray // false
mapped instanceof Array // true

Symbol.match

當執行str.match(myObject)時,若是該屬性存在,會調用它,返回該方法的返回值。

class MyMatcher { [Symbol.match](string) { return 'hello world'.indexOf(string); } } 'e'.match(new MyMatcher()) // 1

Symbol.replace

當該對象被string.prototype.replace方法調用時,返回該方法的返回值。

const x = {}; x[Symbol.replace] = (...s) => console.log(s); 'Hello'.replace(x, 'World') // ["Hello", "World"]

Symbol.search

該對象被String.prototype.search方法調用時,會返回該方法的返回值。

class MySearch { constructor(value) { this.value = value; } [Symbol.search](string) { return string.indexOf(this.value); } } 'foobar'.search(new MySearch('foo')) // 0

Symbol.split

該對象被String.prototype.split方法調用時,會返回該方法的返回值

class MySplitter { constructor(value) { this.value = value; } [Symbol.split](string) { var index = string.indexOf(this.value); if (index === -1) { return string; } return [ string.substr(0, index), string.substr(index + this.value.length) ]; } } 'foobar'.split(new MySplitter('foo')) // ['', 'bar'] 'foobar'.split(new MySplitter('bar')) // ['foo', ''] 'foobar'.split(new MySplitter('baz')) // 'foobar'

Symbol.iterator

指向對象的默認遍歷器方法。對象進行for...of循環時,會調用此方法。

class Collection { *[Symbol.iterator]() { let i = 0; while(this[i] !== undefined) { yield this[i]; ++i; } } } let myCollection = new Collection(); myCollection[0] = 1; myCollection[1] = 2; for(let value of myCollection) { console.log(value); } // 1 // 2

Symbol.toPrimitive

對象的Symbol.toPrimitive屬性,指向一個方法。該對象被轉爲原始類型的值時,會調用這個方法,返回該對象對應的原始類型值。

Symbol.toPrimitive被調用時,會接受一個字符串參數,表示當前運算的模式,一共有三種模式。

1. Number:該場合須要轉成數值

2. String : 該場合須要轉成字符串

3. Default:該場合能夠轉成數值或字符串

let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } } }; 2 * obj // 246 3 + obj // '3default' obj == 'default' // true String(obj) // 'str'

Symbol.toStringTag

在該對象上面調用Object.prototype.toString方法時,若是這個屬性存在,他的返回值會出如今方法返回值中。

// 例一 ({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]"  // 例二 class Collection { get [Symbol.toStringTag]() { return 'xxx'; } } var x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]"

Set

相似於數組,全部成員的值都是惟一的,沒有重複的值。

const s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4

Set有如下屬性

constructor:構造函數,默認就是Set函數

size:返回set實例的成員總數

實例方法分爲兩大類,操做方法、遍歷方法

操做方法:

add(value):添加某個值,返回Set結構自己

delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功

has(value):返回一個布爾值,表示該值是否爲Set成員

clear():清除全部成員,沒有返回值

Array.form:能夠將Set結構轉爲數組

這樣去重複,也能夠寫成以下格式

function dedupe(array) { return Array.from(new Set(array)); } dedupe([1, 1, 2, 3]) // [1, 2, 3]

遍歷操做:

keys():返回鍵名遍歷器

values() :返回鍵值遍歷器

entries() : 返回鍵值對遍歷器

forEach():使用回調函數遍歷每一個成員。參數依次爲鍵值、鍵名、集合自己

let set = new Set([1, 2, 3]); set.forEach((value, key) => console.log(value * 2) )

運用

let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]);  // 並集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4}  // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3}  // 差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}

WeakSet

WeakSet與Set相似,也是不重複的集合,WeakSet不能進行遍歷。與Set有兩個區別

1. WeakSet成員只能是對象,若是是其餘值會報錯。

2. WeakSet中的對象都是弱引用,垃圾回收機制不考慮WeakSet對該對象的引用。

若是其餘對象都不在引用該對象,那麼會直接回收,不會考慮WeakSet。

因爲上述特色,WeakSet成員不適合引用,由於會隨時消失。

const a = [[1, 2], [3, 4]]; const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}

WeakSet結構有三個方法

add(value):添加一個新成員

delete(value):刪除指定成員

has(value):是否包含指定成員

Map

鍵值對的集合,鍵的範圍不限於字符串。提供了值-值對應。

const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false

Map結構的實例有如下屬性和操做方法。

Size:返回Map結構的成員總數

set(key,value):設置鍵名對應的值,而後返回整個Map結構

get(key):讀取key的鍵值,找不到返回undefined

has(key):返回一個布爾值,表示某個鍵是否存在

delete(key):刪除某個鍵,返回true,若是失敗返回false

clear():清除全部成員,沒有返回值

遍歷方法

keys():返回鍵名遍歷器

values() :返回鍵值遍歷器

entries() : 返回鍵值對遍歷器

forEach():使用回調函數遍歷每一個成員。參數依次爲鍵值、鍵名、集合自己

與其餘數據結構轉換

Map轉數組

const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

數組轉Map

new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // }

Map轉對象

function strMapToObj(strMap) { let obj = Object.create(null); for (let [k,v] of strMap) { obj[k] = v; } return obj; } const myMap = new Map() .set('yes', true) .set('no', false); strMapToObj(myMap) // { yes: true, no: false }

對象轉Map

function objToStrMap(obj) { let strMap = new Map(); for (let k of Object.keys(obj)) { strMap.set(k, obj[k]); } return strMap; } objToStrMap({yes: true, no: false}) // Map {"yes" => true, "no" => false}

Map轉JSON

function strMapToJson(strMap) { return JSON.stringify(strMapToObj(strMap)); } let myMap = new Map().set('yes', true).set('no', false); strMapToJson(myMap) // '{"yes":true,"no":false}'

JSON轉Map

function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}

WeakMap

WeakMap結構與Map結構相似,用於生成鍵值對的集合

WeakMap與Map的區別有兩點

1. WeakMap只接受對象做爲鍵名,不接受其餘的值做爲鍵名

2. WeakMap的鍵名所指的對象,不計入垃圾回收機制

Proxy

用來修改某些操做的默認行爲,在目標對象以前架設一層攔截,外界對對象的訪問都必須先經過這層攔截。

ES6原生提供Proxy構造函數,用來生成Proxy實例

var proxy = new Proxy(target, handler);
var obj = new Proxy({},{
   get:function(target,key,receiver){
        console.log(`get ${key}`);
        return Reflect.get(target,key,receiver);
   },
   set:function(target,key,value,receiver){
       console.log(`set ${key}`);
       return Reflect.set(target,key,value,receiver);
   }
});
obj.count = 1; // set count
obj.count++; //get count  set count

Proxy接受兩個參數,

第一個參數是所代理的目標,及若是沒有Proxy介入,原來訪問的對象

第二個參數是一個配置對象,對於每個被代理的操做,須要提供一個對應的處理函數。

能夠將Proxy對象,設置到object.proxy屬性,從而能夠在object對象上調用

var object = { proxy: new Proxy(target, handler) };

proxy實例也能夠做爲其餘對象的原型對象

同一個攔截器函數,能夠設置攔截多個操做。

下面是Proxy支持的攔截操做

get(target,propKey,receiver):攔截對象屬性的讀取,好比proxy.foo和proxy['foo']

set(target,propKey,value,receiver):攔截對象屬性的設置,返回一個布爾值

has(target,propKey):攔截propKey in proxy的操做,返回一個布爾值

用來攔截HasProperty操做,即判斷對象是否具備某個屬性時。典型的操做就是in運算符

var handler = {
  has(target,key){
      if(key[0] === '_'){
          return false;
      }
      return key in target;
  }
};
var target = { _prop:'foo',prop:'foo' };
var proxy = new Proxy(target,handler);
console.log('_prop' in proxy); // false
console.log('prop' in proxy); // true

deleteProperty(target,propKey):攔截delete proxy[propKey]的操做,返回一個布爾值

deleteProperty方法用於攔截delete操做,若是這個方法拋出錯誤或者返回false,當前屬性沒法被delete刪除。

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

ownKeys(target)

攔截Object.getOwnPropertyNames(proxy)\Object.getOwnPropertySymblos(proxy)\Object.keys(proxy)

返回一個數組,該方法返回目標對象全部自身的屬性和屬性名,而Object.keys的返回值結果僅包括目標對象自身的可遍歷屬性

let target = {
  _bar: 'foo',
  _prop: 'bar',
  prop: 'baz'
};

let handler = {
  ownKeys (target) {
    return Reflect.ownKeys(target).filter(key => key[0] !== '_');
  }
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "baz"

getOwnPropertyDescriptor(target,propKey)

攔截Object.getOwnPropertyDescriptor(proxy,propkey),返回屬性的描述對象

var handler = {
  getOwnPropertyDescriptor (target, key) {
    if (key[0] === '_') {
      return;
    }
    return Object.getOwnPropertyDescriptor(target, key);
  }
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }

defineProperty(target,propKey,proDesc)

攔截Object.defineProperty(proxy,propkey,propDesc)/Object.defineProperties(proxy,proDesc)返回一個布爾值

var handler = {
  defineProperty (target, key, descriptor) {
    return false;
  }
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'

preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值

var p = new Proxy({}, {
  preventExtensions: function(target) {
    console.log('called');
    Object.preventExtensions(target);
    return true;
  }
});

Object.preventExtensions(p)
// "called"
// true

getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象

var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(p) === proto // true

isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值

var p = new Proxy({}, {
  isExtensible: function(target) {
    console.log("called");
    return true;
  }
});

Object.isExtensible(p)
// "called"
// true

setPrototypeOf(target,proto):攔截Object.setPrototypeOf(proxy,proto),返回一個布爾值

var handler = {
  setPrototypeOf (target, proto) {
    throw new Error('Changing the prototype is forbidden');
  }
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden

apply(target,object,args):攔截proxy實例做爲函數調用的操做,好比proxy(...args)/proxy.call(object,...args)/proxy.apply(...) 

apply方法攔截函數的調用,call和apply操做。方法能夠接受三個參數,分別是目標對象、上下文對象、參數數組。

var twice = {
  apply(target,ctx,args){
      return Reflect.apply(...arguments) * 2;
      //return Reflect.apply(target,ctx,args) * 2;
  }
};
function sum(left,right){
    return left+right;
};
var proxy = new Proxy(sum,twice);
console.log(proxy(1,2)); // 6
console.log(proxy.apply(null,[1,2])); // 6
console.log(proxy.call(null,1,2)); // 6

construct(target,args):攔截Proxy實例做爲構造函數調用的操做,好比new Proxy(...args)

construct方法能夠接受兩個參數

1. target:目標對象

2. args:構建函數的參數對象

var p = new Proxy(function () {}, {
    construct: function(target, args) {
        console.log('called: ' + args.join(', '));
        return { value: args[0] * 10 };
    }
});

new p(1).value; // called: 1

Proxy.revocable()

Proxy.revocable方法返回一個可取消的Proxy實例

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

this問題

Proxy代理的狀況下,目標對象內部的this關鍵字會指向Proxy代理

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

Reflect

Reflect對象與Proxy對象同樣,也是ES6爲了操做對象而提供的新API

Reflect對象的設計目的有這樣幾個

1. 將Object對象的一些明顯屬於語言內部的方法,放到Reflect對象上。

2. 修改某些Object方法的返回結果,讓其變得更合理。

// 老寫法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

3. 讓Object操做都變成函數行爲。某些Object操做是命令式的,好比name in obj

// 老寫法 'assign' in Object // true // 新寫法 Reflect.has(Object, 'assign') // true

4. Reflect對象的方法與Proxy對象的方法一一對應,主要Proxy對象的方法,Reflect就有

var loggedObj = new Proxy(obj, {
  get(target, name) {
    console.log('get', target, name);
    return Reflect.get(target, name);
  },
  deleteProperty(target, name) {
    console.log('delete' + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log('has' + name);
    return Reflect.has(target, name);
  }
});

靜態方法

Reflect對象一共有13個靜態方法

Reflect.apply(target,thisArg,args)

用於綁定this對象後執行給定函數

const ages = [11, 33, 12, 54, 18, 96];
// 舊寫法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新寫法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

Reflect.construct(target,args)

方法等同於new target(...args),提供了一種不使用new,來調用構造函數的方法

function Greeting(name) {
  this.name = name;
}
// new 的寫法
const instance = new Greeting('張三');
// Reflect.construct 的寫法
const instance = Reflect.construct(Greeting, ['張三']);

Reflect.get(target,name,receiver)

查找並返回target對象的name屬性,若是有讀取函數,則讀取函數的this綁定receiver

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8

Reflect.set(target,name,value,receiver)

設置target對象的name屬性等於value,若是設置了賦值函數,則賦值函數this綁定receiver

var myObject = {
  foo: 4,
  set bar(value) {
    return this.foo = value;
  },
};

var myReceiverObject = {
  foo: 0,
};

Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1

Reflect.defineProperty(target,name,desc)

用來爲對象定義屬性。若是第一個參數不是對象,就會拋出錯誤

function MyDate() {
  /**/
}
// 舊寫法
Object.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});
// 新寫法
Reflect.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

Reflect.deleteProperty(target,name)

等同於delete obj[name],用於刪除對象的屬性。

若是刪除成功,或者刪除屬性不存在,返回true。

刪除失敗,或者被刪除的屬性依然存在,返回false。

const myObj = { foo: 'bar' };
// 舊寫法
delete myObj.foo;
// 新寫法
Reflect.deleteProperty(myObj, 'foo');

Reflect.has(target,name)

Reflect.has方法對應name in obj裏面的in運算符

var myObject = {
  foo: 1,
};
// 舊寫法
'foo' in myObject // true
// 新寫法
Reflect.has(myObject, 'foo') // true

Reflect.ownKeys(target)

方法用於返回對象的全部屬性

var myObject = {
  foo: 1,
  bar: 2,
  [Symbol.for('baz')]: 3,
  [Symbol.for('bing')]: 4,
};

// 舊寫法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']

Object.getOwnPropertySymbols(myObject)
//[Symbol.for('baz'), Symbol.for('bing')]

// 新寫法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol.for('baz'), Symbol.for('bing')]

Reflect.isExtensible(target)

返回布爾值,表示當前對象是否能夠擴展

const myObject = {};
// 舊寫法
Object.isExtensible(myObject) // true
// 新寫法
Reflect.isExtensible(myObject) // tru 

Reflect.preventExtensions(target)

用於讓一個對象變爲不可擴展,返回一個布爾值,表示是否操做成功

var myObject = {};
// 舊寫法
Object.isExtensible(myObject) // true
// 新寫法
Reflect.preventExtensions(myObject) // true

Reflect.getOwnPropertyDescriptor(target, name)

用於獲得指定屬性的描述對象,若是第一個參數不是對象,不報錯返回undefined。

var myObject = {};
Object.defineProperty(myObject, 'hidden', {
  value: true,
  enumerable: false,
});
// 舊寫法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// 新寫法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');

Reflect.getPrototypeOf(target)

方法用於讀取對象的__prop__的屬性,對應Object.getPrototypeOf

const myObj = new FancyThing();
// 舊寫法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新寫法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;

Reflect.setPrototypeOf(target, prototype)

方法用於設置對象的__prop__屬性,返回第一個參數對象

const myObj = new FancyThing();
// 舊寫法
Object.setPrototypeOf(myObj, OtherThing.prototype);
// 新寫法
Reflect.setPrototypeOf(myObj, OtherThing.prototype);

Promise

異步編程的一種解決方案,比回調函數和事件,更合理和強大。ES6將其寫入語言標準,提供了Promise對象

Promise就是一個容器,保存某個將來纔會結束的事件(一般是一個異步操做)的結果。

從語法上來講,Promise是一個對象,能夠獲取異步信息,提供統一API,各類操做均可以用一樣的方法進行處理。

Promise對象有兩個特色

1. 對象的狀態不受外界影響,Promise對象表明一個異步操做,三種狀態:Pending進行中,Resolved已完成,Rejected已失敗

只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。

2. 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能

從Pending改變爲Resolved

從Pending改變爲Rejected

只要這兩種狀況發生了,狀態就凝固了,不會再變了。會一直保持這個結果。

有了Promise對象,能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。提供了統一的接口,操做更方便

Promise缺點有三個

沒法取消,一旦新建他就會當即執行。

若是不設置回調,異常則不會反應到外部。

當處於Pending狀態時,沒法得知進展到哪個階段

基本用法

Promise對象是一個構造函數,用來生成Promise實例。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

容許接收一個函數做爲參數,函數的兩個參數分別是resolve,reject,這兩個參數由js引擎提供,不用本身部署。

resolve函數的做用是,將Promise對象的狀態從Pending變爲Resolved。在成功時調用,並將異步操做結果做爲參數傳遞。

reject函數的做用是,將Promise對象的狀態從Pending編程Rejected,在操做失敗時調用,並將異步操做報出的錯誤做爲參數傳遞出去。

實例生成後,能夠用then方法分別制定Resolved狀態和Rejected狀態的回調函數。

promise.then(function(value) {  // success }, function(error) {  // failure });

then方法能夠接受兩個回調函數做爲參數,第一個回調是狀態爲Resolved,第二個回調是狀態爲Rejected時調用。

其中第二個函數是可選的,兩個函數都接受Promise對象傳出的值做爲參數。

function timeout(ms){
    return new Promise((resolve,reject) => {
        if(ms > 1000)
            resolve("--成功");
        else
            reject("--失敗");
    });
}
timeout(1000).then((value)=>{
    console.log("成功"+value);
},(value)=>{
    console.log("失敗"+value);
});
timeout(1001).then((value)=>{
    console.log("成功"+value);
},(value)=>{
    console.log("失敗"+value);
});

上面例子表示,返回一個Promise實例,若是參數大於1000則改變狀態爲Resolved,觸發then綁定事件。

Promise新建後,就會當即執行。then方法指定的回調將在當前腳本全部同步任務完成後執行,因此Resolved最後輸出。

let promise = new Promise((resolve,reject)=>{
    console.log("promise");
    resolve();
});
promise.then(()=>console.log("Resolved."));
console.log("Hi");
// promise > Hi > Resolved

若是調用resolve函數和reject函數時帶有參數,那麼他們的參數會傳遞給回調函數。

reject函數的參數一般是Error對象的實例

resolve函數的參數除了正常值之外,還多是另外一個Promise實例,表示異步操做的結果有多是一個值,也有多是另外一個操做。

若是p1 p2都是Promise實例,可是p2的resolve方法將p1做爲參數,一個異步操做的結果是返回另外一個異步操做。

此時p1狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態。若是p1狀態時Pending,那麼p2的回調就會等待。

若是p1的狀態時Resolve或Rejected,那麼p2的回調就會當即執行。

var p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>reject(new Error('fail')),3000);
});
var p2 = new Promise((resolve,reject)=>{
    setTimeout(()=>resolve(p1),1000);
});
p2.then(result=>console.log(result)).catch(error=>console.log(error.toString()));
//Error : fail

Promise.prototype.then

爲Promise實例添加狀態改變時的回調函數。第一個參數是Resolved狀態的回調,第二個是Rejected狀態的回調

then方法返回的是新的Promise實例,能夠採用鏈式的then,來指定一組按照次序調用的回調函數。

var promise = new Promise((resolve,reject)=>resolve());
promise.then(()=>console.log(1)).then(()=>console.log(2));

Promise.prototype.catch

catch方法是.then(null,rejection)的別名,用於指定發生錯誤時的回調函數。

針對錯誤的拋出能夠有兩種寫法

var promise = new Promise((resolve,reject) => {
    throw "Error";
});
var promise2 = new Promise((resolve,reject) =>{
    reject("Error");
})
promise.catch(error=>console.log(error)); //Error
promise2.catch(error=>console.log(error)); //Error

若是在reject後面,在執行throw是沒有效果的。

Promise.all 

用於將多個Promise實例,包裝成一個新的Promise實例。

var p = Promise.all([p1, p2, p3]);

接受一個數組做爲參數,參數必須是Promise對象的實例,若是不是會進行轉換。

狀態由p1 p2 p3來決定,分爲兩種狀況

1. 只有狀態都變成 Resolved ,p的狀態纔會變成 Resolved

2. 只有有一個狀態爲 Rejected,p的狀態就會變成 Rejected

var p1 = new Promise((resolve,reject)=>resolve());
var p2 = new Promise((resolve,reject)=>resolve());
var p = Promise.all([p1,p2]).then(()=>console.log("成功")).catch(()=>console.log("失敗"));

Promise.race

一樣是將多個Promise實例,包裝成一個新的Promise實例。

var p = Promise.race([p1, p2, p3]);

有一個實例率先改變狀態,p的狀態就跟着改變,並執行回調函數。

Promise.resolve

將現有的對象轉爲Promise對象,就須要使用Promise.resolve。例如把ajax方法,轉爲Promise

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

Promise.resolve參數分爲四種狀況

1. 參數是一個Promise實例

若是參數是Promise實例,那麼不作任何修改原封不動的返回這個實例

2. 參數是一個thenable對象

thenable對象指具備then方法的對象,好比

let thenable = { then: function(resolve, reject) { resolve(42); } };

Promise.resolve會將這個對象轉爲Promise對象,而後就當即執行thenable對象的then方法

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

3. 參數不具備then方法的對象,或根本就不是對象

若是參數是一個原始值或不具備then方法的對象,則返回一個新的Promise對象,狀態爲 d

var p  = Promise.resolve("Hello");
p.then(result=>console.log(result)); //Hello

字符串不具備then方法,返回的Promise實例的狀態一開始就是Resolved,回調會當即執行,同時將參數傳遞給回調。

4. 不帶任何參數

調用時不帶參數,直接返回一個Resolved狀態的Promise對象。

當即resolve的Promise對象,是在本輪事件循環的結束時。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');
// one
// two
// three

setTimeout是下一輪事件循環的開始執行

Promise.reject

會返回一個新的Promise實例,狀態爲rejected

var p = Promise.reject('出錯了');
// 等同於
var p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了

done()

Promise對象的回調鏈,無論以then方法或catch方法結尾,最後一個方法拋出的錯誤,都沒法捕獲

由於能夠提供一個done方法,老是處於回調鏈尾端,保證拋出任何可能出現的錯誤。

Promise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected)
        .catch(function (reason) {
            // 拋出一個全局錯誤
            setTimeout(() => { throw reason }, 0);
}); }; var p = Promise.resolve("Hello"); p.then(result=>{ throw new Error(result); }).done();

finally()

用於指定無論Promise對象最後狀態如何,都會執行的操做。接受一個普通回調做爲參數。

Promise.prototype.finally = function (callback) {
    let P = this.constructor;
    return this.then(
        value  => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => { throw reason })
    );
};
var p  = Promise.resolve("Hello");
p.then(result=>{
    throw new Error(result);
}).finally(function(){
    console.log("finally");
});

應用-加載圖片

const preloadImage = function (path) {
    return new Promise(function (resolve, reject) {
        var image = new Image();
        image.onload  = resolve;
        image.onerror = reject;
        image.src = path;
    });
};
preloadImage("images/0.jpg")
    .then(()=>console.log("圖片加載成功"))
    .catch(result=>console.log("圖片加載失敗"));

Iterator和for...of循環

JS原有表示集合的數據結構,主要是數組和對象,ES6又添加了Map和Set。

須要一種統一的接口機制,來處理全部不一樣的數據結構。

遍歷器(Iterator)就是這樣的一個機制,它是一種接口,爲不一樣的數據結構提供統一訪問機制。

任何數據結構只要不輸Iterator接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)

Iterator做用有三種

1. 爲各類數據結構,提供一個統一的、簡單的訪問接口

2. 使得數據結構的成員可以按照某種次序排列

3. ES6創造了一種新的遍歷命令for...of循環,Iterator接口主要提供for...of使用

Iterator遍歷過程是這樣的

1. 建立一個指針對象,指向當前數據結構的起始位置。遍歷器對象自己就是一個指針對象

2. 第一次調用next方法,能夠將指針指向數據結構的第一個成員

3. 第二次調用next方法,能夠指向第二個成員

4. 不斷調用指針對象的next方法,直到它指向數據結構的結束位置

每一次調用next方法,都會返回數據結構的當前成員的信息。返回一個包含value和done兩個屬性的對象。

其中value屬性是當前成員的值,done屬性是一個布爾值,表明遍歷是否結束。若是done是true,則不會再進行循環。

var it = makeIterator(['a', 'b']);
console.log(it.next()) // { value: 'a' }
console.log(it.next()) // { value: 'b' }
console.log(it.next()) // { done: true }

function makeIterator(array) {
    var nextIndex = 0;
    return {
        next: function() {
            return nextIndex < array.length ?
                {value: array[nextIndex++]} :
                {done: true};
        }
    };
}

這就是一個遍歷器生成函數,做用就是返回一個遍歷器對象。對數組執行這個函數,會返回該數組的遍歷器對象。

在ES6中,有些數據結構原生具有Iterator接口(好比數組),即不用處理,就能夠被for...of循環遍歷,有些不行(好比對象)。

緣由在於,這些數據結構原生部署了Symbol.iterator屬性,另一些數據結構沒有。

凡是部署了Symbol.iteraotr屬性的數據結構,就稱爲部署了遍歷器的接口。調用這個接口,就會返回一個遍歷器對象。

默認Iterator接口

ES6規定,默認的Iterator接口部署在數據結構的Symbol.iterator屬性。

一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是可遍歷的。

Symbol.iterator屬性自己就是一個函數,就是當前數據結構默認的遍歷器生成函數。

執行這個函數,就會返回一個遍歷器,至於屬性名Symbol.iterator,它是一個表達式,返回symbol對象的iterator屬性

這是一個預約好的,類型爲Symbol的特殊值,因此要放在括號內。

const obj = {
    // [Symbol.iterator] : function () {
    //     return {
    //         next: function () {
    //             return {
    //                 value: 1,
    //                 done: true
    //             };
    //         }
    //     };
    // }
    [Symbol.iterator]: () => ({
        next: () => ({
            value: 1, done: true
        })
    })
};

var objs = obj[Symbol.iterator]();
console.log(objs.next()); //{ value: 1, done: true }

對象obj是可遍歷的,由於具備Symbol.iterator屬性。執行這個屬性會返回一個遍歷器對象。

這個對象根本特徵就是具備next方法,每次調用next方法,都會返回一個表明當前成員的信息對象。

ES6,有三類數據結構原生具備Iterator接口:數組、相似數組的對象、Set/Map結構

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

console.log(iter.next()) // { value: 'a', done: false }

上面代碼中,arr是一個數組,原生就具備遍歷器接口,部署在arr的Symbol.iterator屬性上面。

對象之因此沒有默認部署Iterator接口,是由於對象的那個屬性先遍歷,那個後遍歷是不肯定的,須要手動指定。

遍歷器是一種線性處理,對於任何非線性的數據結構,部署遍歷器接口,就等於部署一種線性轉換。

不過,對象部署遍歷器接口並非很必要,這是對象實際上被當作Map結構使用,ES6原生提供了。

一個對象若是要有可被for...of循環調用的Iterator接口,就必須在Symbol.iterator的屬性上部署遍歷器生成方法

class RangeIterator {
    constructor(start, stop) {
        this.value = start;
        this.stop = stop;
    }

    [Symbol.iterator]() {
        return this;
    }

    next() {
        let val = this.value;
        if (val < this.stop) {
            this.value++;
            return {value: val}
        }
        return {done: true};
    }
}
function range(start,stop)
{
    return new RangeIterator(start,stop);
}
for(let item of range(0,3))
{
    console.log(item);
}

代碼是一個類部署Iterator接口的寫法,Symbol.iterator屬性對應一個函數,執行後返回當前對象的遍歷器對象。

下面是經過遍歷器實現指針結構的例子

function Obj(value) {
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
  var iterator = {
    next: next
  };

  var current = this;

  function next() {
    if (current) {
      var value = current.value;
      current = current.next;
      return {
        done: false,
        value: value
      };
    } else {
      return {
        done: true
      };
    }
  }
  return iterator;
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for (var i of one){
  console.log(i);
}
// 1
// 2
// 3

調用該方法會返回遍歷器對象iterator,調用該對象的next方法,返回一個值的同時,自動將內部指針移到下一個實例

下面是爲對象添加Iterator接口的例子

const obj = {
    data:['hello','world'],
    [Symbol.iterator](){
        const self = this;
        let index = 0;
        return{
            next(){
                if(index<self.data.length)
                {
                    return{value:self.data[index++]};
                }
                else{
                    return{done:true};
                }
            }
        }
    }
}
for(let item of obj)
{
    console.log(item);
}

對於相似數組對象存在數組鍵名和length屬性,部署Iterator接口,有一個簡便方法。

就是Symbol.iterator方法直接引用,數組的Iterator接口。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
const obj1 = {
    0:'a',
    1:'b',
    2:'c',
    length:3,
    [Symbol.iterator]:Array.prototype[Symbol.iterator]
};
for(let item of obj1)
{
    console.log(item);
}

注意的是,必須是 0,1,2 這種屬性。普通屬性部署Symbol.iterator是無效的。

調用Iterator接口的場合 

1. 解構賦值

對數組和Set結構進行解構賦值,會默認調用Symbol.iterator方法

const obj1 = {
    0:'a',
    1:'b',
    2:'c',
    length:3,
    [Symbol.iterator]:Array.prototype[Symbol.iterator]
};
const obj2 = new Set().add('a').add('b').add('c');
let [x,y] = obj1;
let [a,b] = obj2;
let [c,...rest] = obj2;
console.log(x+","+y); // a,b
console.log(a+","+b); // a,b
console.log(c+","+rest); // a,b,c

2. 擴展運算符

使用擴展運算符,也會調用默認的Iterator接口 

const str ='hello';
console.log([...str]); // [ 'h', 'e', 'l', 'l', 'o' ]

const arr = ['b','c'];
console.log(['a',...arr,'d']); // [ 'a', 'b', 'c', 'd' ]

上面代碼的擴展運算符,就調用內部Iterator接口

這提供了一種簡便機制,能夠將任何部署Iterator接口的數據結構,轉爲數組。

只要某個數據結構部署了Iterator接口,就能夠對它使用擴展運算符,將其轉爲數組

const obj1 = {
    0:'a',
    1:'b',
    2:'c',
    length:3,
    [Symbol.iterator]:Array.prototype[Symbol.iterator]
};
let arr = [...obj1];
console.log(arr); // [ 'a', 'b', 'c' ]

3. yield*

yield* 後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口

const obj1 = {
    0:'a',
    1:'b',
    2:'c',
    length:3,
    [Symbol.iterator]:Array.prototype[Symbol.iterator]
};
let generator = function* () {
    yield 1;
    yield* obj1;
    yield 5;
};

for(let item of generator())
{
    console.log(item); // 1 a b c 5
}

4. 其餘場合

數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

字符串的Iterator接口

字符串是一個相似的數組對象,也原生具備Iterator接口。

var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }

上面代碼中,調用Symbol.iterator方法返回一個遍歷器對象,在這個遍歷器對象上能夠調用next實現遍歷。

能夠覆蓋原生的Symbol.iterator方法,達到修改遍歷器行爲的目的。

let str = new String("hi");
console.log([...str]); // [ 'h', 'i' ]

str[Symbol.iterator] = ()=>({
    next(){
        if (this._first) {
            this._first = false;
            return { value: "bye" };
        } else {
            return { done: true };
        }
    },
    _first:true
})
console.log([...str]); // [ 'bye' ]
console.log(str); // [String: 'hi']

上面代碼中,字符串str的Symbol.iterator方法被修改了,因此擴展運算符返回的值變成bye,而字符串自己仍是hi

Iterator接口與Generator函數

Symbol.iterator方法的最簡單實現,仍是使用Generator函數。

let obj = {
    * [Symbol.iterator]() {
        yield 'hello';
        yield 'world';
    }
};

for (let x of obj) {
    console.log(x);
}
// hello
// world

遍歷器對象的return(),throw()

遍歷器對象除了具備next方法,還能夠有return方法和throw方法。

若是本身寫遍歷器對象生成函數,那麼next方法是必須部署的,return方法和throw方法是可選部署的。

若是for...of循環提早退出(break,continue),就會調用return方法。

let str = "hello";
function readLinesSync() {
    let index=0;
    return {
        [Symbol.iterator]:()=>({
            next() {
                if(index<str.length)
                    return {value:str[index++]};
                else
                    return{done:true};
            },
            return() {
                console.log("執行return方法");
                return { done: true };
            }
        })
    };
};
for(let item of readLinesSync())
{
    console.log(item); // h 執行return方法
    break;
}

for...of

一個數據結構只要部署了Symbol.iterator屬性,就被視爲具備iterator接口,就能夠用for...of循環遍歷它的成員。

也就是說,for...of循環內部調用的是數據結構的Symbol.iterator方法。

for...of循環可使用的範圍包括數組、Set、Map,某些相似數組的對象、Generator對象、字符串。

數組

數組原生具有iterator接口,for...of循環本質上就是調用這個接口產生的遍歷器。

for...in循環,只能得到對象的鍵名,不能直接獲取鍵值。

for...of循環,容許遍歷得到鍵值。數組的遍歷器接口只返回具備數字索引的屬性。

let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); //  "3", "5", "7"
}

Generator

Generator函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數不一樣。

Generator函數有多種理解,從語法上能夠理解成一個狀態機,封裝了多個內部狀態。

執行Generator函數會返回一個遍歷器對象,除了狀態機,仍是一個遍歷器對象生成函數。

返回的遍歷器對象,能夠依次遍歷Generator函數內部的每個狀態。

形式上,Generator函數是一個普通函數,但有兩個特徵

1. function關鍵字與函數名之間有*號

2. 函數體內部使用yield表達式,定義不一樣的狀態

function* hello(){
    yield "hello";
    yield "world";
    return "ending";
}
var ho = hello();
console.log(ho.next()); // { value: 'hello', done: false }
console.log(ho.next()); // { value: 'world', done: false }
console.log(ho.next()); // { value: 'ending', done: true }

上面代碼定義了一個Generator函數,它內部有兩個yield表達式,該函數有三個狀態。

Generator函數的調用方式和普通函數同樣,是在函數名後面加上一對圓括號。

不一樣的是,調用Generator函數後,該函數並不執行,返回的也不是函數運行結束結果,而是一個指向內部狀態的指針對象。

下一步,必須調用next方法,使得指針移動到下一個狀態。

每次調用next方法, 內部指針就從函數頭部或上一次中止的位置開始執行,直到碰見另外一個yield表達式或return語句

換言之,Generator函數是分段執行,yield表達式是暫停執行的標記,而next方法能夠恢復執行。

ES6沒有規定function關鍵字和函數名之間的星號的位置,因此如下寫法均可以經過。

function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }

yield表達式

Generator函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,提供了一種可暫停的函數。yie表達式就是暫停標誌。

遍歷器對象的next方法運行邏輯以下

1. 遇到yield表達式,就暫停執行後面的操做,並返回對象屬性value是,緊跟在yield後面的表達式的值。 

2. 下一次調用next方法時,再繼續執行下去,直到遇到下一個yield表達式

3. 若是沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式,做爲返回對象的value屬性。

4. 若是該函數沒有return語句,則返回的對象的value屬性值爲undefined。

須要注意的是,yield表達式後面的表達式,只有當調用next方法時,纔會執行。

return語句不具有位置記憶的功能,一個函數裏,只能執行一次return語句,但能夠執行多個yield。

function* f() {
  console.log('執行了!')
}

var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);

Generator函數能夠不用yield表達式,這時就變成了一個單純的暫緩執行函數。

上面代碼中,若是函數f是普通函數,在賦值的時候就會執行。可是f是Generator函數,只有調用next方法時,纔會執行。

注意:yield表達式只能在Generator函數裏面使用,在其餘地方使用就會報錯。

Generator與Iterator接口

Generator函數就是遍歷器生成函數,所以能夠把Generator賦值給對象的Symbol.iterator屬性,從而使得對象具備Iterator接口

var myIterable = {};
myIterable[Symbol.iterator] = function* (){
    yield 1;
    yield 2;
    yield 3;
};
var result = [...myIterable];
console.log(result); // [1,2,3]

上面代碼就是把Generator函數賦值給Symbol.iterator屬性,讓對象有了接口,就能夠被...運算符遍歷了。

Generator函數執行後,返回一個遍歷器對象,該對象自己就具備Symbol.iterator屬性,執行後返回自身。 

next方法參數

yield表達式自己沒有返回值,next方法能夠帶一個參數,這個參數就被當作上一個yield的返回值。

function* f() {
    for (var i = 0; i < 5; i++) {
        var reset = yield i;
        if (reset) {
            break;
        }
    }
}
var g = f();
console.log(g.next()); //{ value: 0, done: false }
console.log(g.next(true)); //{ value: undefined, done: true }

這個功能的語法意義很重要,Generator函數從暫停狀態到恢復執行,他的上下文是不變的。

經過next方法參數,就有辦法在Generator函數開始運行以後,繼續向函數體內注入值。

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

因爲next方法的參數表示上一個yield表達式的返回值,因此第一次使用next方法時,不能帶有參數。

V8引擎直接忽略第一次使用next方法的參數。

for...of循環

循環能夠自動遍歷Generator函數時生成的Iterator對象,再也不須要調用next方法。

function* foo() {
    for (let i = 0; i < 5; i++) {
        yield i;
    }
}
for (let i of foo()) {
    console.log(i);
}

利用Generator函數和for...of循環,能夠實現斐波那契數列

function* feibo() {
    let [prev, curr] = [0, 1];
    while (true) {
        [prev, curr] = [curr, prev + curr];
        yield curr;
    }
}
for (let n of feibo()) {
    if (n > 20)
        break;
    console.log(n);
}

利用for...of循環,能夠寫出遍歷任意對象的方法,原生Javascript對象沒有遍歷接口,沒法使用for...of,經過Generator就能夠爲他加上。

function* objectEntries(obj) {
    let propKeys = Reflect.ownKeys(obj);
    for (let propKey of propKeys) {
        yield [propKey, obj[propKey]];
    }
}
let jane = {first: "Jane", last: "Doe"};
for (let [key, value] of objectEntries(jane)) {
    console.log(`${key}-${value}`);
}

或者,咱們可使用Generator函數加到對象的Symbol.iterator屬性上面

function* objectEntries() {
    let propKeys = Object.keys(this);
    for (let propKey of propKeys) {
        yield [propKey, this[propKey]];
    }
}
let jane = {first: "Jane", last: "Doe"};
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
    console.log(`${key}-${value}`);
}

除了for...of之外,擴展運算符 ... ,解構賦值 ,Array.from方法內部調用的,都是遍歷器接口。

function* objectEntries() {
    let propKeys = Object.keys(this);
    for (let propKey of propKeys) {
        yield [propKey, this[propKey]];
    }
}
let jane = {first: "Jane", last: "Doe"};
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
    console.log(`${key}-${value}`);
}

Generator.prototype.throw()

Generator函數返回的遍歷器對象,都有一個throw方法,能夠在函數體外拋出錯誤,而後在Generator函數體內捕獲

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('內部捕獲', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕獲', e);
}
// 內部捕獲 a
// 外部捕獲 b

對象i連續拋出兩個錯誤,第一個錯誤被Generator內部異常捕獲,第二次由於內部的異常已經執行過了,因此就拋出外部的

Generator.prototype.return()

Generator函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷Generator函數

function* gen() {
    yield 1
    yield 2
}
const g = gen();
console.log(g.return("c")); // { value: 'c', done: true }
console.log(g.next()); // { value: undefined, done: true }

若是return不提供參數,則返回值value爲undefined。

若是Generator函數裏面有try...finally,則return方法會推遲到finally代碼塊執行完再執行。

yield * 表達式

若是在Generator函數裏面調用另外一個Generator函數,是沒有效果的。

這個時候就須要使用yield * 表達式,用來在一個Generator函數裏面執行另外一個函數。

function * foo() {
    yield 1
    yield 2
}
function * bar() {
    yield 3
    yield* foo();
    yield 4
}
for (let v of bar()) {
    console.log(v);
}
// 3 1 2 4

做爲對象屬性的Generator函數

若是一個對象的一個屬性是Generator函數,可使用星號來表示函數,一下兩個對象是等效的。

let obj = {
    * myGeneratorMethod(){
        yield 1
        yield 2
    }
}
let obj2 = {
    myGeneratorMethod: function*() {
        yield 1
        yield 2
    }
}
for (let v of obj.myGeneratorMethod()) {
    console.log(v);
}
for (let v of obj2.myGeneratorMethod()) {
    console.log(v);
}

Generator函數的this

Generator函數的返回的遍歷器對象,是函數的實例,繼承了prototype方法。

Generator應用

異步操做的同步化表達

Generator函數的暫停執行效果,意味着能夠把異步操做寫在yield表達式裏面,等調用next方法時再日後執行。

至關於不須要寫回調函數,異步操做的後續操做能夠放在yield表達式下面,要等到調用next方法再執行。

因此Generator函數的一個重要實際意義就是用來處理異步操做,改寫回調函數。

能夠經過Generator函數逐行讀取文本文件。下面代碼使用yield表達式能夠手動逐行讀取文件。

function* numbers() {
  let file = new FileReader("numbers.txt");
  try {
    while(!file.eof) {
      yield parseInt(file.readLine(), 10);
    }
  } finally {
    file.close();
  }
}

控制流管理

若是一個多步操做很是耗時,可使用Generator按次序自動執行全部步驟。

let steps = [step1Func, step2Func, step3Func];

function *iterateSteps(steps){
  for (var i=0; i< steps.length; i++){
    var step = steps[i];
    yield step();
  }
}

steps封裝了一個任務的多個步驟,iterateSteps依次給這些步驟添加上yield命令。

分解完步驟後,還能夠將項目分解成多個依次執行的任務。

let jobs = [job1, job2, job3];

function* iterateJobs(jobs){
  for (var i=0; i< jobs.length; i++){
    var job = jobs[i];
    yield* iterateSteps(job.steps);
  }
}

數組jobs封裝了一個項目的多個任務,interateJobs則依次爲這些任務添加上yield命令

最後用for...of循環一次性執行全部的步驟

for (var step of iterateJobs(jobs)){
  console.log(step.id);
}

Generator函數異步應用

異步就是一個任務不是連續完成的,先執行第一段,等作好準備,在執行第二段。

好比,任務是讀取文件進行處理,第一段就是發送請求讀取文件,等系統返回文件,在執行第二段處理文件。

這種不連續的操做,就稱爲異步操做。

回調函數

Javascript語言對異步編程的實現,就是用回調函數。把任務的第二部分單獨寫在一個函數裏面。

從新執行這個任務,就直接調用這個函數。好比讀取文件的操做。

fs.readFile('/etc/passwd', 'utf-8', function (err, data) { if (err) throw err; console.log(data); });

 redFile第三個參數,就是回調函數,也就是任務的第二段

Promise

回調函數自己並無什麼問題,可是若是出現多個回調函數嵌套,則會變成橫向發展沒法管理。

由於多個異步操做造成了強耦合,只要有一個操做須要爲修改,它的上下層函數,就都要跟着改變。

這種狀況,被稱爲回調函數地獄「callback hell」

使用Promise就是爲了解決這個問題,容許將回調函數的嵌套,改成鏈式調用,連續讀取多個文件。

var readFile = require('fs-readfile-promise');

readFile(fileA)
    .then(function (data) {
        console.log(data.toString());
    })
    .then(function () {
        return readFile(fileB);
    })
    .then(function (data) {
        console.log(data.toString());
    })
    .catch(function (err) {
        console.log(err);
    });

Promise寫法是回調函數的改進版,使用then之後,異步任務會變得更加清晰。

Promise最大的缺點就是代碼冗餘,任務都被Promise包裝了,語以變得不清晰。

Generator函數 

協程

協程,意思是多個線程相互協做,完成異步任務。

協程大體的流程以下:

1. 協程A開始執行。

2. 協程A執行到一半,進入暫停,執行權轉移到協程B。

3. (一段時間後)協程B交還執行權。

4. 協程A恢復執行。

讀取文件的協程寫法

function *asyncJob() {
  // ...其餘代碼
  var f = yield readFile(fileA);
  // ...其餘代碼
}

asyncJob是一個協程,其中yield是關鍵。表示執行到此處,執行權將交給其餘線程。yield是異步兩個階段的分界線。

協程遇到yield命令就暫停,等到執行權返回,再從暫停的地方繼續日後執行。

它的最大優勢,就是代碼的寫法很是像同步操做。

協程的Generator函數實現

Generator函數協程在ES6實現,最大特色就是交出函數的執行權(暫停執行)

整個Generator函數就是一個封裝的異步函數,操做中須要暫停的部分都用yield註明。

function* gen(x) {
    var y = yield  x + 2;
    return y;
}
var g = gen(1);
g.next(); g.next();

調用Generator函數,返回一個內部指針g。調用g的next方法,會移動內部指針,指向第一個遇到的yield語句。

next方法是分階段執行Generator函數,每次調用會返回一個對象。value表示yield後面表達式的值,done表示是否執行完畢。

異步任務的封裝

function* gen(){
    var url = 'http://api.github.com/users/github';
    var result = yield fetch(url);
    console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data){
    console.log(data.json);
}).then(function(data){
    g.next(data)
}).catch(function(data){
    console.log(data);
});

Generator封裝一個異步操做,先讀取一個接口數據,而後用JSON返回。 

上面操做就是,執行Generator函數,返回一個遍歷器對象,使用next方法,執行異步的第一個階段。

Fetch返回的是一個Promise對象,所以then方法調用下一個next方法。

雖然上面的Generator函數將異步操做表示的很簡潔,可是流程管理卻不是很方便。

Thunk函數

Thunk是自動執行Generator函數的一種方法。

求值策略,函數的參數到底應該什麼時候求值。分爲兩種意見,示例以下:

var x = 1;
function f(m) {
  return m * 2;
}
f(x + 5)

1.傳值調用,在進入函數體以前,就計算x+5的值(等於6),再將6傳入函數裏面。C語言採用這種策略

2.傳名調用,將表達式x+5傳入函數體,只有在用到它的時候求值。Haskell語言採用這種策略。

編譯器的傳名調用實現,每每是將參數放到一個臨時函數中,再將這個臨時函數傳入函數體。這個函數就是Thunk函數。

function f(m) {
    return m * 2;
}

var thrun = function () {
    return x + 5;
}

function f(thrun) {
    return thrun() * 2;
}

Javascript語言中Thrun函數替換的不是表達式,而是多參函數,將其替換成一個只接受回調函數做爲參數的單參數函數

// 正常版本的readFile(多參數版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(單參數版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName);
readFileThunk(callback);

fs模塊的readFile方法是一個多參函數,參數分別爲文件名和回調函數。通過轉換它變成了一個單參函數,只接受回調函數做爲參數。

這個單參版本就叫作Thunk函數。任何函數只要有回調函數,就能寫成Thunk函數的形式。例如:

// ES5版本
var Thunk = function(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};

// ES6版本
  

使用上面的轉換器,生成fs.readFile的Thunk函數。

var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

//完整示例
function f(a, cb) {
  cb(a);
}
const ft = Thunk(f);

ft(1)(console.log) // 1

生產環境的轉換器,建議使用Thunkify模塊。

首先安裝:npm install thunkify

使用方法以下:

var thunkify = require("thunkify");
var fs = require("fs");

var read = thunkify(fs.readFile);
read("package.json")(function(err,str){
    console.log(err);
    console.log(str.toString());
})

Thunk函數能夠用於Generator函數的自動流程管理,能夠自動執行

若是須要異步操做中,保證前一步執行完,才能執行後一步,就須要Thunk函數。

Generator函數封裝了兩個異步操做,yield命令用於將程序的執行權移出Generator函數。

Thunk函數能夠在回調函數裏面,將執行權交還給Generator函數。

var thunkify = require("thunkify");
var fs = require("fs");
var readFileThunk = thunkify(fs.readFile);

var gen = function* () {
    var r1 = yield readFileThunk("package.json");
    console.log(r1.toString());
    var r2 = yield readFileThunk("demo1.html");
    console.log(r2.toString());
}

var g = gen();
var r1 = g.next();
r1.value(function (err, data) {
    if (err) throw err;
    var r2 = g.next(data);
    r2.value(function (err, data) {
        if (err) throw err;
        g.next(data);
    });
})

g是Generator函數的內部指針,表示目前執行到哪一步。next方法負責將指針下移,返回該步的信息(value屬性和done屬性)

Generator函數的執行過程,其實就是將同一個回調函數,反覆傳入next方法的value屬性。變成遞歸來自動完成這個過程。

Thunk函數真正的用途,在於自動執行Generator函數。下面就是基於Thunk函數的Generator執行器

var thunkify = require("thunkify");
var fs = require("fs");
var readFileThunk = thunkify(fs.readFile);

function run(fn) {
    var gen = fn();

    function next(err, data) {
        if (data) console.log(data.length);
        var result = gen.next(data);
        if (result.done) return;
        result.value(next);
    }

    next();
}

var g = function* () {
    var r1 = yield readFileThunk("package.json");
    var r2 = yield readFileThunk("demo1.html");
}

run(g);

run函數就是一個Generator函數的自動執行器,內部的next函數就是Thunk函數的回調函數。

next函數先將指針移到Generator函數的下一步gen.next,而後判斷Generator函數是否結束result.done

若是沒有結束,就將next函數再傳入Thunk函數result.value,不然就直接退出。 

上面代碼中,函數g封裝了n個異步操做的讀取文件操做,只要執行run函數,這些操做就會自動完成。

Co模塊

co模塊是TJ Holowaychuk開發的小工具,用於Generator函數的自動執行。co模塊可讓你不用寫Generator函數的執行器

Generator函數只要傳入co函數,就會自動執行。co函數返回Promise對象,所以可使用then方法添加回調函數。

var thunkify = require("thunkify");
var fs = require("fs");
var readFile = thunkify(fs.readFile);
var co = require("co");

var gen = function* () {
    var f1 = yield readFile("demo1.html");
    var f2 = yield readFile("package.json");
    console.log(f1.length);
    console.log(f2.length);
}

co(gen).then(function () {
    console.log('Generator 函數執行完成');
});

Co模塊原理

Generator就是一個異步操做的容器,自動執行須要一種機制,當異步操做有告終果,可以自動交回執行權。

兩種方法能夠作到

1.回調函數,將異步操做包裝成Thunk函數,在回調函數中交回執行權

2.Promise對象,將異步操做包裝秤Promise對象,用then方法交回執行權

co模塊就是將兩種自動執行器包裝成一個模塊,使用co的前提條件是,Generator函數的yield命令後面,只能是Thunk函數或Promise對象

async函數

ES2017標準引入async函數

http://es6.ruanyifeng.com/#docs/async

Class基本語法

ES6引入了Class的概念,做爲對象的模板可使用class關鍵字定義類。

class至關於ES5的語法糖。下面示例來展現ES6寫法的不一樣

//ES5寫法
function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype.toString = function () {
    return "重構後的" + this.x + "," + this.y;
}

var P = new Point(1, 2);
console.log(P.toString());

//ES6寫法
class Point2 {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return "重構後的" + this.x + "," + this.y;
    }
}

var P2 = new Point2(1, 2);
console.log(P2.toString());

上面代碼定義了類,constructor是構造方法,this就是實例對象。定義類的方法時,不須要添加function關鍵字,方法之間不須要分隔符

使用的時候,直接對類進行new命令就能夠了。prototype屬性在class上面繼續存在,全部的方法都定義在類的prototype屬性上面。

在類的實例上調用方法,其實就是調用原型上的方法。prototype對象的constructor屬性指向類自己。

類的內部全部定義的方法,都是不可枚舉的,ES5定義的方法能夠枚舉。

Object.assign方法能夠向類添加多個方法

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

Object.assign(Point.prototype, {
    toString() {
        console.log("ToString");
    },
    toValue() {
        console.log("ToValue");
    }
})

var P = new Point(1, 2);
P.toString();
P.toValue();

類的屬性名,能夠採用變量來定義。

names = "toString";

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

Object.assign(Point.prototype, {
    [names]() {
        console.log("ToString");
    },
    toValue() {
        console.log("ToValue");
    }
})

var P = new Point(1, 2);
P.toString();
P.toValue();

嚴格模式

類和模塊的內部就是嚴格模式,不須要使用use strict。ES6整個語言都是嚴格模式

constructor方法

constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。

一個必須擁有constructor方法,若是沒有顯示定義,會默認添加一個空的。

constructor方法默認返回實例對象this,能夠指向返回另外一個對象。

類的實例對象

類必須使用new,不然會報錯。屬性除非顯式定義在其自己(this)上,不然都會定義在原型上

類的表達式

和函數同樣,類也可使用表達式來定義。name屬性是函數默認特性

const MyClass = class Me {
    getClassName() {
        return Me.name;
    }
}
let inst = new MyClass();
console.log(inst.getClassName());  //Me

上述代碼中Me只在Class內部使用,指當前類。若是類的內部沒有用到的話,能夠省略Me寫成

const MyClass = class { /* ... */ };

使用表達式能夠寫出當即執行的Class,person就是當即執行的類的實例

let person = new class{
    constructor(name){
        this.name = name;
    }
    sayName(){
        console.log(this.name);
    }
}("cxy");
person.sayName(); //cxy

類不存在變量提高的問題

私有方法和私有屬性

經過命名來區分,_person是私有方法,Peroson是公有方法

this指向

類的方法內部若是含有this,默認指向類的實例。可是若是單獨使用該方法極可能報錯。

能夠在構造方法中綁定this,這樣就不會找不到了

或者直接使用箭頭函數

class Logger {
    constructor() {
        this.printName = this.printName.bind(this);
        this.printName = (name = 'there') => {
            this.print(`Hello ${name}`);
        }
    }
}

Class的Generator方法

若是某個方法以前加上星號(*),就表示該方法是一個Generator函數。

const Foo = class{
    constructor(...args){
        this.args = args;
    }
    *[Symbol.iterator](){
        for(let arg of this.args){
            yield arg;
        }
    }
}
for(let item of new Foo(1,2,3)){
    console.log(item); //1,2,3
}

Class靜態方法和靜態屬性

若是在一個方法前面加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用。稱爲靜態方法

class Foo{
    static classMethod(){
        return "Hello";
    }
}
console.log(Foo.classMethod()); //Hello

若是靜態方法包含this關鍵字,這個this指向類而不是實例 

Foo.prop = 1; 直接類後面.就能夠建立靜態屬性

new.target屬性 

若是構造函數不是經過new命令調用,new.target會返回undefined。類內部調用new.target,返回當前正在運行的Class。

function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必須使用 new 命令生成實例');
  }
}

須要注意的是,子類繼承父類時,new.target會返回子類。能夠利用這個特色來寫出抽象類

class Shape {
    constructor() {
        if (new.target == Shape) {
            throw new Error("本類不能實例化");
        }
    }
}

Class繼承

class經過extends關鍵字實現繼承

class Point {
}

class ColorPoint extends Point {
}

super表示父類的構造函數,能夠新建父類的this對象。而且能夠調用父類方法。相似於Net中的base關鍵字

class Shape {
    constructor(x, y) {
        if (new.target == Shape) {
            throw new Error("本類不能實例化");
        }
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape x=${this.x},y=${this.y}`;
    }
}

class Point extends Shape {
    constructor(x, y, z) {
        super(x, y);
        this.z = z;
    }

    toString() {
        console.log(`Point z=${this.z}-${super.toString()}`);
    }
}

new Point(1, 2, 3).toString(); //Point z=3-Shape x=1,y=2

子類必須在構造函數中調用super方法,不然新建實例會報錯。由於子類沒有本身的this對象,是繼承父類的this對象

不調用super就得不到this對象。

若是子類沒有定義constructor方法,在繼承狀態下會默認添加以下代碼。

class ColorPoint extends Point {
}

// 等同於
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

須要注意,必須在使用this以前,調用super。不然會報錯

Object.getPrototypeOf方法能夠用來從子類上獲取父類

能夠用來判斷是否繼承

super關鍵字

super關鍵字既能夠當作函數,也能夠當作對象

當作函數的時候,表示父類的構造函數。ES6要求子類的構造函數必須執行一次super函數。

當作對象的時候,在普通方法中表示父類原型對象,在靜態方法中指向父類。

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

定義在父類實例上的方法或屬性是沒法經過super調用的。定義在原型是能夠的

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();

ES6規定,經過super調用父類的方法時,方法內部的this指向子類。

Mixin模式的實現

Mixin指多個對象合成一個新的對象,新對象具備各個組成成員的接口,

function mix(...mixins) {
  class Mix {}

  for (let mixin of mixins) {
    copyProperties(Mix, mixin); // 拷貝實例屬性
    copyProperties(Mix.prototype, mixin.prototype); // 拷貝原型屬性
  }

  return Mix;
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"
      && key !== "prototype"
      && key !== "name"
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

上面的mix函數能夠將多個對象合成一個類,使用的時候只要繼承這個類便可

class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}

修飾器

提案階段 http://es6.ruanyifeng.com/#docs/decorator

Module語法

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

ES6在語言層面上,實現了模塊功能。徹底能夠取代CommonJS和AMD

ES6的模塊設計思想是儘可能的靜態化,使得編譯器就能肯定模塊的依賴關係,以及輸入和輸出的變量

CommonJS模塊就是對象,輸入時必須查找對象屬性

// CommonJS模塊
let { stat, exists, readFile } = require('fs');

// 等同於
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代碼的實質是總體加載fs模塊,生成一個對象_fs,而後從這個對象上讀取三個方法。

ES6模塊不是對象,而是經過export命令顯式指定輸出的代碼。在經過import命令輸入。

注意:使用import沒法再js中進行調試代碼,只能調試babel轉換後的代碼。

// ES6模塊
import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊中加載三個方法,其餘不加載。這種加載稱爲「編譯時加載」或靜態加載,即ES6編譯時就完成模塊加載。

效率比CommonJS模塊的加載高出不少,這也致使了無法引用ES6模塊自己,由於他不是對象。

export命令

模塊功能主要是兩個命令構成:export和import。export命令用於規定模塊的對外接口,Import命令用於輸入其餘模塊提供的功能,

一個模塊就是一個獨立的文件,該文件內部的全部變量,外部沒法獲取。

若是你但願外部可以獲取某個變量,必須使用export關鍵字輸出該變量或者使用export{}輸出變量,{}不可省略

//Demo2
export let firstName = "Demo2"
export let lastName = "js"
or
let firstName = "Demo2"
let lastName = "js"
export {firstName, lastName}

//Demo1
import {firstName,lastName} from "./Demo2";
console.log(`${firstName}-${lastName}`); //Demo2-js

優先推薦export{}這種輸出方式,能夠直接在腳本尾部清楚的看出。

export命令除了輸出變量,還能夠輸出函數或類,例如

export function multiply(x, y) {
  return x * y;
};

可使用as關鍵字對接口進行重命名。

// 寫法一
export var m = 1;

// 寫法二
var m = 1;
export {m};

// 寫法三
var n = 1;
export {n as m};

另外,export語句輸出的接口,與其對應的值是動態綁定的,經過接口能夠取得模塊內部實時的值

//Demo2
let foo = 'bar';
setTimeout(() => foo = 'baz', 500)
export {foo}

//Demo1
import {foo} from './Demo2'

setTimeout(() => console.log(foo), 100); //bar
setTimeout(() => console.log(foo), 501); //baz

這一點與CommonJS不一樣,CommonJS模塊輸出的值得緩存,不存在動態更新。

export命令只能存在於模塊頂層,不能再函數裏面寫。

import命令

export定義模塊對外接口後,其餘JS文件就能夠經過import加載

import命令接受一對大括號,指定其餘模塊須要導入的變量名,大括號的變量名必須與對外接口相同。

若是想設置別名,須要使用as關鍵字。

import { lastName as surname } from './profile';

impor的from是指定模塊文件的位置,能夠是相對路徑,也能夠是絕對路徑。.js後綴能夠省略。

若是隻是模塊名,不帶有路徑,那麼必須有配置文件,告訴js引用模塊的位置。

import命令有提高效果,會自動提高到模塊頭部,首先執行。

import語句會執行全部加載的模塊,所以能夠寫成。可是不會輸入任何值

import 'lodash';

目前,經過Babel轉碼,CommonJS模塊的require命令和import命令能夠共存

可是最好不這樣作,由於import在靜態解析階段執行。是模塊中最先執行的,會有可能出問題。

模塊總體加載

能夠經過*號來加載整個模塊,全部輸出值都在這個對象上面。

//Demo2
let firstName = "Demo2"
let lastName = "js"
export {firstName, lastName};

//Demo1
import * as Demo2 from './Demo2'

console.log(`${Demo2.firstName}.${Demo2.lastName}`); //Demo2.js

export default命令

export default命令,能夠爲模塊指定默認輸出。import能夠爲該模塊提供任何名字。export default也能夠用於非匿名函數 export default foo;

//Demo2
export default function(){
    console.log("foo");
}

//Demo1
import foo from './Demo2'
foo(); //foo

一個模塊只能有一個默認輸出。在import的時候注意不要加大括號

export與import複合寫法

若是在一個模塊中,先輸入後輸出同一個模塊。import語句能夠與export寫在一塊兒。

export { foo, bar } from 'my_module';

// 等同於
import { foo, bar } from 'my_module';
export { foo, bar };

模塊的接口更名和總體輸出,也能夠這麼寫

// 接口更名
export { foo as myFoo } from 'my_module';

// 總體輸出
export * from 'my_module';

修改默認接口的寫法以下

export { es6 as default } from './someModule';

// 等同於
import { es6 } from './someModule';
export default es6;

也能夠修改默認接口爲顯式接口

export { default as es6 } from './someModule';

模塊的繼承

模塊之間也能夠進行繼承,

//Demo3
export function foo(){
    console.log("foo");
}

//Demo2
export * from './Demo3'
export default function fo() {
    console.log("fo");
}

//Demo1
import Demo2,* as Demo from './Demo2'
Demo2();
Demo.foo();

Module的加載實現

瀏覽器加載ES6模塊,也使用<script>標籤,可是必須加入type="module"屬性。注意:只有谷歌瀏覽器支持ES6寫法,import這種只有使用webpack進行打包纔可使用。

<script type="module" src="dist/Demo1.js"></script>

瀏覽器對於帶有type="module"的script都是異步加載,不會形成堵塞瀏覽器,等同於打開了defer屬性

若是頁面有多個moudle會按照頁面出現順序依次加載。使用async屬性,不會按照出現順序加載。

ES6模塊與CommonJS模塊的差別

他們有兩大重要差別

1.CommonJS模塊輸出的是一個值得拷貝,ES6模塊輸出的是值得引用

2.CommonJS模塊是運行時加載,ES6模塊時編譯時輸出接口

編程風格

let取代var,由於沒有反作用

全局常量優先使用const

靜態字符串一概使用單引號或反引號,不使用雙引號

數組成員給變量賦值時,優先使用解構賦值

const arr = [1, 2, 3, 4];
// good
const [first, second] = arr;

函數的參數若是是對象成員,優先使用解構賦值

// good
function getFullName(obj) {
  const { firstName, lastName } = obj;
}

// best
function getFullName({ firstName, lastName }) {
}

若是函數返回多個值,優先使用對象的解構賦值,而不是數組賦值。這樣便於之後添加返回值,以及更改返回值的順序

// good
function processInput(input) {
  return { left, right, top, bottom };
}

const { left, right } = processInput(input);

單行定義對象,最後一個成員不以逗號結尾。多行定義的對象,最後一個成員以逗號結尾。

// good
const a = { k1: v1, k2: v2 };
const b = {
  k1: v1,
  k2: v2,
};

對象儘可能靜態化,一旦定義就不得隨意添加新的屬性。若是添加屬性不可避免使用Object.assign方法

// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });

// good
const a = { x: null };
a.x = 3;

若是屬性名是動態的,能夠在建立對象時,使用屬性表達式定義。

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};

對象屬性和方法儘可能採用簡潔寫法,編譯描述和書寫。

使用擴展運算符拷貝數組

// good
const itemsCopy = [...items];

使用Array.from方法,將相似數組的對象轉爲數組。

const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);

當即執行函數能夠寫成箭頭函數的形式

(() => {
  console.log('Welcome to the Internet.');
})();

須要使用函數表達式的場合,儘可能用箭頭函數替代。由於更簡潔而且綁定了this

// good
[1, 2, 3].map((x) => {
  return x * x;
});

// best
[1, 2, 3].map(x => x * x);

箭頭函數取代了Function.prototype.bind不該再用self/_this/that綁定this

// bad
const self = this;
const boundMethod = function(...params) {
  return method.apply(self, params);
}

// acceptable
const boundMethod = method.bind(this);

// best
const boundMethod = (...params) => method.apply(this, params);

簡單的、單行的、不會複用的函數,建議採用箭頭函數。不然仍是應該採用傳統函數的寫法

全部配置項都應集中在一個對象,放在最後一個參數,布爾值不可直接做爲參數

// good
function divide(a, b, { option = false } = {}) {
}

不要再函數體內使用arugments變量,使用rest運算符(...)代替。

由於rest運算符顯示代表你想要獲取參數,而arugments是一個相似數組的對象,rest運算符能夠提供一個真正的數組。

// good
function concatenateAll(...args) {
  return args.join('');
}

使用默認值語法設置函數參數的默認值 

// good
function handleThings(opts = {}) {
  // ...
}

若是須要key:value的數據結構,使用Map結構。由於Map內置遍歷機制

let map = new Map(arr);

for (let key of map.keys()) {
  console.log(key);
}

for (let value of map.values()) {
  console.log(value);
}

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}

使用Class取代須要prototype的操做,由於Class的寫法更簡潔,更易於理解。

// bad
function Queue(contents = []) {
  this._queue = [...contents];
}
Queue.prototype.pop = function() {
  const value = this._queue[0];
  this._queue.splice(0, 1);
  return value;
}

// good
class Queue {
  constructor(contents = []) {
    this._queue = [...contents];
  }
  pop() {
    const value = this._queue[0];
    this._queue.splice(0, 1);
    return value;
  }
}

使用extends實現繼承,這樣更簡單,不會有被破壞instanceof運算的危險。

// good
class PeekableQueue extends Queue {
  peek() {
    return this._queue[0];
  }
}

Module語法是JavaScript模塊的標準寫法,堅持使用這種寫法。使用import取代require,使用export取代module.exports

// commonJS的寫法
var React = require('react');

var Breadcrumbs = React.createClass({
  render() {
    return <nav />;
  }
});

module.exports = Breadcrumbs;

// ES6的寫法
import React from 'react';

class Breadcrumbs extends React.Component {
  render() {
    return <nav />;
  }
};

export default Breadcrumbs;

若是模塊只有一個輸出值,就是用export default,若是有多個輸出值就不使用。不要同時使用兩個

不要在模塊中使用通配符,這樣能夠確保你的模塊中有一個默認輸出

// bad
import * as myObject from './importModule';

// good
import myObject from './importModule';

若是模塊默認輸出一個函數,首字母小寫

若是模塊默認輸出一個對象,首字母大寫

ESLint 的使用

語法規則和代碼風格檢測工具,確保語法正確,風格統一

安裝:npm i -g eslint

安裝插件:

npm i -g eslint-config-airbnb npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react

根目錄新建.eslintrc文件配置

{ "extends": "eslint-config-airbnb" }

建立不符合規則的js

var unusued = 'I have no purpose!';

function greet() {
    var message = 'Hello, World!';
    alert(message);
}

greet();
index.js

使用ESLint檢查:eslint index.js

$

相關文章
相關標籤/搜索