扩展运算符(spread)是三个点(…)。它是 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
[...array] // array表示要转换的数组
console.log(...[1, 2, 3]) // 1 2 3
console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
[...document.querySelectorAll('div')] // [, , ]
2、用途
(1)复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
// 存在问题:a2 并不是 a1 的克隆,而是指向同一份数据的另一个指针。修改 a2,会直接导致a1 的变化。
const a1 = [1, 2]
const a2 = a1
a2[0] = 2
a1 // [2, 2]
// ES5 中用变通方法复制数组(浅拷贝)
const a1 = [1, 2]
const a2 = a1.concat()
// 使用扩展运算符复制数组(浅拷贝)
const a1 = [1, 2]
const a2 = [...a1]
(2)合并数组
合并数组只是进行了浅拷贝,使用的时候需要注意
const arr1 = ['a', 'b']
const arr2 = ['c']
const arr3 = ['d', 'e']
// ES5 的合并数组
arr1.concat(arr2, arr3) // [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
(3)与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
const arr = [1, 2, 3]
// ES5
const first = arr[0], rest = arr.slice(1)
// ES6
const [first, ...rest] = arr
console.log(first) // 输出 1
console.log(rest) // 输出 [2, 3]
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...butLast, last] = [1, 2, 3, 4, 5] // 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5] // 报错
(4) 字符串转数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
// ES6 之前字符串转数组是通过:'hello'.split('');
(5)类数组转为数组
// arguments
function func() {
console.log(arguments) // [Arguments] { '0': 1, '1': 2 }
console.log([...arguments]) [ 1, 2 ]
}
func(1, 2)
// 任何定义了遍历器(Iterator)接口的对象。都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div')
let array = [...nodeList]
(6)数组转为参数数列(函数传参)
将一个数组转为用逗号分隔的参数序列,可以方便地传递给函数。
const arr = [1, 2, 3]
function sum(a, b, c) {
return a + b + c
}
console.log(sum(...arr)) // 输出 6
二、Array.from()
类数组不能直接使用数组的方法,需要先把类数组转化为数组。
ES6 数组的新增方法 Array.from(),该方法用于将类数组对象(array-like)和可遍历的对象(iterable)转换为真正的数组进行使用(包括 ES6 新增的数据结构 Set 和 Map)。
基本语法
Array.from(arrayLike, mapFn, thisArg)
参数
描述
arrayLike
想要转换成数组的类数组对象或可迭代对象
mapFn
如果指定了该参数,新数组中的每个元素会执行该回调函数
thisArg
可选参数,执行回调函数 mapFn 时 this 对象
arrayLike
任何有 length 属性的对象,都可以通过 Array.from() 方法转为数组。
const arrLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
// ES5 的写法
var arr = [].slice.call(arrayLike) // ['a', 'b', 'c']
var arr = [].slice.apply(arrLike) // ['a', 'b', 'c']
// ES6 的写法
var arr = Array.from(arrLike)
// NodeList 对象
let ps = document.querySelectorAll('p')
Array.from(ps).filter(p => {
return p.textContent.length > 100
})
// arguments 对象
function foo() {
var args = Array.from(arguments)
// ...
}
/* 部署了 Iterator 接口的数据结构 */
Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
const namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
const map = new Map([[1, 2], [2, 4], [4, 8]])
Array.from(map) // [[1, 2], [2, 4], [4, 8]]
// 数组对象,Array.from()会返回一个一模一样的新数组
Array.from([1, 2, 3]) // [1, 2, 3]
// 有 length 属性的对象
Array.from({ length: 3 }) // [undefined, undefined, undefined]
mapFn 回调函数
在 Array.from 中第二个参数是一个类似 map 函数的回调函数,该回调函数会依次接收数组中的每一项作为传入的参数,然后对传入值进行处理,最得到一个新的数组。 Array.from(obj, mapFn, thisArg) 也可以用 map 改写成这样 Array.from(obj).map(mapFn, thisArg)。
Array.from(arrayLike, x => x * x)
// 等同于
Array.from(arrayLike).map(x => x * x)
Array.from([1, 2, 3], x => x * x) // [1, 4, 9]
// 取出一组 DOM 节点的文本内容
let spans = document.querySelectorAll('span.name')
// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent)
// Array.from()
let names2 = Array.from(spans, s => s.textContent)
thisArg
Array.from 中第三个参数可以对回调函数中 this 的指向进行绑定,该参数是非常有用的,我们可以将被处理的数据和处理对象分离,将各种不同的处理数据的方法封装到不同的的对象中去,处理方法采用相同的名字。
在调用 Array.from 对数据对象进行转换时,可以将不同的处理对象按实际情况进行注入,以得到不同的结果,适合解耦。
// 定义一个 obj 对象可以认作是,Array.from 回调函数中处理数据的方法集合,handle 是其中的一个方法,把 obj 作为第三个参数传给 Array.from 这样在回调函数中可以通过 this 来拿到 obj 对象。
let obj = {
handle: function(n){
return n + 2
}
}
Array.from([1, 2, 3, 4, 5], function (x){
return this.handle(x)
}, obj) // [3, 4, 5, 6, 7]
三、Array.of()
Array.of() 方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array() // []
Array(3) // [, , ,] 参数只有一个正整数时,实际上是指定数组的长度
Array(3, 11, 8) // [3, 11, 8] 返回由参数组成的新数组
上面代码中,Array() 方法没有参数、一个参数、三个参数时,返回的结果都不一样。只有当参数个数不少于 2 个时,Array() 才会返回由参数组成的新数组。参数只有一个正整数时,实际上是指定数组的长度。
Array.of() 基本上可以用来替代 Array() 或 new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
Array.of() 总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of() 方法可以用下面的代码模拟实现。
function ArrayOf(){
return [].slice.call(arguments)
}
四、实例方法
1、copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。该方法会改变原数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
参数
描述
target
必须参数。从该位置开始替换数据。如果为负值,表示倒数
start
可选参数。从该位置开始读取数据,默认为0。如果为负值,表示从末尾开始计算。
end
可选参数。到该位置停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
这三个参数都应该是数值,如果不是,会自动转为数值。
// 将3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,覆盖原来的1和2。
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5]
2、find()、findIndex()、findLast()、findLastIndex()
数组的检索方法,不会改变原数组。
方法
说明
find()
用于找出第一个符合条件的数组成员。如果有返回该成员,如果没有符合条件的成员,则返回 undefined。
findIndex()
返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 -1。
findLast()
用于找出第一个符合条件的数组成员。如果有返回该成员,如果没有符合条件的成员,则返回 undefined。从数组的最后一个成员开始,依次向前检查。
findLastIndex()
返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 -1。从数组的最后一个成员开始,依次向前检查。
第一个参数是一个回调函数,所有数组成员依次执行该回调函数。
第二个参数,用来绑定回调函数的 this 对象。
[1, 4, -5, 10].find(n => n < 0) // -5
// 回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9
}) // 10
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9
}) // 2
// 绑定回调函数的this对象。
function f(v){
return v > this.age
}
let person = {name: 'John', age: 20}
[10, 12, 26, 15].find(f, person) // 26
const array = [
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 }
]
array.findLast(n => n.value % 2 === 1) // { value: 3 }
array.findLastIndex(n => n.value % 2 === 1) // 2
这四个方法都可以发现 NaN,弥补了数组的 indexOf() 方法的不足。
array.indexOf(item, start = 0) 。 返回数组中某个指定的元素位置,如果在数组中没找到指定元素则返回 -1。
[NaN].indexOf(NaN) // -1
// 借助 Object.is() 方法
[NaN].findIndex(y => Object.is(NaN, y)) // 0
3、filter()
过滤数组成员,满足条件的成员组成一个新数组返回。该方法不会改变原数组。
第一个参数是一个回调函数,所有数组成员依次执行该回调函数,返回结果为 true的成员组成一个新数组返回。
第二个参数,用来绑定回调函数的 this 对象。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3)
}) // [4, 5]
[0, 1, 'a', false].filter(Boolean) // [1, 'a']
// 回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0
}) // [1, 3, 5]
// 绑定回调函数内部的this变量。
const obj = { MAX: 3 }
const myFilter = function (item) {
if (item > this.MAX) return true
}
[2, 8, 3, 4, 1, 3, 2, 9].filter(myFilter, obj) // [8, 4, 9]
4、map()
将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。不会改变原数组。
第一个参数是一个回调函数,所有数组成员依次执行该回调函数,运行结果组成一个新数组返回。
第二个参数,用来绑定回调函数的 this 对象。
[1, 2, 3].map(function (n) {
return n + 1
}) // [2, 3, 4]
// 回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index
}) // [0, 2, 6]
// 绑定回调函数内部的this变量。
const arr = ['a', 'b', 'c']
[1, 2].map(function (e) {
return this[e]
}, arr) // ['b', 'c']
5、forEach()
数组循环。调用数组的每个元素,并将元素传递给回调函数。没有返回结果,返回值为 undefined。修改数组元素会改变原数组。
第一个参数是一个回调函数,所有数组成员依次执行该回调函数,循环一遍。
第二个参数,用来绑定回调函数的 this 对象。
// 回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
const arr = ['a', 'b', 'c']
arr.forEach(function(currentValue, index, arr) {
console.log(currentValue)
})
// 'a' 0 ['a', 'b', 'c']
// 'b' 1 ['a', 'b', 'c']
// 'c' 2 ['a', 'b', 'c']
中断循环
(1)使用 for 循环或者 for in 循环代替。break 结束整个循环, continue 退出本次循环。
let list = [1, 2, 3, 4]
for (let i = 0; i < list.length; i++) {
if (i == 1) {
continue // 退出本次循环
}
if (i == 2) {
break // 结束整个循环
}
}
// break 只会结束最里层的 for 循环,继续外面的 for 循环
for (var i = 1; i < 4; i++) {
for(var j = 1; j < 6; j++) {
console.log(i * j)
if(j > 2) break
}
} // 1 2 3 2 4 6 3 6 9
for 循环中 return 语句:跳出 for 循环,且不执行 for 循环之外的语句,直接跳出当前函数,返回 return 后的值。
因为 js 中 for 是没有局部作用域的概念的,所以只有把 for 循环放在函数中时,才可以在for循环中使用 return 语句。
(2)return、break、continue 的方式都不能终止 forEach 循环,return 在 forEach 里相当于 for 循环里的 continue,能够退出本次循环。
// return 3 的元素跳过
var arr = [1, 2, 3, 4, 5]
arr.forEach(function (item) {
if (item === 3) {
return
}
console.log(item)
})
// 1
// 2
// 4
// 5
(3)使用 try…catch,throw 抛出异常,终止 forEach 循环。
try {
[1, 2, 3].forEach(item => {
if (item === 2) throw('哈哈哈')
console.log(item)
})
} catch(e) {
console.log(e, 'e')
}
// 1
// '哈哈哈' e
(4)通过 some 和 every 来实现。every 在碰到 return false 的时候,中止循环。some 在碰到 return ture 的时候,中止循环。
// 使用 Array.some()
arr.some(item => {
console.log(item)
return item === 2 // 当有数组有一项满足条件时结束并返回 true
})
// 使用 Array.ervey()
arr.every(item => {
console.log(item)
return item !== 2 // 检查数字中是否每一项都满足条件,如果有一项不满足就结束循环并返回 false
})
6、some()、every()
这两个方法类似“断言”(assert),返回一个布尔值,表示判断数组成员是否符合某种条件。不会改变原数组。
第一个参数是一个回调函数,所有数组成员依次执行该回调函数,该函数接受三个参数:当前成员、当前位置和整个数组,然后返回一个布尔值。
第二个参数,用来绑定回调函数的 this 对象。
[1, 2, 3, 4, 5].some(function (elem, index, arr) {
return elem >= 3
}) // true
[1, 2, 3, 4, 5].every(function (elem, index, arr) {
return elem >= 3
}) // false
arr.some() 只要有一项满足条件, 就会返回 true,否则返回 false;arr.every() 只有所有项都满足条件,才会返回 true,否则返回 false。
7、reduce()
reduce() 接受一个函数作为累加器,从数组第一个值开始缩减,直到最后一个值缩减完成。最终计算为一个值。不会改变原数组。
参数
说明
(prev, cur, index, arr) => {}
必需,回调函数。
prev:必需。初始值 initialValue,或者计算结束后的返回值。
cur:必需。arr 数组的当前累加项。
index:可选。当前累加项的索引。
arr:可选。要累加的 arr 非空数组。
initialValue
可选,初始值。当没有初始值时,回调函数第一次累加的 prev 是 arr 的第一项,cur 是 arr 的第二项,index 的值为 1。
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log(a, b) // 1 2 | 3 3 | 6 4 | 10 5
return a + b
}) // 15
[1, 2, 3, 4, 5].reduce(function (a, b) {
return a + b
}, 10) // 25
8、fill()
arr.fill(value, start = 0, end = arr.length) 用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。 起始索引,默认值为 0。 终止索引,默认值为 this.length。
['a', 'b', 'c'].fill(7) // [7, 7, 7]
new Array(3).fill(7) // [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
let arr = new Array(3).fill({name: 'Mike'})
arr[0].name = 'Ben'
arr // [{name: 'Ben'}, {name: 'Ben'}, {name: 'Ben'}]
let arr = new Array(3).fill([])
arr[0].push(5)
arr // [[5], [5], [5]]
9、entries()、keys() 和 values()
用于遍历数组。它们都返回一个遍历器对象,可以用 for…of 循环进行遍历,唯一的区别是 keys() 是对键名的遍历、values() 是对键值的遍历,entries() 是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index)
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem)
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem)
}
// 0 'a'
// 1 'b'
10、includes()
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为 0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4,但数组长度为 3),则会重置为从 0开始。
[1, 2, 3].includes(3, 3) // false
[1, 2, 3].includes(3, -1) // true
没有该方法之前,我们通常使用数组的 indexOf 方法,检查是否包含某个值。
indexOf 方法有两个缺点:
一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于 -1,表达起来不够直观。
二是,它内部使用严格相等运算符(===)进行判断,这会导致对 NaN 的误判。includes使用的是不一样的判断算法,就没有这个问题。
if (arr.indexOf(el) !== -1) {
// ...
}
[NaN].indexOf(NaN) // -1
[NaN].includes(NaN) // true
11、flat()、flatMap()
Array.prototype.flat()
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,不会改变原数组。
flat() 默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将 flat() 方法的参数写成一个整数,表示想要拉平的层数,默认为 1。参数值为 Infinity 时,不管有多少层嵌套,都会转成一维数组。
[1, 2, [3, 4]].flat() // [1, 2, 3, 4] 拉平一层
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]] 拉平一层
[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5] 拉平两层
[1, [2, [3]]].flat(Infinity) // [1, 2, 3] 全部拉平
[1, 2, , 4, 5].flat() // [1, 2, 4, 5] 跳过空位
Array.prototype.flatMap()
对原数组的每个成员执行一个函数(相当于执行 Array.prototype.map() ),然后对返回值组成的数组执行 flat() 方法。该方法返回一个新数组,不改变原数组。
flatMap() 只能展开一层数组。
第一个参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
第二个参数,用来绑定遍历函数里面的 this。
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2]) // [2, 4, 3, 6, 4, 8]
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]
arr.flatMap(function callback(currentValue[, index[, array]]) {
// ...
}[, thisArg])
12、at()
长久以来,JavaScript 不支持数组的负索引,如果要引用数组的最后一个成员,不能写成 arr[-1],只能使用 arr[arr.length - 1]。
这是因为方括号运算符 [] 在 JavaScript 语言里面,不仅用于数组,还用于对象。对于对象来说,方括号里面就是键名,比如 obj[1] 引用的是键名为字符串 1 的键,同理 obj[-1] 引用的是键名为字符串-1的键。由于 JavaScript 的数组是特殊的对象,所以方括号里面的负数无法再有其他语义了,也就是说,不可能添加新语法来支持负索引。
为了解决这个问题,ES2022 为数组实例增加了 at() 方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组(TypedArray)。
const arr = [5, 12, 8, 130, 44]
arr.at(2) // 8
arr.at(-2) // 130
// 如果参数位置超出了数组范围,at()返回undefined。
const sentence = 'This is a sample sentence';
sentence.at(0) // 'T'
sentence.at(-1) // 'e'
sentence.at(-100) // undefined
sentence.at(100) // undefined
13、toReversed()、toSorted()、toSpliced()、with()
很多数组的传统方法会改变原数组,比如 push()、pop()、unshift()、shift() 等等。数组只要调用了这些方法,它的值就变了。现在有一个提案,允许对数组进行操作时,不改变原数组,而返回一个原数组的拷贝。
这样的方法一共有四个。
Array.prototype.toReversed() -> Array
Array.prototype.toSorted(compareFn) -> Array
Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
Array.prototype.with(index, value) -> Array
它们分别对应数组的原有方法。
这四个新方法对应的原有方法,含义和用法完全一样,唯一不同的是不会改变原数组,而是返回原数组操作后的拷贝。
toReversed() 对应 reverse(),用来颠倒数组成员的位置。
toSorted() 对应 sort(),用来对数组成员排序。
toSpliced() 对应 splice(),用来在指定位置,删除指定数量的成员,并插入新成员。
with(index, value) 对应 splice(index, 1, value),用来将指定位置的成员替换为新的值。
const sequence = [1, 2, 3]
sequence.toReversed() // [3, 2, 1]
sequence // [1, 2, 3]
const outOfOrder = [3, 1, 2]
outOfOrder.toSorted() // [1, 2, 3]
outOfOrder // [3, 1, 2]
const array = [1, 2, 3, 4]
array.toSpliced(1, 2, 5, 6, 7) // [1, 5, 6, 7, 4]
array // [1, 2, 3, 4]
const correctionNeeded = [1, 1, 3]
correctionNeeded.with(1, 2) // [1, 2, 3]
correctionNeeded // [1, 1, 3]
14、isArray()
判断 JS 对象,如果值是 Array,则为 true,否则为 false。
Array.isArray(obj)
// 下面的函数调用都返回 true
Array.isArray([])
Array.isArray([10])
Array.isArray(new Array())
Array.isArray(new Array('a', 'b', 'c'))
// 鲜为人知的事实:其实 Array.prototype 也是一个数组。
Array.isArray(Array.prototype)
// 下面的函数调用都返回 false
Array.isArray()
Array.isArray({})
Array.isArray(null)
Array.isArray(undefined)
Array.isArray(17)
Array.isArray('Array')
Array.isArray(true)
Array.isArray(false)
Array.isArray(new Uint8Array(32))
Array.isArray({ __proto__: Array.prototype })
ES5 中判断数组的方法
(1)typeof 类型检测
对于基本类型的判断没有问题,但是判断数组时,返回 object。( string、number、undefined、boolean、symbol、object、function)
// 基本类型
typeof 123 // number
typeof '123' // string
typeof true // boolean
// 引用类型
typeof [1, 2, 3] // object
(2)通过 instanceof 判断
instanceof 运算符用于判断当前对象是否是某个构造函数的实例。
var arr = ['a', 'b', 'c']
console.log(arr instanceof Array) // true
console.log(arr.constructor === Array) // true
数组实例的原型链指向的是 Array.prototype 属性,instanceof 运算符就是用来检测 Array.prototype 属性是否存在于数组的原型链上,上面代码中的 arr 变量就是一个数组,所有拥有 Array.prototype 属性,返回值 true,这样就很好的判断数组类型了。
但是,需要注意的是,prototype 属性是可以修改的,所以并不是最初判断为 true 就一定永远为真。
(3)通过 constructor 判断
constructor 是构造函数的原型对象(prototype)上的属性,指向了当前的构造函数。
new 出来的实例对象上的原型对象有 constructor 属性指向构造函数 Array,由此我们可以判断一个数组类型。
var arr = new Array('a', 'b', 'c')
arr.constructor === Array //true
constructor 是可以被重写,所以不能确保一定是数组。(常见原型继承)
var str = 'abc'
str.constructor = Array
str.constructor === Array // true
(4)Object.prototype.toString
对象的 toString 方法,返回 [object type] ,type 为当前的数据类型。
Object.prototype.toString.call(arr) === '[object Array]'
五、数组中的空位
数组的空位指的是,数组的某一个位置没有任何值。
空位不是 undefined,某一个位置的值等于 undefined,依然是有值的。空位是没有任何值。
由于空位的处理规则非常不统一,所以建议避免出现空位。
forEach()、filter()、reduce()、every() 和 some() 都会跳过空位。
map() 会跳过空位,但会保留这个值
join() 和 toString() 会将空位视为 undefined,而 undefined 和 null 会被处理成空字符串。
// forEach 方法
[, 'a'].forEach((x, i) => console.log(i)) // 1
// filter 方法
['a', , 'b'].filter(x => true) // ['a', 'b']
// every 方法
[, 'a'].every(x => x === 'a') // true
// reduce 方法
[1, , 2].reduce((x, y) => x + y) // 3
// some 方法
[, 'a'].some(x => x !== 'a') // false
// map 方法
[, 'a'].map(x => 1) // [, 1]
// join方法
[, 'a', undefined, null].join('#') // '#a##'
// toString 方法
[, 'a', undefined, null].toString() // ',a,,'
Array.from() 方法会将数组的空位,转为 undefined,也就是说,这个方法不会忽略空位。
Array.from(['a', , 'b']) // ['a', undefined, 'b']
扩展运算符(…)也会将空位转为 undefined。
[...['a', , 'b']] // ['a', undefined, 'b']
copyWithin() 会连空位一起拷贝。
[, 'a', 'b', ,].copyWithin(2, 0) // [, 'a', , 'a']
fill() 会将空位视为正常的数组位置。
new Array(3).fill('a') // ['a', 'a', 'a']
for…of 循环也会遍历空位。
let arr = [, ,]
for (let i of arr) {
console.log(1)
}
// 1
// 1
entries()、keys()、values()、find() 和 findIndex() 会将空位处理成 undefined。
// entries()
[...[, 'a'].entries()] // [[0, undefined], [1, 'a']]
// keys()
[...[, 'a'].keys()] // [0, 1]
// values()
[...[, 'a'].values()] // [undefined, 'a']
// find()
[, 'a'].find(x => true) // undefined
// findIndex()
[, 'a'].findIndex(x => true) // 0