ES6學習筆記(數(shù)據(jù)類型的擴展方法——字符串、數(shù)值)

這篇文章僅記錄了本人在系統(tǒng)性學習ES6時,遇到的產(chǎn)生困惑或留意的點,用于以后開發(fā)中的review。

1.字符串擴展

1-1.JSON.stringify()

根據(jù)標準,JSON 數(shù)據(jù)必須是 UTF-8 編碼。但是,現(xiàn)在的JSON.stringify()方法有可能返回不符合 UTF-8 標準的字符串。

為了確保返回的是合法的 UTF-8 字符,ES2019 改變了JSON.stringify()的行為。如果遇到0xD8000xDFFF之間的單個碼點,或者不存在的配對形式,它會返回轉(zhuǎn)義字符串,留給應用自己決定下一步的處理。

JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""

1-2.模板字符串

如果使用模板字符串表示多行字符串,所有的空格和縮進都會被保留在輸出之中。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`);

上面代碼中,所有模板字符串的空格和換行,都是被保留的,比如<ul>標簽前面會有一個換行。如果你不想要這個換行,可以使用trim方法消除它。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

模板字符串中嵌入變量,需要將變量名寫在${}之中。

由于模板字符串的大括號內(nèi)部,就是執(zhí)行 JavaScript 代碼,因此如果大括號內(nèi)部是一個字符串,將會原樣輸出。

可以進行運算,以及引用對象屬性。還能調(diào)用函數(shù)。如果大括號中的值不是字符串,將按照一般的規(guī)則轉(zhuǎn)為字符串。比如,大括號中是一個對象,將默認調(diào)用對象的toString方法。

let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"


//調(diào)用函數(shù)
function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

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

const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;
const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
//   <tr><td><Jane></td></tr>
//   <tr><td>Bond</td></tr>
//
//   <tr><td>Lars</td></tr>
//   <tr><td><Croft></td></tr>
//
// </table>

1-3.標簽模板

模板字符串的功能,不僅僅是上面這些。它可以緊跟在一個函數(shù)名后面,該函數(shù)將被調(diào)用來處理這個模板字符串。這被稱為“標簽模板”功能(tagged template)。

alert`hello`
// 等同于
alert(['hello'])

標簽模板其實不是模板,而是函數(shù)調(diào)用的一種特殊形式。但是,如果模板字符里面有變量,就不是簡單的調(diào)用了,而是會將模板字符串先處理成多個參數(shù),再調(diào)用函數(shù)。

let a = 5;
let b = 10;

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

可以簡單理解為將參數(shù)中的字符串提取出來分割出一個裝有字符串的數(shù)組(字符串分割位置由變量所處位置決定),參數(shù)中的變量依次提取并計算結(jié)果追加在數(shù)組后

這里有一個值得注意的地方:

let a = 5;
let b = 10;

tag`Hello ${ a + b } ${ a * b } world `;
// 等同于
tag(["Hello ", " ", " world "], 15, 50);

當變量出現(xiàn)在參數(shù)的最后時,參數(shù)數(shù)組中的最后一個元素將會是一個空字符串""

2.字符串的新增方法

2-1.String.raw()

該方法返回一個斜杠都被轉(zhuǎn)義(即斜杠前面再加一個斜杠)的字符串,往往用于模板字符串的處理方法。

String.raw`Hi\n${2+3}!`
// 實際返回 "Hi\\n5!",顯示的是轉(zhuǎn)義后的結(jié)果 "Hi\n5!"

String.raw`Hi\u000A!`;
// 實際返回 "Hi\\u000A!",顯示的是轉(zhuǎn)義后的結(jié)果 "Hi\u000A!"

我們看到的打印出的結(jié)果是轉(zhuǎn)義之后的

String.raw`Hi\\n`
// 返回 "Hi\\\\n"

String.raw`Hi\\n` === "Hi\\\\n" // true

如果寫成正常函數(shù)的形式,它的第一個參數(shù),應該是一個具有raw屬性的對象,且raw屬性的值應該是一個數(shù)組,對應模板字符串解析后的值。

// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
//`s${1 + 2}\n5`
//等同于
String.raw({ raw: ['s', '\\n5'] }, 1 + 2) // "s3\n5"

在大多數(shù)情況下, String.raw()是用來處理模版字符串的. 不要被上面復雜的參數(shù)要求嚇到,因為像所有的 tag functions一樣,你通常不需要把它看成一個普通函數(shù),你只需要把它放在模板字符串前面就可以了,而不是在它后面加個括號和一堆參數(shù)來調(diào)用它,引擎會替你去調(diào)用它。

2-2.String.repeat()

repeat方法返回一個新字符串,表示將原字符串重復n次。參數(shù)如果是小數(shù),會被向下取整。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(2.9) // "nana"

如果repeat的參數(shù)是負數(shù)或者Infinity,會報錯。
但是,如果參數(shù)是 0 到-1 之間的小數(shù),則等同于 0,這是因為會先進行取整運算。0 到-1 之間的小數(shù),取整以后等于-0,repeat視同為 0。

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
'na'.repeat(-0.9) // ""

參數(shù)NaN等同于 0。如果repeat的參數(shù)是字符串,則會先轉(zhuǎn)換成數(shù)字。

'na'.repeat(NaN) // ""
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

2-3.padStart(),padEnd()

padStart()和padEnd()一共接受兩個參數(shù),第一個參數(shù)是字符串補全生效的最大長度,第二個參數(shù)是用來補全的字符串。

如果用來補全的字符串與原字符串,兩者的長度之和超過了最大長度,則會截去超出位數(shù)的補全字符串。

'abc'.padStart(10, '0123456789')
// '0123456abc'

如果省略第二個參數(shù),默認使用空格補全長度。

'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '

不管第二個參數(shù)是什么類型,都將先調(diào)用參數(shù)的toString方法將其轉(zhuǎn)為字符串之后進行拼接

'1'.padStart(10, 0) // "0000000001"
'1'.padStart(10, null) // "nullnulln1"
'1'.padStart(10, NaN) // "NaNNaNNaN1"
'1'.padStart(10, {}) // "[object O1"
'1'.padStart(10, []) // "1"
'1'.padStart(10, "") // "1"
'1'.padStart(10, undefined) // "         1"

注意:這里有兩個特殊的地方,當?shù)诙€參數(shù)為空數(shù)組時其返回值和參數(shù)為空字符串時的返回值相同,參數(shù)為undefined時,返回值以空格進行填充其結(jié)果與不傳第二個參數(shù)的結(jié)果一致。

2-4.replaceAll()

它的用法與replace()相同,返回一個新字符串,不會改變原字符串。

String.prototype.replaceAll(searchValue, replacement)

如果searchValue是一個不帶有g(shù)修飾符的正則表達式,replaceAll()會報錯。這一點跟replace()不同。

// 不報錯
'aabbcc'.replace(/b/, '_')

// 報錯
'aabbcc'.replaceAll(/b/, '_')

replaceAll()的第二個參數(shù)replacement是一個字符串,表示替換的文本,其中可以使用一些特殊字符串。

  • $&:匹配的子字符串。
  • $`:匹配結(jié)果前面的文本。
  • $':匹配結(jié)果后面的文本。
  • $n:匹配成功的第n組內(nèi)容,n是從1開始的自然數(shù)。這個參數(shù)生效的前提是,第一個參數(shù)必須是正則表達式。
  • $$:指代美元符號$。
