# ❓ 输出以下代码执行的结果并解释为什么

var obj = {
  '2': 3,
  '3': 4,
  length: 2,
  splice: Array.prototype.splice,
  push: Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
1
2
3
4
5
6
7
8
9
10

首先看上去这肯定是对类数组的考题

# 数组对象

JavaScript 数组的有一些特性是其他对象所没有的:

  • 当有新的元素添加到列表中的时候,自动更新 length 属性

  • 设置 length 为一个较小值的时候将会截断数组

  • Array.prototype 中继承一些有用的方法

  • 其类属性为 Array

这些特性让 JavaScript 数组和常规对象有明显的区别。但是它们不是定义数组的本质特性。

# 类数组对象

把拥有一个数值 length 属性和对应非负整数属性的对象看作一种类型的数组

我们定义的 function 函数中 Arguments 对象就是一个类数组对象,同样在客户端 JavaScript 中,一些 DOM 的方法比如 document.getElementsByTagName() 也是返回的类数组对象

// 判断是否是类数组对象
const isArrayLike = obj =>
  obj && // 非 null undefined
  typeof obj === 'object' && // 是对象
  isFinite(obj.length) && // 是有穷数
  obj.length >= 0 && // 为非负数
  obj.length === Math.floor(obj.length) && // 整数
  obj.length < Math.pow(2, 32) // < 4294967296
1
2
3
4
5
6
7
8

同时 JavaScript 数组方法是特意定义为通用的,因此它们不仅应用在真正的数组而且在类数组对象上都能正确的工作。类数组对象没有继承自 Array.prototype,不能直接直接调用数组的方法,但是也是可以通过 Function.call 方法调用。

那么我们改变一下题目

var obj2 = {
  '2': 3,
  '3': 4,
  length: 2
}

Array.prototype.push.call(obj2, 1)
Array.prototype.push.call(obj2, 2)
// 这样的到的有效结果是一样的  {2: 1, 3: 2, length: 4}

// 通过数组的方法 // 1 2
Array.prototype.forEach.call(obj2, item => console.log(item))
// 其本质还是一个类数组
1
2
3
4
5
6
7
8
9
10
11
12
13

# 结果

这是题目在 Chrome 浏览器控制台输出结果

Chrome控制台输出题目结果

我们改变的题目输出结果

改变的题目控制台输出题目结果

我们可以看到有效结果是一样的,那么为什么结果会是如此呢?

push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。 push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。MDN

so ~

// obj.push(1)
// 等同于
obj['2'] = 1
obj.length++ // length = 3
// obj.push(2)
// 等同于
obj['3'] = 2
obj.length++ // length = 4
// 那么之后的结果就是我们看到的了
1
2
3
4
5
6
7
8
9

但是还是有不一样的地方比如

[empty × 2, 1, 2, splice: ƒ, push: ƒ]{2: 1, 3: 2, length: 4}

那么我们接下来继续看

继续看比较

只有当对象 splice 属性是一个 Function 的时候输出才为 [empty × 2, 1, 2, splice: ƒ, push: ƒ]

那么为此我又去 Firefox 控制台下面试了一下,结果如下图:

火狐控制台尝试

跟 Chrome 没有定义 splice 为 Function 是一致的

所以说可能是 Chrome 对其做的优化吧。