參考資料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"
},
ES5只有兩種聲明變量方式:var命令 和 function 命令。
ES6還有四種聲明變量方式:let命令 、Const命令、import命令、class命令
用來聲明變量,相似於var,聲明的變量只在代碼快({}表示代碼塊)內有效。而且不會受到變量提高的影響。
若是區塊中存在let和const命令,就會造成封閉做用域,在聲明以前使用變量就會報錯。這種行爲稱爲:暫時性死區
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
let不容許重複聲明,不能在函數內部從新聲明參數。有塊級做用域,就再也不須要 當即執行函數表達式(IIFE)了
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");
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
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 屬性,返回該函數的函數名。
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屬性。
Object.is用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)一致。
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值的屬性,按照生成時間排序
用來讀取或設置當前對象的 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
與__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.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,沒法轉爲對象,會報錯。
返回一個數組,成員是參數對象自身的鍵名(不含繼承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.keys(obj2)); // [ 'Old', 'Old2' ]
返回一個數組,成員是參數對象自身的鍵值(不含繼承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.values(obj2)); // [ 12, 13 ]
返回一個數組,成員是參數對象自身的鍵值對(不含繼承)
let obj1 = {Name: "C"}; let obj2 = {Old: 12, Old2 : 13}; Object.setPrototypeOf(obj2, obj1); console.log(Object.entries(obj2)); // [ [ 'Old', 12 ], [ 'Old2', 13 ] ]
返回某個對象屬性的描述對象(不含繼承)
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 }
ES6引入了一種原始數據類型Symbol,表示獨一無二的值。
Symbol值經過Symbol函數生成。凡是屬性名屬於Symbol類型,都是獨一無二的,能夠保證不會產生衝突。
let s = Symbol(); typeof s // "symbol"
Symbol函數前不能使用new命令,由於生成Symbol是一個原始類型的值,不是對象。
Symbol能夠接受一個字符串做爲參數,用來表示描述或區分。若是Symbol參數是一個對象,則會調用該對象的toString方法。
Symbol不能與其餘類型的值進行運算,可是能夠顯示轉換爲字符串,也能夠轉換Boolean類型,但不能轉爲數值。
因爲每一個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值。若是有就返回,沒有就新建。
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
能夠返回一個已登記的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
當其餘對象使用instanceof運算符時,判斷是否爲該對象的實例時,會調用此方法。
class MyClass{ [Symbol.hasInstance](foo){ return foo instanceof Array; } } var result = [1,2,3] instanceof new MyClass(); console.log(result); //true
布爾值屬性,表示該對象使用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; } }
建立實例時,默認會調用這個方法,使用這個屬性返回的函數當作構造函數,來建立新的實例對象。
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
當執行str.match(myObject)時,若是該屬性存在,會調用它,返回該方法的返回值。
class MyMatcher { [Symbol.match](string) { return 'hello world'.indexOf(string); } } 'e'.match(new MyMatcher()) // 1
當該對象被string.prototype.replace方法調用時,返回該方法的返回值。
const x = {}; x[Symbol.replace] = (...s) => console.log(s); 'Hello'.replace(x, 'World') // ["Hello", "World"]
該對象被String.prototype.search方法調用時,會返回該方法的返回值。
class MySearch { constructor(value) { this.value = value; } [Symbol.search](string) { return string.indexOf(this.value); } } 'foobar'.search(new MySearch('foo')) // 0
該對象被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'
指向對象的默認遍歷器方法。對象進行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被調用時,會接受一個字符串參數,表示當前運算的模式,一共有三種模式。
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'
在該對象上面調用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]"
相似於數組,全部成員的值都是惟一的,沒有重複的值。
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與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):是否包含指定成員
鍵值對的集合,鍵的範圍不限於字符串。提供了值-值對應。
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結構與Map結構相似,用於生成鍵值對的集合
WeakMap與Map的區別有兩點
1. WeakMap只接受對象做爲鍵名,不接受其餘的值做爲鍵名
2. WeakMap的鍵名所指的對象,不計入垃圾回收機制
用來修改某些操做的默認行爲,在目標對象以前架設一層攔截,外界對對象的訪問都必須先經過這層攔截。
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實例
let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked
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對象與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);
異步編程的一種解決方案,比回調函數和事件,更合理和強大。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實例添加狀態改變時的回調函數。第一個參數是Resolved狀態的回調,第二個是Rejected狀態的回調
then方法返回的是新的Promise實例,能夠採用鏈式的then,來指定一組按照次序調用的回調函數。
var promise = new Promise((resolve,reject)=>resolve());
promise.then(()=>console.log(1)).then(()=>console.log(2));
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實例,包裝成一個新的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實例,包裝成一個新的Promise實例。
var p = Promise.race([p1, p2, p3]);
有一個實例率先改變狀態,p的狀態就跟着改變,並執行回調函數。
將現有的對象轉爲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實例,狀態爲rejected
var p = Promise.reject('出錯了'); // 等同於 var p = new Promise((resolve, reject) => reject('出錯了')) p.then(null, function (s) { console.log(s) }); // 出錯了
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();
用於指定無論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("圖片加載失敗"));
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屬性的數據結構,就稱爲部署了遍歷器的接口。調用這個接口,就會返回一個遍歷器對象。
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是無效的。
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. 其餘場合
數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口
new Map([['a',1],['b',2]])
)字符串是一個相似的數組對象,也原生具備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
Symbol.iterator方法的最簡單實現,仍是使用Generator函數。
let obj = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; } }; for (let x of obj) { console.log(x); } // hello // world
遍歷器對象除了具備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; }
一個數據結構只要部署了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函數是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) { ··· }
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函數就是遍歷器生成函數,所以能夠把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屬性,執行後返回自身。
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方法的參數。
循環能夠自動遍歷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函數返回的遍歷器對象,都有一個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函數返回的遍歷器對象,還有一個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代碼塊執行完再執行。
若是在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函數,可使用星號來表示函數,一下兩個對象是等效的。
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函數的返回的遍歷器對象,是函數的實例,繼承了prototype方法。
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); }
異步就是一個任務不是連續完成的,先執行第一段,等作好準備,在執行第二段。
好比,任務是讀取文件進行處理,第一段就是發送請求讀取文件,等系統返回文件,在執行第二段處理文件。
這種不連續的操做,就稱爲異步操做。
Javascript語言對異步編程的實現,就是用回調函數。把任務的第二部分單獨寫在一個函數裏面。
從新執行這個任務,就直接調用這個函數。好比讀取文件的操做。
fs.readFile('/etc/passwd', 'utf-8', function (err, data) { if (err) throw err; console.log(data); });
redFile第三個參數,就是回調函數,也就是任務的第二段
回調函數自己並無什麼問題,可是若是出現多個回調函數嵌套,則會變成橫向發展沒法管理。
由於多個異步操做造成了強耦合,只要有一個操做須要爲修改,它的上下層函數,就都要跟着改變。
這種狀況,被稱爲回調函數地獄「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包裝了,語以變得不清晰。
協程,意思是多個線程相互協做,完成異步任務。
協程大體的流程以下:
1. 協程A開始執行。
2. 協程A執行到一半,進入暫停,執行權轉移到協程B。
3. (一段時間後)協程B交還執行權。
4. 協程A恢復執行。
讀取文件的協程寫法
function *asyncJob() { // ...其餘代碼 var f = yield readFile(fileA); // ...其餘代碼 }
asyncJob是一個協程,其中yield是關鍵。表示執行到此處,執行權將交給其餘線程。yield是異步兩個階段的分界線。
協程遇到yield命令就暫停,等到執行權返回,再從暫停的地方繼續日後執行。
它的最大優勢,就是代碼的寫法很是像同步操做。
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是自動執行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模塊是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對象
ES2017標準引入async函數
http://es6.ruanyifeng.com/#docs/async
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方法是類的默認方法,經過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,這樣就不會找不到了
或者直接使用箭頭函數
class Logger { constructor() { this.printName = this.printName.bind(this); this.printName = (name = 'there') => { this.print(`Hello ${name}`); } } }
若是某個方法以前加上星號(*),就表示該方法是一個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 }
若是在一個方法前面加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用。稱爲靜態方法
class Foo{ static classMethod(){ return "Hello"; } } console.log(Foo.classMethod()); //Hello
若是靜態方法包含this關鍵字,這個this指向類而不是實例
Foo.prop = 1; 直接類後面.就能夠建立靜態屬性
若是構造函數不是經過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經過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關鍵字既能夠當作函數,也能夠當作對象
當作函數的時候,表示父類的構造函數。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();
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
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和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命令只能存在於模塊頂層,不能再函數裏面寫。
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命令,能夠爲模塊指定默認輸出。import能夠爲該模塊提供任何名字。export default也能夠用於非匿名函數 export default foo;
//Demo2 export default function(){ console.log("foo"); } //Demo1 import foo from './Demo2' foo(); //foo
一個模塊只能有一個默認輸出。在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();
瀏覽器加載ES6模塊,也使用<script>標籤,可是必須加入type="module"屬性。注意:只有谷歌瀏覽器支持ES6寫法,import這種只有使用webpack進行打包纔可使用。
<script type="module" src="dist/Demo1.js"></script>
瀏覽器對於帶有type="module"的script都是異步加載,不會形成堵塞瀏覽器,等同於打開了defer屬性
若是頁面有多個moudle會按照頁面出現順序依次加載。使用async屬性,不會按照出現順序加載。
他們有兩大重要差別
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';
若是模塊默認輸出一個函數,首字母小寫
若是模塊默認輸出一個對象,首字母大寫
語法規則和代碼風格檢測工具,確保語法正確,風格統一
安裝: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();
使用ESLint檢查:eslint index.js
$