如何判断一个变量是否为数组?

javaScript中有五种方法可以判断一个变量是否为数组,分别是:

  • typeof运算符
  • instanceof运算符
  • constructor属性
  • Object.prototype.toString()
  • Array.isArray()
  • 特性嗅探?

typeof运算符

下表总结了typeof可能的返回值

类型 结果
Undefined “undefined”
Null “object”
Boolean “boolean”
Number “number”
String “string”
Symbol “symbol”
宿主对象 Implementation-dependent
函数对象 “function”
任何其他对象 任何其他对象

从上表和实际执行结果可以看出,当typeof的参数为null、对象、数组的时候都会返回object,因此用typeof运算符来判断某变量是否为数组是不靠谱的。

instanceof运算符

判断某个构造函数的prototype属性所指向的对象是否存在于object的原型链上

object instanceof constructor

执行结果如下,可以判断出是否是数组

1
2
var a = [];
console.log(a instanceof Array); //true

constructor属性

原型对象是类的唯一标识,构造函数是类的外在表现 – js权威指南

我们用构造函数来判断类的类型,数组实例的构造函数应该为Array

1
2
const a1 = [];
console.log(a.constructor === Array); // true

但是对象的constructor是可以修改的,这种方法并不靠谱。

需要注意嵌套 frame 的情况下,instanceof、constructor都会无效

1
2
3
4
5
6
7
8
<iframe src='a.htm'></iframe>
<script>
window.onload = function() {
var a = window.frames[0].a;
console.log(a instanceof Array); // false
console.log(a.constructor === Array); // false
};
</script>

a.htm 代码:

1
2
3
<script>
window.a = [1, 2, 3];
</script>

我们看到 index.htm 代码中,变量 a 确实是一个数组,但是 a instanceof Array 的结果却是 false。

这是因为每个 frame 都有一套自己的执行环境,跨 frame 实例化的对象彼此不共享原型链。如果打印 a instanceof window.frames[0].Array,那么结果就是 true 了。

Object.prototype.toString()

这个方法也是一些类库进行数组(甚至其他类型)判断的主流方式。看一下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
console.log(Object.prototype.toString.call(10));  // [object Number]
console.log(Object.prototype.toString.call('hello')); // [object String]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(function(){})); // [object Function]
console.log(Object.prototype.toString.call(/a/g)); // [object RegExp]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(new Error())); // [object Error]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]

console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]

console.log(Object.prototype.toString.call(location)); // [object Location]
console.log(Object.prototype.toString.call(history)); // [object History]
console.log(Object.prototype.toString.call(window)); // [object Window]

function a() {
console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();

Array.isArray()

ES5的这个方法,在保证兼容性的前提下可以使用

特性嗅探?

1
2
3
4
5
6
7
8
9
10
11
12
var a = [0, 1, 2];

if (a.sort) {
// 是数组
}

var a = {sort: 'me'};

if (a.sort) {
// 数组?
// 其实我真的不是数组
}

简直不靠谱。

总结

我们来写一个js类型判断函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 如果是基本类型,就使用 typeof,引用类型就使用 toString。此外鉴于 typeof 的结果是小写,希望所有的结果都是小写。

var class2type = {};
// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
// 在 IE6 中,null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]!
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}

var isArray = Array.isArray || function( obj ) {
return type(obj) === "array";
}

数组的原生方法有哪些?

Array.prototype上的方法 描述 返回值 是否改变原数组 注意点
concat() 方法用于合并两个或多个数组。 新数组 否 返回一个浅拷贝的数组;在嵌套数组合并的时候,之扁平化最外层数组;
copyWithin(target[, start[, end]]) 方法浅复制数组的一部分到同一数组中的另一个位置。 改变后的数组 是 无
entries() 方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对 新的Array Iterator对象 否 新Array Iterator对象的原型(proto:Array Iterator)上有一个next方法,可用用于遍历迭代器取得原数组的[key,value]
every(callback(元素值,元素的索引,原数组)[, thisArg]) 方法测试数组的所有元素是否都通过了指定函数的测试 布尔值 否 无
some(callback(element[, index[, array]])[, thisArg]) 方法测试是否至少有一个元素通过由提供的函数实现的测试 布尔值 否 无
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素 返回通过测试函数为true的元素组成的新数组 否 无
forEach() 方法对数组的每个元素执行一次提供的函数 undefined 否 无
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果 返回一个由回调函数的返回值组成的新数组 否 无
fill() “undefined”
find() “undefined”
findIndex() “undefined”
flat() “undefined”
flatMap() “undefined”
includes() “undefined”
indexOf() “undefined”
join() “undefined”
keys() “undefined”
lastIndexOf() “undefined”
pop() “undefined”
push() “undefined”
reduce() “undefined”
reduceRight() “undefined”
reverse() “undefined”
shift() “undefined”
slice() “undefined”
sort() “undefined”
splice() “undefined”
toLocaleString() “undefined”
toString() “undefined”
unshift() “undefined”
values() “undefined”
Array.prototype上的属性 描述
constructor “undefined”
length “undefined”