// $& 表示匹配的字符串,即`b`本身
// 所以返回結(jié)果與原字符串一致
'abbc'.replaceAll('b', '$&')
// 'abbc'

// $` 表示匹配結(jié)果之前的字符串
// 對于第一個`b`,$` 指代`a`
// 對于第二個`b`,$` 指代`ab`
'abbc'.replaceAll('b', '$`')
// 'aaabc'

// $' 表示匹配結(jié)果之后的字符串
// 對于第一個`b`,$' 指代`bc`
// 對于第二個`b`,$' 指代`c`
'abbc'.replaceAll('b', `$'`)
// 'abccc'

// $1 表示正則表達式的第一個組匹配,指代`ab`
// $2 表示正則表達式的第二個組匹配,指代`bc`
'abbc'.replaceAll(/(ab)(bc)/g, '$2$1')
// 'bcab'

// $$ 指代 $
'abc'.replaceAll('b', '$$')
// 'a$c'

2.數(shù)值的擴展

2-1二進制和八進制表示法

ES6 提供了二進制和八進制數(shù)值的新的寫法,分別用前綴0b(或0B)和0o(或0O)表示。

0b111110111 === 503 // true
0o767 === 503 // true

從 ES5 開始,在嚴格模式之中,八進制就不再允許使用前綴0表示,ES6 進一步明確,要使用前綴0o表示。

