第 46 题:输出以下代码执行的结果并解释为什么
Opened this issue · 48 comments
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)
留下了没技术的泪水
求解释.
以下为个人猜想没有确切的理论依据:
push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
根据MDN的说法理解,push
方法应该是根据数组的length
来根据参数给数组创建一个下标为length
的属性,我们可以做以下测试:
根据这个测试我们发现,push
方法影响了数组的length
属性和对应下标的值。
然后,正如楼上所说:
在对象中加入splice属性方法,和length属性后。这个对象变成一个类数组。
这个时候控制台输出的是一个数组,但是实际上它是一个伪数组,并没有数组的其他属性和方法,我们可以通过这种方法验证:
所以我认为题目的解释应该是:
- 使用第一次push,obj对象的push方法设置
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
我的理解是这样的
1: call push这个方法如果对象有length属性,length属性会加1 并且返回,这个是在某本书的上看到的,一直记得。
MDN
push方法将值追加到数组中。
push 方法有意具有通用性。该方法和 call() 或 apply() 一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。
唯一的原生类数组(array-like)对象是 Strings,尽管如此,它们并不适用该方法,因为字符串是不可改变的。
关于push
上面的同学解释的挺清楚的了,
我就想知道为什么对象添加了splice
属性后会变成类数组对象?
留下了没技术的眼泪,求大佬出详细答案。
push 是特意设计为通用的,我们可以使用它来获得便利。正如下面的例子所示,Array.prototype.push 可以在一个对象上工作。 注意,我们没有创建一个数组来存储对象的集合。 相反,我们将该集合存储在对象本身上,并使用在 Array.prototype.push 上使用的 call 来调用该方法,使其认为我们正在处理数组,而它只是像平常一样运作。
var obj = {
length: 0,
addElem: function addElem (elem) {
// obj.length is automatically incremented
// every time an element is added.
[].push.call(this, elem);
}
};
// Let's add some empty objects just to illustrate.
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印
@Moriarty02 同学讲的 push
很棒,不过关于这句话 这个对象如果有 push 和 splice 会输出会转换为数组
我亲自试了一下,发现对象没有 push
只要有 length
和 splice
就会变为类数组。
@kangkai124 我这边试了一下,只要一个对象的 length
属性为数字,同时splice
属性为函数时, 对象的函数输出结果就会变成 伪数组。
var obj1 = {
length: 1,
splice: function () {},
}; // Object [empty, splice: ƒ]
var obj2 = {
length: '1',
splice: function () {},
}; // {length: "1", splice: ƒ}
var obj3 = {
length: 1,
splice: {},
}; // {length: 1, splice: {…}}
array-list 拥有length属性 , 属性为数字,即为类数组对象。 后面的 push等属性 是让类数组对象拥有部分数组方法特性。
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)
涉及知识点:
- 类数组(ArrayLike):
一组数据,由数组来存,但是如果要对这组数据进行扩展,会影响到数组原型,ArrayLike的出现则提供了一个中间数据桥梁,ArrayLike有数组的特性, 但是对ArrayLike的扩展并不会影响到原生的数组。
- push方法:
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。
如果讲这道题改为:
var obj = {
'2': 3,
'3': 4,
'length': 0,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
此时的打印结果就是:
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#L330
devtools
判断类数组的方法:
/**
* @param {?Object} obj
* @return {boolean}
*/
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;
}
判断的过程:
- 存在且是对象
- 对象上的
splice
属性是函数类型 - 对象上有
length
属性且为正整数
看文档:
1:push 方法根据 length 属性来决定从哪里开始插入给定的值。
2:push 是特意设计为通用的,Array.prototype.push 可以在一个对象上工作。
解析:
原题 length = 2。所以当然从第三个开始push,而obj中index为2和3的都被占用了。自然会替换掉。
所以:很得到的答案很明显。
涨姿势了,数组的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;
}
这一题考察的是伪数组: (以下献丑)
首先搞清楚这一题要搞清楚 push ,其实push的时候会首先查询数组(伪数组)的 length 属性,接着在数组的最后一个添加上新的元素即 arr[length] (数组从零开始),然后length 增加一。 在这一题中,首先 伪数组查到length 是 2 ,就会 直接在 2 这个下标(属性) 上push 1 , 而length 会增加 1 变成 3 ,接着重复这个过程。
题外话: 伪数组 没有 length 的时候默认是 0。
前端小白, 有错勿怪, 欢迎指正。
没记错的话,在不同环境下会产生不同结果,
chrome下的console在判定数据类型事比较粗糙,slice与length同时存在时,判定为Array
node下的console会判定是 Object
看了一下运行结果后,有点懵逼,然后去翻了一下v8里array.js的实现代码。
function ArrayPush() {
// 这个代码应该是c++实现的,作用应该是检查当前对象能不能转化成数组对象
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
if (%IsObserved(this))
return ObservedArrayPush.apply(this, arguments);
var array = TO_OBJECT_INLINE(this);
var n = TO_UINT32(array.length); // 获取array对象的length属性
var m = %_ArgumentsLength(); // 获取参数的长度
for (var i = 0; i < m; i++) {
array[i+n] = %_Arguments(i); // 从下标为length开始push值
}
var new_length = n + m;
array.length = new_length;
return new_length;
}
从实现的代码可以看出来,只要是对象拥有Array.prototype.push方法就会按照数组的push去执行。这句话说的直白点。
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)
执行obj.push(1)时,当前length为2,正好替换了obj['2']的值,然后length变为3,obj.push(2)时就是替换obj['3']的值。就出来了浏览器的运行结果
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)
原题中
obj.length = 2 作为 splice方法的第一个参数,
当obj.push(1)时,相当于 ['', '', '3', '4'].splice(2, 1, 1)
当obj.push(2)时,相当于['', '', '3', '4'].splice(3, 1, 2)
obj = {
'0': '',
'1': '',
'2': 1,
'3': 2
}
function push (arr, item) {
arr[arr.length] = item;
}
难道这就是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:
- Let O be the result of calling ToObject passing the this value as the argument.
- Let lenVal be the result of calling the [[Get]] internal method of O with argument "length".
- Let n be ToUint32(lenVal).
- Let items be an internal List whose elements are, in left to right order, the arguments that were passed to this function invocation.
- Repeat, while items is not empty
- Remove the first element from items and let E be the value of the element.
- Call the [[Put]] internal method of O with arguments ToString(n), E, and true.
Increase n by 1.
调用对象O 的 [[Put]] 内部方法,传参为 ToString(n) (键名),E(值),true,然后 n 增加 1
- Call the [[Put]] internal method of O with arguments "length", n, and true.
Return n. - The length property of the push method is 1.
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.
第七步是原因
/**
* 1. 当一个对象拥有length属性,并且splice属性是个函数,对我们来说就可以看作是一个类数组
* 2. 既然是类数组,对象的键就是数组的下标,对象的值就是数组当前下标的值
* 3. 此时撇开length属性不管,这个类数组可以看作:[empty, empty, 3, 4]
* 4. 当length属性起作用时,它将这个类数组的长度截断了,此时可以看作:[empty, empty]
* 5. 之后这个类数组进行了两次push操作,结果可以看作:[empty, empty, 1, 2]
* 6. 当然,这个类数组中还包含push和splice函数以及它的length,但并没有数组的其它方法,所以实
* 际上它只是一个对象而已
*/
let obj = {
'2': 3,
'3': 4,
length: 2,
splice: [].splice,
push: [].push
};
obj.push(1);
obj.push(2);
console.log(obj); // [empty, empty, 1, 2]
console.log(Object.prototype.toString.call(obj)); // [object Object]
我感觉 学了个假的 js
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)
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
存在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: ƒ]
同理变形一下
var obj = {
'0': 3,
'1': 4,
'length': 3,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj) //Object(5) [3, 4, empty, 1, 2, splice: ƒ, push: ƒ]
以下为个人猜想没有确切的理论依据:
push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
根据MDN的说法理解,
push
方法应该是根据数组的length
来根据参数给数组创建一个下标为length
的属性,我们可以做以下测试:
根据这个测试我们发现,
push
方法影响了数组的length
属性和对应下标的值。
然后,正如楼上所说:在对象中加入splice属性方法,和length属性后。这个对象变成一个类数组。
这个时候控制台输出的是一个数组,但是实际上它是一个伪数组,并没有数组的其他属性和方法,我们可以通过这种方法验证:
所以我认为题目的解释应该是:
- 使用第一次push,obj对象的push方法设置
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
那为什么一开始就有length
和splice
,但是打印出来是个对象呢?
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)
数组对象
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
同时 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))
// 其本质还是一个类数组
结果
这是题目在 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
// 那么之后的结果就是我们看到的了
但是还是有不一样的地方比如
[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 对其做的优化吧。
@kangkai124 我这边试了一下,只要一个对象的
length
属性为数字,同时splice
属性为函数时, 对象的函数输出结果就会变成 伪数组。var obj1 = { length: 1, splice: function () {}, }; // Object [empty, splice: ƒ] var obj2 = { length: '1', splice: function () {}, }; // {length: "1", splice: ƒ} var obj3 = { length: 1, splice: {}, }; // {length: 1, splice: {…}}
不够准确,length应该为自然数,负整数是不行的
01-输出以下代码执行的结果并解释为什么
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)
类(伪)数组(arraylike)
-
就是像数组的对象(某些对象看起来像但不是)
-
通过索引属性访问元素
-
拥有 length 属性的对象
-
underscore 中的定义
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = property('length'); var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; // 其中 JavaScript 中能精确表示的最大数字 };
-
没有数组的方法(push forEach)
arrayLike.push('sex') // 01.js:20 Uncaught TypeError: arrayLike.push is not a function
形式
console.log(array[0]); // name
console.log(arrayLike[0]); // name
array[0] = "new name";
arrayLike[0] = "new name";
console.log(array[0]); // new name
console.log(arrayLike[0]); // new name
间接调用
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"]
转为真正的数组
Array.from(arrayLike);
数组的push
push
方法具有通用性。该方法和call()
或apply()
一起使用时,可应用在类似数组的对象上。push
方法根据length
属性来决定从哪里开始插入给定的值。如果length
不能被转成一个数值,则插入的元素索引为 0,包括length
不存在时。当length
不存在时,将会创建它。
唯一的原生类数组(array-like)对象是
Strings
,尽管如此,它们并不适用该方法,因为字符串是不可改变的。
大白话
其实push的时候会首先查询数组(伪数组)的 length 属性,接着在数组的最后一个添加上新的元素即 arr[length]
var testObj = {
"2": 3,
"3": 4,
length: 2,
push: Array.prototype.push,
};
testObj.push(1)
console.log(testObj) //// {2: 1, 3: 4, length: 3, push: ƒ}
testObj.push(2)
console.log(testObj) //{2: 1, 3: 2, length: 4, push: ƒ}
- 第一点就是每次 push 后 length 会加1
'splice': Array.prototype.splice
/**
* @param {?Object} obj
* @return {boolean}
*/
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;
}
为什么对象添加了splice属性后并没有调用就会变成类数组对象这个问题,这是控制台中 DevTools 猜测类数组的一个方式
- 存在且是对象
- 对象上的
splice
属性是函数类型 - 对象上有
length
属性且为正整数
楼上同学都答过了,我说一下个人理解,obj是一个类数组,本质是一个对象,但是拥有了数组的push和slice方法;而push方法其实就是在obj[length] 附上一个值,然而让length++这么一个操作;
第 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)
没有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多个的原理了
只能说涨姿势了
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)
node环境中运行结果:
{
'2': 1,
'3': 2,
length: 4,
splice: [Function: splice],
push: [Function: push]
}
使用浏览器运行代码
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
- 元素变化
因为push()是按照对象设置的length属性来判断追加位置,而不是根据最大键值。
如此题第一次push(1)相当于obj[length] = 1,
而不是obj[4]=1;
添加元素后length++;
补充:数组的length属性
在JavaScript中数组的length是动态的,为最大键值数加一
let arr = [];
arr[1000] = 1;
console.log(arr.length); // 1001
数组的length属性是可写的,如果人为将length设置的小于当前数组内元素数目,元素数目会自动减少到length设置的大小。
let arr = [1,2,3];
arr.length = 0;
console.log(arr)// []
- 使用浏览器运行代码
Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
obj被转换为类数组对象。而在node环境打印,依然是对象
在Chrome Devtools中测试
obj1 = {
splice: Array.prototype.splice,
};
// {splice: ƒ}
obj2 = {
2: 1,
splice: Array.prototype.splice,
};
// {2: 1, splice: ƒ}
obj3 = {
length: 1,
splice: Array.prototype.splice,
};
// Object [empty, splice: ƒ]
obj4 = {
length: 2,
splice: Array.prototype.splice,
};
// Object(2) [empty × 2, splice: ƒ]
obj5 = {
length: 2,
splice: () => {},
};
// Object(2) [empty × 2, splice: ƒ]
obj6 = {
length: 2,
splice: 3,
};
// {length: 2, splice: 3}
对象有length属性和splice函数, splice可以是自定义函数,在Chrome Devtools中就会被判定为类数组对象。
这题在我看来是两方面知识的理解:
鸭式辨型
:像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子。push
:push方法是根据length属性来决定从哪里开始插入给定的值。
知道了上面这两点之后先来看obj
:
- obj像数组一样,如果访问
obj[2]
将会得到3,访问obj.length
会得到2,并且我们还赋给了这个对象push方法。
再来看push
方法:
-
如果我们更改数组的长度,那么push方法就会在更改的长度之后进行压入,并且将长度
+1
:var arr = [1, 2, 3]; arr.length = 4; arr.push(4); console.log(arr); //(5)[1,2,3,empty,4]
所以题解也就清晰了:
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)
因为指定了类数组的length
,所以push会根据length来压入,所以会在0,1
这两个位置之后进行压入,此时2位置的3被更改为1。
之后push将改变length,原先的'length':2
将+1
变成3,此时再压入2。
至于splice
,这是让我不解的地方,经测试如果类数组同时拥有length和splice方法
时,就会展示成数组的样式,但是本身不是数组。
var obj = {
length: 2,
splice: Array.prototype.splice,
};
console.log(obj);//Object(2) [empty × 2, splice: ƒ]
console.log(Array.isArray(obj));//false
对于Microsoft Edge和chrome
他们会将其展示成数组的形式,而对于node和Firefox
则会使用{}
包起来。
看了一下运行结果后,有点懵逼,然后去翻了一下v8里array.js的实现代码。
function ArrayPush() { // 这个代码应该是c++实现的,作用应该是检查当前对象能不能转化成数组对象 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push"); if (%IsObserved(this)) return ObservedArrayPush.apply(this, arguments); var array = TO_OBJECT_INLINE(this); var n = TO_UINT32(array.length); // 获取array对象的length属性 var m = %_ArgumentsLength(); // 获取参数的长度 for (var i = 0; i < m; i++) { array[i+n] = %_Arguments(i); // 从下标为length开始push值 } var new_length = n + m; array.length = new_length; return new_length; }
从实现的代码可以看出来,只要是对象拥有Array.prototype.push方法就会按照数组的push去执行。这句话说的直白点。
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)
执行obj.push(1)时,当前length为2,正好替换了obj['2']的值,然后length变为3,obj.push(2)时就是替换obj['3']的值。就出来了浏览器的运行结果
想問如何讀 V8 code
感觉类似这个Array.prototype.push.call(obj, 1,2)
不得不说,面试是面试,工作是工作。如果代码审查时看到谁写这么难以理解的东西,直接两巴掌
这种题是哪些天才想出来的?哈哈
push 设计为通用方法
push 会 插入到 length 位置,如果 length 为正整数的话,否则插入到 0 的位置
splice 和 length 是 isArrayLike 的判断条件,使其认为为伪数组
在对象中加入splice属性方法,和length属性后。这个对象变成一个类数组。
我将这个代码直接在Chrome浏览器控制台运行,输出结果依然是对象,不是数组啊