第 46 题:输出以下代码执行的结果并解释为什么?
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], length 为 4
伪数组(ArrayLike)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(45)
看了一下运行结果后,有点懵逼,然后去翻了一下v8里array.js的实现代码。
从实现的代码可以看出来,只要是对象拥有Array.prototype.push方法就会按照数组的push去执行。这句话说的直白点。
执行obj.push(1)时,当前length为2,正好替换了obj['2']的值,然后length变为3,obj.push(2)时就是替换obj['3']的值。就出来了浏览器的运行结果
原题中
obj.length = 2 作为 splice方法的第一个参数,
当obj.push(1)时,相当于 ['', '', '3', '4'].splice(2, 1, 1)
当obj.push(2)时,相当于['', '', '3', '4'].splice(3, 1, 2)
以上为个人想法,欢迎各位讨论。
function push (arr, item) {
arr[arr.length] = item;
}
length 2 从2 开始覆盖
难道这就是redux可时光倒流的原理吗?
秀儿又是你
数组会根据length来进行push操作。例如以上的obj会根据length==2 来更新下标,也就是push会从2(length==2,意味着在[0,1,***]后添加)开始
然而下标值又是key值,所以会把key值为2,3的value替换成1,2
obj中length为指针 指向索引2
调用 obj.push(1) 等同于 obj[obj.length] = 1 length++
调用 obj.push(2) 等同于 obj[obj.length] = 2 length++
所以 变为 [,,1,2]
规范
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
When the push method is called with zero or more arguments item1, item2, etc., the following steps are taken:
Increase n by 1.
Return n.
NOTE The push function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the push function can be applied successfully to a host object is implementation-dependent.
第七步是原因
我感觉 学了个假的 js
MDN官网解释如下
存在length的值为number类型和splice值为函数类型,将obj变为伪数组;
根据length的值来创建数组长度,0,1下标无值所以实际上打印为
Object(2) [empty × 2, 2: 3, 3: 4, splice: ƒ, push: ƒ]
obj.push(1) //下标为2的值等于1,
[empty × 2, 1, 3: 4, splice: ƒ, push: ƒ]
obj.push(2) //下标为3的值等于2,
[empty × 2, 1, 2, splice: ƒ, push: ƒ]
同理变形一下
那为什么一开始就有
length
和splice
,但是打印出来是个对象呢?数组对象
JavaScript 数组的有一些特性是其他对象所没有的:
当有新的元素添加到列表中的时候,自动更新
length
属性设置
length
为一个较小值的时候将会截断数组从
Array.prototype
中继承一些有用的方法其类属性为
Array
这些特性让
JavaScript
数组和常规对象有明显的区别。但是它们不是定义数组的本质特性。类数组对象
把拥有一个数值
length
属性和对应非负整数属性的对象看作一种类型的数组我们定义的
function
函数中Arguments
对象就是一个类数组对象,同样在客户端 JavaScript 中,一些 DOM 的方法比如document.getElementsByTagName()
也是返回的类数组对象同时 JavaScript 数组方法是特意定义为通用的,因此它们不仅应用在真正的数组而且在类数组对象上都能正确的工作。类数组对象没有继承自
Array.prototype
,不能直接直接调用数组的方法,但是也是可以通过Function.call
方法调用。那么我们改变一下题目
结果
这是题目在 Chrome 浏览器控制台输出结果
我们改变的题目输出结果
我们可以看到有效结果是一样的,那么为什么结果会是如此呢?
so ~
但是还是有不一样的地方比如
[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 对其做的优化吧。
不够准确,length应该为自然数,负整数是不行的
01-输出以下代码执行的结果并解释为什么
类(伪)数组(arraylike)
就是像数组的对象(某些对象看起来像但不是)
通过索引属性访问元素
拥有 length 属性的对象
underscore 中的定义
没有数组的方法(push forEach)
形式
间接调用
转为真正的数组
数组的push
大白话
其实push的时候会首先查询数组(伪数组)的 length 属性,接着在数组的最后一个添加上新的元素即 arr[length]
'splice': Array.prototype.splice
为什么对象添加了splice属性后并没有调用就会变成类数组对象这个问题,这是控制台中 DevTools 猜测类数组的一个方式
splice
属性是函数类型length
属性且为正整数楼上同学都答过了,我说一下个人理解,obj是一个类数组,本质是一个对象,但是拥有了数组的push和slice方法;而push方法其实就是在obj[length] 附上一个值,然而让length++这么一个操作;
调用Array的push方法,累加对象length
给对象加上Array的splice方法变成伪数组
obj.splice= Array.prototype.splice
第 46 题:输出以下代码执行的结果并解释为什么
没有obj.push操作,直接输出obj可以看到,
即一个带有length和splice属性的对象会被浏览器解析成类数组。
然后当进行push时,因为obj的length为2,所以push会对第三位进行push,而obj的第三位已经有值了为3,所以会被替换为1,最终结果即为:
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
解这道题需要去了解Array.prototype.push 在V8下如何实现的
这是V8的push实现代码:
function ArrayPush () {
var n = TO_UNIT32(this.length);
var m = %_ArgumentsLength();
for (var i = 0; i < m; i++) { // 逐个复制元素
this[i + n ] = %_Arguments(i);
}
this.length = n + m; // 修改数组的length
return this.length;
}
通过代码我们知道会先获取原数组长度 n, 然后开始从下标n循环赋值:this[i+n]。this.length重新赋值为原数组长度加新push参数的长度,所以就能理解 {2:1, 3:2,length:4}是怎么来的了。
empty x 2则是因为下标从2开始,0和1都是空的,和数组一样如图:
同时应该也能明白 push(...[])为什么能push多个的原理了
只能说涨姿势了
node环境中运行结果:
使用浏览器运行代码
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
因为push()是按照对象设置的length属性来判断追加位置,而不是根据最大键值。
如此题第一次push(1)相当于obj[length] = 1,
而不是obj[4]=1;
添加元素后length++;
补充:数组的length属性
在JavaScript中数组的length是动态的,为最大键值数加一
数组的length属性是可写的,如果人为将length设置的小于当前数组内元素数目,元素数目会自动减少到length设置的大小。
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
obj被转换为类数组对象。而在node环境打印,依然是对象
在Chrome Devtools中测试
对象有length属性和splice函数, splice可以是自定义函数,在Chrome Devtools中就会被判定为类数组对象。
这题在我看来是两方面知识的理解:
鸭式辨型
:像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子。push
:push方法是根据length属性来决定从哪里开始插入给定的值。知道了上面这两点之后先来看
obj
:obj[2]
将会得到3,访问obj.length
会得到2,并且我们还赋给了这个对象push方法。再来看
push
方法:如果我们更改数组的长度,那么push方法就会在更改的长度之后进行压入,并且将长度
+1
:所以题解也就清晰了:
因为指定了类数组的
length
,所以push会根据length来压入,所以会在0,1
这两个位置之后进行压入,此时2位置的3被更改为1。之后push将改变length,原先的
'length':2
将+1
变成3,此时再压入2。至于
splice
,这是让我不解的地方,经测试如果类数组同时拥有length和splice方法
时,就会展示成数组的样式,但是本身不是数组。对于
Microsoft Edge和chrome
他们会将其展示成数组的形式,而对于node和Firefox
则会使用{}
包起来。想問如何讀 V8 code
感觉类似这个Array.prototype.push.call(obj, 1,2)
不得不说,面试是面试,工作是工作。如果代码审查时看到谁写这么难以理解的东西,直接两巴掌
没记错的话,在不同环境下会产生不同结果,
chrome下的console在判定数据类型事比较粗糙,slice与length同时存在时,判定为Array
node下的console会判定是 Object
这一题考察的是伪数组: (以下献丑)
首先搞清楚这一题要搞清楚 push ,其实push的时候会首先查询数组(伪数组)的 length 属性,接着在数组的最后一个添加上新的元素即 arr[length] (数组从零开始),然后length 增加一。 在这一题中,首先 伪数组查到length 是 2 ,就会 直接在 2 这个下标(属性) 上push 1 , 而length 会增加 1 变成 3 ,接着重复这个过程。
题外话: 伪数组 没有 length 的时候默认是 0。
前端小白, 有错勿怪, 欢迎指正。
涨姿势了,数组的push 方法根据 length 属性来决定从哪里开始插入给定的值以及判断伪数组的方法:
function isArrayLike(obj) {
if (!obj || typeof obj !== 'object')
return false;
try {
if (typeof obj.splice === 'function') {
const len = obj.length;
return typeof len === 'number' && (len >>> 0 === len && (len > 0 || 1 / len > 0));
}
} catch (e) {
}
return false;
}
看文档:
1:push 方法根据 length 属性来决定从哪里开始插入给定的值。
2:push 是特意设计为通用的,Array.prototype.push 可以在一个对象上工作。
解析:
原题 length = 2。所以当然从第三个开始push,而obj中index为2和3的都被占用了。自然会替换掉。
所以:很得到的答案很明显。
devtools
判断类数组的方法:判断的过程:
splice
属性是函数类型length
属性且为正整数涉及知识点:
一组数据,由数组来存,但是如果要对这组数据进行扩展,会影响到数组原型,ArrayLike的出现则提供了一个中间数据桥梁,ArrayLike有数组的特性, 但是对ArrayLike的扩展并不会影响到原生的数组。
push 方法有意具有通用性。该方法和 call() 或 apply() 一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。
唯一的原生类数组(array-like)对象是 Strings,尽管如此,它们并不适用该方法,因为字符串是不可改变的。
Array.from()、splice()、concat()等
题分析:
这个obj中定义了两个key值,分别为splice和push分别对应数组原型中的splice和push方法,因此这个obj可以调用数组中的push和splice方法,调用对象的push方法:push(1),因为此时obj中定义length为2,所以从数组中的第二项开始插入,也就是数组的第三项(下表为2的那一项),因为数组是从第0项开始的,这时已经定义了下标为2和3这两项,所以它会替换第三项也就是下标为2的值,第一次执行push完,此时key为2的属性值为1,同理:第二次执行push方法,key为3的属性值为2。此时的输出结果就是:
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]---->
[
2: 1,
3: 2,
length: 4,
push: ƒ push(),
splice: ƒ splice()
]
因为只是定义了2和3两项,没有定义0和1这两项,所以前面会是empty。
如果讲这道题改为:
此时的打印结果就是:
Object(2) [1, 2, 2: 3, 3: 4, splice: ƒ, push: ƒ]---->
[
0: 1,
1: 2,
2: 3,
3: 4,
length: 2,
push: ƒ push(),
splice: ƒ splice()
]
原理:此时length长度设置为0,push方法从第0项开始插入,所以填充了第0项的empty
至于为什么对象添加了splice属性后并没有调用就会变成类数组对象这个问题,这是控制台中 DevTools 猜测类数组的一个方式:
https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/event_listeners/EventListenersUtils.js
array-list 拥有length属性 , 属性为数字,即为类数组对象。 后面的 push等属性 是让类数组对象拥有部分数组方法特性。
@kangkai124 我这边试了一下,只要一个对象的
length
属性为数字,同时splice
属性为函数时, 对象的函数输出结果就会变成 伪数组。@Moriarty02 同学讲的
push
很棒,不过关于这句话这个对象如果有 push 和 splice 会输出会转换为数组
我亲自试了一下,发现对象没有push
只要有length
和splice
就会变为类数组。push 是特意设计为通用的,我们可以使用它来获得便利。正如下面的例子所示,Array.prototype.push 可以在一个对象上工作。 注意,我们没有创建一个数组来存储对象的集合。 相反,我们将该集合存储在对象本身上,并使用在 Array.prototype.push 上使用的 call 来调用该方法,使其认为我们正在处理数组,而它只是像平常一样运作。
var obj = {
length: 0,
};
// Let's add some empty objects just to illustrate.
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印
留下了没技术的眼泪,求大佬出详细答案。
关于
push
上面的同学解释的挺清楚的了,我就想知道为什么对象添加了
splice
属性后会变成类数组对象?我的理解是这样的
1: call push这个方法如果对象有length属性,length属性会加1 并且返回,这个是在某本书的上看到的,一直记得。
MDN
这样也就有了结果里面的
key===2 value =1
key===3 value =2
3.额外的
这个对象如果有push和splice会输出会转换为数组,下图为去掉splice
包含splice方法
以下为个人猜想没有确切的理论依据:
根据MDN的说法理解,
push
方法应该是根据数组的length
来根据参数给数组创建一个下标为length
的属性,我们可以做以下测试:根据这个测试我们发现,
push
方法影响了数组的length
属性和对应下标的值。然后,正如楼上所说:
我们使用题目中的代码时得到了这个结果:
这个时候控制台输出的是一个数组,但是实际上它是一个伪数组,并没有数组的其他属性和方法,我们可以通过这种方法验证:
所以我认为题目的解释应该是:
obj[2]=1;obj.length+=1
2.使用第二次push,obj对象的push方法设置
obj[3]=2;obj.length+=1
3.使用console.log输出的时候,因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印
4.打印时因为数组未设置下标为 0 1 处的值,故打印为empty,主动 obj[0] 获取为 undefined
第一第二步还可以具体解释为:因为每次push只传入了一个参数,所以 obj.length 的长度只增加了 1。push方法本身还可以增加更多参数,详见 MDN
求解释.
留下了没技术的泪水