// 非嚴格模式
(function(){
  console.log(0o11 === 011);
})() // true

// 嚴格模式
(function(){
  'use strict';
  console.log(0o11 === 011);
})() // Uncaught SyntaxError: Octal literals are not allowed in strict mode.

轉(zhuǎn)成10進制可以用parseInt方法或者Number方法

2-2.Number.isFinite(), Number.isNaN()

ES6 在Number對象上,新提供了Number.isFinite()和Number.isNaN()兩個方法。

它們與傳統(tǒng)的全局方法isFinite()和isNaN()的區(qū)別在于,傳統(tǒng)方法先調(diào)用Number()將非數(shù)值的值轉(zhuǎn)為數(shù)值,再進行判斷,而這兩個新方法只對數(shù)值有效,Number.isFinite()對于非數(shù)值一律返回false, Number.isNaN()只有對于NaN才返回true,非NaN一律返回false。

2-3.Number.parseInt(), Number.parseFloat()

ES6 將全局方法parseInt()和parseFloat(),移植到Number對象上面,行為完全保持不變。

2-4.Number.isInteger()

Number.isInteger()用來判斷一個數(shù)值是否為整數(shù)。
如果對數(shù)據(jù)精度的要求較高,不建議使用Number.isInteger()判斷一個數(shù)值是否為整數(shù)。

Number.isInteger(5E-324) // false
Number.isInteger(5E-325) // true

上面代碼中,5E-325由于值太小,會被自動轉(zhuǎn)為0,因此返回true。\

Number.isInteger(3.0000000000000002) // true

上面代碼中,Number.isInteger的參數(shù)明明不是整數(shù),但是會返回true。原因就是這個小數(shù)的精度達到了小數(shù)點后16個十進制位,轉(zhuǎn)成二進制位超過了53個二進制位,導致最后的那個2被丟棄了。

Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false

如果參數(shù)不是數(shù)值,Number.isInteger返回false。

2-5.Number.EPSILON

ES6 在Number對象上面,新增一個極小的常量Number.EPSILON。根據(jù)規(guī)格,它表示 1 與大于 1 的最小浮點數(shù)之間的差。
對于 64 位浮點數(shù)來說,大于 1 的最小浮點數(shù)相當于二進制的1.00..001,小數(shù)點后面有連續(xù) 51 個零。這個值減去 1 之后,就等于 2 的 -52 次方。

Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"

Number.EPSILON實際上是 JavaScript 能夠表示的最小精度。誤差如果小于這個值,就可以認為已經(jīng)沒有意義了,即不存在誤差了。
引入一個這么小的量的目的,在于為浮點數(shù)計算,設(shè)置一個誤差范圍。我們知道浮點數(shù)計算是不精確的。

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}

0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true

1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true

2-6.安全整數(shù)和 Number.isSafeInteger()

JavaScript 能夠準確表示的整數(shù)范圍在-253到253之間(不含兩個端點),超過這個范圍,無法精確表示這個值。

Math.pow(2, 53) // 9007199254740992

9007199254740992  // 9007199254740992
9007199254740993  // 9007199254740992

Math.pow(2, 53) === Math.pow(2, 53) + 1
// true

實際使用這個函數(shù)時,需要注意。驗證運算結(jié)果是否落在安全整數(shù)的范圍內(nèi),不要只驗證運算結(jié)果,而要同時驗證參與運算的每個值。

Number.isSafeInteger(9007199254740993)
// false
Number.isSafeInteger(990)
// true
Number.isSafeInteger(9007199254740993 - 990)
// true
9007199254740993 - 990
// 返回結(jié)果 9007199254740002
// 正確答案應該是 9007199254740003
9007199254740993 === 9007199254740992
// true

上面代碼中,9007199254740993不是一個安全整數(shù),但是Number.isSafeInteger會返回結(jié)果,顯示計算結(jié)果是安全的。這是因為,這個數(shù)超出了精度范圍,導致在計算機內(nèi)部,以9007199254740992的形式儲存。
所以,如果只驗證運算結(jié)果是否為安全整數(shù),很可能得到錯誤結(jié)果。下面的函數(shù)可以同時驗證兩個運算數(shù)和運算結(jié)果。

function trusty (left, right, result) {
  if (
    Number.isSafeInteger(left) &&
    Number.isSafeInteger(right) &&
    Number.isSafeInteger(result)
  ) {
    return result;
  }
  throw new RangeError('Operation cannot be trusted!');
}

