之前读了《你不知道的javaScript》系列的上卷,感觉对于理解 js 的核心概念十分有效。
最近又将中卷的部分读完了,下面对于中卷的内容进行一些记录。
中卷主要是分语法和类型,异步和性能两方面。下面我们开始。
类型和语法
类型
内置类型
JavaScript有七种内置类型:
空值(null)
未定义(undefined)
布尔值(boolean)
数字(number)
字符串(string)
对象(object)
符号(symbol, ES6中新增)
除对象之外,其他统称为“基本类型”。
值
数组
使用delete运算符可以将单元从数组中删除,但是请注意,单元删除后,数组的length属性并不会发生变化。
如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。
1 | var a = [] |
类数组
例如,一些DOM查询操作会返回DOM元素列表,它们并非真正意义上的数组,但十分类似。
另一个例子是通过arguments对象(类数组)将函数的参数当作列表来访问(从ES6开始已废止)。
字符串
JavaScript中字符串是不可变的,而数组是可变的。
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。
而数组的成员函数都是在其原始值上进行操作。
1 | let a = 'foo' |
数字
与大部分现代编程语言(包括几乎所有的脚本语言)一样,JavaScript中的数字类型是基于IEEE 754标准来实现的,该标准通常也被称为“浮点数”。
JavaScript使用的是双精度格式(即64位二进制)。
1 | // 无效语法 |
较小的数值
二进制浮点数最大的问题(不仅JavaScript,所有遵循IEEE 754规范的语言都是如此),是会出现如下情况:
1 | 0.1 + 0.2 === 0.3 // false |
简单来说,二进制浮点数中的0.1和0.2并不是十分精确,它们相加的结果并非刚好等于0.3,而是一个比较接近的数字0.30000000000000004,所以条件判断结果为false。
那么应该怎样来判断0.1 + 0.2和0.3是否相等呢?最常见的方法是设置一个误差范围值,通常称为“机器精度”(machineepsilon),对JavaScript的数字来说,这个值通常是2^-52
特殊数值
1 | undefined |
特殊等式
NaN和-0在相等比较时的表现有些特别。
由于NaN和自身不相等,所以必须使用ES6中的Number.isNaN(..)(或者polyfill)。
而-0等于0(对于===也是如此),因此我们必须使用isNegZero(..)这样的工具函数。
值和引用
简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/传递,包括null、undefined、字符串、数字、布尔和ES6中的symbol。
复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。
原生函数
String() / Number() / Boolean() / Array() / Object() / Function() / RegExp() / Date() / Error() / Symbol()
原生函数可以被当作构造函数来使用
通过构造函数(如new String(“abc”))创建出来的是封装了基本类型值(如”abc”)的封装对象
内部属性[[class]]
1 | Object.prototype.toString().call([1,2,3]) // '[object Array]' |
封装对象包装
就是隐式转换成复杂类型
1 | let a = 'abc' |
拆封
就是转换成简单数据类型
1 | let a = new String('abc') |
原生函数作为构造函数
强制类型转换
将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况
隐式的情况称为强制类型转换(coercion)
1 | let a = 42 |
抽象值操作
ToString
对普通对象来说,除非自行定义,否则toString()(Object.prototype.toString()
)返回内部属性[[Class]]
的值,如"[object Object]"
。
数组的默认toString()方法经过了重新定义,将所有单元字符串化以后再用”, “连接起来
1 | let a = 1.07*1000*1000*1000*1000*1000*1000*1000 |
ToNumber
true转换为1, false转换为0。undefined转换为NaN, null转换为0
ToBoolean
假值(falsy value)
1 | undefined |
显式强制类型转换
1.字符串和数字之间的显式转换
字符串和数字之间的显式转换
主要是 + 运算符在这里的运用
1 | let a = 42 |
奇特的~运算符
~x大致等同于-(x+1)。很奇怪,但相对更容易说明问题:
1 | ~42 // -(42+1) ==> -43 |
indexOf() 中 >= 0
和== -1
这样的写法不是很好,称为“抽象渗漏”,意思是在代码中暴露了底层的实现细节,这里是指用-1作为失败时的返回值,这些细节应该被屏蔽掉。
现在我们终于明白有什么用处了!和indexOf()一起可以将结果强制类型转换(实际上仅仅是转换)为真/假值
字位截除~~
和!!
很相似
对~~
我们要多加注意。首先它只适用于32位数字,更重要的是它对负数的处理与Math. floor(..)不同。
1 | Math.floor(-49.6) // 50 |
2.显式解析数字字符串
解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。但解析和转换两者之间还是有明显的差别。
解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
1 | let a = '42' |
3.显式转换为布尔值
1 | let d = 0 |
隐式强制类型转换
1.字符串和数字之间的隐式强制类型转换
1 | let a = '42' |
之前的文章中提到了隐式转换更详细的内容
2.布尔值到数字的隐式强制类型转换
3.隐式强制类型转换为布尔值
4.||
和 &&
5.符号(Symbol)的强制类型转换
Symbol不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是true)
宽松相等和严格相等
宽松相等(loose equals)==和严格相等(strict equals)===都用来判断两个值是否“相等”,但是它们之间有一个很重要的区别,特别是在判断条件上
正确的解释是:“==允许在相等比较中进行强制类型转换,而===不允许。”
1.两种相等比较的性能
如果两个值的类型不同,我们就需要考虑有没有强制类型转换的必要,有就用==,没有就用===,不用在乎性能。
2.抽象相等
2.1 字符串和数字之间的相等比较
1 | let a = 42 |
具体怎么转换?是a从42转换为字符串,还是b从”42”转换为数字?
ES5规范11.9.3.4-5这样定义:
(1) 如果Type(x)是数字,Type(y)是字符串,则返回x == ToNumber(y)的结果。
(2) 如果Type(x)是字符串,Type(y)是数字,则返回ToNumber(x) == y的结果。
2.2 其他类型和布尔类型之间的相等比较
规范11.9.3.6-7是这样说的:
(1) 如果Type(x)是布尔类型,则返回ToNumber(x) == y的结果;
(2) 如果Type(y)是布尔类型,则返回x == ToNumber(y)的结果。
2.3 null和undefined之间的相等比较
ES5规范11.9.3.2-3规定:
(1) 如果x为null, y为undefined,则结果为true。
(2) 如果x为undefined, y为null,则结果为true。
2.4 对象和非对象之间的相等比较
ES5规范11.9.3.8-9做如下规定:
(1) 如果Type(x)是字符串或数字,Type(y)是对象,则返回x == ToPrimitive(y)的结果;
(2) 如果Type(x)是对象,Type(y)是字符串或数字,则返回ToPrimitive(x) == y的结果。
比较少见的情况
1.返回其他数字
1 | var i = 2 |
我们应该正确合理地运用强制类型转换,避免这些极端的情况。
2.假值的相等比较
1 | '0' == false // true |
3.极端例子
1 | [] == ![] // true |
4.完整性检查
5.安全运用隐式强制类型转换
我们要对==两边的值认真推敲,以下两个原则可以让我们有效地避免出错。
• 如果两边的值中有true或者false,千万不要使用==。
• 如果两边的值中有[]、””或者0,尽量不要使用==。
这时最好用===来避免不经意的强制类型转换。这两个原则可以让我们避开几乎所有强制类型转换的坑。
- 本文作者: Jambo
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!