文档
数组方法
添加/移除数组元素
arr.push(...items)
—— 从尾端添加元素arr.pop()
—— 从尾端提取元素arr.shift()
—— 从首端提取元素arr.unshift(...items)
—— 从首端添加元素
splice
- 数组是对象,所以我们可以尝试使用
delete
:
1 2 3 4 5 6 7 8 |
let arr = ["I", "go", "home"]; delete arr[1]; // remove "go" alert( arr[1] ); // undefined // now arr = ["I", , "home"]; alert( arr.length ); // 3 |
- 元素被删除了,但数组仍然有
3
个元素,我们可以看到arr.length == 3
- 这很正常,因为
delete obj.key
是通过key
来移除对应的值 - 对于对象来说是可以的。但是对于数组来说,我们通常希望剩下的元素能够移动并占据被释放的位置
- 这很正常,因为
splice
可以做所有事情:添加,删除和插入元素- 它从索引
start
开始修改arr
:删除deleteCount
个元素并在当前位置插入elem1, ..., elemN
- 最后返回被删除的元素所组成的数组
- 它从索引
1 |
arr.splice(start[, deleteCount, elem1, ..., elemN]) |
1 2 3 4 5 |
let arr = ["I", "study", "JavaScript"]; arr.splice(1, 1); // 从索引 1 开始删除 1 个元素 alert( arr ); // ["I", "JavaScript"] |
- 删除了
3
个元素,并用另外两个元素替换它们:
1 2 3 4 5 6 |
let arr = ["I", "study", "JavaScript", "right", "now"]; // 删除数组的前三项,并使用其他内容代替它们 arr.splice(0, 3, "Let's", "dance"); alert( arr ) // 现在 ["Let's", "dance", "right", "now"] |
splice
返回了被删除的元素所组成的数组:
1 2 3 4 5 6 |
let arr = ["I", "study", "JavaScript", "right", "now"]; // 删除前两个元素 let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- 被从数组中删除了的元素 |
- 将
deleteCount
设置为0
,splice
方法就能够插入元素而不用删除任何元素:
1 2 3 4 5 6 7 8 |
let arr = ["I", "study", "JavaScript"]; // 从索引 2 开始 // 删除 0 个元素 // 然后插入 "complex" 和 "language" arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript" |
- 允许负向索引
1 2 3 4 5 6 7 8 |
let arr = [1, 2, 5]; // 从索引 -1(尾端前一位) // 删除 0 个元素, // 然后插入 3 和 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 |
slice
- 语法是:
- 会返回一个新数组,将所有从索引
start
到end
(不包括end
)的数组项复制到一个新的数组 start
和end
都可以是负数,在这种情况下,从末尾计算索引
- 会返回一个新数组,将所有从索引
1 |
arr.slice([start], [end]) |
1 2 3 4 5 |
let arr = ["t", "e", "s", "t"]; alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素) alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素) |
- 也可以不带参数地调用它:
arr.slice()
会创建一个arr
的副本
concat
- 创建一个新数组,其中包含来自于其他数组和其他项的值
- 它接受任意数量的参数 —— 数组或值都可以
- 结果是一个包含来自于
arr
,然后是arg1
,arg2
的元素的新数组
1 |
arr.concat(arg1, arg2...) |
1 2 3 4 5 6 7 8 9 10 |
let arr = [1, 2]; // 从 arr 和 [3,4] 创建一个新数组 alert( arr.concat([3, 4]) ); // 1,2,3,4 // 从 arr、[3,4] 和 [5,6] 创建一个新数组 alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // 从 arr、[3,4]、5 和 6 创建一个新数组 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 |
- 通常,它只复制数组中的元素。其他对象,即使它们看起来像数组一样,但仍然会被作为一个整体添加:
1 2 3 4 5 6 7 8 |
let arr = [1, 2]; let arrayLike = { 0: "something", length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[object Object] |
- 如果类数组对象具有
Symbol.isConcatSpreadable
属性,那么它就会被concat
当作一个数组来处理:此对象中的元素将被添加:
1 2 3 4 5 6 7 8 9 10 |
let arr = [1, 2]; let arrayLike = { 0: "something", 1: "else", [Symbol.isConcatSpreadable]: true, length: 2 }; alert( arr.concat(arrayLike) ); // 1,2,something,else |
遍历:forEach
- 允许为数组的每个元素都运行一个函数
1 2 3 |
arr.forEach(function(item, index, array) { // ... do something with item }); |
- 显示了数组的每个元素:
1 2 |
// 对每个元素调用 alert ["Bilbo", "Gandalf", "Nazgul"].forEach(alert); |
- 介绍了它们在目标数组中的位置:
1 2 3 |
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { alert(`${item} is at index ${index} in ${array}`); }); |
indexOf/lastIndexOf
和 includes
arr.indexOf(item, from)
—— 从索引from
开始搜索item
,如果找到则返回索引,否则返回-1
arr.includes(item, from)
—— 从索引from
开始搜索item
,如果找到则返回true
(译注:如果没找到,则返回false
)
1 2 3 4 5 6 7 |
let arr = [1, 0, false]; alert( arr.indexOf(0) ); // 1 alert( arr.indexOf(false) ); // 2 alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true |
- 注意,
indexOf
和includes
使用严格相等===
进行比较- 所以,如果我们搜索
false
,它会准确找到false
而不是数字0
- 所以,如果我们搜索
- 方法
arr.lastIndexOf
与indexOf
相同,但从右向左查找
1 2 3 4 |
let fruits = ['Apple', 'Orange', 'Apple']; alert( fruits.indexOf('Apple') ); // 0(第一个 Apple) alert( fruits.lastIndexOf('Apple') ); // 2(最后一个 Apple) |
- 方法
includes
可以正确的处理NaN
- 方法
includes
的一个次要但值得注意的特性是,它可以正确处理NaN
,这与indexOf
不同: - 这是因为
includes
是在比较晚的时候才被添加到JavaScript
中的,并且在内部使用了更新了的比较算法
- 方法
1 2 3 |
const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1(错,应该为 0) alert( arr.includes(NaN) );// true(正确) |
find
和 findIndex/findLastIndex
- 语法
- 有一个对象数组。我们如何找到具有特定条件的对象?
1 2 3 4 |
let result = arr.find(function(item, index, array) { // 如果返回 true,则返回 item 并停止迭代 // 对于假值(falsy)的情况,则返回 undefined }); |
- 例如,我们有一个存储用户的数组,每个用户都有
id
和name
字段。让我们找到id == 1
的那个用户:
1 2 3 4 5 6 7 8 9 |
let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; let user = users.find(item => item.id == 1); alert(user.name); // John |
arr.findIndex
方法(与arr.find
)具有相同的语法,但它返回找到的元素的索引,而不是元素本身。如果没找到,则返回-1
arr.findLastIndex
方法类似于findIndex
,但从右向左搜索,类似于lastIndexOf
1 2 3 4 5 6 7 8 9 10 11 12 |
let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"}, {id: 4, name: "John"} ]; // 寻找第一个 John 的索引 alert(users.findIndex(user => user.name == 'John')); // 0 // 寻找最后一个 John 的索引 alert(users.findLastIndex(user => user.name == 'John')); // 3 |
filter
find
方法搜索的是使函数返回true
的第一个(单个)元素- 如果需要匹配的有很多,我们可以使用
arr.filter(fn)
- 语法与
find
大致相同,但是filter
返回的是所有匹配元素组成的数组:
- 语法与
1 2 3 4 |
let results = arr.filter(function(item, index, array) { // 如果 true item 被 push 到 results,迭代继续 // 如果什么都没找到,则返回空数组 }); |
1 2 3 4 5 6 7 8 9 10 |
let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; // 返回前两个用户的数组 let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2 |
转换数组:map
arr.map
方法是最有用和经常使用的方法之一- 它对数组的每个元素都调用函数,并返回结果数组
1 2 3 |
let result = arr.map(function(item, index, array) { // 返回新值而不是当前元素 }) |
1 2 |
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6 |
重新排序:sort(fn)
arr.sort
方法对数组进行 原位(in-place
) 排序,更改元素的顺序- 原位是指在此数组内,而非生成一个新数组
- 它还返回排序后的数组,但是返回值通常会被忽略,因为修改了
arr
本身\ - 示例:
- 这些元素默认情况下被按字符串进行排序
- 从字面上看,所有元素都被转换为字符串,然后进行比较
1 2 3 4 5 6 |
let arr = [ 1, 2, 15 ]; // 该方法重新排列 arr 的内容 arr.sort(); alert( arr ); // 1, 15, 2 |
- 要使用我们自己的排序顺序,我们需要提供一个函数作为
arr.sort()
的参数
1 2 3 4 5 |
function compare(a, b) { if (a > b) return 1; // 如果第一个值比第二个值大 if (a == b) return 0; // 如果两个值相等 if (a < b) return -1; // 如果第一个值比第二个值小 } |
1 2 3 4 5 6 7 8 9 10 11 |
function compareNumeric(a, b) { if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; } let arr = [ 1, 2, 15 ]; arr.sort(compareNumeric); alert(arr); // 1, 2, 15 |
- 使用
localeCompare
for strings
1 2 3 4 5 |
let countries = ['Österreich', 'Andorra', 'Vietnam']; alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich(错的) alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam(对的!) |
颠倒顺序:reverse
- 用于颠倒
arr
中元素的顺序- 它也会返回颠倒后的数组
arr
- 它也会返回颠倒后的数组
1 2 3 4 |
let arr = [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1 |
split
- 用“逗号后跟着一个空格”作为分隔符:
1 2 3 4 5 6 7 |
let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字) } |
split
方法有一个可选的第二个数字参数 —— 对数组长度的限制。如果提供了,那么额外的元素会被忽略- 但实际上它很少使用:
1 2 3 |
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); alert(arr); // Bilbo, Gandalf |
- 调用带有空参数
s
的split(s)
,会将字符串拆分为字母数组:
1 2 3 |
let str = "test"; alert( str.split('') ); // t,e,s,t |
join
arr.join(glue)
与split
相反- 它会在它们之间创建一串由
glue
粘合的arr
项
- 它会在它们之间创建一串由
1 2 3 4 5 |
let arr = ['Bilbo', 'Gandalf', 'Nazgul']; let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串 alert( str ); // Bilbo;Gandalf;Nazgul |
reduce
和reduceRight
- 当我们需要遍历一个数组时 —— 我们可以使用
forEach
,for
或for..of
- 当我们需要遍历并返回每个元素的数据时 —— 我们可以使用
map
arr.reduce
方法和arr.reduceRight
方法和上面的种类差不多,但稍微复杂一点- 它们用于根据数组计算单个值
accumulator
—— 是上一个函数调用的结果,第一次等于initial
(如果提供了initial
的话)item
—— 当前的数组元素index
—— 当前索引arr
—— 数组本身
1 2 3 |
let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]); |
- 通过一行代码得到一个数组的总和:
- 也可以省略初始值:
- 如果没有初始值,那么
reduce
会将数组的第一个元素作为初始值,并从第二个元素开始迭代
1 2 3 4 5 |
let arr = [1, 2, 3, 4, 5]; let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 |
- 建议始终指定初始值
1 2 3 4 5 |
let arr = []; // Error: Reduce of empty array with no initial value // 如果初始值存在,则 reduce 将为空 arr 返回它(即这个初始值)。 arr.reduce((sum, current) => sum + current); |
Array.isArray
- 数组是基于对象的,不构成单独的语言类型
- 所以
typeof
不能帮助从数组中区分出普通对象:
- 所以
1 2 |
alert(typeof {}); // object alert(typeof []); // object(相同) |
- 但是数组经常被使用,因此有一种特殊的方法用于判断:
Array.isArray(value)
- 如果
value
是一个数组,则返回true
;否则返回false
- 如果
1 2 3 |
alert(Array.isArray({})); // false alert(Array.isArray([])); // true |
大多数方法都支持 “thisArg
”
- 几乎所有调用函数的数组方法 —— 比如
find
,filter
,map
,除了sort
是一个特例,都接受一个可选的附加参数thisArg
- 上面的部分中没有解释该参数,因为该参数很少使用
1 2 3 4 5 |
arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... // thisArg 是可选的最后一个参数 |
thisArg
参数的值在func
中变为this
- 例如,在这里我们使用
army
对象方法作为过滤器,thisArg
用于传递上下文(passes the context
):
- 例如,在这里我们使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
let army = { minAge: 18, maxAge: 27, canJoin(user) { return user.age >= this.minAge && user.age < this.maxAge; } }; let users = [ {age: 16}, {age: 20}, {age: 23}, {age: 30} ]; // 找到 army.canJoin 返回 true 的 user let soldiers = users.filter(army.canJoin, army); alert(soldiers.length); // 2 alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23 |
Iterable object
(可迭代对象)
概述
- 可迭代(
Iterable
) 对象是数组的泛化- 这个概念是说任何对象都可以被定制为可在
for..of
循环中使用的对象
- 这个概念是说任何对象都可以被定制为可在
- 数组是可迭代的
- 但不仅仅是数组。很多其他内建对象也都是可迭代的。例如字符串也是可迭代的
Symbol.iterator
- 例如,我们有一个对象,它并不是数组,但是看上去很适合使用
for..of
循环
1 2 3 4 5 6 7 |
let range = { from: 1, to: 5 }; // 我们希望 for..of 这样运行: // for(let num of range) ... num=1,2,3,4,5 |
- 为了让
range
对象可迭代(也就让for..of
可以运行)我们需要为对象添加一个名为Symbol.iterator
的方法(一个专门用于使对象可迭代的内建symbol
)- 当
for..of
循环启动时,它会调用这个方法(如果没找到,就会报错)
这个方法必须返回一个 迭代器(iterator
) —— 一个有next
方法的对象 - 从此开始,
for..of
仅适用于这个被返回的对象 - 当
for..of
循环希望取得下一个数值,它就调用这个对象的next()
方法 next()
方法返回的结果的格式必须是{done: Boolean, value: any}
,当done=true
时,表示循环结束,否则value
是下一个值
- 当
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 |
let range = { from: 1, to: 5 }; // 1. for..of 调用首先会调用这个: range[Symbol.iterator] = function() { // ……它返回迭代器对象(iterator object): // 2. 接下来,for..of 仅与下面的迭代器对象一起工作,要求它提供下一个值 return { current: this.from, last: this.to, // 3. next() 在 for..of 的每一轮循环迭代中被调用 next() { // 4. 它将会返回 {done:.., value :...} 格式的对象 if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; // 现在它可以运行了! for (let num of range) { alert(num); // 1, 然后是 2, 3, 4, 5 } |
- 注意可迭代对象的核心功能:关注点分离
range
自身没有next()
方法- 相反,是通过调用
range[Symbol.iterator]()
创建了另一个对象,即所谓的“迭代器”对象,并且它的next
会为迭代生成值 - 因此,迭代器对象和与其进行迭代的对象是分开的
- 从技术上说,我们可以将它们合并,并使用
range
自身作为迭代器来简化代码- 现在
range[Symbol.iterator]()
返回的是range
对象自身:它包括了必需的next()
方法,并通过this.current
记忆了当前的迭代进程 - 这样更短,对吗?是的。有时这样也可以
- 但缺点是,现在不可能同时在对象上运行两个
for..of
循环了:它们将共享迭代状态,因为只有一个迭代器,即对象本身
但是两个并行的for..of
是很罕见的,即使在异步情况下
- 现在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
let range = { from: 1, to: 5, [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; for (let num of range) { alert(num); // 1, 然后是 2, 3, 4, 5 } |
字符串是可迭代的
- 数组和字符串是使用最广泛的内建可迭代对象
- 对于一个字符串,
for..of
遍历它的每个字符:
1 2 3 4 |
for (let char of "test") { // 触发 4 次,每个字符一次 alert( char ); // t, then e, then s, then t } |
- 对于代理对(
surrogate pairs
),它也能正常工作- 译注:这里的代理对也就指的是
UTF-16
的扩展字符
- 译注:这里的代理对也就指的是
1 2 3 4 |
let str = '𝒳😂'; for (let char of str) { alert( char ); // 𝒳,然后是 😂 } |
显式调用迭代器
- 采用与
for..of
完全相同的方式遍历字符串,但使用的是直接调用- 这段代码创建了一个字符串迭代器,并“手动”从中获取值
- 很少需要我们这样做,但是比
for..of
给了我们更多的控制权
1 2 3 4 5 6 7 8 9 10 11 12 |
let str = "Hello"; // 和 for..of 做相同的事 // for (let char of str) alert(char); let iterator = str[Symbol.iterator](); while (true) { let result = iterator.next(); if (result.done) break; alert(result.value); // 一个接一个地输出字符 } |
可迭代(iterable
)和类数组(array-like
)
Iterable
如上所述,是实现了Symbol.iterator
方法的对象Array-like
是有索引和length
属性的对象,所以它们看起来很像数组- 当我们将
JavaScript
用于编写在浏览器或任何其他环境中的实际任务时,我们可能会遇到可迭代对象或类数组对象,或两者兼有- 例如,字符串即是可迭代的(
for..of
对它们有效),又是类数组的(它们有数值索引和length
属性)
- 例如,字符串即是可迭代的(
- 但是一个可迭代对象也许不是类数组对象。反之亦然,类数组对象可能不可迭代
- 例如,上面例子中的
range
是可迭代的,但并非类数组对象,因为它没有索引属性,也没有length
属性 - 下面这个对象则是类数组的,但是不可迭代:
- 例如,上面例子中的
1 2 3 4 5 6 7 8 |
let arrayLike = { // 有索引和 length 属性 => 类数组对象 0: "Hello", 1: "World", length: 2 }; // Error (no Symbol.iterator) for (let item of arrayLike) {} |
- 可迭代对象和类数组对象通常都 不是数组,它们没有
push
和pop
等方法
Array.from
- 可以接受一个可迭代或类数组的值,并从中获取一个“真正的"数组"
- 就可以对其调用数组方法了
- 在
(*)
行的Array.from
方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素复制到这个新数组
1 2 3 4 5 6 7 8 9 10 |
// 类数组 let arrayLike = { 0: "Hello", 1: "World", length: 2 }; let arr = Array.from(arrayLike); // (*) alert(arr.pop()); // World(pop 方法有效) |
- 如果是可迭代对象,也是同样:
1 2 3 |
// 假设 range 来自上文的例子中 let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (数组的 toString 转化方法生效) |
Array.from
的完整语法允许我们提供一个可选的“映射(mapping
)”函数:- 可选的第二个参数
mapFn
可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外thisArg
允许我们为该函数设置this
- 可选的第二个参数
1 |
Array.from(obj[, mapFn, thisArg]) |
1 2 3 4 5 6 |
// 假设 range 来自上文例子中 // 求每个数的平方 let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25 |
- 用
Array.from
将一个字符串转换为单个字符的数组:- 与
str.split
方法不同,它依赖于字符串的可迭代特性 - 因此,就像
for..of
一样,可以正确地处理代理对(surrogate pair
)
- 与
1 2 3 4 5 6 7 8 |
let str = '𝒳😂'; // 将 str 拆分为字符数组 let chars = Array.from(str); alert(chars[0]); // 𝒳 alert(chars[1]); // 😂 alert(chars.length); // 2 |
1 2 3 4 5 6 7 8 |
let str = '𝒳😂'; let chars = []; // Array.from 内部执行相同的循环 for (let char of str) { chars.push(char); } alert(chars); |
- 甚至可以基于
Array.from
创建代理感知(surrogate-aware
)的slice
方法- 也就是能够处理
UTF-16
扩展字符的slice
方法
- 也就是能够处理
1 2 3 4 5 6 7 8 9 10 |
function slice(str, start, end) { return Array.from(str).slice(start, end).join(''); } let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 // 原生方法不支持识别代理对(译注:UTF-16 扩展字符) alert( str.slice(1, 3) ); // 乱码(两个不同 UTF-16 扩展字符碎片拼接的结果) |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!