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,但是精度比后者高
三角函数
- Math.sinh(value) returns the hyperbolic sine of value
- Math.cosh(value) returns the hyperbolic cosine of value
- Math.tanh(value) returns the hyperbolic tangent of value
- Math.asinh(value) returns the hyperbolic arcsine of value
- Math.acosh(value) returns the hyperbolic arccosine of value
- Math.atanh(value) returns the hyperbolic arctangent 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^102^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`]
最长的一篇了,新知识有点乏味,但是新特性比较好用