es6的学习笔记(六):一些内在的提升

数字(Number)的扩展

数字的二进制,八进制,十六进制简洁表示

var binary=0b101;
var oc = 0o101;
var hex = 0x101;
console.log([binary,oc,hex]);//5,65,257

可以用0b表示二进制,0o表示八进制,0x表示16进制

Number.isNaN

这个函数和全局函数isNaN的不同点在于,全局的isNaN在执行判断是否是NaN前,进行了Number(value)的操作。这两个函数的返回值分别表示:value,Number(value)是否是NaN.

Number.isNaN(123)
// <‐ false, integers are not NaN
Number.isNaN(Infinity)
// <‐ false, Infinity is not NaN
Number.isNaN(`a hundred`)
// <‐ false, `a hundred` is not NaN
Number.isNaN(NaN)
// <‐ true, NaN is NaN
Number.isNaN(`a hundred` / `two`)
// <‐ true, `a hundred` / `two` is NaN, NaN is NaN

isNaN(`a hundred`)
// <‐ true, because Number(`a hundred`) is NaN

而对于isNaN,会先执行Number(value),所以字符,也会被变成NaN,所以字符的输出结果是true。

Number.isFinite

类似于isNaN和Number.isNaN,区别在于判断之前是否进行了Number(value)的操作,对比如下:

isFinite(NaN)
// <‐ false
isFinite(Infinity)
// <‐ false
isFinite(‐Infinity)
// <‐ false
isFinite(null)
// <‐ true, because Number(null) is 0
isFinite(‐13)
// <‐ true, because Number(‐13) is ‐13
isFinite(`10`)
// <‐ true, because Number(`10`) is 10


Number.isFinite(NaN)
// <‐ false
Number.isFinite(Infinity)
// <‐ false
Number.isFinite(‐Infinity)
// <‐ false
Number.isFinite(null)
// <‐ false, because null is not a number
Number.isFinite(‐13)
// <‐ true
Number.isFinite(`10`)
// <‐ false, because `10` is not a number

事实上,Number.isFinite = value => typeof value === number && isFinite(value)

Number.parseInt

这个函数和parseInt完全相同。不过parseInt在es6中也得到了扩展。不需要提供基数,使用0x这样的符号也是支持的。但是不支持0b,0o如果提供了基数,会在第一个不认识的字符放弃,之前的转换还是正确的

console.log(Number.parseInt === parseInt)
// <‐ true

parseInt(`0xf00`)
// <‐ 3840
parseInt(`0xf00`, 16)
// <‐ 3840

parseInt(`0xf00`, 10)
// <‐ 0
parseInt(`5xf00`, 10)
// <‐ 5, illustrating there's no special treatment here


parseInt(`0b011`)
// <‐ 0
parseInt(`0b011`, 2)
// <‐ 0
parseInt(`0o100`)
// <‐ 0
parseInt(`0o100`, 8)
// <‐ 0
parseInt(`0b011`.slice(2), 2)
// <‐ 3
parseInt(`0o110`.slice(2), 8)
// <‐ 72

Number(`0b011`)
// <‐ 3
Number(`0o110`)  //事实上,直接使用0b101,就是十进制的5,不需要模板字面量``
// <‐ 72

Number.parseFloat

事实上,和parseInt一样,这个函数也是等于全局的parseFloat.这样的好处其实是为了把全局的逐渐转移到局部的相关数据类型上,来避免全局变量的污染

console.log(Number.parseFloat === parseFloat)
// <‐ true

Number.isInteger

这个方法是全局没有的,就是判断一个数字是否是整数。内部没有经过Number(value)操作。

console.log(Number.isInteger(Infinity)); // <‐ false
console.log(Number.isInteger(‐Infinity)); // <‐ false
console.log(Number.isInteger(NaN)); // <‐ false
console.log(Number.isInteger(null)); // <‐ false
console.log(Number.isInteger(0)); // <‐ true
console.log(Number.isInteger(‐10)); // <‐ true
console.log(Number.isInteger(10.3)); // <‐ false

可以认为他内部的操作原理如下:

Number.isInteger = value => Number.isFinite(value) && value%1 === 0

Number.EPSILON

中文读过来就是一不西龙,是一个常数,数值特别小,可以用它来判断浮点数之间的差距是否足够小,小到可以忽略的地步。

