放肆青春的博客
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)

放肆青春

一个前端菜鸟的技术成长之路
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)
  • 前端

    • 前端 概览
    • 前端汇总

    • front 博文

    • front 项目总结

    • front 高级

    • front tools

  • vue

    • vue 概览
    • vue 汇总

    • vue 博文

    • vue 项目总结

    • vue 高级

  • html

    • html 概览
    • html 汇总

    • html 博文

  • css

    • css 概览
    • css 汇总

    • css 博文

    • sass

    • less

  • js

    • javascript 概览
    • JS 汇总

    • ES6

    • JS 博文

      • js 基础语法
      • js 数据类型
      • js 字符串
      • js 数组
      • js 对象
      • js 变量
      • js 函数
      • js 事件
      • js 循环
      • js 浅拷贝和深拷贝
        • 浅拷贝
        • 深拷贝
          • 函数深拷贝
          • symbol 深拷贝
          • Date 日期对象深拷贝
          • RegExp 正则对象深拷贝
        • 深拷贝方案
          • 1. JSON.parse
          • 2. MessageChannel(消息通道)
          • 3. 函数库
          • 4. 自己封装函数
      • js 动画
      • js DOM
      • js 防抖节流
      • js 原型及原型链
      • js this
      • js 作用域
      • js 继承
      • js 闭包
      • js 内存
      • js垃圾回收
    • JS 工具

  • node

    • node 概览
    • node 汇总

    • node 框架

    • node 博文

  • react

    • react 概览
    • react 汇总

    • react 博文

    • react 高级

  • 微信小程序

    • 微信小程序 概览
    • 微信小程序总结
    • 微信小程序文章
    • 微信小程序 博文

    • 微信小程序 高级

  • 微信公众号

    • 微信公众号 概览
    • 微信公众号总结
    • 微信公众号文章
  • 多端开发

    • 多端开发
    • dsbridge 概览
    • jsbridge 概览
    • webview
    • uniapp

      • uniapp 概览
    • taro

      • taro 概览
    • flutter

      • flutter 概览
      • flutter 环境搭建
    • electron

      • electron 概览
  • front
放肆青春
2020-07-09

js 浅拷贝和深拷贝

# 浅拷贝

    1. Object.assign()

Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

Object.assign()是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址。

Object.assign 可以用来处理数组,但是会把数组视为对象。 Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]

    1. Array.prototype.concat()和 Array.prototype.slice() (适用数组)

Array 的 slice 和 contact 方法都不会修改原数组,而是会返回一个对原数组进行浅拷贝的新数组。这两种方法同 Object.assign()一样,都是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址。

# 深拷贝

  1. 基本数据类型

# 函数深拷贝

  1. 通过 eval()

var b = eval("0,"+a);//方法一

  1. 通过 new Function()

var c = new Function("return "+a)();//方法二

上面两种方法无法拷贝函数的静态属性

函数的处理要简单说下,我认为克隆函数是没有必要的其实,两个对象使用一个在内存中处于同一个地址的函数也是没有任何问题的,如下是 lodash 对函数的处理:

const isFunc = typeof value == "function";
if (isFunc || !cloneableTags[tag]) {
  return object ? value : {};
}
1
2
3
4

# symbol 深拷贝

  1. Object.getOwnPropertySymbols(obj)

# Date 日期对象深拷贝

if (data instanceof Date) {
  return new Date(data);
}
1
2
3

# RegExp 正则对象深拷贝

if (data instanceof RegExp) {
  return new RegExp(data);
}
1
2
3

# 深拷贝方案

# 1. JSON.parse

大部分需要深拷贝的场景,都可以使用以下代码:

JSON.parse(JSON.stringify(object))

!!!但这种办法会忽略 undefined、function、symbol 和循环引用的对象

缺点:这种方法可以实现数组和对象和基本数据类型的深拷贝,但不能处理函数。因为 JSON.stringify()方法是将一个 javascript 值转换我一个 JSON 字符串,不能接受函数。其他影响如下:

  • 拷贝 Date 引用类型会变成字符串
  • 键值变成空对象:对象的值中为 Map、Set、RegExp 这几种类型。
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
  • 如果对象中有 NAN、infinity、-infinity,那么序列化的结果会变成 null
  • 无法拷贝:不可枚举属性、对象的原型链。
  • 循环引用的会抛出错误

# 2. MessageChannel(消息通道)

使用 MessageChannel(消息通道)进行深拷贝

MessageChannel 的消息在发送和接收的过程需要序列化和反序列化。利用这个特性,我们可以实现深拷贝

但这个深拷贝只能解决 undefined 和循环引用对象的问题,对于 Symbol 和 function 依然束手无策

