• Copy


    深拷贝

    Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.

    1. var a = {name:1, sya:function(){ console.log("打印")},age:undefined}; // {name:1,sya:f}
    2. var b = JSON.parse(JSON.stringify(a)); // {name:1}
    • 拷贝的对象的值中如果有 function、 undefined、 symbol 这几种类型, 经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
    • 拷贝 Date 引用类型会变成字符串;
    • 无法拷贝不可枚举的属性;
    • 无法拷贝对象的原型链;
    • 拷贝 RegExp 引用类型会变成空对象;
    • 对象中含有 NaN、 Infinity 以及 - Infinity, JSON 序列化的结果会变成 null;
    • 无法拷贝对象的循环应用, 即对象成环(obj[key] = obj)
    1. function Obj() {
    2. this.func = function () {
    3. alert(1)
    4. };
    5. this.obj = {
    6. a: 1
    7. };
    8. this.arr = [1, 2, 3];
    9. this.und = undefined;
    10. this.reg = /123/;
    11. this.date = new Date(0);
    12. this.NaN = NaN;
    13. this.infinity = Infinity;
    14. this.sym = Symbol(1);
    15. }
    16. let obj1 = new Obj();
    17. Object.defineProperty(obj1, 'innumerable', {
    18. enumerable: false,
    19. value: 'innumerable'
    20. });
    21. console.log('obj1', obj1);
    22. let str = JSON.stringify(obj1);
    23. let obj2 = JSON.parse(str);
    24. console.log('obj2', obj2);

    1. // 深拷贝
    2. var sourceObj = {
    3. name: 'tt',
    4. age: 18,
    5. job: 'web',
    6. friends: ['t1', 't2']
    7. }
    8. // util作为判断变量具体类型的辅助模块
    9. var util = (function() {
    10. var class2Type = {}
    11. var objTypes = ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date”]
    12. objTypes.forEach(function(item) {
    13. class2Type['[object ' + item + ']'] = item.toLowerCase()
    14. })
    15. function isType(obj, type) {
    16. return getType(obj) === type
    17. }
    18. function getType(obj) {
    19. return class2type[Object.prototype.toString.call(obj)] || 'object'
    20. }
    21. return {
    22. isType: isType,
    23. getType: getType
    24. }
    25. })()
    26. // deep参数用来判断是否是深度复制
    27. function copy(obj, deep){
    28. // 如果obj不是对象,那么直接返回值就可以了
    29. if(obj == null || typeof obj !== 'object'){
    30. return obj
    31. }
    32. // 定义需要的局部变量,根据obj的类型来调整target的类型
    33. var i,
    34. target = util.isType(obj,"array") ? [] : {},
    35. value,
    36. valueType
    37. for(i in obj){
    38. value = obj[i]
    39. valueType = util.getType(value)
    40.     // 只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制
    41. if(deep && (valueType === "array" || valueType === "object")){
    42. target[i] = copy(value)
    43. }else{
    44. target[i] = value
    45. }
    46. }
    47. return target
    48. }
    49. var targetObj = copy(sourceObj, true);
    50. targetObj.friends.push ('t3');
    51. console.log(sourceObj) // ['t1', 't2']
    1. // 深拷贝
    2. Object.prototype.clone = function() {
    3. var Constructor = this.constructor
    4. var obj = new Constructor()
    5. for (var attr in this) {
    6. if (this.hasOwnProperty(attr)) {
    7. if (typeof this[attr] !== 'function') {
    8. if (this[attr] === null) {
    9. obj[attr] = null
    10. } else {
    11. obj[attr] = this[attr].clone()
    12. }
    13. }
    14. }
    15. }
    16. return obj
    17. }
    18. /* Method of Array*/
    19. Array.prototype.clone = function() {
    20. var thisArr = this.valueOf()
    21. var newArr = []
    22. for (var i = 0; i < thisArr.length; i++) {
    23. newArr.push(thisArr[i].clone())
    24. }
    25. return newArr
    26. }
    27. /* Method of Boolean, Number, String*/
    28. Boolean.prototype.clone = function() {
    29. return this.valueOf()
    30. }
    31. Number.prototype.clone = function() {
    32. return this.valueOf()
    33. }
    34. String.prototype.clone = function() {
    35. return this.valueOf()
    36. }
    37. /* Method of Date*/
    38. Date.prototype.clone = function() {
    39. return new Date(this.valueOf())
    40. }
    41. /* Method of RegExp*/
    42. RegExp.prototype.clone = function() {
    43. var pattern = this.valueOf()
    44. var flags = ''
    45. flags += pattern.global ? 'g' : ''
    46. flags += pattern.ignoreCase ? 'i' : ''
    47. flags += pattern.multiline ? 'm' : ''
    48. return new RegExp(pattern.source, flags)
    49. }
    1. function deepClone(source){
    2. // 判断复制的目标是数组还是对象
    3. const targetObj = source.constructor === Array ? [] : {};
    4. for(let keys in source){ // 遍历目标
    5. if(source.hasOwnProperty(keys)){
    6. if(source[keys] && typeof source[keys] === 'object'){
    7. targetObj[keys] = source[keys].constructor === Array ? [] : {};
    8. targetObj[keys] = deepClone(source[keys]);
    9. }else{ // 如果不是,就直接赋值
    10. targetObj[keys] = source[keys];
    11. }
    12. }
    13. }
    14. return targetObj;
    15. }
    16. //测试一下
    17. var a = {name:1, sya:function(){ console.log("打印")},age:undefined};
    18. var b = deepClone(a);
    19. b.name=2;
    20. console.log(a)//{name:1,sya:f,age:undefined}
    21. console.log(b)//{name:2,sya:f,age:undefined}
    22. // 深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
    23. // 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、ErrorFunction 这样的引用类型并不能正确地拷贝;
    24. // 对象的属性里面成环,即循环引用没有解决。
    1. 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;

    2. 当参数为 Date、RegExp 类型,则直接生成一个新的实例返回;

    3. 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链;

    4. 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏,作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。

    1. const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null);
    2. const deepClone = function (obj, hash = new WeakMap()) {
    3. if (obj.constructor === Date) return new Date(obj) // 日期对象直接返回一个新的日期对象
    4. if (obj.constructor === RegExp) return new RegExp(obj) // 正则对象直接返回一个新的正则对象
    5. if (hash.has(obj)) return hash.get(obj) // 如果循环引用了就用 weakMap 来解决
    6. let allDesc = Object.getOwnPropertyDescriptors(obj); // 遍历传入参数所有键的特性
    7. // ts报错
    8. // (<any>Object).getOwnPropertyDescriptors(obj); // 遍历传入参数所有键的特性
    9. // (Object as any).getOwnPropertyDescriptors(obj); // 遍历传入参数所有键的特性
    10. let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); //继承原型链
    11. hash.set(obj, cloneObj);
    12. for (let key of Reflect.ownKeys(obj)) {
    13. cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
    14. }
    15. return cloneObj
    16. }
    17. // 下面是验证代码
    18. let obj = {
    19. num: 0,
    20. str: '',
    21. boolean: true,
    22. unf: undefined,
    23. nul: null,
    24. obj: {
    25. name: '我是一个对象',
    26. id: 1
    27. },
    28. arr: [0, 1, 2],
    29. func: function () {
    30. console.log('我是一个函数')
    31. },
    32. date: new Date(0),
    33. reg: new RegExp('/我是一个正则/ig'),
    34. [Symbol('1')]: 1,
    35. };
    36. Object.defineProperty(obj, 'innumerable', {
    37. enumerable: false,
    38. value: '不可枚举属性'
    39. });
    40. obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
    41. obj.loop = obj // 设置loop成循环引用的属性
    42. let cloneObj = deepClone(obj)
    43. cloneObj.arr.push(4)
    44. console.log('obj', obj)
    45. console.log('cloneObj', cloneObj)

    1. // 深拷贝 lodash _.cloneDeep()
    2. var _ = require("lodash");
    3. var obj1 = {
    4. a: 1,
    5. b: { f: { g: 1 } },
    6. c: [1, 2, 3],
    7. };
    8. var obj2 = _.cloneDeep(obj1);
    9. console.log(obj1.b.f === obj2.b.f);
    1. // 深拷贝 jQuery.extend()
    2. // $.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
    3. var $ = require("jquery");
    4. var obj1 = {
    5. a: 1,
    6. b: { f: { g: 1 } },
    7. c: [1, 2, 3],
    8. };
    9. var obj2 = $.extend(true, {}, obj1);
    10. console.log(obj1.b.f === obj2.b.f);

    MessageChannel 方式

    • 实现
      • 建立两个端,一个端发送消息,另一个端接收消息
    • 优点
      • 能解决循环引用的问题,还支持大量的内置数据类型。
    • 缺点
      • 这个方法是异步的
    1. // 深度拷贝 使用 MessageChannel 方式
    2. function structuralClone(obj) {
    3. return new Promise((resolve) => {
    4. const { port1, port2 } = new MessageChannel();
    5. port2.onmessage = (ev) => resolve(ev.data);
    6. port1.postMessage(obj);
    7. });
    8. }
    9. const obj = {
    10. name: 'SeriousLose',
    11. obj: {
    12. name: 'fly',
    13. },
    14. };
    15. structuralClone(obj).then((res) => {
    16. console.log(res);
    17. });

    History API 方式

    • 实现
      • 利用history.replaceState。这个api在做单页面应用的路由时可以做无刷新的改变url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页面中不要把原有的路由逻辑搞乱了。所以我们在克隆完一个对象的时候,要恢复路由的原状
    • 优点
      • 能解决循环引用的问题,还支持大量的内置数据类型。同步的
    • 缺点
      • 有的浏览器对调用频率有限制。比如Safari 30 秒内只允许调用 100 次
    1. // history.replaceState 方式
    2. function structuralClone(obj) {
    3. const oldState = history.state;
    4. history.replaceState(obj, document.title);
    5. const copy = history.state;
    6. history.replaceState(oldState, document.title);
    7. return copy;
    8. }
    9. var obj = {};
    10. var b = { obj };
    11. obj.b = b;
    12. var copy = structuralClone(obj);
    13. console.log(copy);

    Notification API

    • Notification API 主要是用于桌面通知的
    • 在浏览器的右下角有一个弹窗
    • 优点
      • 能解决循环引用的问题,还支持大量的内置数据类型。同步的
    • 缺点
      • 这个api的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在http协议的情况下会提示你再https的场景下使用。
    1. // Notification API
    2. function structuralClone(obj) {
    3. return new Notification('', { data: obj, silent: true }).data;
    4. }
    5. var obj = {};
    6. var b = { obj };
    7. obj.b = b;
    8. var copy = structuralClone(obj);
    9. console.log(copy);

    考验能力

    基础编码能力

    递归编码能力

    代码的严谨性

    代码抽象能力

    js编码能力

    熟练掌握WeakMap的能力

    引用类型各种AP的熟练程度

    准确判断JS各种数据类型的能力

    考虑问题全面性

    边界情况

    解决循环引用

    1. // 练习题1
    2. var a = {n: 1}
    3. var b = a;
    4. a.x = a = {n: 2}
    5. console.log(a.x); // undefined
    6. console.log(b.x); // {n: 2}
    堆内存
    var a = {n: 1}a{n: 1}
    var b = a;a、b{n: 1}
    .的优先级高于=号,先计算.   a.x = a = {n:2}
    a.x = a = {n: 2}a、b{n: 1,x=a={n:2}}
    a={n:2} 开辟新堆内存,a引用地址改变;b引用地址未改变
    x=a={n:2}a{n:2}
    b{n:1,x:{n:2}}
    1. // 练习题2
    2. var a = {n: 1}
    3. var b = a;
    4. b.n = a.f = {n: 2};
    5. console.log(a); // {n:{n:2},f:{n:2}}
    6. console.log(b); // {n:{n:2},f:{n:2}}
    堆内存
    var a = {n: 1}a{n: 1}
    var b = a;a、b{n: 1}
    .的优先级高于=号,先计算.   b.n = a.f = {n:2}
    b.n = a.f = {n:2}a、b{n=a.f={n:2}}
    a.f = {n:2}a、b{n:{n:2},f:{n:2}}

    一文读懂 javascript 深拷贝与浅拷贝

  • 相关阅读:
    2.6一个小工具的使用snipaste
    LeetCode416 分割等和子集
    【langchain手把手2】构造Prompt模版
    nginx动态新增模块
    WebDAV之葫芦儿·派盘+读出通知
    数据库连接池知识点总结-DX的笔记
    【python】接入MySQL实际操作案例
    VUE3照本宣科——响应式与生命周期钩子
    开发人员请注意:在 PyPI 上的 Python 包中发现 BlazeStealer 恶意软件
    实现登陆模块时Cookie,Session,Token的理解
  • 原文地址:https://blog.csdn.net/SeriousLose/article/details/128184817