trusty(9007199254740993, 990, 9007199254740993 - 990)
// RangeError: Operation cannot be trusted!

trusty(1, 2, 3)
// 3

2-7.Math.trunc()

Math.trunc方法用于去除一個數(shù)的小數(shù)部分,返回整數(shù)部分。

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

對于非數(shù)值,Math.trunc內(nèi)部使用Number方法將其先轉(zhuǎn)為數(shù)值。

Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0

對于空值和無法截取整數(shù)的值,返回NaN。

Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN
Math.trunc(undefined) // NaN

值得注意的是與Math.floor方法并不相同,當參數(shù)值小于0時,其表現(xiàn)與Math.ceil一致

Math.trunc(-1.6) //-1
Math.floor(-1.6); //-2
Math.ceil(-1.6)  // -1

2-8.Math.sign()

Math.sign方法用來判斷一個數(shù)到底是正數(shù)、負數(shù)、還是零。對于非數(shù)值,會先將其轉(zhuǎn)換為數(shù)值。

它會返回五種值。

  • 參數(shù)為正數(shù),返回+1;
  • 參數(shù)為負數(shù),返回-1;
  • 參數(shù)為 0,返回0;
  • 參數(shù)為-0,返回-0;
  • 其他值,返回NaN。

如果參數(shù)是非數(shù)值,會自動轉(zhuǎn)為數(shù)值。對于那些無法轉(zhuǎn)為數(shù)值的值,會返回NaN。

Math.sign('')  // 0
Math.sign(true)  // +1
Math.sign(false)  // 0
Math.sign(null)  // 0
Math.sign('9')  // +1
Math.sign('foo')  // NaN
Math.sign()  // NaN
Math.sign(undefined)  // NaN

2-9.Math.cbrt()

Math.cbrt()方法用于計算一個數(shù)的立方根。對于非數(shù)值,Math.cbrt()方法內(nèi)部也是先使用Number()方法將其轉(zhuǎn)為數(shù)值。

Math.cbrt(-1) // -1
Math.cbrt(0)  // 0
Math.cbrt(1)  // 1
Math.cbrt(2)  // 1.2599210498948732
Math.cbrt('8') // 2
Math.cbrt('hello') // NaN

2-10.Math.clz32()

Math.clz32()方法將參數(shù)轉(zhuǎn)為 32 位無符號整數(shù)的形式,然后返回這個 32 位值里面有多少個前導 0。

Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2

左移運算符(<<)與Math.clz32方法直接相關(guān)。

Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1 << 1) // 30
Math.clz32(1 << 2) // 29
Math.clz32(1 << 29) // 2

對于小數(shù),Math.clz32方法只考慮整數(shù)部分。對于空值或其他類型的值,Math.clz32方法會將它們先轉(zhuǎn)為數(shù)值,然后再計算。

Math.clz32(3.2) // 30
Math.clz32(3.9) // 30

Math.clz32() // 32
Math.clz32(NaN) // 32
Math.clz32(Infinity) // 32
Math.clz32(null) // 32
Math.clz32('foo') // 32
Math.clz32([]) // 32
Math.clz32({}) // 32
Math.clz32(true) // 31

2-11.Math.imul()

Math.imul方法返回兩個數(shù)以 32 位帶符號整數(shù)形式相乘的結(jié)果,返回的也是一個 32 位的帶符號整數(shù)。

Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4

如果只考慮最后 32 位,大多數(shù)情況下,Math.imul(a, b)與a * b的結(jié)果是相同的,即該方法等同于(a * b)|0的效果(超過 32 位的部分溢出)。之所以需要部署這個方法,是因為 JavaScript 有精度限制,超過 2 的 53 次方的值無法精確表示。這就是說,對于那些很大的數(shù)的乘法,低位數(shù)值往往都是不精確的,Math.imul方法可以返回正確的低位數(shù)值。

(0x7fffffff * 0x7fffffff)|0 // 0
Math.imul(0x7fffffff, 0x7fffffff) // 1

上面這個乘法算式,返回結(jié)果為 0。但是由于這兩個二進制數(shù)的最低位都是 1,所以這個結(jié)果肯定是不正確的,因為根據(jù)二進制乘法,計算結(jié)果的二進制最低位應該也是 1。這個錯誤就是因為它們的乘積超過了 2 的 53 次方,JavaScript 無法保存額外的精度,就把低位的值都變成了 0。Math.imul方法可以返回正確的值 1。

