JavaScript中的in运算符与数组

794 阅读4分钟

前言

JavaScript中有很多怪异点,这个in运算符后接数组就很奇特(虽然大家很少用到这个)。语义上讲in是个介词,词性prep,表示在什么什么里面。但在js中可能就不太一样了。

问题引出

不慌,先来看看python中的效果。

>>> array = [1,2,3,4]
>>> 0 in array
False
>>> 4 in array
True
>>> 4.0 in array
True
>>> 4n in array
SyntaxError: invalid syntax

显然,此处in就是表示判断左边的元素是否存在与数组中。

>>> 4==4.0
True
>>> array.append('5')
>>> 5 in array
False

挨个匹配的,如果相等则返回True,显然字符串‘5’和数字5并不相等。非常合理是不是。

再来看看浏览器中的情况。

let a = [1,2,3,4];
1 in a;//true  合理

1.0 in a;//true
0 in a;//true  合理吗?
'1' in a;//true

3n in a;//true 离谱

image.png 发生甚么事啦。

一起来研究下。

数组的本质

数组Array 时按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。

任何类型的数组都可以放入数组。

let array = [a,2,[3,4]];// a 是[1,2,3,4]
array
(3) [Array(4), 2, Array(2)]

数组实际上一种的对象。typeof运算符会返回数组的类型是object

typeof a ;//"object"
Object.keys(a);//(4) ["0", "1", "2", "3"]

这里可以看出来,数组的键全是字符串数组,从0开始。

来看看对象的设定,js中的规定,对象的键都的是字符串。

当访问对象的值是,有点式object.key和括号式object[key]。

let ob = {'0':0,'1':1,'k':'v'};

ob.k ;//"v"
ob["k"];  
"v"  // 两种都能取值
ob.0  // 报错啦!数字加点,还以为是小数
Uncaught SyntaxError: Unexpected number
ob['0'] //数字字符串没毛病
0
ob[0]  //[]内为数字时,也能访问
0

规定

  • 点式不能使用数字,否则识别为小数,报错。

  • 键名传入非字符串时,会被转为字符串

正因此,数组才能使用数字索引访问。而数组中的键都是字符串。

in 运算符 与包含

MDN上的解释是这样的:

如果指定的属性在指定的对象或其原型链中,则**in 运算符**返回true

显然这个in并不是包含关系,而是包含该属性。

"toString" in {}; //true

in右操作数必须是一个对象值,不能是字符串。而左侧需要一个字符串,

当左侧传入不是字符串时,则调用tostring(),转为字符串.

顺带一提,要表达包含关系,可以使用Array.prototype.includes() ,例如:

[1, 2, NaN].includes('1');//false
[1, 2, NaN].includes(1.0);//true
[1, 2, NaN].includes(NaN);//true  

1.0.toString ()与(1).toString()

这个问题也是个笔试常考题目。首先是不能写1.toString(),因为有歧义,不知道这个.是小数点,还是调用方法。只要能规避歧义的,都能得到正确的结果。比如:

1 .toString;//'1'
(1).toString();//'1'
1.0.toString();//'1'

现在的问题是为什么1.0.toString()得到的是字符串'1',而不是字符串’1.0‘呢?

1.0 === 1;//true

从存储上来讲,JavaScript数字全部是浮点数。 根据 IEEE 754标准中的64位二进制(binary64), 也称作双精度规范(double precision)来储存。那么1.0===1也合情合理。

在toString ()的实现算法中,是根据数字的大小来将数字解码成字符串的。具体太复杂了,可以看这个

因此1.0 或是1.00,乃至是1.0000000000000001.toString() 都是’1‘ (精度不够了)。

那么问题来了,有没有数tostring()可以得到1.0呢?

俺也不知道

3n是什么?

前文的内容完全解释了1.0 in a 和’1‘ in a得到true的问题,但是最后一个3n 是什么呢。

答案是ES2020新特性:一个用于处理任意精度整数的新数字基元--n

为了更精确地表示没有位数限制的整数,ES2020引入了不同于Number数字类型的BigInt数字类型, 只用来表示整数(大整数),没有位数的限制,任何位数的整数都可以精确表示。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n

typeof 3n //"bigint"
3n ==3;//true
3n === 3;//false
3n+4n;//7n
3n.toString();//"3"

所以3n in [1,2,3,4] ===true非常合理。

那么1.0n.tostring() 能得到1.0吗?当然,报错了。bigInt只能用于整数。

坐等ES2035修复小数的精度问题。

最后说下for in

for in 可以用与遍历,跟in差不多意思,用于遍历得到的是索引。

遍历数组时,当然得到的是字符串而不是数字。

for (let i in [1,2,3]){console.log(2+i)};
//20
//21
//22

也能遍历到原型链上的属性。

let a = [1,2,3,4];
a.k='v';
Array.prototype.key='val';
for (let i in a){console.log(i)};
0
1
2
3
k
key

ES6的for of,用于遍历数组。

for (let i of a){console.log(i)}
1
2
3
4 //到4就结束了,拿不到属性k

for of 遍历对象或类数组会报错。

综上:for in 适合遍历对象

for of 遍历数组

总结

js中的in不是表示包含关系,而且表示属性存在与否

数组中的属性均为字符串,in运算符判断是存在类型转换