类型
一些老生常谈的变量类型的介绍这里就不赘述了,需要有一点注意的就是: JS中变量是没有类型的,他们持有的值才有
值
对js中几个最基本的内置类型: 数组,字符串,数字,特殊值进行深入理解
数组
数组的一些常规特性这里也不赘述了,讲两种不太常见的情况
稀疏数组
含有空缺的数组,创建一个稀疏数组:
const arr = []
arr[0] = 1;
arr[3] = 4;
console.log(arr.length) // 4
console.log(typeof arr[2]); // undefined
arr.forEach((item) => {
console.log(item); // 1,4
});
console.log(arr) // [1, empty × 2, 3]
打印这个数组回发现,空缺的位置使用empty进行填补,并且他的值是undefined,遍历时与直接将undefined赋值给元素不同,empty遍历时不会遍历
类数组
具有数组索引,并且具有length属性,但不具有数组的其他特征的对象,常见的有:
document.querySelector等方法获取的NodeListarguments对象
常见的类数组转换为数组的方法:
- 解构:
[...arguments] - call:
[].slice.call(arguments) - Array.from:
Array.from(arguments)
字符串
js中的字符串是不可变的,因此无法通过下标修改某个字符
let str = 'abc'
str[0] = 'hhhh'
console.log(str) //abc
同时也无法借用数组的可变更成员函数: splice,reverse等,因为这些方法试图修改字符串
数字
较小数值
0.1+0.2 === 0.3 // false很多同学可能都见过这种情况,不仅是js拥有,所有遵循IEEE 745规范的语言都是如此,简单来说浮点数中的0.1,0.2并不精确,他们相加为0.30000000000000004,所以判断结果为false
出现误差的原因:
IEEE 745规范规范中,十进制小数转换成二进制按照以下规律:小数部分乘2取整数,若相乘之后的小数部分不为0,则继续乘以2,直到小数部分为0,将取出的整数正向排序
如: 0.1转换为二进制
0.1 * 2 = 0.2 --------------- 取整数 0,小数 0.2
0.2 * 2 = 0.4 --------------- 取整数 0,小数 0.4
0.4 * 2 = 0.8 --------------- 取整数 0,小数 0.8
0.8 * 2 = 1.6 --------------- 取整数 1,小数 0.6
0.6 * 2 = 1.2 --------------- 取整数 1,小数 0.2
0.2 * 2 = 0.4 --------------- 取整数 0,小数 0.4
0.4 * 2 = 0.8 --------------- 取整数 0,小数 0.8
0.8 * 2 = 1.6 --------------- 取整数 1,小数 0.6
0.6 * 2 = 1.2 --------------- 取整数 1,小数 0.2
...
最后将取出的整数正向排列: 0.0001100110011001100110011001100110011001100110011001101...
上述例子可以看到,整数部分一直不会是0,所以会无限循环下去,后面的二进制只能存储52位,这又是为什么呢?下面会给你答案
IEEE 754
二进制浮点数算数标准的简称,其最常用的两种浮点数表示方式为: 单精度浮点数(32位),双精度浮点数(64位),这里重点介绍以下js中使用的双精度浮点数:
64Bits分为以下三个部分:
- 符号部分(S): 占1个bit
- 指数部分(E):表示次方数,占11个bit
- 尾数: 用来表示精度的部分(就是值的部分),占52个bit,
综上所述,0.1转换为二进制,尾数会是无限循环,双精度浮点数最多存储52个尾数.这也就导致0.1转换为二进制之后就已经不是数学意义上的0.1了
误差范围
那么如何判断这种理应相等但由于精确度引起的不相等情况呢?
我们可以设定一个误差范围,只要小于这个误差范围的数值,就可以看作是相等的,js中这个值通常是2^-52存储在属性Number.EPSILON,
可以直接使用:
function numCloseEnoughEqual(a,b){
return Math.abs(a-b) < Number.EPSILON
}
整数和浮点数的安全范围
浮点数最大值为: 1.798e+308定义在Number.MAX_VALUE中
浮点数最小值为: 5e-325定义在Number.MIN_VALUE中
整数最小值为: 定义在Number.MIN_SAFE_INTEGER,中
整数的最大值为: 定义在Number.MAX_SAFE_INTEGER中
整数的检测
使用Number.isInteger可以检测一个数是否是整数
使用Number.isSafeInteger可以检测一个数是否是安全整数(小于等于Number.MAX_SAFE_INTEGER)
特殊值
null和undefined类型比较特殊,每个类型分别只有一个值: null和undefined
- undefined: 表示没有值
- null: 表示值为空
void运算符
undefined是一个内置标识符,但它可以重新被定义:
const undefined = 9;
接下来与undefined比较相当于和9比较
对此我们可以使用void来代替undefined进行比较运算,防止undefined被提前篡改导致比较不准确
void __没有返回值,因此范围结果是undefined
一般情况下我们使用void 0来获得undefined(源于C语言)
特殊的数字
-
不是数字的数字
数学运算时,操作数不是数值类型,则会返回
NaN,意思是not a number,一个警戒值,表示数学运算没有成功,这是失败后返回的结果- NaN也是数值类型
- 唯一一个自身不等于自身的值,判断一个值是否是NaN应使用
Number.isNaN方法
-
无穷数
正无穷:
Infinity,负无穷:-Infinity:-
1/0 等于Infinity,任意一个操作数为负数则结果为-Infinity
-
计算结果溢出时,js使用
就近原则取值Number.MAX_VALUE + Math.pow(2,970) // Infinity Number.MAX_VALUE + Math.pow(2,969) // 1.7976.....出现上述结果的原因就是: 前者结果更接近Infinity因此"向上取整",后者更接近Number.MAX_VALUE,所以"向下取整"
-
无穷除以无穷不等于1而等于NaN,这是因为无穷除以无穷在js中属于未定义的操作,所以结果为
NaN
-
-
零值
js中有+0(0),-0之分,有以下几个特点:
-
+0 === -0=> true -
0/-3=-0,0*-3=-0,加减运算不会得到-0 -
数字-0转换为字符串得到的是字符串 "0",字符串"-0"转换为数字得到的是-0 -
判断是否为
-0function isNefZero(num){ num = Number(num) return num === 0 && 1 / num === -Infinity } -
为什么需要
-0呢在一些应用程序中数字的符号位可能用来代表其他信息(如移动方向),物体在0位置的移动方向就可以通过符号位来确定
-
特殊的等式
上文我们提到过由于0 === -0,我们需要polyfill一个方法来进行-0的判断,同样的对于NaN我们也需要专用的Number.isNaN方法进行判断.对于这两个比较特殊的等式,es6之后新加入了Object.is方法来判断两个值是否绝对相等,可以用来处理上述两种特殊情况
let a = 0 / -1
Object.is(a,-0) // true
let b = -1 * 's'
Object.is(b,NaN) // true
除了对特殊值进行比较之外,尽量使用===,其相率更高,Object.is主要用来处理哪些特殊的相等比较
值和引用
js中值进行复制或者传递的时候,我们无法决定使用值复制还是引用复制,这是由值类型决定的:
- 基本数据类型通过
值复制来进行传递或赋值:Number,String,undefined,null,Symbol,Boolean - 复杂数据类型通过
引用复制来进行传递或赋值:Object
主要原因是: js内存有堆和栈,栈是一个连续的存储空间,主要存储基本数据类型,堆内存主要存储复杂数据类型如图:
- 对于基本数据类型: 值直接存放在栈中
- 对于复杂数据类型: 栈中存储复杂数据类型的引用,具体的值存储在堆内存中
因此复杂数据类型复制的时候实际复制的是栈中的引用,基本数据类型复制的时候复制的是栈中正儿八经的值,对于复杂数据类型,如果值更改了,所有引用这个值的变量都会更改:
let obj1 = { a: 2}
function fn(o){
o.a = 3
reutrn o
}
let obj2 = fn(obj1)
console.log(obj1.a,obj2.a) // 3,3 全部被修改了,原因就是复杂数据类型使用`引用复制`进行传递,obj1做为参数赋值,导致o也成为了obj1的引用,修改o等于修改obj1