JavaScript:ES6的一些習慣

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
  • ES6Number對象上面,新增一個極小的常量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_INTEGERNumber.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',
);

對象擴展

  • 屬性、函數簡潔表示法:keyvalue一樣的話,只寫一遍
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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • 一、ES6簡介 ? 歷時將近6年的時間來制定的新 ECMAScript 標準 ECMAScript 6(亦稱 ...
    一歲一枯榮_閱讀 6,105評論 8 25
  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創建并操作數組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,263評論 0 4
  • 人生能有幾個十年?即使能夠活到百歲,也正如楊絳先生所說的那樣:“少年貪玩,青年迷戀愛情,壯年汲汲于成名成家,暮年自...
    方素衣閱讀 650評論 3 50
  • 作為子女,要學規矩,其實這兩句話全完是從夫人哪里學到的,去年確定了戀愛關系,大年初三,就去夫人家了,一家人對于我的...
    可樂少年c閱讀 1,738評論 0 2
  • 每年的畢業季,都會讓人感觸頗深。你我不約而同想到自己的畢業時光 。我們真的是有默契,不是嗎? 畢業嘛,多少都會有傷...
    亦大乎閱讀 364評論 0 0