前端進(jìn)階JS數(shù)組高級用法大全教程示例
1.批量制造數(shù)據(jù)
一、創(chuàng)建新數(shù)組使用 for 循環(huán)批量 push 數(shù)據(jù)
function createData() {
const data = [];
for (let i = 0; i < 1000; i++) {
data.push({
name: `name${i + 1}`,
});
}
return data;
}
const data = createData();
console.log(data);
二、創(chuàng)建空數(shù)組,填充full,然后map
function createData() {
// 如果不 fill 循環(huán)默認(rèn)會跳過空值
return new Array(1000).fill(null).map((v, i) => ({ name: `name${i + 1}` }));
}
const data = createData();
console.log(data);
三、Array.from 第二個初始化函數(shù)返回數(shù)據(jù)
function createData() {
return Array.from({ length: 1000 }, (v, i) => ({ name: `name${i + 1}` }));
}
const data = createData();
console.log(data);
2.數(shù)組合并去重
一、Set去重
const arr1 = [1, 2, 3]; const arr2 = [3, 4, 5]; console.log(new Set([...arr1, ...arr2]));
二、for循環(huán),indexOf判斷是否存在
const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
function mergeArray(arr1, arr2) {
// 克隆
const cloneArr1 = arr1.slice(0);
let v;
for (let i = 0; i < arr2.length; i++) {
v = arr2[i];
// 按位非,反轉(zhuǎn)操作數(shù)的位,表象是對后面數(shù)字取負(fù)減一
// 當(dāng)數(shù)組中不存在此項 indexOf 返回 -1 按位非得 0 不走 if 邏輯
// 如果兩個數(shù)組都包含NaN,想要去重可使用includes
if (~cloneArr1.indexOf(v)) {
continue;
}
cloneArr1.push(v);
}
return cloneArr1;
}
console.log(mergeArray(arr1, arr2));
去重對象?
const arr1 = [{ id: 1 }, { id: 2 }, { id: 3 }];
const arr2 = [{ id: 3 }, { id: 4 }, { id: 5 }];
console.log(Array.from(new Set([...arr1, ...arr2])));
// [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 3 }, { id: 4 }, { id: 5 } ]
// 這樣對象都是獨立的引用,肯定無法去除屬性相同的數(shù)據(jù)啦
如果是相同引用呢?
const obj3 = { id: 3 };
const arr1 = [{ id: 1 }, { id: 2 }, obj3];
const arr2 = [obj3, { id: 4 }, { id: 5 }];
console.log(Array.from(new Set([...arr1, ...arr2]))); // 確實可以,但是你開發(fā)這樣做?
我們可以這樣做
const arr1 = [{ id: 1 }, { id: 2 }, { id: 3 }];
const arr2 = [{ id: 3 }, { id: 4 }, { id: 5 }];
function mergeArray(arr1, arr2) {
// 克隆
const cloneArr1 = arr1.slice(0);
let v;
for (let i = 0; i < arr2.length; i++) {
v = arr2[i];
// 能找到相同 id 屬性值的數(shù)據(jù)則進(jìn)入判斷
if (~cloneArr1.findIndex((el) => el.id === v.id)) {
continue;
}
cloneArr1.push(v);
}
return cloneArr1;
}
console.log(mergeArray(arr1, arr2)); // [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
3.創(chuàng)建數(shù)組的幾種方式
- 字面量
// 字面量 const arr1 = [1, 2, 3, ...[4, 5, 6]]; // 1,2,3,4,5,6 const arr2 = [, , , , ,]; // [empty × 5]
- new Array(當(dāng)參數(shù)只有一個且是數(shù)字時,new Array()表示數(shù)組的長度,其余參數(shù)則是數(shù)組的內(nèi)容)
const arr3 = new Array(5); // [empty × 5]
const arr4 = new Array(1, 2, 3); // 1,2,3
const arr5 = new Array("a"); // ["a"]
- Array.of(參數(shù)只用來作為數(shù)組中的內(nèi)容)
const arr6 = Array.of(5); // [5] const arr7 = Array.of(1, 'abc', true); // [1, "abc", true]
- Array.from 可傳入類數(shù)組和可遍歷對象轉(zhuǎn)換為真數(shù)組
- (第一個參數(shù)傳入對應(yīng)類數(shù)組和可遍歷對象,第二個函數(shù)參數(shù)則相當(dāng)于對生成的數(shù)組做一次map)
- 可遍歷和類數(shù)組 ==> 數(shù)組、字符串、Set、Map、NodeList、HTMLCollection、arguments以及擁有 length 屬性的任意對象
const arr8 = Array.from([1, 2, 3]); // [1,2,3]
const arr9 = Array.from({ length: 3 }, (value, index) => {
return index + 1;
}); // [1,2,3]
const arr10 = Array.from({ 0: "a", 1: "b", 2: "c", length: 3 }); // ["a", "b", "c"]
- 其他的很多可以返回數(shù)組的方法都算
// Array.prototype.slice
const arr11 = Array.prototype.slice.call(document.querySelectorAll("div")); // [div, div, div....]
// Array.prototype.concat
const arr12 = Array.prototype.concat.call([], [1, 2, 3]); // [1, 2, 3]
4.類數(shù)組
- 是一個普通對象,不具備數(shù)組自帶豐富的內(nèi)建方法
- key是以數(shù)字或者字符串?dāng)?shù)字組成
- 必須有l(wèi)ength屬性
const arrayLike = {
0: "a",
1: "b",
2: "c",
name: "test",
length: 3,
push: Array.prototype.push, //自己實現(xiàn)
splice: Array.prototype.splice,
};
//由于類數(shù)組對象length屬性聲明了對象有多少個屬性,所以可以使用for遍歷對象屬性:
for (let i = 0; i < arrayLike.length; i++) {
console.log(i + ":" + arrayLike[i]);
}

