# 玩转数组高级技法，成为JS高手

33,748 阅读3分钟

## 1.批量制造数据

``````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);
``````

``````function createData() {
// 如果不 fill 循环默认会跳过空值
return new Array(1000).fill(null).map((v, i) => ({ name: `name\${i + 1}` }));
}

const data = createData();
console.log(data);
``````

``````function createData() {
return Array.from({ length: 1000 }, (v, i) => ({ name: `name\${i + 1}` }));
}

const data = createData();
console.log(data);
``````

## 2.数组合并去重

``````const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];

console.log(new Set([...arr1, ...arr2]));
``````

``````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];
// 按位非，反转操作数的位，表象是对后面数字取负减一
// 当数组中不存在此项 indexOf 返回 -1 按位非得 0 不走 if 逻辑
// 如果两个数组都包含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 } ]
// 这样对象都是独立的引用，肯定无法去除属性相同的数据啦
``````

``````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]))); // 确实可以，但是你开发这样做？
``````

``````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 属性值的数据则进入判断
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.创建数组的几种方式

• 字面量
``````// 字面量
const arr1 = [1, 2, 3, ...[4, 5, 6]]; // 1,2,3,4,5,6
const arr2 = [, , , , ,]; // [empty × 5]
``````
• new Array（当参数只有一个且是数字时，new Array()表示数组的长度，其余参数则是数组的内容）
``````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（参数只用来作为数组中的内容）
``````const arr6 = Array.of(5); // [5]
const arr7 = Array.of(1, 'abc', true); // [1, "abc", true]
``````
• Array.from 可传入类数组和可遍历对象转换为真数组

（第一个参数传入对应类数组和可遍历对象，第二个函数参数则相当于对生成的数组做一次map）

可遍历和类数组 ==> 数组、字符串、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"]
``````
• 其他的很多可以返回数组的方法都算
``````// 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.类数组

• 是一个普通对象，不具备数组自带丰富的内建方法
• key是以数字或者字符串数字组成
• 必须有length属性
``````const arrayLike = {
0: "a",
1: "b",
2: "c",
name: "test",
length: 3,
push: Array.prototype.push, //自己实现
splice: Array.prototype.splice,
};

//由于类数组对象length属性声明了对象有多少个属性,所以可以使用for遍历对象属性:
for (let i = 0; i < arrayLike.length; i++) {
console.log(i + ":" + arrayLike[i]);
}
``````

### 常见的类数组

• arguments
``````function person(name, age, sex) {
console.log("person arguments:", arguments);
console.log("person type:", Object.prototype.toString.call(arguments));
}

person("name", "age", "sex");
``````

• 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);
``````

• 奇特：字符串（具备类数组的特性，但一般类数组指对象）
``````const str = "abc";
console.log(Object.keys(str)); // ['0', '1', '2']
console.log(Array.from(str)); // ['a', 'b', 'c']
``````

### 判断是否是类数组

``````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() 判断是否在正常数字范围
if (!isFinite(arr.length)) return false;
// 构造函数等于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
``````

### 类数组如何转换为数组

• 复制遍历
``````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 ]
``````
• 扩展运算符
``````console.log([...document.body.childNodes]); // [div, script, script...]

// arguments
function argumentsTest() {
console.log([...arguments]); // [ 1, 2, 3 ]
}
argumentsTest(1, 2, 3);
``````

### 如何让类数组使用上数组丰富的内建方法

• 在类数组对象上直接定义数组原型的方法
• 运用call或者apply显示绑定this的指向

``````const arrayLike = {
0: "i love",
1: "you",
length: 2,
};

console.log([].filter.call(arrayLike, (item) => item.includes("i"))); // [ 'i love' ]
``````

``````[].__proto__.myfilter = function (callback) {
let newArr = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i])) {
newArr.push(this[i]);
}
}
return newArr;
};
``````

### 类数组和数组的区别

length属性
toString返回[object Array][object Object]
instanceofArrayObject
constructor[Function: Array][Function: Object]
Array.isArraytruefalse

## 5.数组方法的使用注意事项

### 数组的长度

``````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
``````

### 数组的空元素 empty

• empty:数组的空位，指数组的某一位置没有任何值，有空位的数组也叫稀疏数组
• 稀疏数组性能会较差，可以避免创建
• 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> ]
``````

