JS的数组、对象和类数组对象

昨天的TS的课上听到了类数组对象,想到之前面试的时候被问到过,回想了一下,当时就提了一下函数的arguments参数是类数组对象,对其数据结构和与数组或者对象的区别的理解其实很模糊,来补一篇。

一、数组 Array

定义:一组有序的数据集合,其索引为从0开始且自然增长的整数,其元素值可以是任何js数据。其包含一个名为length的属性,该属性表示数组元素的个数。
数组元素的索引和length属性是在数组定义时根据数组元素语言自动定义的。如下图。

对数组,日常工作中对数组的处理蛮多的,涉及到一些的算法,力扣上的很多题也是对数组的各种神操作。
附上三篇比较基础的整理:
1.数组操作方法  https://blog.csdn.net/weixin_42423019/article/details/83143713
2.数组扁平化  https://blog.csdn.net/weixin_42423019/article/details/106583620
3.数组去重  https://blog.csdn.net/weixin_42423019/article/details/106538550

二、对象 Object

定义:js中对象类型为一组无序的由键值对组成的数据集合,其元素的键名和值都可以自定义。
对象没有length属性,如下图。但是可以自定义从0开始的数值索引来模拟数组,就是下面的类数组对象啦。

三、类数组对象

定义:只包含使用从零开始,且自然递增的整数做键名,并且定义了length表示元素个数的对象(注意这句,它有个显式的length属性),我们就认为它是类数组对象。如下图。
但是,它不具有数组所具有的方法,毕竟它是个对象啦。

1.常见的类数组对象

  • 在浏览器环境中,NodeList、HTMLCollection、NamedNodeMap都是类数组对象。// todo
  • 在函数调用中,function代码内置的arguments变量(保存传入的参数)也是一个类数组对象。
  • 在ES5标准中,字符串string就是一个只读的类数组对象。(定义一个str,str[0]、str.length均可取到值)

主要说说第二点,ES5中和ES6中的arguments的表现是不一样的。

// ES5非严格模式中的表现:在ES5非严格模式下,arguments对象总是会被更新反映出实参的变化

function fn(a, b) {
    console.log(a === arguments[0])
    console.log(b === arguments[1])
    a = 'haha'
    b = 'heihei'
    console.log(a === arguments[0])
    console.log(b === arguments[1])
}

fn(1, 3)
// true
// true
// true
// true

// ES5严格模式中的表现:在ES5严格模式下,arguments并没有随着实参的变化而变化。

'use strict'
...
fn(1, 3)
// true
// true
// false
// false

// ES6两种模式中的表现(严格和非严格一样的):
// 在ES6两种模式下,arguments没有随着实参的变化而变化。

//true
//true
//false
//false

2.类数组对象借用数组所具有的方法

通过call或者apply方法(不仅限于类数组对象,对象也可以借用)

(这里又有一道被面试问到的经典题目,call、apply、bind的区别。回想一下,当时答得太浅了,只说了两点:参数和调用过程的不同。// todo)

上面提到,类数组对象终究是对象,不具有数组的一些方法,例如map,shift,unshift,splice,slice,concat,reverse,sort,可以通过call、apply来借用Array的方法。

// 借用数组的方法(稍微借两种试试)
var arrObj = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }

Array.prototype.join.call(arrObj, '&') // dog&cat&rabbit

Array.prototype.slice.call(arrayObj, 0) //  ["dog", "cat", "rabbit"]

Array.prototype.map.call(arrObj, item => {
  return item.toUpperCase()
}) // ["DOG", "CAT", "RABBIT"]

3.将类数组对象转化为数组5种方式

  • Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
  • slice()
  • splice()
  • concat()
  • for循环手动插入
    var arrObj = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }
    
    Array.prototype.slice.call(arrObj)
    
    Array.prototype.splice.call(arrObj, 0)
    
    Array.from(arrObj)
    
    Array.prototype.concat.apply([], arrObj)
    
    var arr = []
    for(var i = 0; i < arrObj.length; i++) {
        arr.push(arrObj[i])
    }
    console.log(arr)
    
    // ["dog", "cat", "rabbit"]

4.判断一个对象是不是类数组对象 isArrayLike()

(发散:又有一个经典题目,如何判断一个对象是不是数组 isArray()  // todo)

  • lodash源码,用到两个函数:

_.isLength(value) 检查 value 是否为有效的类数组长度。
_.isArrayLike(value) 检查 value 是否是类数组。 如果一个值被认为是类数组,那么它不是一个函数,并且value.length是个整数,大于等于 0,小于或等于 Number.MAX_SAFE_INTEGER

const MAX_SAFE_INTEGER = 9007199254740991 
// 能够被“安全”呈现的最大整数是 2^53 - 1
// 在ES6中被定义为Number.MAX_SAFE_INTEGER
// 最小整数是 -9007199254740991,ES6中被定义为 Number.MIN_SAFE_INTEGER

function isLength(value) {
  return typeof value === 'number' &&
    value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER
}

function isArrayLike(value) {
  return value != null && typeof value !== 'function' && isLength(value.length)
}

var arrObj = { 0: 'dog', 1: 'cat', 2: 'rabbit', 'length': 3 }
isArrayLike(arrObj) // true
  • jquery源码

       找了我半天,在core.js里。lodash就很好找,一个方法一个js。

function isArrayLike( obj ) {

	var length = !!obj && obj.length,
		type = toType( obj ); // toType() 了正确区分对象

	if ( typeof obj === "function" || isWindow( obj ) ) { 
                                   // isWindow()判断传入的是否是window对象
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

每句都看得懂,组合起来就是不会写T^T,请给阿姨点一首《路太弯》。

 


版权声明:本文为weixin_42423019原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。