数组还能有假的吗?

69 阅读4分钟

什么是伪数组

伪数组(也称为类数组)指的是具有length属性和索引值,但没有数组所具有的内置方法的对象。

  1. 伪数组拥有数组的属性,
    -具有 length 属性  但length属性不是动态的,不会随着成员的变化而变化
    -按索引方式储存数据
    -不具有数组的push(), forEach()等方法
  2. 伪数组本质是一个 Object,而真实的数组是一个 Array。
    伪数组的原型  Object.__prototype__ 通过改变原型指向可以将伪数组转为真数组

常见伪数组

一些常见的伪数组包括 arguments 对象、DOM元素列表以及 NodeLists。

  1. 函数内部的 arguments,扩展操作符可以将 arguments 展开成独立的参数
  2. DOM 对象列表  如 通过 document.getElementByTags 获取的 dom元素
  3. jQuery对象  如 $('div')
// 伪数组
var arrLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
};

伪数组转为真数组

1. 遍历添加入一个空数组

var arr = [];
for(var i = 0; i < arrLike.length; i++){
   arr.push(arrLike[i]);
}

2. 利用数组的slice()方法

slice()返回一个新的数组。

;[].slice.call(arrLike);
//slice() 方法 返回数组中被选中的元素。

使用Array 选型上的 slice() 方法,

Array.prototype.slice.call(): 可以使用 call() 方法将 slice() 方法应用于伪数组。

const arrLike = {0: "a", 1: "b", 2: "c", length: 3};
const realArray = Array.prototype.slice.call(arrLike);
console.log(realArray); // ["a", "b", "c"]

使用slice()返回一个新的数组,用call()或apply()把他的作用环境指向伪数组。slice 返回的数组中,不会保留索引值以外的其他额外属性。

模拟 slice() 内部实现

Array.prtotype.slice = function(start, end){
    var result = new Array();
    var start = start | 0;
    var end = end | this.length;
    for(var i = start; i < end; i++){
        result.push(this[i]);
    }
    return result;
}

3. 改变原型指向

arrLike.__proto__ = Array.prototype;

通过改变原型指向,arrLike就继承了Array.prototype中的方法,可以使用push(),unshift()等方法了,length值也会随之动态改变。

这种直接修改原型链的方法,还会保留下伪数组中的所有属性,包括不是索引值的属性

4. Array.from()

Array.from() 方法从一个伪数组或可迭代对象(Set、Map)转换为一个真正的数组,该过程属于数组的浅拷贝。 Array.from() 只保留索引值内的属性。

const arrLike = {0: "a", 1: "b", 2: "c", length: 3};
const realArray = Array.from(arrLike);
console.log(realArray); // ["a", "b", "c"]

5. 展开运算符

使用展开运算符也可以非常简单地将伪数组转换为数组

const arrLike = {0: "a", 1: "b", 2: "c", length: 3};
const realArray = [...arrLike];
console.log(realArray); // ["a", "b", "c"]

以上方法都能够将伪数组转换为真实的数组,可以根据实际需要选择使用哪种方法。

如何判断数组

1. 使用instanceof

instanceof 运算符可以用来判断某个构造函数的prototype属性所指向的对象是否存在于另外检测对象的原型链上

arr instanceof Array

const a = [];
const b = {};
console.log(a instanceof Array);//true
console.log(a instanceof Object);//true, 在数组的原型链上也能找到Object构造函数
console.log(b instanceof Array);//false

大部分情况都可以用instanceof判断是否是数组,但是在判断一个变量是对象或非对象时,会存在坑,
因为 instanceof 在判断数组时,即会把数组当做Array类型,又会把数组当做Object类型,所以要严格验证一个数组,最好是用constructor,能严格区分数组和对象。

2. 使用constructor

实例化的数组拥有一个constructor属性,这个属性指向生成实例的构造函数。

数组是由一个叫Array的函数实例化的

console.log([].constructor == Array);  //true
console.log({}.constructor == Object);  //true
console.log("string".constructor == String); //true
console.log((123).constructor == Number);  //true
console.log(true.constructor == Boolean);  //true

但是 constructor 属性是可以被改写的,改了constructor属性的话,那么使用这种方法就无法判断出数组的真是身份。

//定义一个数组
const a = [];
//作死将constructor属性改成了别的
a.contrtuctor = Object;
console.log(a.constructor == Array);//false (哭脸)
console.log(a.constructor == Object);//true (哭脸)
console.log(a instanceof Array);//true (instanceof火眼金睛)

3. 使用原型上的toString方法判断

使用Object.prototype.toString方法来判断,每一个继承自Object的对象都拥有toString的方法。

为什么不能直接调用自身的 toString()呢?

const a = ['Hello','Howard'];  //数组的 toString 方法会返回逗号拼接的字符串 toString == join
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
a.toString();//"Hello,Howard"
b.toString();//"[object Object]"
c.toString();//"Hello,Howard"

从上面的代码可以看出,除了对象之外,其他的数据类型的toString返回的都是内容的字符串,只有对象的toString方法会返回对象的类型。

Object.prototype.toString.call(arr) === '[object Array]', 重点记住 object Array

4. 使用Array.isArray 判断

Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false

总结

伪数组与真数组的区别在于是否具备数组特有的方法和属性如 push()、pop()、slice() 等数组方法。如果需要使用数组方法,需要将伪数组转换成真正的数组。