#### 基于值进行运算，空位的值作为undefined

• find,findIndex,includes等, indexOf除外
• 当被作为迭代的时候，参与Object.entries、扩展运算符、for of 等

#### join和toString，遇到空位怎么处理

• 视为空字符串
• toString 内部其实会调用 join 方法

### 数组不会自动添加分号

• (，[， + , -,/，其作为一行代码的开头，很可能产生意外的情况，所以，没事代码最后写个分号，保准没错
``````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

indexOfnumber××
includesboolean
``````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)); // 区分空位和undefined，判断此位上是否有值
``````

### 数组可变长度问题

• length 代表数组中元素个数，数组额外附加属性不计算在内
• length 可写，可以通过修改length改变数组的长度
• 数组操作不存在越界，找不到下标，返回undefined
``````const array = [1, 2, 3, 4, 5, 6];

array[10] = 10;  // 尽量不要这样破坏数组默认线性存储的结构
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
``````

### 数组查找和过滤

someboolean××
findundefined | object×
findelndexnumber×
everyboolean×
filterarray×××

### 改变原数组的方法

• 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误区

• delete删除数组元素，后面元素不会补齐，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

• 大量数据操作的时候 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.数组的高级用法

### 1.万能数据生成器

``````const createValues = (creator, length = 10) => Array.from({ length }, creator);

// 第一个参数控制随机数生成，第二个控制其数组长度
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);

// 生成数组，里面元素是 1 ~ 100 以内每次从 1 开始每次递增 3 的数字
const values = createRange(1, 100, 3);

console.log(values);
``````

### 3.数据生成器

``````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.清空数组

``````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.数组去重

``````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);
``````

``````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));
``````

``````function uniqueArray(arr = [], key) {
const keyValues = new Set();
return arr.filter((item) => {
if (!keyValues.has(item[key])) {
return true;
}
return false;
});
}

const arr = [{ a: 1 }, { a: 1 }, { a: 2 }];

console.log("filter 去重：", uniqueArray(arr, "a")); // filter 去重： [ { a: 1 }, { a: 2 } ]
``````

### 6.数组交集

• `Array.prototype.filter + includes`判断
• 但是会存在性能和引用类型相同判断的问题
``````const arr1 = [0, 1, 2];
const arr2 = [3, 2, 0];

function intersectSet(arr1, arr2) {
return 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]));
}

// 原始数据类型
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);
});
}

// 使用Map
function intersectMap(arr1, arr2) {
const map = new Map();
arr1.forEach((val) => map.set(val));
return arr2.filter((val) => map.has(val));
}

// 使用includes
function intersectIncludes(arr1, arr2) {
return arr1.filter((item) => arr2.includes(item));
}

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("intersectIncludes");
intersectIncludes(data1, data2);
console.timeEnd("intersectIncludes");
``````

### 7.数组差集

``````// 引用类型
function difference(arr1, arr2, key) {
const map = new Map();
arr1.forEach((val) => map.set(val[key]));
return arr2.filter((val) => !map.has(val[key]));
}

// 原始数据类型
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("result1:", result); // result1: [ { p: 3 } ]

const arr3 = [0, 1, 2];
const arr4 = [3, 2, 0];
const result1 = differenceBase(arr3, arr4);
console.log("result2:", result1); // result2: [ 3 ]
``````

### 8.数组删除虚（假）值

``````const array = [false, 0, undefined, , "", NaN, 9, true, undefined, null, "test"];
const newArray = array.filter(Boolean);
console.log(newArray); // [ 9, true, 'test' ]
``````

### 9.获取数组中最大值和最小值

``````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
``````

``````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(...ages);
const min = Math.min(...ages);
console.log(ages);
console.log("max:", max + ",min:" + min);
``````

### 10.reduce高级用法

#### querystring

• 作用∶页面传递参数
• 规律∶地址url问号(?)拼接的键值对

URLSearchParams：

``````const urlSP = new URLSearchParams(location.search);
function getQueryString(key) {
return urlSP.get(key);
}

// 获取页面上查询参数 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"));
``````

``````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"));
``````

#### 折上折

• 优惠1：9折
• 优惠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;
}

// 从后往前执行传入的函数
const getPrice = compose(discountPlus, reduce, discount);

const print = console.log;

