这是我参与更文挑战的第5天,活动详情查看: 更文挑战
Array
创建
const arr1 = new Array(), // []
arr2 = new Array(10), // arr.length == 10;
arr3 = new Array(10,11), // [10, 11]
arr4 = new Array("10"), // ["10"]
arr5 = [10]; // [10]
通过Array()构造函数创建数组,如果只传入一个参数,且参数是数字,那么数组的长度等于数字。这样创建的数组里面没有任何元素,只有相应的空位(empty),而不是undefined,当然通过[]获取时,还是会显示undefined。
数组的length属性是不可遍历的,但却是可写的。虽然可以通过Object.defineProperty改writable,让其变成不可写,然后给数组添加元素时会报错。
用数组字面量[]创建会更加直观。
const arr = new Array(10),
arr2 = Array(10);
两种创建方式没有区别。
Array静态方法
from()
ES6新增。
可以将类数组(有length属性和可索引元素的结构)转换成数组实例,浅克隆。
可以将可迭代的结构,转换成数组实例(迭代每一项并存入数组)。
Array.from(arrayLike[, mapFn[, thisArg]])
-
arrayLike想要转换成数组的伪数组对象或可迭代对象。 -
mapFn可选,传入一个函数,新数组中的每个元素会执行该回调函数,相当于调用map。Array.from(obj, mapFn, thisArg)就相当于Array.from(obj).map(mapFn, thisArg) -
thisArg可选,设置执行回调函数mapFn时this对象,对箭头函数无效。
Array.from("array");
// ["a", "r", "r", "a", "y"]
let strObj = new String("abcd");
strObj[4] = "e";
strObj[5] = "f";
Array.from(strObj);
// ["a", "b", c", "d"]
内部应该是对传进来的参数进行了转型,调用了Object(arrayLike),所以把字符串转换成了字符串对象,而字符串对象是一个原生的类数组,from根据类数组的长度确定迭代的次数,通过类数组的索引取出每一位的值,因为String对象的长度是不可改变的,所以即使增加了索引也无法迭代出来。
其实字符串对象实现了iterable接口,是一个可迭代的数据结构,可以通过[Symbol.iterator]方法,创建迭代器。从而迭代每一个字符。
Array.from(true);
Array.from(1);
// []
因为这些原始类型被转换成对象后,并没有length属性,所以内部创建了一个长度为0的数组,并返回。
内部实现详见MDN。
// 此处待补
还可以将集合、映射和可迭代对象转换为一个新数组。
function createArr(len) {
return Array.from(new Array(len), num =>Math.floor(Math.random() *10));
}
利用第二参数完成一些简单的操作。
Array.from(new Array(10));
// [undefined, undefined, undefined]
自动填满,全是undefined。
of()
可以把传入的参数转换为数组。
Array.of(false, 1, "2", [3], {index: 4}, function five() {});
// [false, 1, "2", Array(1), {…}, ƒ]
传入的参数个数不限,类型不限。
Array.of = function() {
return Array.prototype.slice.call(arguments);
};
在ES6之前要实现这样的功能,只能通过arguments来收集参数,在转换成数组返回。
isArray()
用于确定传递的值是否为Array类型。
在网页中只有一个全局作用域时,用instanceof就可以判断。
但是,如果网页里有多个框架,则可能涉及两个不同的全局执行上下文,因此就会有两个不同版本的Array构造函数。
Array.isArray()不用管它是在哪个全局执行上下文中创建的。
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
上述代码有相同的效果。
数值空位
const arr1 = new Array(3);
const arr2 = [ , , ,];
// [empty × 3]
arr2.length === 3
// true
如果用数组字面量创建数组,里面不填元素,只有逗号时,每一个逗号及其前面的空位算作一个长度" ,"。
Array.from(arr1);
// [undefined, undefined, undefined]
for(const val of arr2) { val === undefined }
// true true true
[,,,,].fill(1)
// [1,1,1,1]
ES6规范重新定义了如何处理这些空位,普遍将这些空位当成值为undefined的元素。其中包括Array.from()和迭代方法(for-of)。
Array.from会把空位转换成undefined,for-of把数组这个可迭代结构迭代出来,其值也是undefined。fill把每一个空位都填充了1。
其本质应该是,每次取值都用arr[i],如果是空位就会返回undefined,所以只要长度变化了,其他空位都当成undefined处理。
for(const val in [,,,,]) { console.log(val) }
// 什么也没有输出
new Array(3).map(num => num*2)
// 返回[empty × 3]
[1,,,,3].join("-")
// "1----3" 相当于 "1" + "-" + "" + "-" + "" + "-" + "" + "-" + "3"
[1,,,,3].toString()
// "1,,,,3"
ES6之前的方法会忽略数组内的空位。
就像for循环和map都会跳过空位,join()把空位当成空字符串来进行拼接。
由于行为不一致和存在性能隐患,尽量避免使用相同数组空位。
数组索引
const arr = [1,2,3,4];
arr[8] = 9;
// [1, 2, 3, 4, empty × 4, 9]
// arr.length == 8 + 1
如果把一个值设置给超过数组最大索引的索引,数组长度会自动扩展到索引值加1。
arr.length = 3;
// [1,2,3]
数组里只剩下3个元素,末尾其他元素就被删除了。
arr.length = 6;
// [1, 2, 3, empty × 3]
length设置为大于数组元素个数的值,新添的元素都是空位。空位会被ES6之前的方法忽略,例如forEach。
Array实例方法
迭代器方法
-
返回数组索引的迭代器。
const arr = ["a", "b", "c"], indexIt = arr.keys(); indexIt.toString() // "[object Array Iterator]" // Symbol.toStringTagkeys()方法可以看作迭代器创建函数,返回一个迭代器。
indexIt === indexIt[Symbol.iterator]() // true同样这个迭代器也实现了可迭代协议,它可以调用
Symbol.iterator返回自身,也就是说,这两个方法返回的迭代器对象是同一个。这样做的目的在于可以用ES6新增的迭代方法(Array.from()、for-of、展开操作符、数组解构等),因为这些方法,根据传入的可迭代结构的对象,自动调用这个对象的
Symbol.iterator方法,创建一个迭代器,然后进行迭代操作。Array.from(indexIt); // Array.from(arr.keys()); // [0, 1, 2] indexIt.next(); // Object Iterator {value: undefined, done: true}把数组索引迭代器转型成数组。
因为是同一个迭代器,而且迭代器又是一次性的(临时对象),所以Array.from()调用完,再获取下一个元素时就没了。
const emptyArr = new Array(10); [...empty.keys()] // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]数组内的空位索引也会返回。
const emptyArrIndexIt = emptyArr.keys(); emptyArr.length = 4; Array.from(emptyArrIndexIt); // [0, 1, 2, 3]迭代器储存的是数组的原地址,数组改变,迭代器也跟着变化。
-
返回数组值的迭代器
const arr = ["a", "b", "c", "d"], valuesIt = arr.values(), valuesArr = Array.from(valuesIt); // ["a", "b", "c", "d"] valuesIt === valuesIt[Symbol.iterator]() // true和keys()一样,实现思想一样。
Array.prototype.values === Array.prototype[Symbol.iterator] // trueArray.prototype.values是Array.prototype[Symbol.iterator]的默认实现,两个迭代器创建函数一样。 -
返回数组键值对的迭代器。
const arr = ["a", "b", "c"], entriesIt = arr.entries(), entriesArr = Array.from(entriesIt); // [[0, "a"], [1, "b"], [2, "c"]]利用迭代器实现二维数组排序。
function arrSort(arr) { // 得到数组键值对迭代器 let entries = arr.entries(); // 判断下一个是否存在 let hasNext = true; // 循环排序 while(hasNext) { let next = entries.next(); // 存在就排序 if(!next.done) { next.value[1].sort((a, b) => a - b); }else { // 不存在就让下一次进不了循环 hasNext = false; } } } var arr = [[1,34],[456,2,3,44,234],[4567,1,4,5,6],[34,78,23,1]]; sortArr(arr); /*(4) [Array(2), Array(5), Array(5), Array(4)] 0:(2) [1, 34] 1:(5) [2, 3, 44, 234, 456] 2:(5) [1, 4, 5, 6, 4567] 3:(4) [1, 23, 34, 78] length:4 __proto__:Array(0) */迭代器的保存的就是原数组的地址,所以排序也会改变原数组。
这三个方法都差不多。
复制和填充方法
ES6新增了两个方法:批量复制copyWithin(),填充数组方法fill()。
-
浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。
-
fill()
转换方法
-
返回由数组中每个元素的等效字符串(相当于每个元素都调用
String()转型成字符串)拼接而成的一个逗号分隔的字符串。多维数组也是,想把里面的数组转换成字符串,再转换外面的。
const arr1 = new Array(10), arr2 = [1,null,undefined]; arr1.toString(); // ",,,,,,,,," arr2.toString(); // "1,,"注意:数组内的元素如果是null或undefined或空位时,调用toString会用
""空字符串拼接。事实上下面的转换方法也都如此。 -
MDN上很透彻
-
将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。
和
Array.prototype.toString()一样,会用String()转型函数,把元素都变成字符串了再拼接。const arr = ["1", new Boolean(true), 1000, Object()]; arr.join(); // "1,true,1000,[object Object]"无参数时,默认使用
,号拼接。const arr1 = [1,2,3,4]; arr1.join("-"); // "1-2-3-4" arr1.join(" + "); // "1 + 2 + 3 + 4" arr1.join("|"); // "1|2|3|4" const empty = [,,,,]; empty.join(); // ",,," empty.join("-"); // "---"
栈方法
使用数组提供的方法,让它看起来像某种数据结构。会改变原数组。
栈是一种后进先出的结构。从尾端加入,从尾端移出。
-
const arr = [1,2,3]; arr.push(4); arr.push(5,6,7); // [1,2,3,4,5,6,7]将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
const obj = { 0: "a", 1: "b" } Array.prototype.push.call(obj, "c"); // {0: "c", 1: "b", length: 1}push方法具有通用性。该方法和call()或apply()一起使用时,可应用在类数组上。push方法根据length属性来决定从哪里开始插入给定的值。如果length不能被转成一个数值,则插入的元素索引为 0,包括length不存在时。当length不存在时,将会创建它。const str = "abcd"; Array.prototype.push.call(str, "e", "f", "g"); // TypeError: Cannot assign to read only property 'length' of object '[object String]'String类数组不适用,因为其长度是不可变的。const a = ["a", "b"]; const b = ["c", "d"]; a.push(...b); // 展开运算符 Array.prototype.push.apply(a, b); // apply接收放在数组里的参数把一个数组增加到另一个数组末尾。
像数组一样使用对象,详见MDN。
-
const arr = ["a", "b", "c", "d", "e"]; let deleteEle = arr.pop(); // "e"pop方法从一个数组中删除并返回最后一个元素。pop方法有意具有通用性。该方法和call()或apply()一起使用时,可应用在类数组上。pop方法根据length属性来确定最后一个元素的位置。如果不包含length属性或length属性不能被转成一个数值,会将length置为0,并返回undefined。如果你在一个空数组上调用 pop(),它返回
undefined。也就是说,如果数组删完了,再删除会返回undefined。
通过push()在栈顶增加元素,pop()移出栈顶的元素(弹栈)。模拟一个栈的结构。
队列方法
队列是一种先进先出的数据结构。从尾端加入,从首端移出。
-
从数组中删除第一个元素,并返回该元素的值。
const arr = ["a", "b", "c", "d", "e"]; let deleteEle = arr.pop(); // "a"和
pop()类似,一个删除首,一个删除尾。 -
push()
用来从尾端加入元素。
-
将一个或多个元素添加到数组的开头,并返回该数组的新长度。
const arr = [4,5,6]; arr.unshift(1,2,3); // [1, 2, 3, 4, 5, 6] arr = [4,5,6]; arr.unshift(1); arr.unshift(2); arr.unshift(3); // [3, 2, 1, 4, 5, 6]如果传入多个参数,它们会被以块的形式插入到对象的开始位置,它们的顺序和被作为参数传入时的顺序一致。 所以,传入多个参数调用一次
unshift,和传入一个参数调用多次unshift(例如,循环调用),它们将得到不同的结果。 -
pop()
从尾端删除元素。
shift()和push()正方向上的队列。unshift()和pop()是反方向上的队列。
排序方法
-
将数组中元素的位置颠倒,并返回该数组。改变原数组。
const arr1 = [1,2,3,4,5], arr2 = [["a", "b", "c"], [1, 2, 3]]; arr1.reverse(); // [5,4,3,2,1] arr2.reverse(); // [[1, 2, 3], ["a", "b", "c"]]数组内的元素颠倒了。
var obj = { 0: "a", 1: "b", 2: "c", length: 2 } Array.prototype.reverse.call(obj); // {0: "b", 1: "a", 2: "c", length: 2}同样可以应用于类数组,会根据类数组的
length和index属性改变。明明有三个元素,但长度只有2,所以第三个元素就没有位置颠倒。 -
对数组的元素进行排序。
const arr = [1, 1000, 4, 32]; arr.sort(); // [1, 1000, 32, 4] const eleArr = [1, "a", "A"]; eleArr.sort(); // [1, "A", "a"] // charCode // 1 --> 49 // "a" --> 97 // "A" --> 65默认排序顺序是将元素转换为字符串,按照转换为的字符串的诸个字符的Unicode位点进行排序。
调用
sort()后,原数组已经排序完成。arr.sort(function(a, b) { return a - b; }) // [1, 4, 32, 1000]sort()方法可以接受一个比较函数,用于判断哪个值排在前面。比较函数接受两个参数(a和b),分别表示数组内连续的两个元素。如果函数的返回值小于0,a和b不会互换位置。如果函数的返回值大于0,a和b就交换位置。
如果数组内的元素是数字,
a - b小于零,位置保持不变,表示数字小的排在前面,数字大的排在后面。升序排列。其他根据数据结构传入的比较函数详见MDN。
操作方法
-
合并两个或多个数组。不会改变现有数组,返回一个新数组。
const arr1 = [1,2,3,4], arr2 = [6,7,8]; const newArr = arr1.concat(5, arr2); // [1, 2, 3, 4, 5, 6, 7, 8]不管参数传入的是数字,还是数组,都会添加至新数组末尾。可能期望是把一个数组添加至新数组末尾,但它会打平数组,就像
...arr2一样,展开数组,把每一个元素添加至末尾。arr2[Symbol.isConcatSpreadable] = false; arr1.concat(5,arr2); // [1, 2, 3, 4, 5, [6, 7, 8]]可以通过知名符号:Symbol.isConcatSpreadable来控制是否打平数组或类数组。默认是不会打平类数组的。
const herb = { name: "herb", age: 18, sex: "男" } const num = [1, 2]; const arr = num.concat(herb); // [1, 2, {name: "herb", age: 18, sex: "男"}] arr[2].sex = "女"; // {name: "herb", age: 18, sex: "女"}返回一个浅克隆的数组,修改对象的属性,外面的对象属性也会跟着改变。
-
浅克隆一个数组,可以指定克隆的范围,返回一个新数组。
const colors = ["red", "green", "blue", "yellow", "purple"]; const colors1 = colors.slice(); // ["red", "green", "blue", "yellow", "purple"] const colors2 = colors.slice(1,3); // ["green", "blue"] const colors3 = colors.slice(-2); // ["yellow", "purple"]基本上的细节和
String.protototype.slice()差不多。 -
可以任意位置增加、删除数组中的元素,会改变原数组。
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])start开始位置,支持负数。deleteCount删除的个数。item1, item2,...从start位置开始,添加进数组的元素。返回一个数组,里面是从数组中删除的元素。
const colors = ["red", "green", "blue", "yellow", "purple"]; colors.splice(); // 无事发生,应该是判断arguments.length == 0 colors.splice(undefined); // 全部删除,因为Number(undefined) == NaN,如果参数为NaN就变成0,从零开始全部删除。 colors = ["red", "green", "blue", "yellow", "purple"]; // 重置 colors.splice(false, 1, "red"); // Number(false) == 0, 从零开始删除一个,再在零的位置上增加一个。参数先转成数字类型,再转成整型,如果是NaN就为0,如果该方法支持负数,那索引为负数时,加上数组的长度,如果还为负数就变为0,如果参数进行了这么多次转换还不符合逻辑,就什么都不做,
String.prototype.substring会交换参数位置,左小右大。
搜索和位置方法
-
严格相等搜索
-
在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
arr.indexOf(searchElement[, fromIndex])searchElement要搜索的元素,fromIndex从哪里开始,支持负数,默认从零开始。采用严格相等(===)
-
和
indexOf()类似,只不过是从数组的后面向前查找。无论第二个参数怎么输入,都是从后面向前查找。
-
ES7新增的方法,判断一个数组是否包含一个指定的值。
const arr = new Array(10); arr.includes(undefined); // true arr.indexOf(undefined); // -1把空位当成undefiend。
-
-
断言函数搜索
-
arr.find(callback[, thisArg])callback断言函数,按照定义的断言函数搜索数组,从最小的索引开始,每个索引都会调用这个函数。断言函数接受三个参数:元素、索引、数组本身。元素是数组中当前搜索的元素,索引是当前元素的索引,数组是正在搜索的数组。返回true表示匹配成功。thisArg可选参数,指定断言函数内部的this指向。const team = [ { name: "herb", age:22 }, { name: "ice", age: 18 } ] team.find((people) => people.age > 15); // {name: "herb", age: 22} team.find((people) => people.name = "herb"); // {name: "herb", age: 22}找打匹配项后,就不再继续搜索了。断言函数可以很灵活的进行判断,找到符合你要求的值,但找到就不往后找了。
当在回调中删除数组中的一个值时,当访问到这个位置时,其传入的值是 undefined。详见MDN。
-
通过断言函数找到第一个匹配的元素,返回其在数组的索引。
和
find()类似。详见MDN。
-
迭代方法
- every()
- some()
- forEach()
- map()
- filter()
归并方法
- reduce()
- reduceRight()
待补。。。