2-12.Math.fround()

Math.fround方法返回一個數(shù)的32位單精度浮點數(shù)形式。

對于32位單精度格式來說,數(shù)值精度是24個二進制位(1 位隱藏位與 23 位有效位),所以對于 -224 至 224 之間的整數(shù)(不含兩個端點),返回結(jié)果與參數(shù)本身一致。

Math.fround(0)   // 0
Math.fround(1)   // 1
Math.fround(2 ** 24 - 1)   // 16777215

如果參數(shù)的絕對值大于 224,返回的結(jié)果便開始丟失精度。

Math.fround(2 ** 24)       // 16777216
Math.fround(2 ** 24 + 1)   // 16777216

Math.fround方法的主要作用,是將64位雙精度浮點數(shù)轉(zhuǎn)為32位單精度浮點數(shù)。如果小數(shù)的精度超過24個二進制位,返回值就會不同于原值,否則返回值不變(即與64位雙精度值一致)。

// 未丟失有效精度
Math.fround(1.125) // 1.125
Math.fround(7.25)  // 7.25

// 丟失精度
Math.fround(0.3)   // 0.30000001192092896
Math.fround(0.7)   // 0.699999988079071
Math.fround(1.0000000123) // 1

對于 NaN 和 Infinity,此方法返回原值。對于其它類型的非數(shù)值,Math.fround 方法會先將其轉(zhuǎn)為數(shù)值,再返回單精度浮點數(shù)。

Math.fround(NaN)      // NaN
Math.fround(Infinity) // Infinity

Math.fround('5')      // 5
Math.fround(true)     // 1
Math.fround(null)     // 0
Math.fround([])       // 0
Math.fround({})       // NaN

2-13.Math.hypot()

Math.hypot方法返回所有參數(shù)的平方和的平方根。
如果參數(shù)不是數(shù)值,Math.hypot方法會將其轉(zhuǎn)為數(shù)值。只要有一個參數(shù)無法轉(zhuǎn)為數(shù)值,就會返回 NaN。

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

3.對數(shù)方法

3-1.Math.expm1()

Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1。

Math.expm1(-1) // -0.6321205588285577
Math.expm1(0)  // 0
Math.expm1(1)  // 1.718281828459045

3-2.Math.log1p()

Math.log1p(x)方法返回1 + x的自然對數(shù),即Math.log(1 + x)。如果x小于-1,返回NaN。

Math.log1p(1)  // 0.6931471805599453
Math.log1p(0)  // 0
Math.log1p(-1) // -Infinity
Math.log1p(-2) // NaN

3-3.Math.log10()

Math.log10(x)返回以 10 為底的x的對數(shù)。如果x小于 0,則返回 NaN。

Math.log10(2)      // 0.3010299956639812
Math.log10(1)      // 0
Math.log10(0)      // -Infinity
Math.log10(-2)     // NaN
Math.log10(100000) // 5

3-4.Math.log2()

Math.log2(x)返回以 2 為底的x的對數(shù)。如果x小于 0,則返回 NaN。

Math.log2(3)       // 1.584962500721156
Math.log2(2)       // 1
Math.log2(1)       // 0
Math.log2(0)       // -Infinity
Math.log2(-2)      // NaN
Math.log2(1024)    // 10
Math.log2(1 << 29) // 29

雙曲函數(shù)方法

ES6 新增了 6 個雙曲函數(shù)方法。

  • Math.sinh(x) 返回x的雙曲正弦(hyperbolic sine)
  • Math.cosh(x) 返回x的雙曲余弦(hyperbolic cosine)
  • Math.tanh(x) 返回x的雙曲正切(hyperbolic tangent)
  • Math.asinh(x) 返回x的反雙曲正弦(inverse hyperbolic sine)
  • Math.acosh(x) 返回x的反雙曲余弦(inverse hyperbolic cosine)
  • Math.atanh(x) 返回x的反雙曲正切(inverse hyperbolic tangent)

4.指數(shù)運算符

ES2016 新增了一個指數(shù)運算符(**)。

2 ** 2 // 4
2 ** 3 // 8

這個運算符的一個特點是右結(jié)合,而不是常見的左結(jié)合。多個指數(shù)運算符連用時,是從最右邊開始計算的。

// 相當于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512

