数组、对象、浅拷贝与深拷贝

# 数组和对象在前端开发中,是比较常见的数据结构,这里将它们在日常开发中,频繁使用的一些方法做一个整理归类。
# 浅拷贝和深拷贝的概念也需要重新强化记忆,希望对大家有所帮助。

一、数组

我会将属性和方法分开来写,尽量说将他们说的明白;(题外话,写到这里,忍不住好奇去了解了下什么是数组,怪不得人家大佬呢~)


我们常见的数据都是已经实例化以后的,所以它的一个标准实例属性,只有length;constructor是原型链上的引用,prototype是构造函数的属性。

数组常用的方法:

    以下方法将会修改原数组

  1. push():在数组末尾添加一个或多个元素;
    // 示例:a=[1,2,3];a.push(2); // [1,2,3,2]
  2. pop():删除并返回数组最后一个元素;
    // 示例:a=[1,2,3];a.pop(); // [1,2]
  3. unshift():在数组开头添加一个或多个元素;
    // 示例:a=[1,2,3];a.unshift(2); // [2,1,2,3]
  4. shift():删除并返回数组第一个元素;
    // 示例:a=[1,2,3];a.shift(); // [2,3]
  5. 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]
  6. reverse():反转数组;
    // 示例:a=[1,2,3];a.reverse(); // [3,2,1]
  7. 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]
  8. fill():用固定值填充数组;
    // 示例:a=[1,2,3];a.fill(2); // [2,2,2]

    以下方法将不会修改原数组

  1. concat():合并多个数组;
    // 示例:a=[1,2,3];b=[4,5,6];c=a.concat(b); // [1,2,3,4,5,6]
  2. join():将数组转为字符串;
    // 示例:a=[1,2,3];b=a.join(无分割符,默认逗号); // [1,2,3] >>>1,2,3
  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]
  4. indexOf():查找元素首次出现的索引;
    // 示例:a=[1,2,3];b=a.indexOf(3); // 2
  5. lastIndexOf():查找元素最后一次出现的索引;
    // 示例:a=[1,2,3,6,4,6,2,6];b=a.lastIndexOf(6); // 7
  6. includes():判断数组是否包含某个值;
    // 示例:a=[1,2,3];a.includes(3); // true
    // 示例:a=[1,2,3];a.includes(5); // false
  7. find():查找第一个符合条件的元素;
    // 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.find(函数); // 3
  8. findIndex():查找第一个符合条件的索引;
    // 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.indIndex(函数); // 2
  9. filter():过滤符合条件的元素
    // 示例:a=[1,2,3,4,5,6];function 函数(sub){return sub > 2};b=a.filter(函数); // [3,4,5,6]
  10. map():对每个元素执行函数并返回新数组
    // 示例:a=[1,2,3];function 函数(sub){return sub * 2};b=a.map(函数); // [2,4,6]
  11. reduce():从左到右累加计算
    // 示例:a=[1,2,3];function 函数(total,sub){return total + sub};b=a.reduce(函数,0);
    // reduce这里解释一下上面函数后面的0是什么,它是一个可选参数,作为total初始值
    // 6
  12. reduceRight():从右到左累加计算
    // 示例:a=[1,2,3];function 函数(total,sub){return total + sub};b=a.reduceRight(函数,0);
    // reduce这里解释一下上面函数后面的0是什么,它是一个可选参数,作为total初始值
    // 6
  13. some():判断是否有元素符合条件
    // 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.some(函数); // true
  14. every():判断是否所有元素符合条件
    // 示例:a=[1,2,3];function 函数(sub){return sub > 2};b=a.every(函数); // false

    以下用来遍历数据的迭代方法

  1. forEach():遍历数组,无返回值;
  2. entries():返回数组迭代对象,该对象包含数组的键值对;
  3. keys():返回数组迭代对象,该对象包含数组的键;
  4. values():返回数组迭代对象,该对象包含数组的值;
    //  entries、keys、values,返回的是迭代器,我们需要通过.next().value
    //  去获取迭代后的属性
    //  一般我们见到这三种方法时,通常都会看见for…of,它本质上是迭代器的语法糖
    //  自动调用.next()
  5. //  示例:
    // 以下两种方式等价:
    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);
    }

    以下是一些静态方法

  1. Array.isArray():判断一个对象是否为数组
  2. Array.from():通过拥有length属性的对象或可迭代的对象返回一个数组
  3. Array.of():将一组值转换为数组,不考虑值得数量和类型

二、对象

{} 这就是一个对象,不过它现在没有包含任何东西,还是个毛胚;
我们可以给对象添加属性以及方法,让他变的更丰满一些;

var name ={
    type:1,
    keys:"yes",
    run: function(){
        console.log(this.type)
    }
}

在不涉及Vue2的响应式时,对象的增删改查,这里就略过了。在ES6+中的解构赋值、展开运算符、可选链等等,不理解的朋友可以百度查询一下;

Object内置的一些工具包:

  1. Object.assign(目标对象,源对象):用于将源对象所有可枚举属性复制到目标对象中;
  2. Object.keys():获取属性名列表,返回的是一个数组;
  3. Object.values():获取值列表,返回的是一个数组;

三、浅拷贝

浅拷贝是指创建一个新对象或数组,这个新对象/数组拥有原始对象/数组的属性及元素的一份精确拷贝;对于基本数据类型,拷贝的是值本身;对于引用数据类型,拷贝的是内存引用地址;也就是说,浅拷贝仅仅只复制原始数据的顶层属性,嵌套属性仍共用引用地址。


在开始前,我们需要先了解一下数据类型的有关知识;我们知道了浅拷贝只拷贝顶层,但是有时候在修改代码时,我们改动了新对象/数组,结果原始对象和属性也变了,它不一定是你修改属性时的指向问题,还有可能是因为数据类型的影响,只有基本类型才会被浅拷贝创建副本,非基本类型一律拷贝引用地址;
基本数据类型:string、number、boolean、null、underfined、symbol、bigint;


对象浅拷贝:

  1. Object.assign():示例:const b = {name:111};const a = Object.assign({},b);
  2. 展开运算符:示例:const b = {name:111};const a = {...b};

需要注意的是,a=b,不是浅拷贝,而是引用赋值;他们指向的是同一个内存地址,是同一个对象的两个别名;


数组浅拷贝:

  1. slice():截取整个数组,然后赋值给新数组;
  2. concat():将原数组合并到一个空数组中然后赋值给新数组;
  3. 展开运算符:示例:b = [1,2,3,4,5];a = [...b]
  4. from():示例:a=Array.from(b) // [1,2,3,4,5]

什么时候我们使用浅拷贝呢?

  1. 缺点对象/数组没有嵌套结构;
  2. 程序性能作为关键考虑因素;
  3. 确实需要共享嵌套时;

四、深拷贝

深拷贝指创建一个新对象,并递归赋值原始对象的所有属性和嵌套属性,使新对象和原始对象完全独立,修改新对象不会导致原始对象发生变化。


深拷贝的实现方法:

  1. 使用JSON.stringify和JSON.parse:通过将对象转为JSON字符串再解析为新对象;
    局限性:无法处理undefined、function等类型;会忽略原型链和构造函数;无法处理循环引用;
  2. 手动递归实现:
    /**
     * 深拷贝函数
     * @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;
    }

  3. 使用第三方库:如loadash的.cloneDeep;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值