学习材料是winter前辈的《重学前端》课程,记录一下盲点。
类型
- 为什么推荐用void 0代替undefined
undefined不是关键字,此为js公认的设计失误之一;void接任意表达式都是undefined。为了防止undefined被无意篡改,推荐用void 0来获取undefined值。
window上的undefined是无法修改的,局部变量可以修改。 - string的最大长度
string并非表示其展示的字符串,而是其UTF编码,最大长度为2^53 - 1。
字符是以Unicode的方式表示的,每一个Unicode的码点表示一个字符,UTF是Unicode的编码方式,规定了码点在计算机中的表示方法,常见的有UTF16和UTF8,可以理解为两套字典。
js是UTF16,所以表示为U+4位16进制数。
绝大部分的常用字符都是4位16进制数,也就是U+0000到U+FFFF,被称为基本字符区域BMP,这部分的码点个数跟表现出来的字符串长度是一致的。
还有剩余部分从U+010000到U+10FFFF的字符被称为补充字符,也叫做代理,用两个BMP码点表示,形成一个代理对,表示一个补充字符。
正常来说都是BMP,校验长度也应该是不需要考虑补充字符的。补充字符举个栗子:'\uD83C\uDF03'
🌃,长度为2。
补充字符除了用代理对表示,还可以用String.fromCodePoint构造,比如上面这个例子,使用0x1F303
作为参数即可;\u只表示BMP,对应的字符构造方法是String.fromCharCode。
- 0.1 + 0.2 !== 0.3
这个之前在笔记里有写,与浮点数的计算方式有关,这个二进制算出来是个无限循环。
正确的计算方式是Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
。 - 为什么给对象添加的方法能用在基本类型上
一开始没太明白这句话的意思,实际上说的是为什么可以像'abc'.charAt(0)
(3.2).toFixed(2)
这样在基本类型上调用对象的方法。
原因是基本类型的.
操作符提供了装箱操作,将基本类型转为对象类型String、Number、Boolean、Symbol,这些对象类型原型上的方法自然可以被调用。 - 类型转换
==运算是设计失误,关于隐式转换规则,之前有写过一篇记录。但是不知道拆装箱转换。
装箱上面说了,就是从基本类型转为对象类型,前三种都可以用new来得到。symbol有点特殊,无法用new构造,使用Symbol构造的是symbol基本类型。构造Symbol对象类型可以用Object(Symbol('xxx'))
来得到。
拆箱则是逆操作,原理是对象的Symbol.toPrimitive方法,具体则是valueOf和toString,两者经过后都未返回基本类型则抛出TypeError异常。
调用valueOf和toString的顺序则是取决于上下文,比如
let o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o * 2
// 因为是算术运算,所以是先valueOf再toString,最后类型异常
let o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
String(o)
// 字符串操作,先toString再valueOf,最后类型异常
抛出的异常都是Cannot convert object to primitive value
,要说明一下这两个方法的返回值,如果返回基本类型,会进行隐式转换,返回对象类型则会再进行拆箱转换。当然隐式转换本身也包括对象类型的拆箱。
另外,在es6之后,还允许显示声明对象的Symbol.toPrimitive方法来覆盖toString和valueOf
let o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
String(o)
// 只会调用Symbol.toPrimitive,前面两个都没调用,打印hello
网友评论