指數(shù)運算符可以與等號結(jié)合,形成一個新的賦值運算符(**=)。

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

5.BigInt 數(shù)據(jù)類型

JavaScript 所有數(shù)字都保存成 64 位浮點數(shù),這給數(shù)值的表示帶來了兩大限制。一是數(shù)值的精度只能到 53 個二進制位(相當于 16 個十進制位),大于這個范圍的整數(shù),JavaScript 是無法精確表示的,這使得 JavaScript 不適合進行科學和金融方面的精確計算。二是大于或等于2的1024次方的數(shù)值,JavaScript 無法表示,會返回Infinity。

// 超過 53 個二進制位的數(shù)值,無法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

// 超過 2 的 1024 次方的數(shù)值,無法表示
Math.pow(2, 1024) // Infinity

ES2020 引入了一種新的數(shù)據(jù)類型 BigInt(大整數(shù)),來解決這個問題,這是 ECMAScript 的第八種數(shù)據(jù)類型。BigInt 只用來表示整數(shù),沒有位數(shù)的限制,任何位數(shù)的整數(shù)都可以精確表示。

const a = 2172141653n;
const b = 15346349309n;

// BigInt 可以保持精度
a * b // 33334444555566667777n

// 普通整數(shù)無法保持精度
Number(a) * Number(b) // 33334444555566670000

為了與 Number 類型區(qū)別,BigInt 類型的數(shù)據(jù)必須添加后綴n。
BigInt 同樣可以使用各種進制表示,都要加上后綴n。

1234 // 普通整數(shù)
1234n // BigInt
typeof 123n // 'bigint'

// BigInt 的運算
1n + 2n // 3n

0b1101n // 二進制
0o777n // 八進制
0xFFn // 十六進制

BigInt 與普通整數(shù)是兩種值,它們之間并不相等。也無法用BigInt與普通整數(shù)進行運算

42n === 42 // false
123n-1 //Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

BigInt 可以使用負號(-),但是不能使用正號(+),因為會與 asm.js 沖突。

-42n // 正確
+42n // 報錯

5-1.BigInt 對象

JavaScript 原生提供BigInt對象,可以用作構(gòu)造函數(shù)生成 BigInt 類型的數(shù)值。轉(zhuǎn)換規(guī)則基本與Number()一致,將其他類型的值轉(zhuǎn)為 BigInt。
BigInt()構(gòu)造函數(shù)必須有參數(shù),而且參數(shù)必須可以正常轉(zhuǎn)為數(shù)值,下面的用法都會報錯。參數(shù)如果是小數(shù),也會報錯。

BigInt(123) // 123n
BigInt('123') // 123n
BigInt(false) // 0n
BigInt(true) // 1n

new BigInt() // TypeError
BigInt(undefined) //TypeError
BigInt(null) // TypeError
BigInt('123n') // SyntaxError
BigInt('abc') // SyntaxError

BigInt(1.5) // RangeError
BigInt('1.5') // SyntaxError

上面代碼中,尤其值得注意字符串123n無法解析成 Number 類型,所以會報錯。
BigInt 對象繼承了 Object 對象的兩個實例方法。

  • BigInt.prototype.toString()
  • BigInt.prototype.valueOf()

它還繼承了 Number 對象的一個實例方法。

  • BigInt.prototype.toLocaleString()

此外,還提供了三個靜態(tài)方法。

  • BigInt.asUintN(width, BigInt): 給定的 BigInt 轉(zhuǎn)為 0 到 2width - 1 之間對應的值。
  • BigInt.asIntN(width, BigInt):給定的 BigInt 轉(zhuǎn)為 -2width - 1 到 2width - 1 - 1 之間對應的值。
  • BigInt.parseInt(string[, radix]):近似于Number.parseInt(),將一個字符串轉(zhuǎn)換成指定進制的 BigInt。
const max = 2n ** (64n - 1n) - 1n;

BigInt.asIntN(64, max)
// 9223372036854775807n
BigInt.asIntN(64, max + 1n)
// -9223372036854775808n
BigInt.asUintN(64, max + 1n)
// 9223372036854775808n

上面代碼中,max是64位帶符號的 BigInt 所能表示的最大值。如果對這個值加1n,BigInt.asIntN()將會返回一個負值,因為這時新增的一位將被解釋為符號位。而BigInt.asUintN()方法由于不存在符號位,所以可以正確返回結(jié)果。

