前言

最近看到这么一道面试题

输出以下代码执行的结果并解释为什么[1]

1
2
3
4
5
6
7
8
9
10
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
11
12
> obj.push(1)
3
> obj.push(2)
4
> console.log(obj)
{
'2': 1,
'3': 2,
length: 4,
splice: [Function: splice],
push: [Function: push]
}

这是因为push 方法根据 length 属性来决定从哪里开始插入给定的值。[2]

之后好奇试了一下popshift方法,pop属性也是根据length属性来决定位置,而shift属性总是从第一个位置开始移除元素。所以这两个方法的结果都是只有length减少,直至0,就不变了。

问题

问题就出在unshift方法上。

1
2
3
4
5
6
7
8
9
10
11
12
> var obj = {'2':3,'3':4,'length':2}
undefined
> obj.unshift = Array.prototype.unshift
[Function: unshift]
> obj.unshift(0)
3
> obj
{ '0': 0, '3': 4, length: 3, unshift: [Function: unshift] }
> obj.unshift(1)
4
> obj
{ '0': 1, '1': 0, length: 4, unshift: [Function: unshift] }

当我们移入0时,位于下标2的3被删除了,而位于下标3的4却没变。感觉和想象的不太一样。

分析

对于这个方法,mdn上的描述是:[3]

unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度

看上去没什么,不过mdn还给出了它的ECMAScript规范[4]其中是这么写的

  1. 1. Let O be ? ToObject(this value).
  2. 2. Let len be ? LengthOfArrayLike(O).
  3. 3. Let argCount be the number of elements in items.
  4. 4. If argCount > 0, then
    1. a. If len + argCount > 253 - 1, throw a TypeError exception.
    2. b. Let k be len.
    3. c. Repeat, while k > 0,
      1. i. Let from be ! ToString(𝔽(k - 1)).
      2. ii. Let to be ! ToString(𝔽(k + argCount - 1)).
      3. iii. Let fromPresent be ? HasProperty(O, from).
      4. iv. If fromPresent is true, then
        1. 1. Let fromValue be ? Get(O, from).
        2. 2. Perform ? Set(O, to, fromValue, true).
      5. v. Else,
        1. 1. Assert: fromPresent is false.
        2. 2. Perform ? DeletePropertyOrThrow(O, to).
      6. vi. Set k to k - 1.
    4. d. Let j be +0𝔽.
    5. e. For each element E of items, do
      1. i. Perform ? Set(O, ! ToString(j), E, true).
      2. ii. Set j to j + 1𝔽.
  5. 5. Perform ? Set(O, “length”, 𝔽(len + argCount), true).
  6. 6. Return 𝔽(len + argCount).

虽然没有完全看懂,但翻译成人话,大概是这么个流程
image.png

对于原题中的数组来说,一开始from是1,此时数组中不存在下标为1的值,所以要把to也就是2-1+1=2下标处的值(即'2':3)删除掉;k-1后为1from变为0,也不存在,k继续减至0。从0开始移入新加入的值,结果就是
{'0':0,'3':4}


  1. https://juejin.cn/post/6844903885488783374 ↩︎

  2. Array.prototype.push() - JavaScript | MDN (mozilla.org) ↩︎

  3. Array.prototype.unshift() - JavaScript | MDN (mozilla.org) ↩︎

  4. ECMAScript® 2023 Language Specification (tc39.es) ↩︎