常見的類數(shù)組
- arguments
function person(name, age, sex) {
console.log("person arguments:", arguments);
console.log("person type:", Object.prototype.toString.call(arguments));
}
person("name", "age", "sex");
打印結(jié)果如下:

- NodeList、HTMLCollection、DOMTokenList等
const nodeList = document.querySelectorAll("box");
console.log("querySelectorAll type:", Object.prototype.toString.call(nodeList));
const htmlCollection = document.getElementsByTagName("div");
console.log("getElementsByTagName type:", Object.prototype.toString.call(htmlCollection));
const DOMTokenList = document.querySelector("div").classList;
console.log("classList:", DOMTokenList);

- 奇特:字符串(具備類數(shù)組的特性,但一般類數(shù)組指對象)
const str = "abc"; console.log(Object.keys(str)); // ['0', '1', '2'] console.log(Array.from(str)); // ['a', 'b', 'c']
判斷是否是類數(shù)組
function isArrayLikeObject(arr) {
// 不是對象直接返回
if (arr == null || typeof arr !== "object") return false;
const lengthMaxValue = Math.pow(2, 53) - 1;
// 是否有 length 屬性
if (!Object.prototype.hasOwnProperty.call(arr, "length")) return false;
// length 屬性是否是number類型
if (typeof arr.length != "number") return false;
//使用 isFinite() 判斷是否在正常數(shù)字范圍
if (!isFinite(arr.length)) return false;
// 構(gòu)造函數(shù)等于Array
if (Array === arr.constructor) return false;
// 長度有效值
if (arr.length >= 0 && arr.length < lengthMaxValue) {
return true;
} else {
return false;
}
}
console.log(isArrayLikeObject(null)); // false
console.log(isArrayLikeObject({ 0: "a", 1: "b", length: 2 })); // true
console.log(isArrayLikeObject({ 0: 1, 2: 3, length: "" })); // false
console.log(isArrayLikeObject({ 0: 1, 2: 3 })); // false
console.log(isArrayLikeObject([1, 2])); // false
類數(shù)組如何轉(zhuǎn)換為數(shù)組
- 復(fù)制遍歷
const arr = [];
const arrayLike = {
0: 1,
1: 2,
length: 2,
};
for (let i = 0; i < arrayLike.length; i++) {
arr[i] = arrayLike[i];
}
console.log(arr); // [1, 2]
- slice, concat等
const arrayLike = {
0: 1,
1: 2,
length: 2,
};
const array1 = Array.prototype.slice.call(arrayLike);
console.log(array1); // [ 1, 2 ]
const array2 = Array.prototype.concat.apply([], arrayLike);
console.log(array2); // [ 1, 2 ]
- Array.from
const arrayLike = {
0: 1,
1: 2,
length: 2,
};
console.log(Array.from(arrayLike)); // [ 1, 2 ]
- Array.apply
const arrayLike = {
0: 1,
1: 2,
length: 2,
};
console.log(Array.apply(null, arrayLike)); // [ 1, 2 ]
- 擴(kuò)展運(yùn)算符
console.log([...document.body.childNodes]); // [div, script, script...]
// arguments
function argumentsTest() {
console.log([...arguments]); // [ 1, 2, 3 ]
}
argumentsTest(1, 2, 3);
如何讓類數(shù)組使用上數(shù)組豐富的內(nèi)建方法
- 在類數(shù)組對象上直接定義數(shù)組原型的方法
- 運(yùn)用call或者apply顯示綁定this的指向
例如我想通過 filter 方法過濾出類數(shù)組中元素包含 "i" 這個字符的所有元素。
const arrayLike = {
0: "i love",
1: "you",
length: 1,
};
console.log([].filter.call(arrayLike, (item) => item.includes("i"))); // [ 'i love' ]
為什么會這樣?其實可以想想 filter 是如何實現(xiàn)的。
[].__proto__.myfilter = function (callback) {
let newArr = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i])) {
newArr.push(this[i]);
}
}
return newArr;
};
可以看出因為 filter 實現(xiàn)是通過 this 進(jìn)行綁定的,哪個數(shù)組調(diào)用了這個filter,filter中的 this 就指向哪個數(shù)組
類數(shù)組和數(shù)組的區(qū)別
| 方法/特征 | 數(shù)組 | 類數(shù)組 |
|---|---|---|
| 自帶方法 | 多個方法 | 無 |
| length屬性 | 有 | 有 |
| toString返回 | [object Array] | [object Object] |
| instanceof | Array | Object |
| constructor | [Function: Array] | [Function: Object] |
| Array.isArray | true | false |
5.數(shù)組方法的使用注意事項
數(shù)組的長度
const arr1 = [1];
const arr2 = [1, ,];
const arr3 = new Array("10");
const arr4 = new Array(10);
console.log("arr1 length: " + arr1.length); // arr1 length: 1
console.log("arr2 length: " + arr2.length); // arr2 length: 2
console.log("arr3 length: " + arr3.length); // arr3 length: 1
console.log("arr4 length: " + arr4.length); // arr4 length: 10
數(shù)組的空元素 empty
empty:數(shù)組的空位,指數(shù)組的某一位置沒有任何值,有空位的數(shù)組也叫稀疏數(shù)組
稀疏數(shù)組性能會較差,可以避免創(chuàng)建
Array.apply(null,Array(3))
[...new Array(3)]
Array.from(Array(3))
一般遍歷如forEach、map、reduce 會自動跳過空位
const arr = [1, ,];
arr.forEach((item) => console.log(item)); // 1
console.log("arr", arr);// arr [ 1, <1 empty item> ]
基于值進(jìn)行運(yùn)算,空位的值作為undefined
- find,findIndex,includes等, indexOf除外
- 當(dāng)被作為迭代的時候,參與Object.entries、擴(kuò)展運(yùn)算符、for of 等
join和toString,空位怎么處理
- 視為空字符串
- toString 內(nèi)部其實會調(diào)用 join 方法
數(shù)組不會自動添加分號
- (,[, + , -,/,其作為一行代碼的開頭,很可能產(chǎn)生意外的情況,所以,沒事代碼最后寫個分號,保準(zhǔn)沒錯
const objA = { a: 1 }
["a"];
console.log(objA); // 1
const objB = ["a"]
["a"];
console.log(objB); // undefined
const a = [[1, 2], 2, 3];
console.log(a)
[0, 2, 3].map((v) => console.log(v * v)); // 報錯
console.log(a);
indexOf與includes
| 方法 | 返回值 | 是否能查找NaN | [, ,]空位 | undefined |
|---|---|---|---|---|
| indexOf | number | × | × | √ |
| includes | boolean | √ | √ | √ |
const array1 = [NaN];
console.log("array.includes NaN:", array1.includes(NaN)); // true
console.log("array.indexOf NaN:", array1.indexOf(NaN) > -1); // false
const array2 = [1, ,];
console.log("array.includes ,,:", array2.includes(undefined)); // true
console.log("array.indexOf ,,:", array2.indexOf(undefined) > -1); // false
const array3 = [undefined];
console.log("array.includes undefined:", array3.includes(undefined)); // true
console.log("array.indexOf undefined:", array3.indexOf(undefined) > -1); // true
console.log(Object.prototype.hasOwnProperty.call(array2, 1)); // 區(qū)分空位和undefined,判斷此位上是否有值
數(shù)組可變長度問題
- length 代表數(shù)組中元素個數(shù),數(shù)組額外附加屬性不計算在內(nèi)
- length 可寫,可以通過修改length改變數(shù)組的長度
- 數(shù)組操作不存在越界,找不到下標(biāo),返回undefined
const array = [1, 2, 3, 4, 5, 6];
array[10] = 10; // 盡量不要這樣破壞數(shù)組默認(rèn)線性存儲的結(jié)構(gòu)
console.log("array.length:", array.length); // 11
array["test"] = "test";
console.log("array.length:", array.length); // 11
array.length = 3;
console.log("array.length:", array.length); // 3
console.log("array value:", array[Number.MAX_VALUE + 1000]); // undefined
數(shù)組查找和過濾
| 方法 | 返回結(jié)果類型 | 是否能短路操作 | 是否需要全部滿足條件 | 遍歷空元素 |
|---|---|---|---|---|
| some | boolean | √ | × | × |
| find | undefined | object | √ | × | √ |
| findelndex | number | √ | × | √ |
| every | boolean | √ | √ | × |
| filter | array | × | × | × |
改變自身的方法
push、pop、unshift、shift
sort、splice、reverse
ES6: copyWithin、fill
let array = [1, 2, 3, 4, 5, 6, 7];
array.push("push");
console.log("array push:", array);
array.pop();
console.log("array pop:", array);
array.unshift("unshift");
console.log("array unshift:", array);
array.shift();
console.log("array shift:", array);
array.reverse();
console.log("array reverse:", array);
array.sort();
console.log("array sort:", array);
array.splice(2, 1);
console.log("array splice:", array);
array.copyWithin(2, 0);
console.log("array copyWithin:", array);
array.fill("fill", 3);
console.log("array fill:", array);
delete誤區(qū)
- delete刪除數(shù)組元素,后面元素不會補(bǔ)齊,delete刪除引用
const array = [1, 2, 3, 4, 5];
delete array[2];
console.log("delete array:", array); // delete array: [ 1, 2, <1 empty item>, 4, 5 ]
push vs concat
- 大量數(shù)據(jù)操作的時候 push 性能會比 concat 性能高很多
const count = 10000;
const array1 = [1, 2, 4, 5, 6];
let newArray = [];
console.time("push");
for (let i = 0; i < count; i++) {
newArray.push(array1[0], array1[1], array1[2], array1[3], array1[4]);
}
console.timeEnd("push");
console.time("concat");
for (let i = 0; i < count; i++) {
newArray = newArray.concat(array1[0], array1[1], array1[2], array1[3], array1[4]);
}
console.timeEnd("concat");
6.數(shù)組的高級用法
1.萬能數(shù)據(jù)生成器
const createValues = (creator, length = 10) => Array.from({ length }, creator);
// 第一個參數(shù)控制隨機(jī)數(shù)生成,第二個控制其數(shù)組長度
const createRandomValues = (len) => createValues(Math.random, len);
const values = createRandomValues();
console.log("values:", values.length, values);
2.序列生成器
const createValues = (creator, length = 10) => Array.from({ length }, creator);
const createRange = (start, stop, step) =>
createValues((_, i) => start + i * step, (stop - start) / step + 1);
// 生成數(shù)組,里面元素是 1 ~ 100 以內(nèi)每次從 1 開始每次遞增 3 的數(shù)字
const values = createRange(1, 100, 3);
console.log(values);
3.數(shù)據(jù)生成器
const createValues = (creator, length = 10) => Array.from({ length }, creator);
function createUser(v, index) {
return {
name: `user-${index}`,
age: (Math.random() * 100) >> 0, // 取整
};
}
const users = createValues(createUser, 100);
console.log("users:", users);
4.清空數(shù)組
const arr = [1, 2, 3];
arr.splice(0);
console.log("splice:", arr); // []
const arr1 = [1, 2, 3];
arr1.length = 0;
console.log("length:", arr1); // []
5.數(shù)組去重
const arr = [
"apple",
"banana",
1,
1,
3,
3,
undefined,
undefined,
,
,
NaN,
NaN,
null,
null,
"true",
true,
{ a: 1 },
];
const arr1 = Array.from(new Set(arr)); // 正常去重
console.log("set:", arr1);
對于數(shù)組里面對象去重
function uniqueArray(arr) {
return Array.from(new Set(arr));
}
const arr = [{ a: 1 }, { a: 1 }];
console.log("set 不同引用:", uniqueArray(arr));
const obj1 = { a: 1 };
const arr2 = [obj1, obj1];
console.log("set 同一引用:", uniqueArray(arr2));
如果我們想認(rèn)為兩個對象里面的 a 屬性的值相同就認(rèn)為是同一數(shù)組的話,可以使用 filter
function uniqueArray(arr = [], key) {
const keyValues = new Set();
let val;
return arr.filter((obj) => {
val = obj[key];
if (keyValues.has(val)) {
return false;
}
keyValues.add(val);
return true;
});
}
const arr = [{ a: 1 }, { a: 1 }, { a: 2 }];
console.log("filter 去重:", uniqueArray(arr, "a")); // filter 去重: [ { a: 1 }, { a: 2 } ]
6.數(shù)組交集
- Array.prototype.filter + includes判斷
- 但是會存在性能和引用類型相同的判斷的問題
const arr1 = [0, 1, 2];
const arr2 = [3, 2, 0];
function intersectSet(arr1, arr2) {
return [...new Set(arr1)].filter((item) => arr2.includes(item));
}
const values = intersectSet(arr1, arr2);
console.log(values); // [ 0, 2 ]
我們可以這樣做:
// 引用類型
function intersect(arr1, arr2, key) {
const map = new Map();
arr1.forEach((val) => map.set(val[key]));
return arr2.filter((val) => map.has(val[key]));
}
// 原始數(shù)據(jù)類型
function intersectBase(arr1, arr2) {
const map = new Map();
arr1.forEach((val) => map.set(val));
return arr2.filter((val) => map.has(val));
}
const arr1 = [{ p: 0 }, { p: 1 }, { p: 2 }];
const arr2 = [{ p: 3 }, { p: 2 }, { p: 1 }];
const result = intersect(arr1, arr2, "p");
console.log("result:", result); // result: [ { p: 2 }, { p: 1 } ]
const arr3 = [0, 1, 2];
const arr4 = [3, 2, 0];
const result1 = intersectBase(arr3, arr4);
console.log("result1:", result1); // result1: [ 2, 0 ]
性能比對:
function createData(length) {
return Array.from({ length }, (val, i) => {
return ~~(Math.random() * length);
});
}
function intersectSet(arr1, arr2) {
return [...new Set(arr1)].filter((item) => arr2.includes(item));
}
// 原始數(shù)據(jù)類型
function intersectMap(arr1, arr2) {
const map = new Map();
arr1.forEach((val) => map.set(val));
return arr2.filter((val) => {
return map.has(val);
});
}
console.time("createData");
const data1 = createData(100000);
const data2 = createData(100000);
console.timeEnd("createData");
console.time("intersectMap");
intersectMap(data1, data2);
console.timeEnd("intersectMap");
console.time("intersectSet");
intersectSet(data1, data2);
console.timeEnd("intersectSet");

7.數(shù)組差集
// 引用類型
function difference(arr1, arr2, key) {
const map = new Map();
arr1.forEach((val) => map.set(val[key]));
return arr2.filter((val) => !map.has(val[key]));
}
// 原始數(shù)據(jù)類型
function differenceBase(arr1, arr2) {
const map = new Map();
arr1.forEach((val) => map.set(val));
return arr2.filter((val) => !map.has(val));
}
const arr1 = [{ p: 0 }, { p: 1 }, { p: 2 }];
const arr2 = [{ p: 3 }, { p: 2 }, { p: 1 }];
const result = difference(arr1, arr2, "p");
console.log("result:", result); // result: [ { p: 3 } ]
const arr3 = [0, 1, 2];
const arr4 = [3, 2, 0];
const result1 = differenceBase(arr3, arr4);
console.log("result1:", result1); // result1: [ 3 ]
8.數(shù)組刪除虛(假)值
const array = [false, 0, undefined, , "", NaN, 9, true, undefined, null, "test"]; const newArray = array.filter(Boolean); console.log(newArray); // [ 9, true, 'test' ]
9.獲取數(shù)組中最大值和最小值
const numArray = [1, 3, 8, 666, 22, 9982, 11, 0];
const max = Math.max.apply(Math, numArray);
const min = Math.min.apply(Math, numArray);
console.log("max:", max + ",min:" + min); // max: 9982,min:0
console.log(Math.max(...numArray)); // 9982
console.log(Math.min(...numArray)); // 0
來看一個實際的例子,我們?nèi)カ@取用戶對象中最大和最小的年齡:
const createValues = (creator, length = 10) => Array.from({ length }, creator);
function createUser(v, index) {
return {
name: `user-${index}`,
age: (Math.random() * 100) >> 0,
};
}
const users = createValues(createUser, 10);
const ages = users.map((u) => u.age);
const max = Math.max.apply(Math, ages);
const min = Math.min.apply(Math, ages);
console.log(ages);
console.log("max:", max + ",min:" + min);
10.reduce高級用法
querystring
- 作用∶頁面?zhèn)鬟f參數(shù)
- 規(guī)律∶地址url問號(?)拼接的鍵值對
URLSearchParams:
const urlSP = new URLSearchParams(location.search);
function getQueryString(key) {
return urlSP.get(key);
}
// 獲取頁面上查詢參數(shù) words 和 wordss 的值
console.log("words:", getQueryString("words"));
console.log("wordss:", getQueryString("wordss"));
URL:
const urlObj = new URL(location.href);
function getQueryString(key) {
return urlObj.searchParams.get(key);
}
// urlObj.searchParams instanceof URLSearchParams 為 true,證明是其實例
console.log("words:", getQueryString("words"));
console.log("wordss:", getQueryString("wordss"));
使用 reduce 手寫查詢:
const urlObj = location.search
.slice(1)
.split("&")
.filter(Boolean)
.reduce((obj, cur) => {
const arr = cur.split("=");
if (arr.length != 2) {
return obj;
}
obj[decodeURIComponent(arr[0])] = decodeURIComponent(arr[1]);
return obj;
}, {});
function getQueryString(key) {
return urlObj[key];
}
console.log("words:", getQueryString("words"));
console.log("wordss:", getQueryString("wordss"));
折上折
- 優(yōu)惠1:9折
- 優(yōu)惠2:200減50
草民版:
function discount(x) {
return x * 0.9;
}
function reduce(x) {
return x > 200 ? x - 50 : x;
}
const print = console.log;
// 享受九折
print(reduce(discount(100))); // 90
// 享受九折 + 滿減
print(reduce(discount(250))); // 175
黃金版:
function discount(x) {
return x * 0.9;
}
function reduce(x) {
return x > 200 ? x - 50 : x;
}
function getPriceMethod(discount, reduce) {
return function _getPrice(x) {
return reduce(discount(x));
};
}
const method = getPriceMethod(discount, reduce);
const print = console.log;
print(method(100));
print(method(250));
王者版:
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
function discount(x) {
console.log("discount");
return x * 0.9;
}
function reduce(x) {
console.log("reduce");
return x > 200 ? x - 50 : x;
}
function discountPlus(x) {
console.log("discountPlus");
return x * 0.95;
}
// 從后往前執(zhí)行傳入的函數(shù)
const getPrice = compose(discountPlus, reduce, discount);
const print = console.log;
print(getPrice(200));
print(getPrice(250));
打印結(jié)果如下圖:

Promise順序執(zhí)行
function runPromises(promiseCreators, initData) {
return promiseCreators.reduce(function (promise, next) {
return promise.then((data) => next(data));
}, Promise.resolve(initData));
}
function login(data) {
console.log("login: data", data);
return new Promise((resolve) => {
setTimeout(() => {
return resolve({
token: "token",
});
}, 500);
});
}
function getUserInfo(data) {
console.log("getUserInfo: data", data);
return new Promise((resolve) => {
setTimeout(() => {
return resolve({
name: "user-1",
id: 988,
});
}, 300);
});
}
function getOrders(data) {
console.log("getOrders: data", data);
return new Promise((resolve) => {
setTimeout(() => {
return resolve([
{
orderId: 1,
productId: 100,
price: 100,
},
]);
}, 100);
});
}
const initData = { name: "name", pwd: "pwd" };
Promise.resolve(initData)
.then((data) => login(data))
.then((data) => getUserInfo(data))
.then((data) => getOrders(data))
.then((data) => console.log("orders", data));
// 使用 reduce 封裝的 runPromises 方法,確保返回 Promise 且執(zhí)行結(jié)果是下一個函數(shù)的入?yún)?
runPromises([login, getUserInfo, getOrders], initData).then((res) => {
console.log("res", res);
});
數(shù)組分組
const hasOwn = Object.prototype.hasOwnProperty;
function group(arr, fn) {
// 不是數(shù)組
if (!Array.isArray(arr)) {
return arr;
}
// 不是函數(shù)
if (typeof fn !== "function") {
throw new TypeError("fn必須是一個函數(shù)");
}
let v;
return arr.reduce((obj, cur, index) => {
v = fn(cur, index);
if (!hasOwn.call(obj, v)) {
obj[v] = [];
}
obj[v].push(cur);
return obj;
}, {});
}
// 按照長度分組
let result = group(["apple", "pear", "orange", "peach"], (v) => v.length);
console.log(result);
// 按照份數(shù)分組
result = group(
[
{
name: "tom",
score: 60,
},
{
name: "Jim",
score: 40,
},
{
name: "Nick",
score: 88,
},
],
(v) => v.score >= 60
);
console.log(result);
打印結(jié)果如下:

7.手寫數(shù)組方法
Array.isArray
- 判斷是否是數(shù)組
const arr = ["1"];
console.log("isArray:", Array.isArray(arr));
非基本使用:
const arr = ["1"];
const proxy = new Proxy(arr, {});
console.log("isArray:", Array.isArray(proxy)); // true
為什么上面 Array.isArray 判斷代理對象是否數(shù)組返回 true 呢?
const arr = ["1"];
const proxy = new Proxy(arr, {});
const log = console.log;
log("__proto__:", proxy.__proto__ === Array.prototype); // __proto__: true
log("instanceof:", proxy instanceof Array); // instanceof: true
log("toString", Object.prototype.toString.call(Proxy)); // toString [object Function]
log("Proxy.prototype:", Proxy.prototype); // Proxy.prototype: undefined
log("proxy instanceof Proxy:", proxy instanceof Proxy); // 報錯

實際 Array.isArray 判斷的是 Proxy里面的 target 屬性

接下來我們真正手寫下 Array.isArray 的方法
- Object.prototype.toString
Array.isArray = function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
const arr = ["1"];
const proxy = new Proxy(arr, {});
console.log(Array.isArray(arr));
console.log(Array.isArray(proxy));
- instanceof
Array.isArray = function (obj) {
if (typeof obj !== "object" || obj === null) {
return false;
}
return obj instanceof Array;
};
const arr = ["1"];
const proxy = new Proxy(arr, {});
console.log(Array.isArray(arr));
console.log(Array.isArray(proxy));
其實還有很多方法可以判斷其數(shù)據(jù)類型,比如 constructor、isPrototypeOf等,不過我還是更推薦上面兩種
Array.prototype.entries
- 作用:返回一個新的 Array Iterator 對象,該對象包含數(shù)組中每個索引的鍵/值對
const arr = ["a", "b", "c"];
const iter = arr.entries();
console.log("iter:", iter);
// next函數(shù)訪問
console.log("iter.next():", iter.next());
console.log("iter.next():", iter.next());
console.log("iter.next():", iter.next());
console.log("iter.next():", iter.next());
// for of迭代
for (let [k, v] of arr.entries()) {
console.log(k, v);
}
打印結(jié)果如下:

done 表示遍歷是否結(jié)束,value 返回當(dāng)前遍歷的值
自己來實現(xiàn)下這個方法:
Array.prototype.entries = function () {
// 轉(zhuǎn)換對象(引用數(shù)據(jù)類型返回自身)
const O = Object(this);
let index = 0;
const length = O.length;
return {
next() {
if (index < length) {
return { value: [index, O[index++]], done: false };
}
return { value: undefined, done: true };
},
};
};
const arr = ["a", "b", "c"];
const iter = arr.entries();
console.log("iter.next():", iter.next());
console.log("iter.next():", iter.next());
console.log("iter.next():", iter.next());
// 不能正常執(zhí)行,因為如果要能 for...of 遍歷需要去實現(xiàn) Symbol.iterator
for (let [k, v] of arr.entries()) {
console.log(`k:${k}`, `v:${v}`);
}
下面添加 Symbol.iterator 方法返回 next 即可for...of
Array.prototype.entries = function () {
const O = Object(this);
let index = 0;
const length = O.length;
function next() {
if (index < length) {
return { value: [index, O[index++]], done: false };
}
return { value: undefined, done: true };
}
return {
next,
[Symbol.iterator]() {
return {
next,
};
},
};
};
數(shù)組還有 Array.prototype.keys,Array.prototype.keys,如果我們像上面這樣寫等于每個方法里面都要實現(xiàn)[Symbol.iterator],我們可以抽離其邏輯,代碼如下:
Array.prototype[Symbol.iterator] = function () {
const O = Object(this);
let index = 0;
const length = O.length;
function next() {
if (index < length) {
return { value: O[index++], done: false };
}
return { value: undefined, done: true };
}
return {
next,
};
};
Array.prototype.entries = function () {
const O = Object(this);
const length = O.length;
let entries = [];
for (let i = 0; i < length; i++) {
entries.push([i, O[i]]);
}
const itr = this[Symbol.iterator].bind(entries)();
return {
next: itr.next,
[Symbol.iterator]() {
return itr;
},
};
};
Array.prototype.keys = function () {
const O = Object(this);
const length = O.length;
let keys = [];
for (let i = 0; i < length; i++) {
keys.push([i]);
}
const itr = this[Symbol.iterator].bind(keys)();
return {
next: itr.next,
[Symbol.iterator]() {
return itr;
},
};
};
Array.prototype.values = function () {
const O = Object(this);
const length = O.length;
let keys = [];
for (let i = 0; i < length; i++) {
keys.push([O[i]]);
}
const itr = this[Symbol.iterator].bind(keys)();
return {
next: itr.next,
[Symbol.iterator]() {
return itr;
},
};
};
const arr = ["a", "b", "c"];
var iter = arr.entries();
console.log("iter.next().value:", iter.next().value);
console.log("iter.next().value:", iter.next().value);
console.log("iter.next().value:", iter.next().value);
for (let [k, v] of arr.entries()) {
console.log(`k:${k}`, `v:${v}`);
}
var iter = arr.keys();
console.log("iter.next().value:", iter.next().value);
console.log("iter.next().value:", iter.next().value);
console.log("iter.next().value:", iter.next().value);
for (let k of arr.keys()) {
console.log(`k:${k}`);
}
var iter = arr.values();
console.log("iter.next().value:", iter.next().value);
console.log("iter.next().value:", iter.next().value);
console.log("iter.next().value:", iter.next().value);
for (let k of arr.values()) {
console.log(`k:${k}`);
}
Array.prototype.includes
- 判斷數(shù)組是否含有某值,可判斷NaN
const arr = [1, 2, 3, { a: 1 }, null, undefined, NaN, ""];
console.log("includes null:", arr.includes(null)); // includes null: true
console.log("indexOf null:", arr.indexOf(null)); // indexOf null: 4
console.log("includes NaN:", arr.includes(NaN)); // includes NaN: true
console.log("indexOf NaN:", arr.indexOf(NaN)); // indexOf NaN: -1
手寫該方法
Number.isNaN = function (param) {
if (typeof param === "number") {
return isNaN(param);
}
return false;
};
Array.prototype.includes = function (item, fromIndex) {
// call, apply調(diào)用,嚴(yán)格模式
if (this == null) {
throw new TypeError("無效的this");
}
let O = Object(this);
let len = O.length >> 0;
if (len <= 0) {
return false;
}
const isNAN = Number.isNaN(item);
for (let i = 0; i < len; i++) {
if (O[i] === item) {
return true;
} else if (isNAN && Number.isNaN(O[i])) {
return true;
}
}
return false;
};
const obj = { a: 3 };
const arr = [1, 2, 3, { a: 1 }, null, undefined, NaN, "", 0, obj, obj];
console.log("includes null:", arr.includes(null));
console.log("includes NaN:", arr.includes(NaN));
其實 includes 還有第二個參數(shù),表示從哪個下標(biāo)開始檢查,我們也來寫寫該方法
注意參數(shù)的情況
- 轉(zhuǎn)為整數(shù):TolntegerOrlnfinity
- +lnfinity , -Infinity
- 可能為負(fù)數(shù)
Number.isNaN = function (params) {
if (typeof params === "number") {
return isNaN(params);
}
return false;
};
// 轉(zhuǎn)換整數(shù)
function ToIntegerOrInfinity(argument) {
const num = Number(argument);
// NaN 和 +0、-0
if (Number.isNaN(num) || num == 0) {
return 0;
}
if (num === Infinity || num == -Infinity) {
return num;
}
let inter = Math.floor(Math.abs(num));
if (num < 0) {
inter = -inter;
}
return inter;
}
Array.prototype.includes = function (item, fromIndex) {
// 嚴(yán)格模式
if (this == null) {
throw new TypeError("無效的this");
}
const O = Object(this);
const len = O.length >> 0;
if (len <= 0) {
return false;
}
let n = ToIntegerOrInfinity(fromIndex);
if (fromIndex === undefined) {
n = 0;
}
if (n === +Infinity) {
return false;
}
// 負(fù)無窮轉(zhuǎn)換為0
if (n === -Infinity) {
n = 0;
}
let k = n >= 0 ? n : len + n;
if (k < 0) {
k = 0;
}
const isNAN = Number.isNaN(item);
for (let i = k; i < len; i++) {
if (O[i] === item) {
return true;
} else if (isNAN && Number.isNaN(O[i])) {
return true;
}
}
return false;
};
const arr = ["a", "b", "c"];
console.log("arr include -100->0:", arr.includes("c", -100)); // true
console.log("arr include -100->0:", arr.includes("a", -1)); // false
console.log("arr include 1:", arr.includes("a", -Infinity)); // true
console.log("arr include 1:", arr.includes("a", Infinity)); // false
Array.from
有三個參數(shù)
- arrayLike:類數(shù)組對象或者可遍歷對象(Map、Set)等
- mapFn:可選參數(shù),在最后生成數(shù)組后執(zhí)行一次map方法后返回
- thisArg:可選參數(shù),實際是Array.from(obj).map(mapFn, thisArg)
特殊值處理
console.log("Array.from1:", Array.from({}));
console.log("Array.from2:", Array.from(""));
console.log("Array.from3:", Array.from({ a: 1, length: "10" }));
console.log("Array.from4:", Array.from({ a: 1, length: "ss" }));
console.log("Array.from5:", Array.from([NaN, null, undefined, 0]));
// 長度極限問題
// const max = Math.pow(2, 32);
// console.log("Array.from:", Array.from({ 0: 1, 1: 2, length: max - 1 })); // 極限
// console.log("Array.from:", Array.from({ 0: 1, 1: 2, length: max })); // 失敗
執(zhí)行結(jié)果如下:

自己實現(xiàn)一個:
//類數(shù)組的特征
let maxSafeInteger = Math.pow(2, 32) - 1;
let ToIntegerOrInfinity = function (value) {
let number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
let ToLength = function (value) {
let len = ToIntegerOrInfinity(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
let isCallable = function (fn) {
return typeof fn === "function" || toStr.call(fn) === "[object Function]";
};
Array.from = function (arrayLike, mapFn, thisArg) {
let C = this;
//判斷對象是否為空
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
//檢查mapFn是否是方法
if (typeof mapFn !== "function" && typeof mapFn !== "undefined") {
throw new TypeError(mapFn + "is not a function");
}
let items = Object(arrayLike);
//判斷 length 為數(shù)字,并且在有效范圍內(nèi)。
let len = ToLength(items.length);
if (len <= 0) return [];
let A = isCallable(C) ? Object(new C(len)) : new Array(len);
for (let i = 0; i < len; i++) {
let value = items[i];
if (mapFn) {
A[i] = typeof thisArg === "undefined" ? mapFn(value, i) : mapFn.call(thisArg, value, i);
} else {
A[i] = value;
}
}
return A;
};
console.log("Array.from1:", Array.from({ a: 1, length: "10" }));
console.log("Array.from2:", Array.from({ a: 1, length: "ss" }));
console.log(
"Array.from3:",
Array.from({ 0: 1, 1: 2, 4: 5, length: 4 }, (x) => x + x)
);
function MyArray(length) {
const len = length * 2;
return new Array(len);
}
function MyObject(length) {
return {
length,
};
}
console.log("Array.from:MyArray", Array.from.call(MyArray, { length: 5 }));
console.log("Array.from:MyObject", Array.from.call(MyObject, { length: 5 }));
打印結(jié)果如下:

Array.prototype.flat
- 指定的深度遞歸遍歷數(shù)組,并將所有元素與遍歷到的子數(shù)組中的元素合并為一個新數(shù)組返回
const array = [1, 3, 4, [4, 5], [6, [7, 8]], [, ,], [undefined, null, NaN]];
console.log("flat 1:", array.flat(1));
console.log("flat 2:", array.flat(2));
執(zhí)行結(jié)果如下:

reduce + 遞歸
const array = [1, [1, , ,]];
const flat = (arr) => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
}, []);
};
console.log(flat(array)); // [ 1, 1 ]
上面的實現(xiàn)存在幾個弊端:
- 無法指定躺平深度
- 性能差的一批(遞歸 + concat)
- 丟數(shù)據(jù)(空值reduce無法遍歷)
正規(guī)軍入場:
let has = Object.prototype.hasOwnProperty;
let maxSafeInteger = Math.pow(2, 32) - 1;
let toInteger = function (value) {
const number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
let toLength = function (value) {
let len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
let push = Array.prototype.push;
Array.prototype.flat = function (deep) {
let O = Object(this);
let sourceLen = toLength(O.length);
let depthNum = 1;
if (deep !== undefined) {
depthNum = toLength(deep);
}
if (depthNum <= 0) {
return O;
}
let arr = [];
let val;
for (let i = 0; i < sourceLen; i++) {
if (has.call(O, i)) {
val = O[i];
if (Array.isArray(val)) {
push.apply(arr, val.flat(depthNum - 1));
} else {
arr.push(val);
}
} else {
arr.push(undefined);
}
}
return arr;
};
let array = [1, 3, [4, 5], [6, [7, 8, [9, , 10]]], [, ,], [undefined, null, NaN]];
console.log(array.flat(2));
打印結(jié)果如下:

8.實戰(zhàn):數(shù)組合并
準(zhǔn)備好兩條數(shù)據(jù),對 uid 相同的數(shù)據(jù)進(jìn)行合并
export const usersInfo = Array.from({ length: 200 }, (val, index) => {
return {
uid: `${index + 1}`,
name: `user-name-${index}`,
age: index + 10,
avatar: `http://www.my-avatar.com/${index + 1}`,
};
});
export const scoresInfo = Array.from({ length: 10 }, (val, index) => {
return {
uid: `${index + 1}`,
score: ~~(Math.random() * 10000),
comments: ~~(Math.random() * 10000),
stars: ~~(Math.random() * 1000),
};
});
基礎(chǔ)版本:
- 兩層for循環(huán),通過key關(guān)聯(lián)
import * as data from "./data.js";
const { usersInfo, scoresInfo } = data;
console.time("merge data");
for (let i = 0; i < usersInfo.length; i++) {
let user: any = usersInfo[i];
for (let j = 0; j < scoresInfo.length; j++) {
let score = scoresInfo[j];
if (user.uid == score.uid) {
user.score = score.score;
user.comments = score.comments;
user.stars = score.stars;
}
}
}
console.timeEnd("merge data");
console.log(usersInfo);
hash基礎(chǔ)版:
- 數(shù)組轉(zhuǎn)換為map對象。數(shù)組查找變?yōu)閷傩圆檎?/li>
import * as data from "./data.js";
const { usersInfo, scoresInfo } = data;
console.time("merge data");
const scoreMap = scoresInfo.reduce((obj, cur) => {
obj[cur.uid] = cur;
return obj;
}, Object.create(null));
for (let i = 0; i < usersInfo.length; i++) {
const user: any = usersInfo[i];
const score = scoreMap[user.uid];
if (score != null) {
user.score = score.score;
user.comments = score.comments;
user.stars = score.stars;
}
}
console.timeEnd("merge data");
console.log(usersInfo);
hash跳出版:
import * as data from "./data.js";
const { usersInfo, scoresInfo } = data;
console.time("merge data");
const scoreMap = scoresInfo.reduce((obj, cur) => {
obj[cur.uid] = cur;
return obj;
}, Object.create(null));
// 被合并數(shù)據(jù)的條數(shù)
const len = scoresInfo.length;
// 已合并的條數(shù)
let count = 0;
// 已遍歷的次數(shù)
let walkCount = 0;
for (let i = 0; i < usersInfo.length; i++) {
const user: any = usersInfo[i];
const score = scoreMap[user.uid];
walkCount++;
if (score != null) {
count++;
user.score = score.score;
user.comments = score.comments;
user.stars = score.stars;
if (count >= len) {
break;
}
}
}
console.timeEnd("merge data");
console.log(`合并完畢:遍歷次數(shù)${walkCount}, 實際命中次數(shù)${count}, 預(yù)期命中次數(shù)${len}`);
console.log(usersInfo);
數(shù)據(jù)合并-基礎(chǔ) hash 跳出-倒敘版
- 在跳出版的基礎(chǔ)上,一個是從前向后,一個是從后往前
- 適應(yīng)場景∶分頁拉取數(shù)據(jù),新數(shù)組添加在最后,倒敘更快
import * as data from "./data.js";
const { usersInfo, scoresInfo } = data;
console.time("merge data");
const scoreMap = scoresInfo.reduce((obj, cur) => {
obj[cur.uid] = cur;
return obj;
}, Object.create(null));
const len = scoresInfo.length;
let count = 0;
let walkCount = 0;
for (let i = usersInfo.length - 1; i >= 0; i--) {
const user: any = usersInfo[i];
const score = scoreMap[user.uid];
walkCount++;
if (score != null) {
count++;
user.score = score.score;
user.comments = score.comments;
user.stars = score.stars;
if (count >= len) {
break;
}
}
}
console.timeEnd("merge data");
console.log(`合并完畢:遍歷次數(shù)${walkCount}, 實際命中次數(shù)${count}, 預(yù)期命中次數(shù)${len}`);
console.log(usersInfo);以上就是前端進(jìn)階JS數(shù)組高級用法教程示例的詳細(xì)內(nèi)容,更多關(guān)于前端JS數(shù)組進(jìn)階教程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js實現(xiàn)圖片區(qū)域可點擊大小隨意改變(適用移動端)代碼實例
這篇文章主要介紹了js實現(xiàn)圖片區(qū)域可點擊大小隨意改變(適用移動端)代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09
jquery實現(xiàn)下拉菜單的二級聯(lián)動利用json對象從DB取值顯示聯(lián)動
這篇文章主要介紹了jquery實現(xiàn)下拉菜單的二級聯(lián)動利用json對象從DB取值顯示聯(lián)動,需要的朋友可以參考下2014-03-03
jquery pagination插件動態(tài)分頁實例(Bootstrap分頁)
這篇文章主要為大家分享了Bootstrap靜態(tài)分頁和jquery pagination插件動態(tài)分頁兩個實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12
基于JavaScript代碼實現(xiàn)pc與手機(jī)之間的跳轉(zhuǎn)
本文通過一段代碼實例給大家介紹pc跳轉(zhuǎn)手機(jī)代碼,手機(jī)跳轉(zhuǎn)pc網(wǎng)站代碼的相關(guān)知識,對js跳轉(zhuǎn)代碼相關(guān)知識感興趣的朋友一起通過本篇文章學(xué)習(xí)吧2015-12-12
JavaScript 原型學(xué)習(xí)總結(jié)
每個對像都有一個隱慝的屬性用于指向到它的父對像(構(gòu)造對像的函數(shù))的原型(這里稱為父原型或隱式原型),并從中繼承它的屬性和方法2010-10-10
一種Javascript解釋ajax返回的json的好方法(推薦)
下面小編就為大家?guī)硪黄环NJavascript解釋ajax返回的json的好方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06