如果BigInt.asIntN()和BigInt.asUintN()指定的位數(shù),小于數(shù)值本身的位數(shù),那么頭部的位將被舍棄。

const max = 2n ** (64n - 1n) - 1n;

BigInt.asIntN(32, max) // -1n
BigInt.asUintN(32, max) // 4294967295n

上面代碼中,max是一個64位的 BigInt,如果轉(zhuǎn)為32位,前面的32位都會被舍棄。

下面是BigInt.parseInt()的例子。

// Number.parseInt() 與 BigInt.parseInt() 的對比
Number.parseInt('9007199254740993', 10)
// 9007199254740992
BigInt.parseInt('9007199254740993', 10)
// 9007199254740993n

上面代碼中,由于有效數(shù)字超出了最大限度,Number.parseInt方法返回的結(jié)果是不精確的,而BigInt.parseInt方法正確返回了對應的 BigInt。

對于二進制數(shù)組,BigInt 新增了兩個類型BigUint64Array和BigInt64Array,這兩種數(shù)據(jù)類型返回的都是64位 BigInt。DataView對象的實例方法DataView.prototype.getBigInt64()和DataView.prototype.getBigUint64(),返回的也是 BigInt。

5-2.轉(zhuǎn)換規(guī)則

可以使用Boolean()、Number()和String()這三個方法,將 BigInt 可以轉(zhuǎn)為布爾值、數(shù)值和字符串類型。
另外,取反運算符(!)也可以將 BigInt 轉(zhuǎn)為布爾值。

Boolean(0n) // false
Boolean(1n) // true
Number(1n)  // 1
String(1n)  // "1"

!0n // true
!1n // false

上面代碼中,注意最后一個例子,轉(zhuǎn)為字符串時后綴n會消失。

5-3.數(shù)學運算

數(shù)學運算方面,BigInt 類型的+、-、*這四個二元運算符,與 Number 類型的行為一致。除法運算/會舍去小數(shù)部分,返回一個整數(shù)。

9n / 5n
// 1n

幾乎所有的數(shù)值運算符都可以用在 BigInt,但是有兩個例外。

  • 不帶符號的右移位運算符>>>
  • 一元的求正運算符+

上面兩個運算符用在 BigInt 會報錯。前者是因為>>>運算符是不帶符號的,但是 BigInt 總是帶有符號的,導致該運算無意義,完全等同于右移運算符>>。后者是因為一元運算符+在 asm.js 里面總是返回 Number 類型,為了不破壞 asm.js 就規(guī)定+1n會報錯。

BigInt 不能與普通數(shù)值進行混合運算。

1n + 1.3 // 報錯

上面代碼報錯是因為無論返回的是 BigInt 或 Number,都會導致丟失精度信息。比如(2n**53n + 1n) + 0.5這個表達式,如果返回 BigInt 類型,0.5這個小數(shù)部分會丟失;如果返回 Number 類型,有效精度只能保持 53 位,導致精度下降。

同樣的原因,如果一個標準庫函數(shù)的參數(shù)預期是 Number 類型,但是得到的是一個 BigInt,就會報錯。

// 錯誤的寫法
Math.sqrt(4n) // 報錯

// 正確的寫法
Math.sqrt(Number(4n)) // 2

上面代碼中,Math.sqrt的參數(shù)預期是 Number 類型,如果是 BigInt 就會報錯,必須先用Number方法轉(zhuǎn)一下類型,才能進行計算。

asm.js 里面,|0跟在一個數(shù)值的后面會返回一個32位整數(shù)。根據(jù)不能與 Number 類型混合運算的規(guī)則,BigInt 如果與|0進行運算會報錯。

1n | 0 // 報錯

5-4.其他運算

BigInt 對應的布爾值,與 Number 類型一致,即0n會轉(zhuǎn)為false,其他值轉(zhuǎn)為true。

if (0n) {
  console.log('if');
} else {
  console.log('else');
}
// else

上面代碼中,0n對應false,所以會進入else子句。

比較運算符(比如>)和相等運算符(==)允許 BigInt 與其他類型的值混合計算,因為這樣做不會損失精度。

0n < 1 // true
0n < true // true
0n == 0 // true
0n == false // true
0n === 0 // false

BigInt 與字符串混合運算時,會先轉(zhuǎn)為字符串,再進行運算。

'' + 123n // "123"

ES6參考鏈接https://es6.ruanyifeng.com/#docs/intro

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容