javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > 前端JS数组进阶教程

前端进阶JS数组高级用法大全教程示例

作者:云牧

这篇文章主要为大家介绍了前端进阶JS数组高级用法教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

1.批量制造数据

一、创建新数组使用 for 循环批量 push 数据

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

二、创建空数组,填充full,然后map

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

三、Array.from 第二个初始化函数返回数据

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

2.数组合并去重

一、Set去重

const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
console.log(new Set([...arr1, ...arr2]));

二、for循环,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];
    // 按位非,反转操作数的位,表象是对后面数字取负减一
    // 当数组中不存在此项 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]
const arr3 = new Array(5); // [empty × 5]
const arr4 = new Array(1, 2, 3); // 1,2,3
const arr5 = new Array("a"); // ["a"]
const arr6 = Array.of(5); // [5]
const arr7 = Array.of(1, 'abc', true); // [1, "abc", true]
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.类数组

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]);
}

常见的类数组

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

打印结果如下:

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]
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 ]
const arrayLike = {
  0: 1,
  1: 2,
  length: 2,
};
console.log(Array.from(arrayLike)); // [ 1, 2 ]
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);

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

例如我想通过 filter 方法过滤出类数组中元素包含 "i" 这个字符的所有元素。

const arrayLike = {
  0: "i love",
  1: "you",
  length: 1,
};
console.log([].filter.call(arrayLike, (item) => item.includes("i"))); // [ 'i love' ]

为什么会这样?其实可以想想 filter 是如何实现的。

[].__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 实现是通过 this 进行绑定的,哪个数组调用了这个filter,filter中的 this 就指向哪个数组

类数组和数组的区别

方法/特征数组类数组
自带方法多个方法
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

join和toString,空位怎么处理

数组不会自动添加分号

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
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,判断此位上是否有值

数组可变长度问题

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

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

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

如果我们想认为两个对象里面的 a 属性的值相同就认为是同一数组的话,可以使用 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.数组交集

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]));
}
// 原始数据类型
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));
}
// 原始数据类型
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.数组差集

// 引用类型
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("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.数组删除虚(假)值

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

10.reduce高级用法

querystring

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

使用 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")); 

折上折

草民版:

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));
}
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 且执行结果是下一个函数的入参
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

为什么上面 Array.isArray 判断代理对象是否数组返回 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 的方法

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

其实还有很多方法可以判断其数据类型,比如 constructor、isPrototypeOf等,不过我还是更推荐上面两种

Array.prototype.entries

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

下面添加 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,
      };
    },
  };
};

数组还有 Array.prototype.keys,Array.prototype.keys,如果我们像上面这样写等于每个方法里面都要实现[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

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

其实 includes 还有第二个参数,表示从哪个下标开始检查,我们也来写写该方法

注意参数的情况

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

有三个参数

特殊值处理

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) =&gt; {
  return arr.reduce((pre, cur) =&gt; {
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  }, []);
};
console.log(flat(array)); // [ 1, 1 ]

上面的实现存在几个弊端:

正规军入场:

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.实战:数组合并

准备好两条数据,对 uid 相同的数据进行合并

export const usersInfo = Array.from({ length: 200 }, (val, index) =&gt; {
  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) =&gt; {
  return {
    uid: `${index + 1}`,
    score: ~~(Math.random() * 10000),
    comments: ~~(Math.random() * 10000),
    stars: ~~(Math.random() * 1000),
  };
});

基础版本:

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基础版:

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));
// 被合并数据的条数
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.comments = score.comments;
    user.stars = score.stars;
    if (count >= len) {
      break;
    }
  }
}
console.timeEnd("merge data");
console.log(`合并完毕:遍历次数${walkCount}, 实际命中次数${count}, 预期命中次数${len}`);
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 = 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(`合并完毕:遍历次数${walkCount}, 实际命中次数${count}, 预期命中次数${len}`);
console.log(usersInfo);

以上就是前端进阶JS数组高级用法教程示例的详细内容,更多关于前端JS数组进阶教程的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文