Number.EPSILON
// <‐ 2.220446049250313e‐16
Number.EPSILON.toFixed(20)
// <‐ `0.00000000000000022204`

0.1 + 0.2
// <‐ 0.30000000000000004
0.1 + 0.2 === 0.3
// <‐ false

console.log(0.1 + 0.2 - 0.3)
// <‐ 5.551115123125783e‐17
console.log(5.551115123125783e-17.toFixed(20))
// <‐ `0.00000000000000005551`

console.log(5.551115123125783e-17 < Number.EPSILON)
// <‐ true

根据上述代码,可以写出一个来判断两个浮点数之间的差值是否可以接受。

function withinMarginOfError (left, right) {
    return Math.abs(left - right) < Number.EPSILON
}
withinMarginOfError((0.1+0.2),0.3);//true

多说一句,并不是每一个浮点数都不能被精确表示,0.1+0.2不能表示为0.3,但是0.3+0.2=0.5是可以精确表示,具体规则我也不是很清楚

Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGER ,Number.isSafeInteger

并不是每一个整数都可以被精确表示。
Number.MAX_SAFE_INTEGER是js中可以被安全的精确表示的数字。最小的就是它的相反数,也可以用Number.MIN_SAFE_INTEGER来表示。2^53-1是计算机有64位,有11位被用来表示指数,1位用来表示符号位。剩余的52位来表示整数,如果这52位都为1,就是最大不失真整数,2^53-1。如果数据超过了这个范围,就会以精度为代价,用11位的指数来进行表示,其中11位指数的第一位用来表示指数的正负
浮点数

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

//下面的代码来验证超过这个范围就会不精确
1 === 2
// <‐ false
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2
// <‐ true
Number.MIN_SAFE_INTEGER - 1 === Number.MIN_SAFE_INTEGER - 2//同时相加,依旧相等,当然,这个加数是有范围的
// <‐ true

Number.isSafeInteger就是用来判断一个数是否是安全的,也就是是否在[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER]之间,这个函数也没有进行Number(value)转换,所以传入字符串等必然报错,小数也必然会报错。

Number.isSafeInteger(`one`); // <‐ false
Number.isSafeInteger(`0`); // <‐ false
Number.isSafeInteger(null); // <‐ false
Number.isSafeInteger(NaN); // <‐ false
Number.isSafeInteger(Infinity); // <‐ false
Number.isSafeInteger(‐Infinity); // <‐ false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER ‐ 1); // <‐ false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER); // <‐ true
Number.isSafeInteger(1); // <‐ true
Number.isSafeInteger(1.2); // <‐ false
Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // <‐ true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1); // <‐ false

事实上,如果两个数都是安全数,他们的计算结果不一定是安全的,因为简单的加法,两个安全的整数想加,超过了安全范围,计算结果就会有偏差。
一个是安全的,一个是不安全的,他们的结果也有可能出现偏差。
两个都是不安全的,虽然他们的结果是安全的,但是结果也有可能出现偏差。

Number.isSafeInteger(9007199254740000)
// <‐ true
Number.isSafeInteger(993)
// <‐ true
Number.isSafeInteger(9007199254740000 + 993)
// <‐ false
9007199254740000 + 993
// <‐ 9007199254740992, should be 9007199254740993

//但是并不是说所有的都是错误的,有可能是正确的,似乎是以2位单位的。但是结果不是很靠谱了
9007199254740000 + 994
// <‐ 9007199254740994

Number.isSafeInteger(9007199254740993)
// <‐ false
Number.isSafeInteger(990)
// <‐ true
Number.isSafeInteger(9007199254740993 ‐ 990)
// <‐ true
9007199254740993 ‐ 990
// <‐ 9007199254740002, should be 9007199254740003

Number.isSafeInteger(9007199254740995)
// <‐ false
Number.isSafeInteger(9007199254740993)
// <‐ false
Number.isSafeInteger(9007199254740995 ‐ 9007199254740993)
// <‐ true
9007199254740995 ‐ 9007199254740993
// <‐ 4, should be 2

综上,可以认为,一次操作,不论是操作数,还是结果不安全,那么这个结果很有可能是不准确的。可以通过下面这个函数来判断计算记过是否准确

