ECMAScript 6 入門
這篇文章不錯,下面的只是隨手記。
塊級作用域
能用
let
的地方,就不要用var
,可以解決i
異常的問題。少用立即執行函數表達式
(IIFE)
。考慮到環境導致的行為差異太大,應該避免在塊級作用域內聲明函數。如果確實需要,也應該寫成函數表達式,而不是函數聲明語句。
允許聲明函數的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
const
的作用域與let
命令相同:只在聲明所在的塊級作用域內有效。const
命令聲明的常量也是不提升,同樣存在暫時性死區,只能在聲明的位置后面使用。提倡多用const
,安全性高。const
一般用于基本類型,對象類型要慎用,可能達不到預期的效果。因為本質是地址不變,地址指向的內容不能保證不變。對象的凍結,應該用
Object.freeze
方法,采用遞歸的方法,將對象和屬性都凍結。
let constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
-
let
命令、const
命令、class
命令聲明的全局變量,不屬于頂層對象的屬性。多用這些關鍵字來聲明變量,減少對window
頂層對象的影響。
解構賦值
主要用在數組和對象上,比較方便。以下幾個方面推薦使用:
- 輸入模塊的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
- 遍歷Map結構
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
// 獲取鍵名
for (let [key] of map) {
// ...
}
// 獲取鍵值
for (let [,value] of map) {
// ...
}
- 提取JSON數據
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData; // data是模式匹配,number是變量
console.log(id, status, number);
// 42, "OK", [867, 5309]
- 從函數返回多個值
// 返回一個數組
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一個對象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
- 函數參數的默認值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
- 交換變量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
Unicode 表示法
- 將將碼點放入大括號,可以表示
4
字節的Unicode
字符
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
'\u{1F680}' === '\uD83D\uDE80' // true 這是老的表示方法,現狀可以不用了,直接用{}方便
-
String.fromCharCode
方法,用于從碼點返回對應字符;(類方法)codePointAt
方法會正確返回32
位的UTF-16
字符的碼點(實例方法)。這兩個方法是一對的,支持4
字節的Unicode
字符。
var s = '??a';
s.codePointAt(0).toString(16) // "20bb7";??
s.codePointAt(2).toString(16) // "61" ; a;參數序號仍然不對,這是缺點
String.fromCodePoint(0x20BB7) // "??"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' // true
- 字符串增加了遍歷器接口,可以被
for...of
循環遍歷,并且可以識別大于0xFFFF
的碼點(4字節),傳統的for
循環無法識別這樣的碼點。
var text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "??"
字符串新增方法
- 判斷包含,開頭,結尾。以前只有
indexOf
,現在有新方法
includes():
返回布爾值,表示是否找到了參數字符串。
startsWith():
返回布爾值,表示參數字符串是否在源字符串的頭部。
endsWith():
返回布爾值,表示參數字符串是否在源字符串的尾部。
var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
-
repeat
方法返回一個新字符串,表示將原字符串重復n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
- 模板字符串
(template string)
是增強版的字符串,用反引號(`)標識。它可以當作普通字符串使用,也可以用來定義多行字符串,或者在字符串中嵌入變量。這個在格式化輸出的時候很有用,推薦使用。
// 普通字符串
`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}?`
-
String.raw()
轉義字符串,可以少寫一個\;也可以理解為輸出原來的樣子,用在模板字符串,format
格式化輸出中。
String.raw `Hi\n!`;
// "Hi\\n!",這里得到的不是 Hi 后面跟個換行符,而是跟著 \ 和 n 兩個字符
String.raw `Hi\u000A!`;
// "Hi\\u000A!",同上,這里得到的會是 \、u、0、0、0、A 6個字符,
// 任何類型的轉義形式都會失效,保留原樣輸出,不信你試試.length
let name = "Bob";
String.raw `Hi\n${name}!`;
// "Hi\\nBob!",內插表達式還可以正常運行
String.raw({raw: "test"}, 0, 1, 2);
// "t0e1s2t",我認為你通常不需要把它當成普通函數來調用
// String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
// String.raw({raw: ['Hi ', '!']}, 5); // "Hi 5!"
// 這個比較難理解,不要這樣用
Number新增特性
-
ES6
提供了二進制和八進制數值的新的寫法,分別用前綴0b(或0B)
和0o(或0O)
表示。
0b111110111 === 503 // true
0o767 === 503 // true
- 如果要將
0b和0o
前綴的字符串數值轉為十進制,要使用Number
方法。
Number('0b111'); // 7
Number('0o10'); // 8
-
Number.isFinite()
用來檢查一個數值是否為有限的(finite)
。
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
-
Number.isNaN()
用來檢查一個值是否為NaN
。
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true'/0) // true
Number.isNaN('true'/'true') // true
-
ES6
將全局方法parseInt()
和parseFloat()
,移植到Number
對象上面,行為完全保持不變。這樣做的目的,是逐步減少全局性方法,使得語言逐步模塊化。
// ES5的寫法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
// ES6的寫法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
-
Number.isInteger()
用來判斷一個值是否為整數。需要注意的是,在JavaScript
內部,整數和浮點數是同樣的儲存方法,所以3和3.0
被視為同一個值。
Number.isInteger(25) // true
Number.isInteger(25.0) // true;這個要注意
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false
-
ES6
在Number
對象上面,新增一個極小的常量Number.EPSILON
。引入一個這么小的量的目的,在于為浮點數計算,設置一個誤差范圍。我們知道浮點數計算是不精確的。但是如果這個誤差能夠小于Number.EPSILON
,我們就可以認為得到了正確結果。
0.1 + 0.2
// 0.30000000000000004
0.1 + 0.2 - 0.3
// 5.551115123125783e-17
5.551115123125783e-17.toFixed(20)
// '0.00000000000000005551'
-
ES6
引入了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
這兩個常量,用來表示這個范圍的上下限。Number.isSafeInteger()
則是用來判斷一個整數是否落在這個范圍之內。
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
// true
Number.MAX_SAFE_INTEGER === 9007199254740991
// true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
// true
Number.MIN_SAFE_INTEGER === -9007199254740991
// true
Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
Number.isSafeInteger(Infinity) // false
Number.isSafeInteger(-Infinity) // false
Number.isSafeInteger(3) // true
Number.isSafeInteger(1.2) // false
Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger(9007199254740992) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
-
Math.trunc
方法用于去除一個數的小數部分,返回整數部分。
Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0
// 對于非數值,Math.trunc內部使用Number方法將其先轉為數值。
Math.trunc('123.456')
// 123
// 對于空值和無法截取整數的值,返回NaN。
Math.trunc(NaN); // NaN
Math.trunc('foo'); // NaN
Math.trunc(); // NaN
-
Math.sign
方法用來判斷一個數到底是正數、負數、還是零。
Math.sign(-5) // -1;負數
Math.sign(5) // +1;正數
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.sign('foo'); // NaN
Math.sign(); // NaN
- Math.cbrt方法用于計算一個數的立方根。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948734
// 對于非數值,Math.cbrt方法內部也是先使用Number方法將其轉為數值。
Math.cbrt('8') // 2
Math.cbrt('hello') // NaN
-
Math.hypot
方法返回所有參數的平方和的平方根。
Math.hypot(3, 4); // 5;勾三股四玄五
Math.hypot(3, 4, 5); // 7.0710678118654755
Math.hypot(); // 0
Math.hypot(NaN); // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5'); // 7.0710678118654755
Math.hypot(-3); // 3
-
ES2016
新增了一個指數運算符(**)
。指數運算符可以與等號結合,形成一個新的賦值運算符(**=)
。
2 ** 2 // 4
2 ** 3 // 8
let a = 1.5;
a **= 2;
// 2.25;等同于 a = a * a;
let b = 4;
b **= 3;
// 64; 等同于 b = b * b * b;
Array新增特性
-
Array.from
方法用于將兩類對象轉為真正的數組:類似數組的對象(array-like object)
和可遍歷(iterable)
的對象(包括ES6新增的數據結構Set和Map)
。這個方法很有用,數組有map
方法,這個可是函數式編程的重點內容啊。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
// 實際應用中,常見的類似數組的對象是DOM操作返回的NodeList集合,以及函數內部的arguments對象。Array.from都可以將它們轉為真正的數組。
// NodeList對象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
// arguments對象
function foo() {
var args = Array.from(arguments);
// ...
}
// 只要是部署了Iterator接口的數據結構,Array.from都能將其轉為數組。
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
// 所謂類似數組的對象,本質特征只有一點,即必須有length屬性。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
// Array.from還可以接受第二個參數,作用類似于數組的map方法,用來對每個元素進行處理,將處理后的值放入返回的數組。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
// Array.from()的另一個應用是,將字符串轉為數組,然后返回字符串的長度。因為它能正確處理各種Unicode字符,可以避免JavaScript將大于\uFFFF的Unicode字符,算作兩個字符的bug。
function countSymbols(string) {
return Array.from(string).length;
}
- 擴展運算符
(...)
也可以將某些數據結構轉為數組。這個也是比較方便的,可以多用。比如,函數定義直接寫成function foo(...args) {}
// arguments對象
function foo() {
var args = [...arguments];
}
// NodeList對象
[...document.querySelectorAll('div')]
-
Array.of
方法用于將一組值,轉換為數組。基本上可以用來替代Array()
或new Array()
,并且不存在由于參數不同而導致的重載。它的行為非常統一。這個函數可以多用用。
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
- 數組實例的find()和findIndex(),用來查找元素,可以和原先的indexOf()按照使用場景,靈活使用。
// find 返回元素
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
// findeIndex返回下標
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
// indexOf方法無法識別數組的NaN成員,但是findIndex方法可以借助Object.is方法做到。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
-
fill
方法使用給定值,填充一個數組。在初始化數組的時候需要用,減少一些for
循環
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
-
entries(),keys()和values()
——用于遍歷數組,結合for...of
一起使用
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
-
Array.prototype.includes
方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes
方法類似。該方法屬于ES7
,但Babel
轉碼器已經支持。chrom
瀏覽器可以使用。
有這個之后,檢查元素的存在與否,語義就更直觀了,推薦使用。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true
- 數組的空位指,數組的某一個位置沒有任何值。注意,空位不是undefined,一個位置的值等于undefined,依然是有值的。空位是沒有任何值,in運算符可以說明這一點。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
-
ES6
則是明確將空位轉為undefined
。由于空位的處理規則非常不統一,所以建議避免出現空位。統一用undefined
代替空位是個好習慣。
// Array.from方法會將數組的空位,轉為undefined,也就是說,這個方法不會忽略空位。
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
// 擴展運算符(...)也會將空位轉為undefined。
[...['a',,'b']]
// [ "a", undefined, "b" ]
// entries()、keys()、values()、find()和findIndex()會將空位處理成undefined。
// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
// keys()
[...[,'a'].keys()] // [0,1]
// values()
[...[,'a'].values()] // [undefined,"a"]
// find()
[,'a'].find(x => true) // undefined
// findIndex()
[,'a'].findIndex(x => true) // 0
函數新增特性
-
ES6
允許為函數的參數設置默認值,即直接寫在參數定義的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
- 參數默認值可以與解構賦值的默認值,結合起來使用。
function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 報錯
上面的寫法不能省略第二個參數,如果結合函數參數的默認值,就可以省略第二個參數。這時,就出現了雙重默認值。
function fetch(url, { method = 'GET' } = {}) {
console.log(method);
}
fetch('http://example.com')
// "GET"
通常情況下,定義了默認值的參數,應該是函數的尾參數。
ES6
引入rest
參數(形式為“...變量名”),用于獲取函數的多余參數,這樣就不需要使用arguments
對象了。rest
參數搭配的變量是一個數組,該變量將多余的參數放入數組中。這種寫法更簡潔明確,推薦使用。
// arguments變量的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest參數的寫法
const sortNumbers = (...numbers) => numbers.sort();
- 注意,
rest
參數之后不能再有其他參數(即只能是最后一個參數),否則會報錯。函數的length
屬性,不包括rest
參數。
// 報錯
function f(a, ...b, c) {
// ...
}
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
- 擴展運算符(spread)是三個點
(...)
。它好比rest
參數的逆運算,將一個數組轉為用逗號分隔的參數序列。該運算符主要用于函數調用。
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
// 擴展運算符與正常的函數參數可以結合使用,非常靈活。
function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...[3]);
- 由于擴展運算符可以展開數組,所以不再需要
apply
方法,將數組轉為函數的參數了。
// ES5的寫法
Math.max.apply(null, [14, 3, 77])
// ES6的寫法
Math.max(...[14, 3, 77])
// 等同于
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);
- 函數的
name
屬性,返回該函數的函數名。
function foo() {}
foo.name // "foo"
-
ES6
允許使用“箭頭”(=>)
定義函數。
// 正常函數寫法
var result = values.sort(function (a, b) {
return a - b;
});
// 箭頭函數寫法
var result = values.sort((a, b) => a - b);
- 函數調用自身,稱為遞歸。如果尾調用自身,就稱為尾遞歸。
遞歸非常耗費內存,因為需要同時保存成千上百個調用幀,很容易發生“棧溢出”錯誤(stack overflow
)。但對于尾遞歸來說,由于只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤。
// 一個階乘函數,計算n的階乘,最多需要保存n個調用記錄,復雜度 O(n) 。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
// 如果改寫成尾遞歸,只保留一個調用記錄,復雜度 O(1) 。
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
ES6 中只要使用尾遞歸,就不會發生棧溢出,相對節省內存。
ES6
的尾調用優化只在嚴格模式下開啟,正常模式是無效的。ES2017
允許函數的最后一個參數有尾逗號(trailing comma)
。這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
對象擴展
- 屬性、函數簡潔表示法:
key
和value
一樣的話,只寫一遍
var ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
- 屬性名表達式,用
[]
包起來
var lastWord = 'last word';
var a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
-
Object.is
用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)
的行為基本一致。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
-
Object.assign
方法用于對象的合并,將源對象(source)
的所有可枚舉屬性,復制到目標對象(target)
。第一個參數是目標對象,后面的參數都是源對象。
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
- 擴展運算符可以用于合并兩個對象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
Module 的加載實現
瀏覽器加載
- 要加入
type="module"
屬性,效果等同于打開了<script>
標簽的defer
屬性。
<script type="module" src="foo.js"></script>
<!-- 等同于 -->
<script type="module" src="foo.js" defer></script>
ES6 模塊與 CommonJS 模塊的差異
CommonJS
模塊輸出的是一個值的拷貝,ES6
模塊輸出的是值的引用。CommonJS
模塊是運行時加載,ES6
模塊是編譯時輸出接口。
ES6
模塊的設計思想,是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。
ES6
的模塊自動采用嚴格模式,不管你有沒有在模塊頭部加上"use strict";
ES6
模塊之中,頂層的this
指向undefined
,即不應該在頂層代碼使用this。
export 命令
一個模塊就是一個獨立的文件。該文件內部的所有變量,外部無法獲取。如果你希望外部能夠讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。
- 推薦的做法是在文件最后集中導出,使用者查看更方便
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
- 也可以直接在導出變量的前面加export關鍵字,這樣做比較省事
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
- export命令除了輸出變量,還可以輸出函數或類(class)。通常情況下,export輸出的變量就是本來的名字,但是可以使用as關鍵字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
import 命令
- 通常的做法是用一個{}導出具體的變量名或者函數名。只需要導出要用的函數或者變量,用不到的就不用導出了。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
- 如果想為輸入的變量重新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名。
import { lastName as surname } from './profile';
- 除了指定加載某個輸出值,還可以使用整體加載,即用星號(*)指定一個對象,所有輸出值都加載在這個對象上面。這種寫法不推薦。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
// main.js
import * as circle from './circle';
console.log('圓面積:' + circle.area(4));
console.log('圓周長:' + circle.circumference(14));
- 為了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,為模塊指定默認輸出。需要注意的是,這時import命令后面,不使用大括號。默認導出的函數名或者類名都不起效果,名字由使用者在外面自己起,相當于匿名的函數或者類。這個有點像ES5的習慣,在有限的場景使用,不推薦。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
- 如果在一個模塊之中,先輸入后輸出同一個模塊,import語句可以與export語句寫在一起。
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };
跨模塊常量
- 如果想設置跨模塊的常量(即跨多個文件),或者說一個值要被多個模塊共享,可以采用下面的寫法。
// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模塊
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模塊
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
- 如果要使用的常量非常多,可以建一個專門的constants目錄,將各種常量寫在不同的文件里面,保存在該目錄下。
// constants/db.js
export const db = {
url: 'http://my.couchdbserver.local:5984',
admin_username: 'admin',
admin_password: 'admin password'
};
// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];
// constants/index.js
export {db} from './db';
export {users} from './users';
// main.js使用的時候,直接加載index.js就可以了。
import {db, users} from './index';
Set
ES6 提供了新的數據結構 Set。它類似于數組,但是成員的值都是唯一的,沒有重復的值。
- Set 函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作為參數,用來初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
- Set 結構的實例有以下屬性。
Set.prototype.constructor:構造函數,默認就是Set函數。
Set.prototype.size:返回Set實例的成員總數。
- Set 實例的方法
add(value):添加某個值,返回Set結構本身。
delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
has(value):返回一個布爾值,表示該值是否為Set的成員。
clear():清除所有成員,沒有返回值。
- 下面是一個對比,看看在判斷是否包括一個鍵上面,Object結構和Set結構的寫法不同。
// 對象的寫法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {
// do something
}
// Set的寫法
const properties = new Set();
properties.add('width');
properties.add('height');
if (properties.has(someName)) {
// do something
}
- Array.from方法可以將 Set 結構轉為數組。這就提供了去除數組重復成員的另一種方法。
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
- 如果想在遍歷操作中,同步改變原來的 Set 結構,目前沒有直接的方法,但有兩種變通方法。一種是利用原 Set 結構映射出一個新的結構,然后賦值給原來的 Set 結構;另一種是利用Array.from方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
- 擴展運算符(...)可以展開set為數組,也展示了一種去除數組重復成員的方法。
// 去除數組的重復成員
[...new Set(array)]
Map
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。
為了解決這個問題,ES6 提供了 Map 數據結構。它類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。
- size屬性返回 Map 結構的成員總數。
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
- set方法設置鍵名key對應的鍵值為value,然后返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。
const m = new Map();
m.set('edition', 6) // 鍵是字符串
m.set(262, 'standard') // 鍵是數值
m.set(undefined, 'nah') // 鍵是 undefined
set方法返回的是當前的Map對象,因此可以采用鏈式寫法。
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
- get方法讀取key對應的鍵值,如果找不到key,返回undefined。
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 鍵是函數
m.get(hello) // Hello ES6!
- has方法返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
- delete方法刪除某個鍵,返回true。如果刪除失敗,返回false。
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
- clear方法清除所有成員,沒有返回值。
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
- Map 結構原生提供三個遍歷器生成函數和一個遍歷方法。
keys():返回鍵名的遍歷器。
values():返回鍵值的遍歷器。
entries():返回所有成員的遍歷器。
forEach():遍歷 Map 的所有成員。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Class 的基本語法
ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。
//定義類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
- 與 ES5 一樣,在“類”的內部可以使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
- 如果某個方法之前加上星號(*),就表示該方法是一個 Generator 函數。
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
}
// hello
// world
- 類相當于實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
- 類的實例屬性可以用等式,寫入類的定義之中。
class MyClass {
myProp = 42;
constructor() {
console.log(this.myProp); // 42
}
}
- 類的靜態屬性只要在上面的實例屬性寫法前面,加上static關鍵字就可以了。
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
- Class 可以通過extends關鍵字實現繼承。子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然后對其進行加工。如果不調用super方法,子類就得不到this對象。
class Point {
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調用父類的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調用父類的toString()
}
}
編程風格
- let 取代 var;
在let和const之間,建議優先使用const,尤其是在全局環境,不應該設置變量,只應設置常量。
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];
- 靜態字符串一律使用單引號或反引號,不使用雙引號。動態字符串使用反引號。
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`;
const c = 'foobar';
解構賦值
- 使用數組成員對變量賦值時,優先使用解構賦值。
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
- 函數的參數如果是對象的成員,優先使用解構賦值。
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {
const { firstName, lastName } = obj;
}
// best
function getFullName({ firstName, lastName }) {
}
- 如果函數返回多個值,優先使用對象的解構賦值,而不是數組的解構賦值。這樣便于以后添加返回值,以及更改返回值的順序。
// bad
function processInput(input) {
return [left, right, top, bottom];
}
// good
function processInput(input) {
return { left, right, top, bottom };
}
const { left, right } = processInput(input);
對象
- 單行定義的對象,最后一個成員不以逗號結尾。多行定義的對象,最后一個成員以逗號結尾。
// bad
const a = { k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};
// good
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
- 對象盡量靜態化,一旦定義,就不得隨意添加新的屬性。如果添加屬性不可避免,要使用Object.assign方法。
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
- 如果對象的屬性名是動態的,可以在創造對象的時候,使用屬性表達式定義。
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
- 對象的屬性和方法,盡量采用簡潔表達法,這樣易于描述和書寫。
var ref = 'some value';
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
數組
- 使用擴展運算符(...)拷貝數組。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
- 使用Array.from方法,將類似數組的對象轉為數組。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
函數
- 立即執行函數可以寫成箭頭函數的形式。
(() => {
console.log('Welcome to the Internet.');
})();
- 那些需要使用函數表達式的場合,盡量用箭頭函數代替。因為這樣更簡潔,而且綁定了this。
// bad
[1, 2, 3].map(function (x) {
return x * x;
});
// 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);
- 所有配置項都應該集中在一個對象,放在最后一個參數,布爾值不可以直接作為參數。
// bad
function divide(a, b, option = false ) {
}
// good
function divide(a, b, { option = false } = {}) {
}
- 不要在函數體內使用arguments變量,使用rest運算符(...)代替。因為rest運算符顯式表明你想要獲取參數,而且arguments是一個類似數組的對象,而rest運算符可以提供一個真正的數組。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
- 使用默認值語法設置函數參數的默認值。
// bad
function handleThings(opts) {
opts = opts || {};
}
// good
function handleThings(opts = {}) {
// ...
}
Map結構
注意區分Object和Map,只有模擬現實世界的實體對象時,才使用Object。如果只是需要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
- 總是用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運算的危險。
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
return this._queue[0];
}
// good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}
模塊
- 使用import取代require。
// bad
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// good
import { func1, func2 } from 'moduleA';
- 使用export取代module.exports
如果模塊只有一個輸出值,就使用export default,如果模塊有多個輸出值,就不使用export default,export default與普通的export不要同時使用。
// 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 './importModule';
// good
import myObject from './importModule';
- 如果模塊默認輸出一個函數,函數名的首字母應該小寫。
function makeStyleGuide() {
}
export default makeStyleGuide;
- 如果模塊默認輸出一個對象,對象名的首字母應該大寫。
const StyleGuide = {
es6: {
}
};
export default StyleGuide;
ESLint的使用
- 安裝ESLint。
$ npm i -g eslint
- 安裝Airbnb語法規則。
$ npm i -g eslint-config-airbnb
- 在項目的根目錄下新建一個.eslintrc文件,配置ESLint。
{
"extends": "eslint-config-airbnb"
}
- 使用ESLint檢查文件。
$ eslint xxx.js