ES6入門摘錄筆記(二)

三,字符串擴展

3.1 Unicode表示法

ES6 做出了改進,只要將碼點放入大括號,就能正確解讀該字符。
有了這種表示法之后,JavaScript 共有6種方法可以表示一個字符。

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

3.2 codePointAt()

這篇文章講的特別好!!解釋了什么事unicode,utf-8,utf-16以及javascript的實現。

對于四字節字符,charAt方法無法讀取整個字符,charCodeAt方法只能分別返回前兩個字節和后兩個字節的值。ES6提供了codePointAt方法,能夠正確處理4個字節儲存的字符,返回一個字符的碼點。

let s = '??a';
s.codePointAt(0) // 134071

codePointAt方法的參數,仍然是不正確的。比如,上面代碼中,字符a在字符串s的正確位置序號應該是1,但是必須向codePointAt方法傳入2。解決這個問題的一個辦法是使用for...of循環,因為它會正確識別32位的UTF-16字符。

codePointAt方法是測試一個字符由兩個字節還是由四個字節組成的最簡單方法。

function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}

3.3 String.fromCodePoint()

ES6提供了String.fromCodePoint方法,可以識別大于0xFFFF的字符,彌補了String.fromCharCode方法的不足。在作用上,正好與codePointAt方法相反。注意,fromCodePoint方法定義在String對象上,而codePointAt方法定義在字符串的實例對象上。

String.fromCodePoint(0x20BB7)
// "??"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true

上面代碼中,如果String.fromCodePoint方法有多個參數,則它們會被合并成一個字符串返回。

3.4 字符串遍歷接口

字符串可以被for...of循環遍歷。最大的優點是可以識別大于0xFFFF的碼點,傳統的for循環無法識別這樣的碼點。

3.5 at()

目前,有一個提案,提出字符串實例的at方法,可以識別 Unicode 編號大于0xFFFF的字符,返回正確的字符。ES5的charAt方法有局限。這個方法可以通過墊片庫實現。

3.6 normalize()

ES6 提供字符串實例的normalize()方法,用來將字符的不同表示方法統一為同樣的形式,這稱為 Unicode 正規化。

'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true

3.7 includes(), startsWith(), endsWith()

傳統上,JavaScript只有indexOf方法,可以用來確定一個字符串是否包含在另一個字符串中。ES6又提供了三種新方法。

let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

上面代碼表示,使用第二個參數n時,endsWith的行為與其他兩個方法有所不同。它針對前n個字符,而其他兩個方法針對從第n個位置直到字符串結束。

3.8 repeat()

repeat方法返回一個新字符串,表示將原字符串重復n次。

'x'.repeat(3) // "xxx"

3.9 padStart(),padEnd()

'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'

上面代碼中,padStart和padEnd一共接受兩個參數,第一個參數用來指定字符串的長度,第二個參數是用來補全的字符串。

3.10 模板字符串(template string)