function safeOp (result, ...operands) {
  const values = [result, ...operands]
  if (!values.every(Number.isSafeInteger)) {
  throw new RangeError('Operation cannot be trusted!')
  }
  return result
}
safeOp(9007199254740000 + 993, 9007199254740000, 993)
// <‐ RangeError: Operation cannot be trusted!
safeOp(9007199254740993 + 990, 9007199254740993, 990)
// <‐ RangeError: Operation cannot be trusted!
safeOp(9007199254740993 ‐ 990, 9007199254740993, 990)
// <‐ RangeError: Operation cannot be trusted!
safeOp(9007199254740993 ‐ 9007199254740995, 9007199254740993, 9007199254740995)
// <‐ RangeError: Operation cannot be trusted!
safeOp(1 + 2, 1, 2)
// <‐ 3

Math函数的扩展

Math.sign()

这个函数比较简单,就是返回-1,0,1,值得注意的是这个函数的数值会先经过Number(value)操作。

Math.sign(1); // <‐ 1
Math.sign(0); // <‐ 0
Math.sign(‐0); // <‐ ‐0
Math.sign(‐30); // <‐ ‐1
Math.sign(NaN); // <‐ NaN
Math.sign(`one`); // <‐ NaN, because Number(`one`) is NaN
Math.sign(`0`); // <‐ 0, because Number(`0`) is 0
Math.sign(`7`); // <‐ 1, because Number(`7`) is 7

Math.trunc()

这个函数类似Math.floor()向下取整,Math.ceil()向上取整,这个函数是不论正负,直接去掉小数,同时,函数的数值会经过Number(value)处理。

Math.trunc(12.34567); // <‐ 12
Math.trunc(‐13.58); // <‐ ‐13
Math.trunc(‐0.1234); // <‐ ‐0
Math.trunc(NaN); // <‐ NaN
Math.trunc(`one`); // <‐ NaN, because Number(`one`) is NaN
Math.trunc(`123.456`); // <‐ 123, because Number(`123.456`) is 123.456

Math.cbrt()

Match.sqrt()是二次开根的缩写,cbrt是三次开根的缩写。同样,输入经过Number(value)处理。

Math.cbrt(‐1); // <‐ ‐1
Math.cbrt(3); // <‐ 1.4422495703074083
Math.cbrt(8); // <‐ 2
Math.cbrt(27); // <‐ 3
Math.cbrt(`8`); // <‐ 2, because Number(`8`) is 8
Math.cbrt(`one`); // <‐ NaN, because Number(`one`) is NaN

Math.expm1(),Math.log1p(),Math.log2(),Math.log10()

Math.exmp1()做了和Math.exp(value) ‐ 1一样的操作,但是精度比后者高。
Math.log1p()做了Math.log(value + 1)一样的操作,但是精度比后者高
Math.log10()做了和Math.log(x) / Math.LN10一样的操作,但是精度更高
Math.log2()就是Math.log(x) / Math.LN2,但是精度比后者高

三角函数

  1. Math.sinh(value) returns the hyperbolic sine of value
  2. Math.cosh(value) returns the hyperbolic cosine of value
  3. Math.tanh(value) returns the hyperbolic tangent of value
  4. Math.asinh(value) returns the hyperbolic arc­sine of value
  5. Math.acosh(value) returns the hyperbolic arc­cosine of value
  6. Math.atanh(value) returns the hyperbolic arc­tangent of value

Math.hypot()

Math.hypot(1,2,3,4)的值应该是sqrt(11+22+33+44),当时精度比直接使用sqrt高。输入参数不能是数组,其实在数组前加上…就可以用了。

字符串和unicode码

事实上,对字符串的一个提升就表现在模板字面量上,可以在//中嵌入变量,表达式。其他的提升主要表现在对字符串的控制和unicode码上。

String#startsWith(),#endsWith(),#includes()

相对来讲都比较简单,都可以使用indexOf来替换,但是简化了它的写法,示例用法如下:

`hello ell`.startsWith(`ell`, 6) //第二个参数可省,默认为0
// <‐ true

`hello gary`.endsWith(`gary`, 10)//第二个参数可省,默认是字符串的Length,实际检测的数组坐标应该是参数-1
// <‐ true
`hello gary`.endsWith(`gary`, 9)
// <‐ false, it ends with `gar` in this case

