Copy

深拷贝

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

var a = {name:1, sya:function(){ console.log("打印")},age:undefined}; // {name:1,sya:f}

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

// 深拷贝
var sourceObj = {
  name: 'tt',
  age: 18,
  job: 'web',
  friends: ['t1', 't2']
}

// util作为判断变量具体类型的辅助模块
var util = (function() {
  var class2Type = {}
  var objTypes = ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date”]

  objTypes.forEach(function(item) {
    class2Type['[object ' + item + ']'] = item.toLowerCase()
  })

  function isType(obj, type) {
    return getType(obj) === type
  }

  function getType(obj) {
    return class2type[Object.prototype.toString.call(obj)] || 'object'
  }

  return {
    isType: isType,
    getType: getType
  }
})()

// deep参数用来判断是否是深度复制
function copy(obj, deep){
  // 如果obj不是对象,那么直接返回值就可以了
  if(obj == null || typeof obj !== 'object'){
    return obj
  }

  // 定义需要的局部变量,根据obj的类型来调整target的类型
   var i,
   target = util.isType(obj,"array") ? [] : {},
   value,
   valueType

   for(i in obj){
        value = obj[i]
        valueType = util.getType(value)
      // 只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制
        if(deep && (valueType === "array" || valueType === "object")){
            target[i] = copy(value)
        }else{
            target[i] = value
        }
    }
    return target
}

var targetObj = copy(sourceObj, true);
targetObj.friends.push ('t3');
console.log(sourceObj) // ['t1', 't2']
// 深拷贝
Object.prototype.clone = function() {
  var Constructor = this.constructor
  var obj = new Constructor()
  for (var attr in this) {
    if (this.hasOwnProperty(attr)) {
      if (typeof this[attr] !== 'function') {
        if (this[attr] === null) {
          obj[attr] = null
        } else {
          obj[attr] = this[attr].clone()
        }
      }
    }
  }
  return obj
}

/* Method of Array*/
Array.prototype.clone = function() {
  var thisArr = this.valueOf()
  var newArr = []
  for (var i = 0; i < thisArr.length; i++) {
    newArr.push(thisArr[i].clone())
  }
  return newArr
}
/* Method of Boolean, Number, String*/
Boolean.prototype.clone = function() {
  return this.valueOf()
}
Number.prototype.clone = function() {
  return this.valueOf()
}
String.prototype.clone = function() {
  return this.valueOf()
}
/* Method of Date*/
Date.prototype.clone = function() {
  return new Date(this.valueOf())
}
/* Method of RegExp*/
RegExp.prototype.clone = function() {
  var pattern = this.valueOf()
  var flags = ''
  flags += pattern.global ? 'g' : ''
  flags += pattern.ignoreCase ? 'i' : ''
  flags += pattern.multiline ? 'm' : ''
  return new RegExp(pattern.source, flags)
}
function deepClone(source){
  // 判断复制的目标是数组还是对象
  const targetObj = source.constructor === Array ? [] : {}; 
  for(let keys in source){ // 遍历目标
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){ 
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // 如果不是,就直接赋值
        targetObj[keys] = source[keys];
      }
    } 
  }
  return targetObj;
}
//测试一下
var a = {name:1, sya:function(){ console.log("打印")},age:undefined}; 
var b = deepClone(a);
b.name=2;
console.log(a)//{name:1,sya:f,age:undefined}
console.log(b)//{name:2,sya:f,age:undefined}