print(getPrice(200));
print(getPrice(250));
``````

#### Promise顺序执行

``````function runPromises(promiseCreators, initData) {
return promiseCreators.reduce(function (promise, next) {
return promise.then((data) => next(data));
}, Promise.resolve(initData));
}

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) => getUserInfo(data))
.then((data) => getOrders(data))
.then((data) => console.log("orders", data));

// 使用 reduce 封装的 runPromises 方法，确保返回 Promise 且执行结果是下一个函数的入参
runPromises([login, getUserInfo, getOrders], initData).then((res) => {
console.log("res", res);
});
``````

#### 数组分组

``````const hasOwn = Object.prototype.hasOwnProperty;
function group(arr, fn) {
// 不是数组
if (!Array.isArray(arr)) {
return arr;
}
// 不是函数
if (typeof fn !== "function") {
throw new TypeError("fn必须是一个函数");
}
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);

// 按照份数分组
result = group(
[
{
name: "tom",
score: 60,
},
{
name: "Jim",
score: 40,
},
{
name: "Nick",
score: 88,
},
],
(v) => v.score >= 60
);

console.log(result);
``````

## 7.手写数组方法

### Array.isArray

• 判断是否是数组
``````const arr = ["1"];

console.log("isArray:", Array.isArray(arr));
``````

``````const arr = ["1"];

const proxy = new Proxy(arr, {});

console.log("isArray:", Array.isArray(proxy)); // 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); // 报错
``````

• 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));
``````

### Array.prototype.entries

• 作用：返回一个新的 Array Iterator 对象，该对象包含数组中每个索引的键/值对
``````const arr = ["a", "b", "c"];

const iter = arr.entries();
console.log("iter:", iter);
// next函数访问
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);
}
``````

done 表示遍历是否结束，value 返回当前遍历的值

``````Array.prototype.entries = function () {
// 转换对象(引用数据类型返回自身)
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());

// 不能正常执行，因为如果要能 for...of 遍历需要去实现 Symbol.iterator
for (let [k, v] of arr.entries()) {
console.log(`k:\${k}`, `v:\${v}`);
}
``````

``````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,
};
},
};
};
``````

``````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

• 判断数组是否含有某值，可判断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调用，严格模式
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));
``````

• 转为整数:TolntegerOrlnfinity
• +lnfinity , -Infinity
• 可能为负数
``````Number.isNaN = function (params) {
if (typeof params === "number") {
return isNaN(params);
}
return false;
};

// 转换整数
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) {
// 严格模式
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;
}
// 负无穷转换为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

• arrayLike：类数组对象或者可遍历对象（Map、Set）等
• mapFn：可选参数，在最后生成数组后执行一次map方法后返回
• thisArg：可选参数，实际是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 })); // 失败
``````

``````//类数组的特征
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 为数字，并且在有效范围内。
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 }));
``````

Array.prototype.flat

• 指定的深度递归遍历数组，并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
``````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));
``````

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 ]
``````

• 无法指定躺平深度
• 性能差的一批（递归 + concat）
• 丢数据（空值reduce无法遍历）

``````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));
``````

## 8.实战：数组合并

``````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),
stars: ~~(Math.random() * 1000),
};
});
``````

### 基础版本：

• 两层for循环，通过key关联
``````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.stars = score.stars;
}
}
}
console.timeEnd("merge data");
console.log(usersInfo);
``````

### hash基础版：

• 数组转换为map对象。数组查找变为属性查找
``````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.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));

// 被合并数据的条数
const len = scoresInfo.length;
// 已合并的条数
let count = 0;
// 已遍历的次数
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.stars = score.stars;

if (count >= len) {
break;
}
}
}

console.timeEnd("merge data");

console.log(`合并完毕:遍历次数\${walkCount}, 实际命中次数\${count}, 预期命中次数\${len}`);
console.log(usersInfo);
``````

• 在跳出版的基础上，一个是从前向后，一个是从后往前
• 适应场景∶分页拉取数据，新数组添加在最后，倒叙更快
``````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.stars = score.stars;

if (count >= len) {
break;
}
}
}
console.timeEnd("merge data");

console.log(`合并完毕：遍历次数\${walkCount}, 实际命中次数\${count}, 预期命中次数\${len}`);

console.log(usersInfo);
``````