`hello gary`.includes(`ga`, 4)
// <‐ true
`hello gary`.includes(`ga`, 7)
// <‐ false

String#repeat()

repeat是可以让目前的字符串重复很多次,生成一个新的字符串

`ha`.repeat(1)
// <‐ `ha`
`ha`.repeat(2)
// <‐ `haha`
`ha`.repeat(5)
// <‐ `hahahahaha`
`ha`.repeat(0)
// <‐ ``

Unicode

Unicode目前一共有17个辅助平面,每个平面包含65536个字符。
js采用UTF-16,用两个字节表示基本平面,用4个字节表示辅助平面。程序如何判断unicode是基本平面还是应该把之后两个组合成一个辅助平面?
具体来说,辅助平面的字符位共有2^20(2^42^16)个,也就是说,对应这些字符至少需要20个二进制位。UTF-16将这20位拆成两半,前10位映射在U+D800到U+DBFF(空间大小2^10),称为高位(H),后10位映射在U+DC00到U+DFFF(空间大小2^10),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。
比如:u+D800U+DC00就表示辅助平面1的第一个字符。总共可以表示2^10
2^10个字符。刚好满足空间需求。这一切的条件都来自于基本平面的d800-dbff是空的,而这刚好就是空的。
所以转码规则如下:
对于基本平面的字符,直接显示基本平面对应的字符,对于非基本平面的字符,用第一个字符减去D800就得到高10位,用后一个字符-DC00就是低十位,组合起来就是对应的unicode32位码。
当然,此处因为是只需要有16个副主平面,所以高4位应该是0b0000-0b1111.
在es6中,可以采用’\ud83d\udc0e’表示,也可以采用’\u{1f40e}’来表示。

console.log(`\ud83d\udc0e`)
//🐎
console.log(`\u{1f40e}`)
//🐎
console.log('\ud83d\udc0e\ud83d\udc71\u2764')
//🐎👱❤

因为es6给unicode添加了iterator,所以直接采用for of 就可以遍历所有经过转码计算的unicode

let s='\ud83d\udc0e\ud83d\udc71\u2764'
for(let i of s)
  console.log(i)
//🐎
//👱
//❤
console.log(s.length)//5
console.log([...s].length)//3 内置迭代器的功能

codePointAt

es6提供了一个可以把unicode转换为10进制的函数,codePointAt;

const text = `\ud83d\udc0e\ud83d\udc71\u2764`
text.codePointAt(0)
// <‐ 128014
text.codePointAt(2)
// <‐ 128113
text.codePointAt(4)
// <‐ 10084
//内置迭代器大大方便了unicode的便利
const text = `\ud83d\udc0e\ud83d\udc71\u2764`
for (let codePoint of text) {
console.log(codePoint.codePointAt(0))
// <‐ 128014
// <‐ 128113
// <‐ 10084
}
[...text].map(cp => cp.codePointAt(0))
// <‐ [128014, 128113, 10084]

//用toString可以方便的看到经过转换后的Unicode16进制码
[...text].map(cp => cp.codePointAt(0).toString(16))
// <‐ [`1f40e`, `1f471`, `2764`]

fromCodePoint()

同时还提供了 String.fromCodePoint() 函数来将数字转换成对应的unicode码

String.fromCodePoint(128014, 128113, 10084)//🐎👱❤
String.fromCodePoint(0x1f40e)//🐎

reverse

当需要把unicode字符reverse的时候,需要先使用…操作符再翻转

const text = `\ud83d\udc0e\ud83d\udc71\u2764`
[...text].reverse().join(``)

String#normalize

这个函数作用不大,主要意义在于比较一些人眼看起来相等,但是实际上却是不同字符组合而成,看起来很像而已。对字符串用上这个函数,就会把组合的转换成未组合的样子,这样,人眼看到的字符和实际的字符就是一致的。

Array的扩展

ES5已经给Array带来了很多的新特性,比如:Array#filter , Array#map , Array#reduce , Array#reduceRight , Array#forEach ,
Array#some , and Array#every,而es6也带来了一些新特性。

Array.from

可以从类数组或者iterator中构建一个数组,有三个参数,虽然只有第一个参数必须给。第二个参数是map函数,可以对每一个新数组进行操作然后返回。

