# 第六章:新增API
從值的轉換到數學計算,ES6給各種內建原生類型和對象增加了許多靜態屬性和方法來輔助這些常見任務。另外,一些原生類型的實例通過各種新的原型方法獲得了新的能力。
注意:?大多數這些特性都可以被忠實地填補。我們不會在這里深入這樣的細節,但是關于兼容標準的shim/填補,你可以看一下“ES6 Shim”([https://github.com/paulmillr/es6-shim/)。](https://github.com/paulmillr/es6-shim/)
## `Array`
在JS中被各種用戶庫擴展得最多的特性之一就是數組類型。ES6在數組上增加許多靜態的和原型(實例)的幫助功能應當并不令人驚訝。
### `Array.of(..)`?靜態函數
`Array(..)`的構造器有一個盡人皆知的坑:如果僅有一個參數值被傳遞,而且這個參數值是一個數字的話,它并不會制造一個含有一個帶有該數值元素的數組,而是構建一個長度等于這個數字的空數組。這種操作造成了不幸的和怪異的“空值槽”行為,而這正是JS數組為人詬病的地方。
`Array.of(..)`作為數組首選的函數型構造器取代了`Array(..)`,因為`Array.of(..)`沒有那種單數字參數值的情況。考慮如下代碼:
```source-js
var a = Array( 3 );
a.length; // 3
a[0]; // undefined
var b = Array.of( 3 );
b.length; // 1
b[0]; // 3
var c = Array.of( 1, 2, 3 );
c.length; // 3
c; // [1,2,3]
```
在什么樣的環境下,你才會想要是使用`Array.of(..)`來創建一個數組,而不是使用像`c = [1,2,3]`這樣的字面語法呢?有兩種可能的情況。
如果你有一個回調,傳遞給它的參數值本應當被包裝在一個數組中時,`Array.of(..)`就完美地符合條件。這可能不是那么常見,但是它可以為你的癢處撓上一把。
另一種場景是如果你擴展`Array`構成它的子類,而且希望能夠在一個你的子類的實例中創建和初始化元素,比如:
```source-js
class MyCoolArray extends Array {
sum() {
return this.reduce( function reducer(acc,curr){
return acc + curr;
}, 0 );
}
}
var x = new MyCoolArray( 3 );
x.length; // 3 -- 噢!
x.sum(); // 0 -- 噢!
var y = [3]; // Array,不是 MyCoolArray
y.length; // 1
y.sum(); // `sum` is not a function
var z = MyCoolArray.of( 3 );
z.length; // 1
z.sum(); // 3
```
你不能(簡單地)只創建一個`MyCoolArray`的構造器,讓它覆蓋`Array`父構造器的行為,因為這個父構造器對于實際創建一個規范的數組值(初始化`this`)是必要的。在`MyCoolArray`子類上“被繼承”的靜態`of(..)`方法提供了一個不錯的解決方案。
### `Array.from(..)`?靜態函數
在JavaScript中一個“類數組對象”是一個擁有`length`屬性的對象,這個屬性明確地帶有0或更高的整數值。
在JS中處理這些值出了名地讓人沮喪;將它們變形為真正的數組曾經是十分常見的做法,這樣各種`Array.property`方法(`map(..)`,`indexOf(..)`等等)才能與它一起使用。這種處理通常看起來像:
```source-js
// 類數組對象
var arrLike = {
length: 3,
0: "foo",
1: "bar"
};
var arr = Array.prototype.slice.call( arrLike );
```
另一種`slice(..)`經常被使用的常見任務是,復制一個真正的數組:
```source-js
var arr2 = arr.slice();
```
在這兩種情況下,新的ES6`Array.from(..)`方法是一種更易懂而且更優雅的方式 —— 也不那么冗長:
```source-js
var arr = Array.from( arrLike );
var arrCopy = Array.from( arr );
```
`Array.from(..)`會查看第一個參數值是否是一個可迭代對象(參見第三章的“迭代器”),如果是,它就使用迭代器來產生值,并將這些值“拷貝”到將要被返回的數組中。因為真正的數組擁有一個可以產生這些值的迭代器,所以這個迭代器會被自動地使用。
但是如果你傳遞一個類數組對象作為`Array.from(..)`的第一個參數值,它的行為基本上是和`slice()`(不帶參數值的!)或`apply()`相同的,它簡單地循環所有的值,訪問從`0`開始到`length`值的由數字命名的屬性。
考慮如下代碼:
```source-js
var arrLike = {
length: 4,
2: "foo"
};
Array.from( arrLike );
// [ undefined, undefined, "foo", undefined ]
```
因為在`arrLike`上不存在位置`0`,`1`,和`3`,所以對這些值槽中的每一個,結果都是`undefined`值。
你也可以這樣產生類似的結果:
```source-js
var emptySlotsArr = [];
emptySlotsArr.length = 4;
emptySlotsArr[2] = "foo";
Array.from( emptySlotsArr );
// [ undefined, undefined, "foo", undefined ]
```
#### 避免空值槽
前面的代碼段中,在`emptySlotsArr`和`Array.from(..)`調用的結果有一個微妙但重要的不同。`Array.from(..)`從不產生空值槽。
在ES6之前,如果你想要制造一個被初始化為在每個值槽中使用實際`undefined`值(不是空值槽!)的特定長數組,你不得不做一些額外的工作:
```source-js
var a = Array( 4 ); // 四個空值槽!
var b = Array.apply( null, { length: 4 } ); // 四個 `undefined` 值
```
但現在`Array.from(..)`使這件事簡單了些:
```source-js
var c = Array.from( { length: 4 } ); // 四個 `undefined` 值
```
警告:?使用一個像前面代碼段中的`a`那樣的空值槽數組可以與一些數組函數工作,但是另一些函數會忽略空值槽(比如`map(..)`等)。你永遠不應該刻意地使用空值槽,因為它幾乎肯定會在你的程序中導致奇怪/不可預料的行為。
#### 映射
`Array.from(..)`工具還有另外一個絕技。第二個參數值,如果被提供的話,是一個映射函數(和普通的`Array#map(..)`幾乎相同),它在將每個源值映射/變形為返回的目標值時調用。考慮如下代碼:
```source-js
var arrLike = {
length: 4,
2: "foo"
};
Array.from( arrLike, function mapper(val,idx){
if (typeof val == "string") {
return val.toUpperCase();
}
else {
return idx;
}
} );
// [ 0, 1, "FOO", 3 ]
```
注意:?就像其他接收回調的數組方法一樣,`Array.from(..)`接收可選的第三個參數值,它將被指定為作為第二個參數傳遞的回調的`this`綁定。否則,`this`將是`undefined`。
一個使用`Array.from(..)`將一個8位值數組翻譯為16位值數組的例子,參見第五章的“類型化數組”。
### 創建 Arrays 和子類型
在前面幾節中,我們討論了`Array.of(..)`和`Array.from(..)`,它們都用與構造器相似的方法創建一個新數組。但是在子類中它們會怎么做?它們是創建基本`Array`的實例,還是創建衍生的子類的實例?
```source-js
class MyCoolArray extends Array {
..
}
MyCoolArray.from( [1, 2] ) instanceof MyCoolArray; // true
Array.from(
MyCoolArray.from( [1, 2] )
) instanceof MyCoolArray; // false
```
`of(..)`和`from(..)`都使用它們被訪問時的構造器來構建數組。所以如果你使用基本的`Array.of(..)`你將得到`Array`實例,但如果你使用`MyCoolArray.of(..)`,你將得到一個`MyCoolArray`實例。
在第三章的“類”中,我們講解了在所有內建類(比如`Array`)中定義好的`@@species`設定,它被用于任何創建新實例的原型方法。`slice(..)`是一個很棒的例子:
```source-js
var x = new MyCoolArray( 1, 2, 3 );
x.slice( 1 ) instanceof MyCoolArray; // true
```
一般來說,這種默認行為將可能是你想要的,但是正如我們在第三章中討論過的,如果你想的話你?*可以*?覆蓋它:
```source-js
class MyCoolArray extends Array {
// 強制 `species` 為父類構造器
static get [Symbol.species]() { return Array; }
}
var x = new MyCoolArray( 1, 2, 3 );
x.slice( 1 ) instanceof MyCoolArray; // false
x.slice( 1 ) instanceof Array; // true
```
要注意的是,`@@species`設定僅適用于原型方法,比如`slice(..)`。`of(..)`和`from(..)`不使用它;它們倆都只使用`this`綁定(哪個構造器被用于發起引用)。考慮如下代碼:
```source-js
class MyCoolArray extends Array {
// 強制 `species` 為父類構造器
static get [Symbol.species]() { return Array; }
}
var x = new MyCoolArray( 1, 2, 3 );
MyCoolArray.from( x ) instanceof MyCoolArray; // true
MyCoolArray.of( [2, 3] ) instanceof MyCoolArray; // true
```
### `copyWithin(..)`?原型方法
`Array#copyWithin(..)`是一個對所有數組可用的新修改器方法(包括類型化數組;參加第五章)。`copyWithin(..)`將數組的一部分拷貝到同一個數組的其他位置,覆蓋之前存在在那里的任何東西。
它的參數值是?*目標*(要被拷貝到的索引位置),*開始*(拷貝開始的索引位置(含)),和可選的?*結束*(拷貝結束的索引位置(不含))。如果這些參數值中存在任何負數,那么它們就被認為是相對于數組的末尾。
考慮如下代碼:
```source-js
[1,2,3,4,5].copyWithin( 3, 0 ); // [1,2,3,1,2]
[1,2,3,4,5].copyWithin( 3, 0, 1 ); // [1,2,3,1,5]
[1,2,3,4,5].copyWithin( 0, -2 ); // [4,5,3,4,5]
[1,2,3,4,5].copyWithin( 0, -2, -1 ); // [4,2,3,4,5]
```
`copyWithin(..)`方法不會擴張數組的長度,就像前面代碼段中的第一個例子展示的。當到達數組的末尾時拷貝就會停止。
與你可能想象的不同,拷貝的順序并不總是從左到右的。如果起始位置與目標為重疊的話,它有可能造成已經被拷貝過的值被重復拷貝,這大概不是你期望的行為。
所以在這種情況下,算法內部通過相反的拷貝順序來避免這個坑。考慮如下代碼:
```source-js
[1,2,3,4,5].copyWithin( 2, 1 ); // ???
```
如果算法是嚴格的從左到右,那么`2`應當被拷貝來覆蓋`3`,然后這個被拷貝的`2`應當被拷貝來覆蓋`4`,然后這個被拷貝的`2`應當被拷貝來覆蓋`5`,而你最終會得到`[1,2,2,2,2]`。
與此不同的是,拷貝算法把方向反轉過來,拷貝`4`來覆蓋`5`,然后拷貝`3`來覆蓋`4`,然后拷貝`2`來覆蓋`3`,而最后的結果是`[1,2,2,3,4]`。就期待的結果而言這可能更“正確”,但是如果你僅以單純的從左到右的方式考慮拷貝算法的話,它就可能讓人糊涂。
### `fill(..)`?原型方法
ES6中的`Array#fill(..)`方法原生地支持使用一個指定的值來完全地(或部分地)填充一個既存的數組:
```source-js
var a = Array( 4 ).fill( undefined );
a;
// [undefined,undefined,undefined,undefined]
```
`fill(..)`可選地接收?*開始*?與?*結束*?參數,它們指示要被填充的數組的一部分,比如:
```source-js
var a = [ null, null, null, null ].fill( 42, 1, 3 );
a; // [null,42,42,null]
```
### `find(..)`?原型方法
一般來說,在一個數組中搜索一個值的最常見方法曾經是`indexOf(..)`方法,如果值被找到的話它返回值的位置索引,沒有找到的話返回`-1`:
```source-js
var a = [1,2,3,4,5];
(a.indexOf( 3 ) != -1); // true
(a.indexOf( 7 ) != -1); // false
(a.indexOf( "2" ) != -1); // false
```
`indexOf(..)`比較要求一個嚴格`===`匹配,所以搜索`"2"`找不到值`2`,反之亦然。沒有辦法覆蓋`indexOf(..)`的匹配算法。不得不手動與值`-1`進行比較也很不幸/不優雅。
提示:?一個使用`~`操作符來繞過難看的`-1`的有趣(而且爭議性地令人糊涂)技術,參見本系列的?*類型與文法*。
從ES5開始,控制匹配邏輯的最常見的迂回方法是`some(..)`。它的工作方式是為每一個元素調用一個回調函數,直到這些調用中的一個返回`true`/truthy值,然后它就會停止。因為是由你來定義這個回調函數,所以你就擁有了如何做出匹配的完全控制權:
```source-js
var a = [1,2,3,4,5];
a.some( function matcher(v){
return v == "2";
} ); // true
a.some( function matcher(v){
return v == 7;
} ); // false
```
但這種方式的缺陷是你只能使用`true`/`false`來指示是否找到了合適的匹配值,而不是實際被匹配的值。
ES6的`find(..)`解決了這個問題。它的工作方式基本上與`some(..)`相同,除了一旦回調返回一個`true`/truthy值,實際的數組值就會被返回:
```source-js
var a = [1,2,3,4,5];
a.find( function matcher(v){
return v == "2";
} ); // 2
a.find( function matcher(v){
return v == 7; // undefined
});
```
使用一個自定義的`matcher(..)`函數還允許你與對象這樣的復雜值進行匹配:
```source-js
var points = [
{ x: 10, y: 20 },
{ x: 20, y: 30 },
{ x: 30, y: 40 },
{ x: 40, y: 50 },
{ x: 50, y: 60 }
];
points.find( function matcher(point) {
return (
point.x % 3 == 0 &&
point.y % 4 == 0
);
} ); // { x: 30, y: 40 }
```
注意:?和其他接收回調的數組方法一樣,`find(..)`接收一個可選的第二參數。如果它被設置了的話,就將被指定為作為第一個參數傳遞的回調的`this`綁定。否則,`this`將是`undefined`。
### `findIndex(..)`?原型方法
雖然前一節展示了`some(..)`如何在一個數組檢索給出一個Boolean結果,和`find(..)`如何從數組檢索中給出匹配的值,但是還有一種需求是尋找匹配的值的位置索引。
`indexOf(..)`可以完成這個任務,但是沒有辦法控制它的匹配邏輯;它總是使用`===`嚴格等價。所以ES6的`findIndex(..)`才是答案:
```source-js
var points = [
{ x: 10, y: 20 },
{ x: 20, y: 30 },
{ x: 30, y: 40 },
{ x: 40, y: 50 },
{ x: 50, y: 60 }
];
points.findIndex( function matcher(point) {
return (
point.x % 3 == 0 &&
point.y % 4 == 0
);
} ); // 2
points.findIndex( function matcher(point) {
return (
point.x % 6 == 0 &&
point.y % 7 == 0
);
} ); // -1
```
不要使用`findIndex(..) != -1`(在`indexOf(..)`中經常這么干)來從檢索中取得一個boolean,因為`some(..)`已經給出了你想要的`true`/`false`了。而且也不要用`a[ a.findIndex(..) ]`來取得一個匹配的值,因為這是`find(..)`完成的任務。最后,如果你需要嚴格匹配的索引,就使用`indexOf(..)`,如果你需要一個更加定制化的匹配,就使用`findIndex(..)`。
注意:?和其他接收回調的數組方法一樣,`findIndex(..)`接收一個可選的第二參數。如果它被設置了的話,就將被指定為作為第一個參數傳遞的回調的`this`綁定。否則,`this`將是`undefined`。
### `entries()`,?`values()`,?`keys()`?原型方法
在第三章中,我們展示了數據結構如何通過一個迭代器來提供一種模擬逐個值的迭代。然后我們在第五章探索新的ES6集合(Map,Set,等)如何為了產生不同種類的迭代器而提供幾種方法時闡述了這種方式。
因為`Array`并不是ES6的新東西,所以它可能不被認為是一個傳統意義上的“集合”,但是在它提供了相同的迭代器方法:`entries()`,`values()`,和`keys()`的意義上,它是的。考慮如下代碼:
```source-js
var a = [1,2,3];
[...a.values()]; // [1,2,3]
[...a.keys()]; // [0,1,2]
[...a.entries()]; // [ [0,1], [1,2], [2,3] ]
[...a[Symbol.iterator]()]; // [1,2,3]
```
就像`Set`一樣,默認的`Array`迭代器與`values()`放回的東西相同。
在本章早先的“避免空值槽”一節中,我們展示了`Array.from(..)`如何將一個數組中的空值槽看作帶有`undefined`的存在值槽。其實際的原因是,在底層數組迭代器就是以這種方式動作的:
```source-js
var a = [];
a.length = 3;
a[1] = 2;
[...a.values()]; // [undefined,2,undefined]
[...a.keys()]; // [0,1,2]
[...a.entries()]; // [ [0,undefined], [1,2], [2,undefined] ]
```
## `Object`
幾個額外的靜態幫助方法已經被加入`Object`。從傳統意義上講,這種種類的函數是關注于對象值的行為/能力的。
但是,從ES6開始,`Object`靜態函數還用于任意種類的通用全局API —— 那些還沒有更自然地存在于其他的某些位置的API(例如,`Array.from(..)`)。
### `Object.is(..)`?靜態函數
`Object.is(..)`靜態函數進行值的比較,它的風格甚至要比`===`比較還要嚴格。
`Object(..)`調用底層的`SameValue`算法(ES6語言規范,第7.2.9節)。`SameValue`算法基本上與`===`嚴格等價比較算法相同(ES6語言規范,第7.2.13節),但是帶有兩個重要的例外。
考慮如下代碼:
```source-js
var x = NaN, y = 0, z = -0;
x === x; // false
y === z; // true
Object.is( x, x ); // true
Object.is( y, z ); // false
```
你應當為嚴格等價性比較繼續使用`===`;`Object.is(..)`不應當被認為是這個操作符的替代品。但是,在你想要嚴格地識別`NaN`或`-0`值的情況下,`Object.is(..)`是現在的首選方式。
注意:?ES6還增加了一個`Number.isNaN(..)`工具(在本章稍后討論),它可能是一個稍稍方便一些的測試;比起`Object.is(x, NaN)`你可能更偏好`Number.isNaN(x)`。你?*可以*?使用笨拙的`x == 0 && 1 / x === -Infinity`來準確地測試`-0`,但在這種情況下`Object.is(x,-0)`要好得多。
### `Object.getOwnPropertySymbols(..)`?靜態函數
第二章中的“Symbol”一節討論了ES6中的新Symbol基本值類型。
Symbol可能將是在對象上最經常被使用的特殊(元)屬性。所以引入了`Object.getOwnPropertySymbols(..)`,它僅取回直接存在于對象上的symbol屬性:
```source-js
var o = {
foo: 42,
[ Symbol( "bar" ) ]: "hello world",
baz: true
};
Object.getOwnPropertySymbols( o ); // [ Symbol(bar) ]
```
### `Object.setPrototypeOf(..)`?靜態函數
還是在第二章中,我們提到了`Object.setPrototypeOf(..)`工具,它為了?*行為委托*?的目的(意料之中地)設置一個對象的`[[Prototype]]`(參見本系列的?*this與對象原型*)。考慮如下代碼:
```source-js
var o1 = {
foo() { console.log( "foo" ); }
};
var o2 = {
// .. o2 的定義 ..
};
Object.setPrototypeOf( o2, o1 );
// 委托至 `o1.foo()`
o2.foo(); // foo
```
另一種方式:
```source-js
var o1 = {
foo() { console.log( "foo" ); }
};
var o2 = Object.setPrototypeOf( {
// .. o2 的定義 ..
}, o1 );
// 委托至 `o1.foo()`
o2.foo(); // foo
```
在前面兩個代碼段中,`o2`和`o1`之間的關系都出現在`o2`定義的末尾。更常見的是,`o2`和`o1`之間的關系在`o2`定義的上面被指定,就像在類中,而且在對象字面量的`__proto__`中也是這樣(參見第二章的“設置`[[Prototype]]`”)。
警告:?正如展示的那樣,在對象創建之后立即設置`[[Prototype]]`是合理的。但是在很久之后才改變它一般不是一個好主意,而且經常會導致困惑而非清晰。
### `Object.assign(..)`?靜態函數
許多JavaScript庫/框架都提供將一個對象的屬性拷貝/混合到另一個對象中的工具(例如,jQuery的`extend(..)`)。在這些不同的工具中存在著各種微妙的區別,比如一個擁有`undefined`值的屬性是否被忽略。
ES6增加了`Object.assign(..)`,它是這些算法的一個簡化版本。第一個參數是?*目標對象*?而所有其他的參數是?*源對象*,它們會按照羅列的順序被處理。對每一個源對象,它自己的(也就是,不是“繼承的”)可枚舉鍵,包括symbol,將會好像通過普通`=`賦值那樣拷貝。`Object.assign(..)`返回目標對象。
考慮這種對象構成:
```source-js
var target = {},
o1 = { a: 1 }, o2 = { b: 2 },
o3 = { c: 3 }, o4 = { d: 4 };
// 設置只讀屬性
Object.defineProperty( o3, "e", {
value: 5,
enumerable: true,
writable: false,
configurable: false
} );
// 設置不可枚舉屬性
Object.defineProperty( o3, "f", {
value: 6,
enumerable: false
} );
o3[ Symbol( "g" ) ] = 7;
// 設置不可枚舉 symbol
Object.defineProperty( o3, Symbol( "h" ), {
value: 8,
enumerable: false
} );
Object.setPrototypeOf( o3, o4 );
```
僅有屬性`a`,`b`,`c`,`e`,和`Symbol("g")`將被拷貝到`target`:
```source-js
Object.assign( target, o1, o2, o3 );
target.a; // 1
target.b; // 2
target.c; // 3
Object.getOwnPropertyDescriptor( target, "e" );
// { value: 5, writable: true, enumerable: true,
// configurable: true }
Object.getOwnPropertySymbols( target );
// [Symbol("g")]
```
屬性`d`,`f`,和`Symbol("h")`在拷貝中被忽略了;非枚舉屬性和非自身屬性將會被排除在賦值之外。另外,`e`作為一個普通屬性賦值被拷貝,而不是作為一個只讀屬性被復制。
在早先一節中,我們展示了使用`setPrototypeOf(..)`來在對象`o2`和`o1`之間建立一個`[[Prototype]]`關系。這是利用`Object.assign(..)`的另外一種形式:
```source-js
var o1 = {
foo() { console.log( "foo" ); }
};
var o2 = Object.assign(
Object.create( o1 ),
{
// .. o2 的定義 ..
}
);
// 委托至 `o1.foo()`
o2.foo(); // foo
```
注意:?`Object.create(..)`是一個ES5標準工具,它創建一個`[[Prototype]]`鏈接好的空對象。更多信息參見本系列的?*this與對象原型*。
## `Math`
ES6增加了幾種新的數學工具,它們協助或填補了常見操作的空白。所有這些操作都可以被手動計算,但是它們中的大多數現在都被原生地定義,這樣JS引擎就可以優化計算的性能,或者進行與手動計算比起來小數精度更高的計算。
與直接的開發者相比,asm.js/轉譯的JS代碼(參見本系列的?*異步與性能*)更可能是這些工具的使用者。
三角函數:
* `cosh(..)`?- 雙曲余弦
* `acosh(..)`?- 雙曲反余弦
* `sinh(..)`?- 雙曲正弦
* `asinh(..)`?- 雙曲反正弦
* `tanh(..)`?- 雙曲正切
* `atanh(..)`?- 雙曲反正切
* `hypot(..)`?- 平方和的平方根(也就是,廣義勾股定理)
算數函數:
* `cbrt(..)`?- 立方根
* `clz32(..)`?- 計數32位二進制表達中前綴的零
* `expm1(..)`?- 與`exp(x) - 1`相同
* `log2(..)`?- 二進制對數(以2為底的對數)
* `log10(..)`?- 以10為底的對數
* `log1p(..)`?- 與`log(x + 1)`相同
* `imul(..)`?- 兩個數字的32為整數乘法
元函數:
* `sign(..)`?- 返回數字的符號
* `trunc(..)`?- 僅返回一個數字的整數部分
* `fround(..)`?- 舍入到最接近的32位(單精度)浮點數值
## `Number`
重要的是,為了你的程序能夠正常工作,它必須準確地處理數字。ES6增加了一些額外的屬性和函數來輔助常見的數字操作。
兩個在`Number`上新增的功能只是既存全局函數的引用:`Number.parseInt(..)`和`Number.parseFloat(..)`。
### 靜態屬性
ES6以靜態屬性的形式增加了一些有用的數字常數:
* `Number.EPSILON`?- 在任意兩個數字之間的最小值:`2^-52`(關于為了應對浮點算數運算不精確的問題而將這個值用做容差的講解,參見本系列的?*類型與文法*?的第二章)
* `Number.MAX_SAFE_INTEGER`?- 可以用一個JS數字值明確且“安全地”表示的最大整數:`2^53 - 1`
* `Number.MIN_SAFE_INTEGER`?- 可以用一個JS數字值明確且“安全地”表示的最小整數:`-(2^53 - 1)`或`(-2)^53 + 1`.
注意:?關于“安全”整數的更多信息,參見本系列的?*類型與文法*?的第二章。
### `Number.isNaN(..)`?靜態函數
標準的全局`isNaN(..)`工具從一開始就壞掉了,因為不僅對實際的`NaN`值返回`true`,而且對不是數字的東西也返回`true`。其原因是它會將參數值強制轉換為數字類型(這可能失敗而導致一個NaN)。ES6增加了一個修復過的工具`Number.isNaN(..)`,它可以正確工作:
```source-js
var a = NaN, b = "NaN", c = 42;
isNaN( a ); // true
isNaN( b ); // true —— 噢!
isNaN( c ); // false
Number.isNaN( a ); // true
Number.isNaN( b ); // false —— 修好了!
Number.isNaN( c ); // false
```
### `Number.isFinite(..)`?靜態函數
看到像`isFinite(..)`這樣的函數名會誘使人們認為它單純地意味著“不是無限”。但這不十分正確。這個新的ES6工具有更多的微妙之處。考慮如下代碼:
```source-js
var a = NaN, b = Infinity, c = 42;
Number.isFinite( a ); // false
Number.isFinite( b ); // false
Number.isFinite( c ); // true
```
標準的全局`isFinite(..)`會強制轉換它收到的參數值,但是`Number.isFinite(..)`會省略強制轉換的行為:
```source-js
var a = "42";
isFinite( a ); // true
Number.isFinite( a ); // false
```
你可能依然偏好強制轉換,這時使用全局`isFinite(..)`是一個合法的選擇。或者,并且可能是更明智的選擇,你可以使用`Number.isFinite(+x)`,它在將`x`傳遞前明確地將它強制轉換為數字(參見本系列的?*類型與文法*?的第四章)。
### 整數相關的靜態函數
JavaScript數字值總是浮點數(IEEE-754)。所以判定一個數字是否是“整數”的概念與檢查它的類型無關,因為JS沒有這樣的區分。
取而代之的是,你需要檢查這個值是否擁有非零的小數部分。這樣做的最簡單的方法通常是:
```source-js
x === Math.floor( x );
```
ES6增加了一個`Number.isInteger(..)`幫助工具,它可以潛在地判定這種性質,而且效率稍微高一些:
```source-js
Number.isInteger( 4 ); // true
Number.isInteger( 4.2 ); // false
```
注意:?在JavaScript中,`4`,`4.`,`4.0`,或`4.0000`之間沒有區別。它們都將被認為是一個“整數”,因此都會從`Number.isInteger(..)`中給出`true`。
另外,`Number.isInteger(..)`過濾了一些明顯的非整數值,它們在`x === Math.floor(x)`中可能會被混淆:
```source-js
Number.isInteger( NaN ); // false
Number.isInteger( Infinity ); // false
```
有時候處理“整數”是信息的重點,它可以簡化特定的算法。由于為了僅留下整數而進行過濾,JS代碼本身不會運行得更快,但是當僅有整數被使用時引擎可以采取幾種優化技術(例如,asm.js)。
因為`Number.isInteger(..)`對`Nan`和`Infinity`值的處理,定義一個`isFloat(..)`工具并不像`!Number.isInteger(..)`一樣簡單。你需要這么做:
```source-js
function isFloat(x) {
return Number.isFinite( x ) && !Number.isInteger( x );
}
isFloat( 4.2 ); // true
isFloat( 4 ); // false
isFloat( NaN ); // false
isFloat( Infinity ); // false
```
注意:?這看起來可能很奇怪,但是無窮即不應當被認為是整數也不應當被認為是浮點數。
ES6還定義了一個`Number.isSafeInteger(..)`工具,它檢查一個值以確保它是一個整數并且在`Number.MIN_SAFE_INTEGER`-`Number.MAX_SAFE_INTEGER`的范圍內(包含兩端)。
```source-js
var x = Math.pow( 2, 53 ),
y = Math.pow( -2, 53 );
Number.isSafeInteger( x - 1 ); // true
Number.isSafeInteger( y + 1 ); // true
Number.isSafeInteger( x ); // false
Number.isSafeInteger( y ); // false
```
## `String`
在ES6之前字符串就已經擁有好幾種幫助函數了,但是有更多的內容被加入了進來。
### Unicode 函數
在第二章的“Unicode敏感的字符串操作”中詳細討論了`String.fromCodePoint(..)`,`String#codePointAt(..)`,`String#normalize(..)`。它們被用來改進JS字符串值對Unicode的支持。
```source-js
String.fromCodePoint( 0x1d49e ); // "??"
"ab??d".codePointAt( 2 ).toString( 16 ); // "1d49e"
```
`normalize(..)`字符串原型方法用來進行Unicode規范化,它將字符與相鄰的“組合標志”進行組合,或者將組合好的字符拆開。
一般來說,規范化不會對字符串的內容產生視覺上的影響,但是會改變字符串的內容,這可能會影響`length`屬性報告的結果,以及用位置訪問字符的行為:
```source-js
var s1 = "e\u0301";
s1.length; // 2
var s2 = s1.normalize();
s2.length; // 1
s2 === "\xE9"; // true
```
`normalize(..)`接受一個可選參數值,它用于指定使用的規范化形式。這個參數值必須是下面四個值中的一個:`"NFC"`(默認),`"NFD"`,`"NFKC"`,或者`"NFKD"`。
注意:?規范化形式和它們在字符串上的效果超出了我們要在這里討論的范圍。更多細節參見“Unicode規范化形式”([http://www.unicode.org/reports/tr15/)。](http://www.unicode.org/reports/tr15/)
### `String.raw(..)`?靜態函數
`String.raw(..)`工具被作為一個內建的標簽函數來與字符串字面模板(參見第二章)一起使用,取得不帶有任何轉譯序列處理的未加工的字符串值。
這個函數幾乎永遠不會被手動調用,但是將與被標記的模板字面量一起使用:
```source-js
var str = "bc";
String.raw`\ta${str}d\xE9`;
// "\tabcd\xE9", not " abcdé"
```
在結果字符串中,`\`和`t`是分離的未被加工過的字符,而不是一個轉譯字符序列`\t`。這對Unicode轉譯序列也是一樣。
### `repeat(..)`?原型函數
在Python和Ruby那樣的語言中,你可以這樣重復一個字符串:
```source-js
"foo" * 3; // "foofoofoo"
```
在JS中這不能工作,因為`*`乘法是僅對數字定義的,因此`"foo"`會被強制轉換為`NaN`數字。
但是,ES6定義了一個字符串原型方法`repeat(..)`來完成這個任務:
```source-js
"foo".repeat( 3 ); // "foofoofoo"
```
### 字符串檢驗函數
作為對ES6以前的`String#indexOf(..)`和`String#lastIndexOf(..)`的補充,增加了三個新的搜索/檢驗函數:`startsWith(..)`,`endsWith(..)`,和`includes(..)`。
```source-js
var palindrome = "step on no pets";
palindrome.startsWith( "step on" ); // true
palindrome.startsWith( "on", 5 ); // true
palindrome.endsWith( "no pets" ); // true
palindrome.endsWith( "no", 10 ); // true
palindrome.includes( "on" ); // true
palindrome.includes( "on", 6 ); // false
```
對于所有這些字符串搜索/檢驗方法,如果你查詢一個空字符串`""`,那么它將要么在字符串的開頭被找到,要么就在字符串的末尾被找到。
警告:?這些方法默認不接受正則表達式作為檢索字符串。關于關閉實施在第一個參數值上的`isRegExp`檢查的信息,參見第七章的“正則表達式Symbol”。
## 復習
ES6在各種內建原生對象上增加了許多額外的API幫助函數:
* `Array`增加了`of(..)`和`from(..)`之類的靜態函數,以及`copyWithin(..)`和`fill(..)`之類的原型函數。
* `Object`增加了`is(..)`和`assign(..)`之類的靜態函數。
* `Math`增加了`acosh(..)`和`clz32(..)`之類的靜態函數。
* `Number`增加了`Number.EPSILON`之類的靜態屬性,以及`Number.isFinite(..)`之類的靜態函數。
* `String`增加了`String.fromCodePoint(..)`和`String.raw(..)`之類的靜態函數,以及`repeat(..)`和`includes(..)`之類的原型函數。
這些新增函數中的絕大多數都可以被填補(參見ES6 Shim),它們都是受常見的JS庫/框架中的工具啟發的。