# 数组和对象在前端开发中,是比较常见的数据结构,这里将它们在日常开发中,频繁使用的一些方法做一个整理归类。
# 浅拷贝和深拷贝的概念也需要重新强化记忆,希望对大家有所帮助。
一、数组
我会将属性和方法分开来写,尽量说将他们说的明白;(题外话,写到这里,忍不住好奇去了解了下什么是数组,怪不得人家大佬呢~)
我们常见的数据都是已经实例化以后的,所以它的一个标准实例属性,只有length;constructor是原型链上的引用,prototype是构造函数的属性。
数组常用的方法:
以下方法将会修改原数组:
- push():在数组末尾添加一个或多个元素;
// 示例:a=[1,2,3];a.push(2); // [1,2,3,2]- pop():删除并返回数组最后一个元素;
// 示例:a=[1,2,3];a.pop(); // [1,2]- unshift():在数组开头添加一个或多个元素;
// 示例:a=[1,2,3];a.unshift(2); // [2,1,2,3]- shift():删除并返回数组第一个元素;
// 示例:a=[1,2,3];a.shift(); // [2,3]- splice():删除/替换/插入元素;
// 示例格式:a.splice(索引,删除元素个数,要插入的新元素)
// 删除示例:a=[1,2,3];a.splice(0,1); // [2,3]
// 替换示例:a=[1,2,3];a.splice(0,1,9); // [9,2,3]
// 插入示例:a=[1,2,3];a.splice(0,0,8); // [8,1,2,3]- reverse():反转数组;
// 示例:a=[1,2,3];a.reverse(); // [3,2,1]- sort():排序数组,字母默认升序,数字必须通过函数去指定升降序;
// 字母示例:a=["b","c","a","p"];a.sort(); // ["a","b","c","p"]
// 数字示例:a=[4,23,13,90];a.sort((a,b)=>{return a-b}); // [4,13,23,90]- fill():用固定值填充数组;
// 示例:a=[1,2,3];a.fill(2); // [2,2,2]以下方法将不会修改原数组:
- concat():合并多个数组;
// 示例:a=[1,2,3];b=[4,5,6];c=a.concat(b); // [1,2,3,4,5,6]- join():将数组转为字符串;
// 示例:a=[1,2,3];b=a.join(无分割符,默认逗号); // [1,2,3] >>>1,2,3- slice():截取数组的一部分,参数分为起始参数(start)和截止参数(end),参数不为负数,就是按索引选取,如果是负数,选取范围为从后往前数start个数的位置开始到从后往前数end个数的位置,无end参数,就从start参数位置开始一直到结尾;左闭右开原则选取;
// 非负数示例:a=[1,2,3,4,5,6];b=a.slice(1,3); // [2,3,4]
// 负数示例:a=[1,2,3,4,5,6];b=a.slice(-3,-2); // [4]- indexOf():查找元素首次出现的索引;
// 示例:a=[1,2,3];b=a.indexOf(3); // 2- lastIndexOf():查找元素最后一次出现的索引;
// 示例:a=[1,2,3,6,4,6,2,6];b=a.lastIndexOf(6); // 7- includes():判断数组是否包含某个值;
// 示例:a=[1,2,3];a.includes(3); // true
// 示例:a=[1,2,3];a.includes(5); // false- find():查找第一个符合条件的元素;
// 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.find(函数); // 3- findIndex():查找第一个符合条件的索引;
// 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.indIndex(函数); // 2- filter():过滤符合条件的元素
// 示例:a=[1,2,3,4,5,6];function 函数(sub){return sub > 2};b=a.filter(函数); // [3,4,5,6]- map():对每个元素执行函数并返回新数组
// 示例:a=[1,2,3];function 函数(sub){return sub * 2};b=a.map(函数); // [2,4,6]- reduce():从左到右累加计算
// 示例:a=[1,2,3];function 函数(total,sub){return total + sub};b=a.reduce(函数,0);
// reduce这里解释一下上面函数后面的0是什么,它是一个可选参数,作为total初始值
// 6- reduceRight():从右到左累加计算
// 示例:a=[1,2,3];function 函数(total,sub){return total + sub};b=a.reduceRight(函数,0);
// reduce这里解释一下上面函数后面的0是什么,它是一个可选参数,作为total初始值
// 6- some():判断是否有元素符合条件
// 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.some(函数); // true- every():判断是否所有元素符合条件
// 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.every(函数); // false以下用来遍历数据的迭代方法:
- forEach():遍历数组,无返回值;
- entries():返回数组迭代对象,该对象包含数组的键值对;
- keys():返回数组迭代对象,该对象包含数组的键;
- values():返回数组迭代对象,该对象包含数组的值;
// entries、keys、values,返回的是迭代器,我们需要通过.next().value
// 去获取迭代后的属性
// 一般我们见到这三种方法时,通常都会看见for…of,它本质上是迭代器的语法糖
// 自动调用.next()- // 示例:
// 以下两种方式等价: var arr = [1,2,3,4]; const iterator = arr.values(); let result; while (!(result = iterator.next()).done) { console.log(result.value); // 等同于 for...of } // 这个是模拟了下面for…of循环的底层行为;这里.next()返回的对象格式是 // {value:0,done:false},是的,你没有看错,当后面还有下一个值时,done的值是false // 语法糖形式 for (const item of arr.values()) { console.log(item); }
以下是一些静态方法:
- Array.isArray():判断一个对象是否为数组
- Array.from():通过拥有length属性的对象或可迭代的对象返回一个数组
- Array.of():将一组值转换为数组,不考虑值得数量和类型
二、对象
{} 这就是一个对象,不过它现在没有包含任何东西,还是个毛胚;
我们可以给对象添加属性以及方法,让他变的更丰满一些;var name ={ type:1, keys:"yes", run: function(){ console.log(this.type) } }
在不涉及Vue2的响应式时,对象的增删改查,这里就略过了。在ES6+中的解构赋值、展开运算符、可选链等等,不理解的朋友可以百度查询一下;
Object内置的一些工具包:
- Object.assign(目标对象,源对象):用于将源对象所有可枚举属性复制到目标对象中;
- Object.keys():获取属性名列表,返回的是一个数组;
- Object.values():获取值列表,返回的是一个数组;
三、浅拷贝
浅拷贝是指创建一个新对象或数组,这个新对象/数组拥有原始对象/数组的属性及元素的一份精确拷贝;对于基本数据类型,拷贝的是值本身;对于引用数据类型,拷贝的是内存引用地址;也就是说,浅拷贝仅仅只复制原始数据的顶层属性,嵌套属性仍共用引用地址。
在开始前,我们需要先了解一下数据类型的有关知识;我们知道了浅拷贝只拷贝顶层,但是有时候在修改代码时,我们改动了新对象/数组,结果原始对象和属性也变了,它不一定是你修改属性时的指向问题,还有可能是因为数据类型的影响,只有基本类型才会被浅拷贝创建副本,非基本类型一律拷贝引用地址;
基本数据类型:string、number、boolean、null、underfined、symbol、bigint;
对象浅拷贝:
- Object.assign():示例:const b = {name:111};const a = Object.assign({},b);
- 展开运算符:示例:const b = {name:111};const a = {...b};
需要注意的是,a=b,不是浅拷贝,而是引用赋值;他们指向的是同一个内存地址,是同一个对象的两个别名;
数组浅拷贝:
- slice():截取整个数组,然后赋值给新数组;
- concat():将原数组合并到一个空数组中然后赋值给新数组;
- 展开运算符:示例:b = [1,2,3,4,5];a = [...b]
- from():示例:a=Array.from(b) // [1,2,3,4,5]
什么时候我们使用浅拷贝呢?
- 缺点对象/数组没有嵌套结构;
- 程序性能作为关键考虑因素;
- 确实需要共享嵌套时;
四、深拷贝
深拷贝指创建一个新对象,并递归赋值原始对象的所有属性和嵌套属性,使新对象和原始对象完全独立,修改新对象不会导致原始对象发生变化。
深拷贝的实现方法:
- 使用JSON.stringify和JSON.parse:通过将对象转为JSON字符串再解析为新对象;
局限性:无法处理undefined、function等类型;会忽略原型链和构造函数;无法处理循环引用;- 手动递归实现:
/** * 深拷贝函数 * @param {*} target 要拷贝的目标 * @param {WeakMap} map 用于存储已拷贝对象(解决循环引用) * @returns {*} 深拷贝后的对象 */ function deepClone(target, map = new WeakMap()) { // 1. 处理基本数据类型(直接返回) if (target === null || typeof target !== 'object') { return target; } // 2. 处理特殊对象类型 // 2.1 处理Date对象 if (target instanceof Date) { return new Date(target); } // 2.2 处理RegExp对象 if (target instanceof RegExp) { return new RegExp(target); } // 2.3 处理Set对象 if (target instanceof Set) { const cloneSet = new Set(); target.forEach(value => { cloneSet.add(deepClone(value, map)); }); return cloneSet; } // 2.4 处理Map对象 if (target instanceof Map) { const cloneMap = new Map(); target.forEach((value, key) => { cloneMap.set(key, deepClone(value, map)); }); return cloneMap; } // 在类型检查部分添加: if (typeof target === 'function') { return target.bind({}); // 创建新函数绑定到空对象 } // 3. 处理循环引用(如果已经拷贝过,直接返回存储的副本) if (map.has(target)) { return map.get(target); } // 4. 创建新对象/数组 const cloneTarget = Array.isArray(target) ? [] : {}; // 5. 将当前对象存入WeakMap,防止循环引用 map.set(target, cloneTarget); // 6. 拷贝Symbol属性 const symbolKeys = Object.getOwnPropertySymbols(target); if (symbolKeys.length) { symbolKeys.forEach(symKey => { cloneTarget[symKey] = deepClone(target[symKey], map); }); } // 7. 递归拷贝所有属性 for (const key in target) { if (target.hasOwnProperty(key)) { cloneTarget[key] = deepClone(target[key], map); } } // 8. 拷贝不可枚举属性(可选) // 如果需要拷贝不可枚举属性,可以使用Object.getOwnPropertyDescriptors // 这里为了性能考虑,默认不拷贝 return cloneTarget; }
- 使用第三方库:如loadash的.cloneDeep;