//code1 从类数组对象中定义数组并map
const things = {
0: {
type: `fruit`,
name: `Apple`,
amount: 3
},
1: {
type: `vegetable`,
name: `Onion`,
amount: 1
},
length: 2
}

console.log(Array.from(things))
//[object Object],[object Object]
console.log(Array.from(things, thing => thing.type))
// <‐ [`fruit`, `vegetable`, ...]

//从可迭代的生成器中生成数组并map
function* gen()
{
  yield 1;
  yield 2;
  yield* '1222334';
}
console.log(Array.from(gen()));//1,2,1,2,2,2,3,3,4
console.log(Array.from(gen(),value=>value*2));//2,4,2,4,4,4,6,6,8

Array.of

事实上,Array.of = (…params) => params,可以这么理解。

console.log(Array.of()); // <‐ []
console.log(Array.of(undefined)); // <‐ [undefined]
console.log(Array.of(1)); // <‐ [1]
console.log(Array.of(3)); // <‐ [3]
console.log(Array.of(`3`)); // <‐ [`3`]
console.log(Array.of(1, 2)); // <‐ [1, 2]
console.log(Array.of(-1, -2)); // <‐ [‐1, ‐2]
console.log(Array.of(-1)); // <‐ [‐1]

Array#copyWithin

Array.prototype.copyWithin(target, start = 0, end = this.length),内部复制函数,将一个数组的[start,end)的元素复制到数组中的target位置。如下:

const items = [1, 2, 3, ,,,,,,,]
// <‐ [1, 2, 3, undefined x 7]
const items = [1, 2, 3, ,,,,,,,]
items.copyWithin(6, 1, 3)
// <‐ [1, 2, 3, undefined × 3, 2, 3, undefined × 2]

当然,这个方法也可以使用以前的方法来实现

const items = [1, 2, 3, ,,,,,,,]
const copy = items.slice(1, 3)
// <‐ [2, 3]
items.splice(6, 3 ‐ 1, ...copy)
console.log(items)
// <‐ [1, 2, 3, undefined × 3, 2, 3, undefined × 2]

只是没有新的方法那么便利。

Array#fill

接收三个参数,只有第一个参数是必须的,第二个表示起始位置,第三个表示结束位置,[start,end)

[`a`, `b`, `c`].fill(`x`); // <‐ [`x`, `x`, `x`]
[`a`, `b`, `c`,,,].fill(`x`, 2)
// <‐ [`a`, `b`, `x`, `x`, `x`]
new Array(5).fill(`x`, `x`, 3)
// <‐ [`x`, `x`, `x`, undefined x 2]

Array#find Array#findIndex

寻找数组中满足要求的元素,一个返回该元素,一个返回Index

[`a`, `b`, `c`, `d`, `e`].find(item => item === `c`)
// <‐ `c`
[`a`, `b`, `c`, `d`, `e`].find((item, i) => i === 0)
// <‐ `a`
[`a`, `b`, `c`, `d`, `e`].find(item => item === `z`)
// <‐ undefined


[`a`, `b`, `c`, `d`, `e`].findIndex(item => item === `c`)
// <‐ 2
[`a`, `b`, `c`, `d`, `e`].findIndex((item, i) => i === 0)
// <‐ 0
[`a`, `b`, `c`, `d`, `e`].findIndex(item => item === `z`)
// <‐ ‐1

Array#keys

Array#keys返回值是一个遍历器,值是index

for (let key of [`a`, `b`, `c`, `d`].keys()) {
console.log(key)
}
//0 1 2 3 4

Array#values

values的返回值也是一个遍历器,值就是数组的值

for (let key of [`a`, `b`, `c`, `d`].values()) {
console.log(key)
}
//a b c d

Array#entries()

返回值也是一个遍历器,值是[index,数组值]

[...[`a`, `b`, `c`, `d`].entries()]
// <‐ [[0, `a`], [1, `b`], [2, `c`], [3, `d`]]

Array.prototype[Symbol.iterator]

其实是和Array.values函数是一样的,返回值都是一个迭代器或者遍历器,输出的数值均是数组的值

const list = [`a`, `b`, `c`, `d`]
list[Symbol.iterator] === list.values
// <‐ true
[...list[Symbol.iterator]()]
// <‐ [`a`, `b`, `c`, `d`]

最长的一篇了,新知识有点乏味,但是新特性比较好用