如何将一个类数组变量转化为数组?

先来说下什么是类数组

把拥有数值length属性和对应非负整数属性的对象看做一种类型的数组,也就是类数组。

类数组判断的几个点

  • 首先typeof返回的是object
  • 有数值属性length,且length是有限的非负整数且在0~Math.pow(2, 32)之间

类数组有别于数组的点

  • 不具备数组length的一些特性
  • 其类属于Object,所以不能从Array.prototype中继承一些有用的方法,但是可以间接地使用Function.call方法调用
  • 可以用真正数组遍历的方法来遍历类数组

将类数组变量转化为数组

1
2
3
4
5
6
7
function likeArray() {
console.log(Array.prototype.slice.call(arguments)); // [1, 2, 3]
console.log(Array.from(arguments)); // [1, 2, 3]
console.log([...arguments]); // [1, 2, 3]
}

likeArray(1,2,3);

判断一个对象是否是类数组

1
2
3
4
5
6
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

说一说ES6中对于数组有哪些扩展?

  • 扩展运算符
  • Array.from() – 用于将类数组对象和可遍历的对象转为真正的数组
  • Array.of() – 用于将一组值,转换为数组
  • 实例方法:copyWithin() find() findIndex() fill() entries() keys() values() includes() flat() flatMap()
  • 数组空位(ES6 则是明确将空位转为undefined)

你知道Array.prototype的类型是什么吗?

1
Array.isArray(Array.prototype); // true

Array.prototype 本身也是一个 Array;
因为 Array.prototype 也是个数组,所以它也有 length 属性,这个值为 0,因为它是个空数组。

求数组最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 原始方法
var arr = [6, 4, 1, 8, 2, 11, 23];
var result = arr[0];
for (var i = 1; i < arr.length; i++) {
result = Math.max(result, arr[i]);
}
console.log(result);

// resuce
function max(prev, next) {
return Math.max(prev, next);
}
console.log(arr.reduce(max));

// 排序
arr.sort(function(a, b) {
return a - b;
})
console.log(arr[arr.length - 1]);

// apply
console.log(Math.max.apply(null, arr));

// es6
console.log(Math.max(...arr));

数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 双循环去重
function unique(array) {
var res = [];
for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
for (var j = 0, resLen = res.length; j < resLen; j++) {
if (array[i] === res[j]) {
break;
}
}
// 如果array[i]是唯一的,那么执行完循环,j等于resLen
if (j === resLen) {
res.push(array[i]);
}
}
return res;
}

// indexOf
function unique(array) {
var res = [];
for (var i = 0, len = array.length; i < len; i++) {
var current = array[i];
if (res.indexOf(current) === -1) {
res.push(current);
}
}
return res;
}

// filter
function unique(array) {
var res = array.filter(function(item, index, array) {
return array.indexOf(item) === index;
})
return res;
}

// Object键值对
function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
console.log(typeof item + JSON.stringify(item))
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
}

// es6
function unique(array) {
return Array.from(new Set(array));
}

function unique(array) {
return [...new Set(array)];
}

function unique (arr) {
const seen = new Map()
return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}

数组乱序

最经典的fiisher-Yates洗牌算法;借助图形来直观的看下:

最后的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
function shuffle(arr) {
var len = arr.length,
r, t;

while(len) {
r = Math.floor(Math.random()*len--);
t = arr[r];
arr[r] = arr[len];
arr[len] = t;
}

return arr;
}

数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 递归方式处理
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(aaarr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}

// reduce方式
function flatten(arr) {
return arr.reduce(function(prev, next) {
return prev.concat(Array.isArray(next) ? flatten(next) : next);
},[]);
}

// es6扩展运算符方式
function flatten(arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}