// 深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
// 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
// 对象的属性里面成环,即循环引用没有解决。
  1. 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;

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

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

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

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null);

  const deepClone = function (obj, hash = new WeakMap()) {
    if (obj.constructor === Date) return new Date(obj) // 日期对象直接返回一个新的日期对象
    if (obj.constructor === RegExp) return new RegExp(obj) // 正则对象直接返回一个新的正则对象
    if (hash.has(obj)) return hash.get(obj)  // 如果循环引用了就用 weakMap 来解决
    let allDesc = Object.getOwnPropertyDescriptors(obj); // 遍历传入参数所有键的特性 
                 // ts报错
                 // (<any>Object).getOwnPropertyDescriptors(obj); // 遍历传入参数所有键的特性
           // (Object as any).getOwnPropertyDescriptors(obj); // 遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); //继承原型链
    hash.set(obj, cloneObj);
    for (let key of Reflect.ownKeys(obj)) {
      cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
    }
    return cloneObj
  }

  // 下面是验证代码
  let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
      name: '我是一个对象',
      id: 1
    },
    arr: [0, 1, 2],
    func: function () {
      console.log('我是一个函数')
    },
    date: new Date(0),
    reg: new RegExp('/我是一个正则/ig'),
    [Symbol('1')]: 1,
  };
  Object.defineProperty(obj, 'innumerable', {
    enumerable: false,
    value: '不可枚举属性'
  });
  obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
  obj.loop = obj // 设置loop成循环引用的属性
  let cloneObj = deepClone(obj)
  cloneObj.arr.push(4)
  console.log('obj', obj)
  console.log('cloneObj', cloneObj)

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

  var $ = require("jquery");
  var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3],
  };
  var obj2 = $.extend(true, {}, obj1);
  console.log(obj1.b.f === obj2.b.f);

MessageChannel 方式

  • 实现
    • 建立两个端,一个端发送消息,另一个端接收消息
  • 优点
    • 能解决循环引用的问题,还支持大量的内置数据类型。
  • 缺点
    • 这个方法是异步的
// 深度拷贝 使用 MessageChannel 方式

  function structuralClone(obj) {
    return new Promise((resolve) => {
      const { port1, port2 } = new MessageChannel();
      port2.onmessage = (ev) => resolve(ev.data);
      port1.postMessage(obj);
    });
  }
  const obj = {
    name: 'SeriousLose',
    obj: {
      name: 'fly',
    },
  };
  structuralClone(obj).then((res) => {
    console.log(res);
  });

History API 方式

  • 实现
    • 利用history.replaceState。这个api在做单页面应用的路由时可以做无刷新的改变url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页面中不要把原有的路由逻辑搞乱了。所以我们在克隆完一个对象的时候,要恢复路由的原状
  • 优点
    • 能解决循环引用的问题,还支持大量的内置数据类型。同步的
  • 缺点
    • 有的浏览器对调用频率有限制。比如Safari 30 秒内只允许调用 100 次
// history.replaceState 方式

  function structuralClone(obj) {
    const oldState = history.state;
    history.replaceState(obj, document.title);
    const copy = history.state;
    history.replaceState(oldState, document.title);
    return copy;
  }

  var obj = {};
  var b = { obj };
  obj.b = b;
  var copy = structuralClone(obj);
  console.log(copy);

Notification API

  • Notification API 主要是用于桌面通知的
  • 在浏览器的右下角有一个弹窗
  • 优点
    • 能解决循环引用的问题,还支持大量的内置数据类型。同步的
  • 缺点
    • 这个api的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在http协议的情况下会提示你再https的场景下使用。
// Notification API

  function structuralClone(obj) {
    return new Notification('', { data: obj, silent: true }).data;
  }

  var obj = {};
  var b = { obj };
  obj.b = b;
  var copy = structuralClone(obj);
  console.log(copy);

考验能力

基础编码能力

递归编码能力

代码的严谨性

代码抽象能力

js编码能力

熟练掌握WeakMap的能力

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

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

考虑问题全面性

边界情况

解决循环引用

// 练习题1
var a = {n: 1}  
var b = a;  
a.x = a = {n: 2}  
console.log(a.x);  // undefined
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}}
// 练习题2
var a = {n: 1}  
var b = a;  
b.n = a.f = {n: 2};
console.log(a);  // {n:{n:2},f:{n:2}}
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 深拷贝与浅拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值