function deepCopy(obj) {
  return new Promise((resolve) => {
    const { port1, port2 } = new MessageChannel();
    port2.onmessage = (ev) => resolve(ev.data);
    port1.postMessage(obj);
  });
}
1
2
3
4
5
6
7

# 3. 函数库

lodash,提供_.cloneDeep 用来做深拷贝

# 4. 自己封装函数

递归实现深拷贝 缺陷:当遇到两个互相引用的对象时,会出现死循环的情况,从而导致爆栈。

function deepCopy(obj) {
  var result = Array.isArray(obj) ? [] : {};
  if (obj && typeof obj === "object") {
    for (let i in obj) {
      if (obj.hasOwnProperty(i)) {
        // 思考:这句是否有必要?
        if (obj[i] && typeof obj[i] === "object") {
          result[i] = deepCopy(obj[i]);
        } else {
          result[i] = obj[i];
        }
      }
    }
  }
  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

深拷贝函数最终版

支持基本数据类型、Symbol、Array、Object、原型链、RegExp、Date、 Map、Set 、Function、不可枚举属性 类型及解决循环引用问题

function deepClone(data) {
  // 可遍历类型 Map Set Object Array
  const typeMap = "[object Map]";
  const typeSet = "[object Set]";
  const typeObject = "[object Object]";
  const typeArray = "[object Array]";
  // 非原始类型的 不可遍历类型  Date RegExp Function
  const typeDate = "[object Date]";
  const typeRegExp = "[object RegExp]";
  const typeFunction = "[object Function]";

  // 非原始类型的 不可遍历类型的 集合(原始类型已经被过滤了不用再考虑了)
  const notForType = [typeDate, typeRegExp, typeFunction];

  // 是否是引用类型
  const isObject = (target) => {
    if (target === null) {
      return false;
    } else {
      const type = typeof target;
      return type === "object" || type === "function";
    }
  };

  // 获取标准类型
  const getType = (target) => {
    return Object.prototype.toString.call(target);
  };

  /*
   * 1、处理原始类型 Number String Boolean Symbol Null Undefined
   * 2、处理不可遍历类型 Date RegExp Function
   * 3、处理循环引用情况 WeakMap
   * 4、处理可遍历类型 Set Map Array Object
   * */
  const clone = (target, map = new WeakMap()) => {
    // 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
    if (!isObject(target)) {
      return target;
    }

    // 处理不可遍历类型
    const type = getType(target);
    if (notForType.includes(type)) {
      switch (type) {
        case typeDate:
          // 日期
          return new Date(target);
        case typeRegExp:
          // // 正则
          // var flags = "";
          // flags += target.global ? "g" : "";
          // flags += target.multiline ? "m" : "";
          // flags += target.ignoreCase ? "i" : "";
          // const regExp = new RegExp(target.source, flags);
          // regExp.lastIndex = target.lastIndex; // lastIndex 表示每次匹配时的开始位置
          // return regExp;
          // 正则
          return new RegExp(target);
        case typeFunction:
          // 函数
          return target;
        default:
          return target;
      }
    }

    // 用于返回
    let result;

    // 处理循环引用
    if (map.get(target)) {
      // 已经放入过map的直接返回
      return map.get(target);
    }

    // 处理可遍历类型
    switch (type) {
      case typeSet:
        // Set
        result = new Set();
        map.set(target, result);
        target.forEach((item) => {
          result.add(clone(item, map));
        });
        return result;
      case typeMap:
        // Map
        result = new Map();
        map.set(target, result);
        target.forEach((item, key) => {
          result.set(key, clone(item, map));
        });
        return result;
      case typeArray:
        // 数组
        result = [];
        map.set(target, result);
        target.forEach((item, index) => {
          result[index] = clone(item, map);
        });
        return result;
      case typeObject:
        // 对象
        result = {};
        map.set(target, result);
        [
          ...Object.keys(target),
          ...Object.getOwnPropertySymbols(target),
        ].forEach((item) => {
          result[item] = clone(target[item], map);
        });
        return result;
      default:
        return target;
    }
  };

  return clone(data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

参考:https://www.cnblogs.com/jsunwang/p/13410952.html (opens new window)

解决 Symbol 问题:https://www.cnblogs.com/piaobodewu/p/13767736.html (opens new window)

更新时间: 2/10/2022, 7:21:32 PM
js 循环
js 动画

← js 循环 js 动画→

最近更新
01
前端权限管理
02-24
02
vue2指令
02-24
03
vue2 hook
02-24
更多文章>
Theme by Vdoing | Copyright © 2019-2022 放肆青春
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式