模板字符串是增強版的字符串,用反引號(`)標識。它可以當作普通字符串使用,也可以用來定義多行字符串,或者在字符串中嵌入變量。如果使用模板字符串表示多行字符串,所有的空格和縮進都會被保留在輸出之中。

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

模板字符串甚至還能嵌套。

3.11 實例:模板編譯

這里是模板字符串的一個例子。
通過模板字符串,生成正式模板的實例。具體代碼看教程。

3.12 標簽模板(tagged template)

模板字符串的功能,不僅僅是上面這些。它可以緊跟在一個函數名后面,該函數將被調用來處理這個模板字符串。這被稱為“標簽模板”。標簽模板其實不是模板,而是函數調用的一種特殊形式。“標簽”指的就是函數,緊跟在后面的模板字符串就是它的參數。

alert`123`
// 等同于
alert(123)

let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

“標簽模板”的一個重要應用,就是過濾 HTML 字符串,防止用戶輸入惡意內容。
標簽模板的另一個應用,就是多語言轉換(國際化處理)。模板字符串本身并不能取代Mustache之類的模板庫,因為沒有條件判斷和循環處理功能,但是通過標簽函數,你可以自己添加這些功能。

模板處理函數的第一個參數(模板字符串數組),還有一個raw屬性。

3.13 string.raw

ES6還為原生的String對象,提供了一個raw方法。

String.raw方法,往往用來充當模板字符串的處理函數,返回一個斜杠都被轉義(即斜杠前面再加一個斜杠)的字符串,對應于替換變量后的模板字符串。

String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

3.14 模板字符串的限制

前面提到標簽模板里面,可以內嵌其他語言。但是,模板字符串默認會將字符串轉義,導致無法嵌入其他語言。為了解決這個問題,現在有一個提案,放松對標簽模板里面的字符串轉義的限制。如果遇到不合法的字符串轉義,就返回undefined,而不是報錯,并且從raw屬性上面可以得到原始字符串。注意,這種對字符串轉義的放松,只在標簽模板解析字符串時生效,不是標簽模板的場合,依然會報錯。

四,正則的擴展

4.1 RegExp 構造函數

在 ES5 中,RegExp構造函數的參數有兩種情況。第一種情況是,參數是字符串,這時第二個參數表示正則表達式的修飾符(flag); 第二種情況是,參數是一個正則表示式,這時會返回一個原有正則表達式的拷貝,但不允許此時使用第二個參數添加修飾符,否則會報錯。

ES6 改變了。如果RegExp第一個參數是一個正則對象,那么可以使用第二個參數指定修飾符。而且,返回的正則表達式會忽略原有的正則表達式的修飾符,只使用新指定的修飾符。

4.2 字符串的正則方法

4.3 u 修飾符

ES6 對正則表達式添加了u修飾符,含義為“Unicode模式”,用來正確處理大于\uFFFF的 Unicode 字符。一旦加上u修飾符號,就會修改下面這些正則表達式的行為。

var s = '??';

/^.$/.test(s) // false
/^.$/u.test(s) // true

/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true

/??{2}/.test('????') // false
/??{2}/u.test('????') // true

利用這一點,可以寫出一個正確返回字符串長度的函數。

function codePointLength(text) {
  var result = text.match(/[\s\S]/gu);
  return result ? result.length : 0;
}

還可以用spread判斷:

function length(str) {
  return [...str].length;
}

4.4 y 修飾符

ES6 還為正則表達式添加了y修飾符,叫做“粘連”(sticky)修飾符。

y修飾符的作用與g修飾符類似,也是全局匹配,后一次匹配都從上一次匹配成功的下一個位置開始。不同之處在于,g修飾符只要剩余位置中存在匹配就可,而y修飾符確保匹配必須從剩余的第一個位置開始,這也就是“粘連”的涵義。

y修飾符的設計本意,就是讓頭部匹配的標志^在全局匹配中都有效。

4.5 sticky 屬性

與y修飾符相匹配,ES6 的正則對象多了sticky屬性,表示是否設置了y修飾符。

var r = /hello\d/y;
r.sticky // true

4.6 flags 屬性

ES6 為正則表達式新增了flags屬性,會返回正則表達式的修飾符。

// ES5 的 source 屬性
// 返回正則表達式的正文
/abc/ig.source
// "abc"

// ES6 的 flags 屬性
// 返回正則表達式的修飾符
/abc/ig.flags
// 'gi'

4.7 s 修飾符

點(.)是一個特殊字符,代表任意的單個字符,但是行終止符(line terminator character)除外。但是,很多時候我們希望匹配的是任意單個字符,這時有一種變通的寫法。/foo[^]bar/.test('foo\nbar') // true
這種解決方案畢竟不太符合直覺,所以現在有一個提案,引入/s修飾符,使得.可以匹配任意單個字符。/foo.bar/s.test('foo\nbar') // true

這被稱為dotAll模式,即點(dot)代表一切字符。所以,正則表達式還引入了一個dotAll屬性,返回一個布爾值,表示該正則表達式是否處在dotAll模式。

4.8 后行斷言

JavaScript 語言的正則表達式,只支持先行斷言(lookahead)和先行否定斷言(negative lookahead),不支持后行斷言(lookbehind)和后行否定斷言(negative lookbehind)。目前,有一個提案,引入后行斷言,V8 引擎4.9版已經支持。

4.9 Unicode 屬性類

目前,有一個提案,引入了一種新的類的寫法\p{...}
\P{...},允許正則表達式匹配符合 Unicode 某種屬性的所有字符。

4.10 具名組匹配

正則表達式使用圓括號進行組匹配。const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
上面代碼中,正則表達式里面有三組圓括號。使用exec方法,就可以將這三組匹配結果提取出來。

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

組匹配的一個問題是,每一組的匹配含義不容易看出來,而且只能用數字序號引用,要是組的順序變了,引用的時候就必須修改序號。
現在有一個“具名組匹配”(Named Capture Groups)的提案,允許為每一個組匹配指定一個名字,既便于閱讀代碼,又便于引用。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

如果具名組沒有匹配,那么對應的groups對象屬性會是undefined。

解構賦值和替換
有了具名組匹配以后,可以使用解構賦值直接從匹配結果上為變量賦值。

引用
如果要在正則表達式內部引用某個“具名組匹配”,可以使用\k<組名>的寫法。數字引用(\1)依然有效。

五,數值的擴展

5.1 二進制和八進制表示法

ES6 提供了二進制和八進制數值的新的寫法,分別用前綴0b(或0B)和0o(或0O)表示。從 ES5 開始,在嚴格模式之中,八進制就不再允許使用前綴0表示,ES6 進一步明確,要使用前綴0o表示。ES5中沒有二進制的定義。

5.2 Number.isFinite(), Number.isNaN()

ES6 在Number對象上,新提供了Number.isFinite()和Number.isNaN()兩個方法。ES5中有global的傳統的全局方法isFinite()和isNaN()。
區別在于,傳統方法先調用Number()將非數值的值轉為數值,再進行判斷,而這兩個新方法只對數值有效,Number.isFinite()對于非數值一律返回false, Number.isNaN()只有對于NaN才返回true,非NaN一律返回false。

5.3 Number.parseInt(), Number.parseFloat()

ES6 將全局方法parseInt()和parseFloat(),移植到Number對象上面,行為完全保持不變。這樣做的目的,是逐步減少全局性方法,使得語言逐步模塊化。

5.4 Number.isInteger()

Number.isInteger()用來判斷一個值是否為整數。需要注意的是,在 JavaScript 內部,整數和浮點數是同樣的儲存方法,所以3和3.0被視為同一個值。

5.5 Number.EPSILON

ES6 在Number對象上面,新增一個極小的常量Number.EPSILON。根據規格,它表示1與大于1的最小浮點數之間的差。

Number.EPSILON可以用來設置“能夠接受的誤差范圍”。比如,誤差范圍設為2的-50次方(即Number.EPSILON * Math.pow(2, 2)),即如果兩個浮點數的差小于這個值,我們就認為這兩個浮點數相等。

5.6 安全整數和 Number.isSafeInteger()

JavaScript 能夠準確表示的整數范圍在-253到253之間(不含兩個端點),超過這個范圍,無法精確表示這個值。ES6引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER這兩個常量,用來表示這個范圍的上下限。

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()則是用來判斷一個整數是否落在這個范圍之內。實際使用這個函數時,需要注意。驗證運算結果是否落在安全整數的范圍內,不要只驗證運算結果,而要同時驗證參與運算的每個值。

5.7 Math對象的擴展

Math.trunc() Math.sign() Math.cbrt() Math.clz32() Math.imul() Math.fround() Math.hypot()
對數方法(1) Math.expm1()(2)Math.log1p()(3)Math.log10()(4)Math.log2()
ES6新增了6個雙曲函數方法。Math.sinh(x)等

5.8 Math.signbit()

目前,有一個提案,引入了Math.signbit() 方法判斷一個數的符號位是否設置了。

5.9 指數運算符

ES2016 新增了一個指數運算符()。指數運算符可以與等號結合,形成一個新的賦值運算符(=)。注意,在 V8 引擎中,指數運算符與Math.pow的實現不相同,對于特別大的運算結果,兩者會有細微的差異。

5.10 Integer 數據類型

JavaScript 所有數字都保存成64位浮點數,這決定了整數的精確程度只能到53個二進制位。大于這個范圍的整數,JavaScript 是無法精確表示的。
現在有一個提案,引入了新的數據類型 Integer(整數),來解決這個問題。整數類型的數據只用來表示整數,沒有位數的限制,任何位數的整數都可以精確表示。

1n + 2n // 3n
0b1101n // 二進制
0o777n // 八進制
0xFFn // 十六進制

Integer 類型不能與 Number 類型進行混合運算。相等運算符(==)會改變數據類型,也是不允許混合使用。精確相等運算符(===)不會改變數據類型,因此可以混合使用。

幾乎所有的 Number 運算符都可以用在 Integer,但是有兩個除外:不帶符號的右移位運算符>>>和一元的求正運算符+,使用時會報錯。

六,函數的擴展

6.1 函數參數的默認值

ES6 之前,不能直接為函數的參數指定默認值,只能采用變通的方法。為了避免參數y賦值了但是對應的布爾值為false的問題,通常需要先判斷一下參數y是否被賦值,如果沒有,再等于默認值。

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

ES6 允許為函數的參數設置默認值,即直接寫在參數定義的后面。function Point(x = 0, y = 0)。 參數變量是默認聲明的,所以不能用let或const再次聲明。

使用參數默認值時,函數不能有同名參數。

// 不報錯
function foo(x, x, y) {
  // ...
}
// 報錯
function foo(x, x, y = 1) {
  // ...
}

與解構賦值默認值結合使用

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

只有當函數foo的參數是一個對象時,變量x和y才會通過解構賦值生成。如果函數foo調用時沒提供參數,變量x和y就不會生成,從而報錯。通過提供函數參數的默認值,就可以避免這種情況。

參數默認值的位置
通常情況下,定義了默認值的參數,應該是函數的尾參數。因為這樣比較容易看出來,到底省略了哪些參數。如果非尾部的參數設置默認值,實際上這個參數是沒法省略的,除非顯式輸入undefined。如果傳入undefined,將觸發該參數等于默認值,null則沒有這個效果。

函數的 length 屬性
指定了默認值以后,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值后,length屬性將失真。

這是因為length屬性的含義是,該函數預期傳入的參數個數。某個參數指定默認值以后,預期傳入的參數個數就不包括這個參數了。同理,后文的 rest 參數也不會計入length屬性。如果設置了默認值的參數不是尾參數,那么length屬性也不再計入后面的參數了。

作用域

一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域(context)。等到初始化結束,這個作用域就會消失。這種語法行為,在不設置參數默認值時,是不會出現的。

var x = 1;
function f(x, y = x) {
  console.log(y);
}
f(2) // 2

上面代碼中,參數y的默認值等于變量x。調用函數f時,參數形成一個單獨的作用域。在這個作用域里面,默認值變量x指向第一個參數x,而不是全局變量x,所以輸出是2。

例子2,

let x = 1;
function f(y = x) {
  let x = 2;
  console.log(y);
}
f() // 1

如果此時,全局變量x不存在,就會報錯。

應用

function throwIfMissing() {
  throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}
foo()
// Error: Missing parameter

利用參數默認值,可以指定某一個參數不得省略,如果省略就拋出一個錯誤。注意函數名throwIfMissing之后有一對圓括號,這表明參數的默認值不是在定義時執行,而是在運行時執行。如果參數已經賦值,默認值中的函數就不會運行。

另外一個應用,可以將參數默認值設為undefined,表明這個參數是可以省略的。

6.2 rest 參數

ES6 引入 rest 參數(形式為...變量名),用于獲取函數的多余參數,這樣就不需要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多余的參數放入數組中。

arguments對象不是數組,而是一個類似數組的對象。所以為了使用數組的方法,必須使用Array.prototype.slice.call先將其轉為數組。rest 參數就不存在這個問題,它就是一個真正的數組,數組特有的方法都可以使用。

注意,rest 參數之后不能再有其他參數(即只能是最后一個參數),否則會報錯。函數的length屬性,不包括 rest 參數。

6.3 嚴格模式

從 ES5 開始,函數內部可以設定為嚴格模式。
ES2016 做了一點修改,規定只要函數參數使用了默認值、解構賦值、或者擴展運算符,那么函數內部就不能顯式設定為嚴格模式,否則會報錯。

這樣規定的原因是,函數內部的嚴格模式,同時適用于函數體和函數參數。但是,函數執行的時候,先執行函數參數,然后再執行函數體。這樣就有一個不合理的地方,只有從函數體之中,才能知道參數是否應該以嚴格模式執行,但是參數卻應該先于函數體執行。

兩種方法可以規避這種限制。第一種是設定全局性的嚴格模式,這是合法的。第二種是把函數包在一個無參數的立即執行函數里面。

6.4 name 屬性

函數的name屬性,返回該函數的函數名。這個屬性早就被瀏覽器廣泛支持,但是直到 ES6,才將其寫入了標準。

需要注意的是,ES6 對這個屬性的行為做出了一些修改。傳入匿名函數的時候:

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

如果將一個具名函數賦值給一個變量,則 ES5 和 ES6 的name屬性都返回這個具名函數原本的名字。
Function構造函數返回的函數實例,name屬性的值為anonymous。bind返回的函數,name屬性值會加上bound前綴。

6.5 箭頭函數

如果箭頭函數的代碼塊部分多于一條語句,就要使用大括號將它們括起來,并且使用return語句返回。var sum = (num1, num2) => { return num1 + num2; }
由于大括號被解釋為代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號(),否則會報錯。

如果箭頭函數只有一行語句,且不需要返回值,可以采用下面的寫法,就不用寫大括號了。let fn = () => void doesNotReturn();

箭頭函數可以與變量解構結合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

箭頭函數有幾個使用注意點。

(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。

(2)不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。

(3)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。同理super、new.target不能用。

(4)不可以使用yield命令,因此箭頭函數不能用作 Generator 函數。

this指向的固定化,并不是因為箭頭函數內部有綁定this的機制,實際原因是箭頭函數根本沒有自己的this,導致內部的this就是外層代碼塊的this。正是因為它沒有this,所以也就不能用作構造函數。由于箭頭函數沒有自己的this,所以當然也就不能用call()、apply()、bind()這些方法去改變this的指向。

(this部分這里參見教程的代碼例子,很豐富。)

6.6 綁定 this

ES7提出了“函數綁定”(function bind)運算符,用來取代call 、apply、bind
調用。雖然該語法還是ES7的一個提案,但是Babel轉碼器已經支持。

函數綁定運算符是并排的兩個冒號(::),雙冒號左邊是一個對象,右邊是一個函數。該運算符會自動將左邊的對象,作為上下文環境(即this對象),綁定到右邊的函數上面。

6.7 尾調用優化

尾調用(Tail Call) 是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數的最后一步是調用另一個函數。

function f(x){
  return g(x);
}

我們知道,函數調用會在內存形成一個“調用記錄”,又稱“調用幀”(call frame),保存調用位置和內部變量等信息。如果在函數A的內部調用函數B,那么在A的調用幀上方,還會形成一個B的調用幀。等到B運行結束,將結果返回到A,B的調用幀才會消失。所有的調用幀,就形成一個“調用棧”(call stack)。

尾調用由于是函數的最后一步操作,所以不需要保留外層函數的調用幀,因為調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就可以了。這就叫做“尾調用優化”(Tail call optimization)。注意,只有不再用到外層函數的內部變量,內層函數的調用幀才會取代外層函數的調用幀,否則就無法進行“尾調用優化”。

函數調用自身,稱為遞歸。如果尾調用自身,就稱為尾遞歸。遞歸非常耗費內存,因為需要同時保存成千上百個調用幀,很容易發生“棧溢出”錯誤(stack overflow)。但對于尾遞歸來說,由于只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤。由此可見,“尾調用優化”對遞歸操作意義重大,所以一些函數式編程語言將其寫入了語言規格。ES6 是如此,第一次明確規定,所有 ECMAScript 的實現,都必須部署“尾調用優化”。

遞歸函數的改寫。做到這一點的方法,就是把所有用到的內部變量改寫成函數的參數。這樣做的缺點就是不太直觀,第一眼很難看出來。兩個方法可以解決這個問題。方法一是在尾遞歸函數之外,再提供一個正常形式的函數。第二種方法就簡單多了,就是采用 ES6 的函數默認值。
總結一下,遞歸本質上是一種循環操作。純粹的函數式編程語言沒有循環操作命令,所有的循環都用遞歸實現,這就是為什么尾遞歸對這些語言極其重要。

ES6 的尾調用優化只在嚴格模式下開啟,正常模式是無效的。
關于尾遞歸優化的實現看教程代碼,有點小復雜。

6.8 函數參數的尾逗號

ES2017 允許函數的最后一個參數有尾逗號(trailing comma)。
此前,函數定義和調用時,都不允許最后一個參數后面出現逗號。
這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。但是 JSON 不支持尾后逗號。

6.9 catch 語句的參數

目前,有一個提案,允許try...catch結構中的catch語句調用時不帶有參數。這個提案跟參數有關,也放在這一章介紹。

七,數組的擴展

7.1 擴展運算符(spread)

...是擴展運算符。它好比 rest 參數的逆運算,將一個數組轉為用逗號分隔的參數序列。Rest操作符一般用在函數參數的聲明中,而Spread用在函數的調用中。

由于擴展運算符可以展開數組,所以不再需要apply方法,將數組轉為函數的參數了。

擴展運算符的應用
(1)復制數組
數組是復合的數據類型,直接復制的話,只是復制了指向底層數據結構的指針,而不是克隆一個全新的數組。
ES5 只能用變通方法來復制數組。

const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]

擴展運算符提供了復制數組的簡便寫法。

const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

(2)合并數組

擴展運算符提供了數組合并的新寫法。

// ES5的合并數組
arr1.concat(arr2, arr3);
// ES6的合并數組
[...arr1, ...arr2, ...arr3]

(3)與解構賦值結合

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

如果將擴展運算符用于數組賦值,只能放在參數的最后一位,否則會報錯。

(4)字符串
有一個重要的好處,那就是能夠正確識別四個字節的 Unicode 字符。

(5)實現了 Iterator 接口的對象
任何 Iterator 接口的對象(參閱 Iterator 一章),都可以用擴展運算符轉為真正的數組。如let array = [...nodeList];。對于那些沒有部署 Iterator 接口的類似數組的對象,擴展運算符就無法將其轉為真正的數組。

(6)Map 和 Set 結構,Generator 函數

7.2 Array.from()

Array.from方法用于將兩類對象轉為真正的數組:類似數組的對象(array-like object)和可遍歷(iterable)的對象(包括ES6新增的數據結構Set和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都可以將它們轉為真正的數組。

值得提醒的是,擴展運算符(...)也可以將某些數據結構轉為數組。擴展運算符背后調用的是遍歷器接口(Symbol.iterator),如果一個對象沒有部署這個接口,就無法轉換。

Array.from方法還支持類似數組的對象。所謂類似數組的對象,本質特征只有一點,即必須有length屬性。因此,任何有length屬性的對象,都可以通過Array.from方法轉為數組,而此時擴展運算符就無法轉換。

// arguments對象
function foo() {
  const args = [...arguments];
}

// NodeList對象
[...document.querySelectorAll('div')]

Array.from還可以接受第二個參數,作用類似于數組的map方法,用來對每個元素進行處理,將處理后的值放入返回的數組。

Array.from()的另一個應用是,將字符串轉為數組,然后返回字符串的長度。因為它能正確處理各種Unicode字符,可以避免JavaScript將大于\uFFFF的Unicode字符,算作兩個字符的bug。

7.3 Array.of()

Array.of方法用于將一組值,轉換為數組。Array.of總是返回參數值組成的數組。如果沒有參數,就返回一個空數組。Array.of基本上可以用來替代Array()或new Array(),并且不存在由于參數不同而導致的重載。它的行為非常統一。
這個方法的主要目的,是彌補數組構造函數Array()的不足。因為參數個數的不同,會導致Array()的行為有差異。只有當參數個數不少于2個時,Array()才會返回由參數組成的新數組。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

7.4 數組實例的 copyWithin()

Array.prototype.copyWithin(target, start = 0, end = this.length)

7.5 數組實例的 find() 和 findIndex()

[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

數組實例的findIndex方法的用法與find方法非常類似,返回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則返回-1。這兩個方法都可以接受第二個參數,用來綁定回調函數的this對象。另外,這兩個方法都可以發現NaN,彌補了數組的IndexOf方法的不足。indexof它內部使用嚴格相等運算符(===)進行判斷,這會導致對NaN的誤判。

[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0

7.6 數組實例的fill()

fill方法使用給定值,填充一個數組。fill方法還可以接受第二個和第三個參數,用于指定填充的起始位置和結束位置。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

7.7 數組實例的 entries(),keys() 和 values()

它們都返回一個遍歷器對象(詳見《Iterator》一章),可以用for...of循環進行遍歷。如果不使用for...of循環,可以手動調用遍歷器對象的next方法,進行遍歷。

7.8 數組實例的 includes()

與字符串的includes方法類似,參數負的情況稍有不同。

7.9 數組的空位

數組的空位指,數組的某一個位置沒有任何值。比如,Array構造函數返回的數組都是空位。Array(3) // [, , ,]
注意,空位不是undefined,一個位置的值等于undefined,依然是有值的。空位是沒有任何值,in運算符可以說明這一點。

ES5 對空位的處理,已經很不一致了,大多數情況下會忽略空位。
forEach(), filter(), every() 和some()都會跳過空位。
map()會跳過空位,但會保留這個值
join()和toString()會將空位視為undefined,而undefined和null會被處理成空字符串。
ES6 則是明確將空位轉為undefined。
由于空位的處理規則非常不統一,所以建議避免出現空位。

八,對象的擴展

8.1 屬性的簡潔表示法

ES6 允許直接寫入變量和函數,作為對象的屬性和方法。這樣的書寫更加簡潔。

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
function f(x, y) {
  return {x, y};
}

// 等同于

function f(x, y) {
  return {x: x, y: y};
}

除了屬性簡寫,方法也可以簡寫。

const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  method: function() {
    return "Hello!";
  }
};

如果某個方法的值是一個 Generator 函數,前面需要加上星號。

8.2 屬性名表達式

但是,如果使用字面量方式定義對象(使用大括號),在 ES5 中只能使用方法一(標識符)定義屬性。

var obj = {
  foo: true,
  abc: 123
};

ES6 允許字面量定義對象時,用方法二(表達式)作為對象的屬性名,即把表達式放在方括號內。

let propKey = 'foo';

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

表達式還可以用于定義方法名。注意,屬性名表達式與簡潔表示法,不能同時使用,會報錯。

注意,屬性名表達式如果是一個對象,默認情況下會自動將對象轉為字符串[object Object],這一點要特別小心。

8.3 方法的 name 屬性

函數的name屬性,返回函數名。對象方法也是函數,因此也有name屬性。如果對象的方法使用了取值函數(getter)和存值函數(setter),則name屬性不是在該方法上面,而是該方法的屬性的描述對象的get和set屬性上面,返回值是方法名前加上get和set。

const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"

有兩種特殊情況:bind方法創造的函數,name屬性返回bound加上原函數的名字;Function構造函數創造的函數,name屬性返回anonymous。如果對象的方法是一個 Symbol 值,那么name屬性返回的是這個 Symbol 值的描述。

8.4 Object.is()

ES5 比較兩個值是否相等,只有兩個運算符:相等運算符(==)和嚴格相等運算符(===)。它們都有缺點,前者會自動轉換數據類型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一種運算,在所有環境中,只要兩個值是一樣的,它們就應該相等。
ES6 提出“Same-value equality”(同值相等)算法,用來解決這個問題。Object.is就是部署這個算法的新方法。

8.5 Object.assign()

Object.assign方法用于對象的合并,將源對象(source)的所有可枚舉屬性,復制到目標對象(target)。Object.assign方法的第一個參數是目標對象,后面的參數都是源對象。Object.assign拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。屬性名為 Symbol 值的屬性,也會被Object.assign拷貝。

注意,如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性。
注意點
(1)淺拷貝(2)同名屬性的替換(3)數組處理時視為對象 (4)取值函數的處理將求值后再復制。

應用:(1)為對象添加屬性(2)為對象添加方法(3)克隆對象(4)合并多個對象(5)為屬性指定默認值

8.6 屬性的可枚舉性和遍歷

可枚舉性
對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行為。ES5中 Object.getOwnPropertyDescriptor 方法可以獲取該屬性的描述對象。描述對象的enumerable屬性,稱為”可枚舉性“,如果該屬性為false,就表示某些操作會忽略當前屬性。

目前,有四個操作會忽略enumerable為false的屬性。

for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
Object.keys():返回對象自身的所有可枚舉的屬性的鍵名。
JSON.stringify():只串行化對象自身的可枚舉的屬性。
Object.assign(): 忽略enumerable為false的屬性,只拷貝對象自身的可枚舉的屬性。

另外,ES6 規定,所有 Class 的原型的方法都是不可枚舉的。總的來說,操作中引入繼承的屬性會讓問題復雜化,大多數時候,我們只關心對象自身的屬性。所以,盡量不要用for...in循環,而用Object.keys()代替。

屬性的遍歷
ES6 一共有5種方法可以遍歷對象的屬性。

(1)for...in
(2)Object.keys(obj
(3)Object.getOwnPropertyNames(obj)(4)Object.getOwnPropertySymbols(obj)
(5)Reflect.ownKeys(obj)

以上的5種方法遍歷對象的鍵名,都遵守同樣的屬性遍歷的次序規則。

首先遍歷所有數值鍵,按照數值升序排列。
其次遍歷所有字符串鍵,按照加入時間升序排列。
最后遍歷所有 Symbol 鍵,按照加入時間升序排列。

8.7 Object.getOwnPropertyDescriptors()

ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定對象所有自身屬性(非繼承屬性)的描述對象。

該方法的引入目的,主要是為了解決Object.assign()無法正確拷貝get屬性和set屬性的問題。這是因為Object.assign方法總是拷貝一個屬性的值,而不會拷貝它背后的賦值方法或取值方法。這時,Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以實現正確拷貝。

Object.getOwnPropertyDescriptors方法的另一個用處,是配合Object.create方法,將對象屬性克隆到一個新對象。這屬于淺拷貝。

另外,Object.getOwnPropertyDescriptors方法可以實現一個對象繼承另一個對象。

8.8 _proto_屬性, Object.setPrototypeOf(), Object.getPrototypeOf()

JavaScript 語言的對象繼承是通過原型鏈實現的。ES6 提供了更多原型對象的操作方法。

proto屬性 該屬性沒有寫入 ES6 的正文,而是寫入了附錄,原因是proto前后的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API,只是由于瀏覽器廣泛支持,才被加入了 ES6。標準明確規定,只有瀏覽器必須部署這個屬性,其他運行環境不一定需要部署,而且新的代碼最好認為這個屬性是不存在的。使用下面的Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。

Object.setPrototypeOf() Object.setPrototypeOf方法的作用與proto相同,用來設置一個對象的prototype對象,返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。

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

8.9 super 關鍵字

我們知道,this關鍵字總是指向函數所在的當前對象,ES6 又新增了另一個類似的關鍵字super,指向當前對象的原型對象。注意,super關鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。目前,只有對象方法的簡寫法可以讓 JavaScript 引擎確認,定義的是對象的方法。

const obj = {
  find() {
    return super.foo;
  }
};

JavaScript 引擎內部,super.foo等同于Object.getPrototypeOf(this).foo(屬性)或Object.getPrototypeOf(this).foo.call(this)(方法)。

8.10 Object.keys(),Object.values(),Object.entries()

ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名。

ES2017 引入了跟Object.keys 配套的Object.values 和Object.entries ,作為遍歷一個對象的補充手段,供for...of 循環使用。

Object.entries的基本用途是遍歷對象的屬性。Object.entries方法的另一個用處是,將對象轉為真正的Map結構。

8.11 對象的擴展運算符(spread)

ES2017 將這個運算符引入了對象。
(1)解構賦值

單純的解構賦值,所以可以讀取對象繼承的屬性;擴展運算符的解構賦值,只能讀取對象o自身的屬性。

(2)擴展運算符
擴展運算符(...)用于取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

這等同于使用Object.assign方法。

let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

擴展運算符的參數對象之中,如果有取值函數get,這個函數是會執行的。

8.12 Null 傳導運算符

編程實務中,如果讀取對象內部的某個屬性,往往需要判斷一下該對象是否存在。比如,要讀取message.body.user.firstName
,安全的寫法是寫成下面這樣。const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default';

這樣的層層判斷非常麻煩,因此現在有一個提案,引入了“Null 傳導運算符”(null propagation operator)?.
,簡化上面的寫法。const firstName = message?.body?.user?.firstName || 'default';

上面代碼有三個?.運算符,只要其中一個返回null或undefined,就不再往下運算,而是返回undefined。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容

  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創建并操作數組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,259評論 0 4
  • 1.屬性的簡潔表示法 允許直接寫入變量和函數 上面代碼表明,ES6 允許在對象之中,直接寫變量。這時,屬性名為變量...
    雨飛飛雨閱讀 1,140評論 0 3
  • 哪位作者說過,沒有方向, 當你努力就是白搭。 在過去的這些年頭里我沒想過沒策劃過我的人生, 我抱著這樣一個心態...
    青檀Darling閱讀 273評論 0 1
  • 有一個朋友叫靜靜,說心里話,我挺想靜靜。 周末一場小雪,下了相當于沒下,希望通過以雪蓋霾的人們嘆了口氣。 于是我開...
    coco劉閱讀 871評論 0 0
  • 我為2016做了生涯四度規劃,選擇了“學習、助人、健康、愛好”四個關鍵詞來發展自己,可我任性地跑偏了。 2016年...
    Amy蕾閱讀 512評論 0 2