总结一下 JS 中深浅拷贝的问题 :

这篇文章是对于,在 JS 中 深浅拷贝 问题的一个总结 :


前置知识 :


  • JS 中的数据类型 ?
  • 数据分类 :
  1. 基本数据类型 :(String,Number,Boolean,Null,Undefined,Symbol (ES6 中新增))。
  2. 引用数据类型 :(统称为 Object  类型,细分则有:Object,Array,Date,RegExp,Function)。
  • 特点 :
  1. 基本数据类型 的特点 :直接存储在 栈(stack)中 的数据。
  2. 引用数据类型 的特点 :存储的是该对象在 栈中的引用真实的数据 存放在 堆 内存中。
  • 总结 :引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

JS 中深浅拷贝相关 :


  • 深浅拷贝的含义 ?

首先我们要明确的一点是,深浅拷贝只是针对于 Object 和 Array 这样的引用数据类型的。

浅拷贝 :只复制指向某个对象的指针,而不是赋值对象本身,新的和旧的对象还是共享同一块内存 (地址)。

深拷贝 :  会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象的时候不会改到原对象 (地址和值)。

  • 赋值和浅拷贝有什么区别 ?

赋值 :

当我们把一个对象赋值给一个新的变量,赋的其实是该对象在栈中的地址,并不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的改变,两个对象是联动的(地址)。

浅拷贝 :

浅拷贝是按位拷贝对象,它会创建一个新的对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值 ();如果属性是内存地址(引用类型),拷贝的就是内存地址(地址),因此如果其中一个对象改变了这个地址,就会影响到另外一个对象。既:默认拷贝构造函数只是对对象进行浅拷贝复制(每个成员依次拷贝),既只复制对象空间而不复制资源。

  • 浅拷贝的实现方式 :

方法一 :ES6方法 Object.assign()

Object.assign() 方法把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的只是对象的属性的引用,而不是对象本身。

let obj = {a:{a:'wuhao',b:521}};
let newObj = Object.assign({},obj);
newObj.a.a = "hello";
console.log(obj.a.a);//hello

注意 :当 object 只有一层的时候,它等同于深拷贝 

let obj = {username:'wuhao'};
let newObj = Object.assign({},obj);
newObj.username = 'hello';
console.log(obj.username);//'hello'

方法二 :Array.prototype.concat()

只适用于类数组对象

let arr = [1,3,{username="wuhao"}];
let newArr = arr.concat();
newArr[2].username = 'hello';
console.log(arr[2].username);//'hello'

方法三 :Array.prototype.slice()

只适用于类数组对象

let arr = [1,3,{username:"wuhao"}];
let newArr = arr.slice();
newArr[2].username = "hello";
console.log(arr[2].username);//"hello"

关于 Array 的 slice 和 concat 方法的补充说明 :

Array 的 slice 和 concat 方法不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

原数组的元素会按照下述规则拷贝 :

  • 如果该元素是对象引用(不是实际的对象),slice 和 concat 会拷贝这个对象引用到新的数组中。两个对象引用都引用了同一个对象。如果被引用的对象发生变化,则新的和原来的数组中的这个元素也会发生改变。
  • 对于字符串,数字,布尔值,slice 和 concat  会拷贝这些值到新的数组中,在别的数组中修改这些字符串或者数字或者布尔值,将不会影响另一个数组。
let arr = [1, 3, {username: ' kobe'}];
let arr3 = arr.slice();
arr3[1] = 2
console.log(arr,arr3);//[1,3,{username:'kobe'}],[1,2,{username:"kebe"}]

方法四 :ES6扩展运算符

var obj = { a:1, arr: [2,3] };
var obj1 = {...obj}

方法五 :遍历赋值实现

//方法一
function deepClone(obj) {
        // 只拷贝对象
        if (typeof obj !== 'object') return;
        // 根据obj的类型判断是新建一个数组还是对象
        var newObj = obj instanceof Array ? [] : {};
        // 遍历obj,并且判断是obj的属性才拷贝
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                newObj[key] = obj[key];
            }
        }
        return newObj;
}

//=================================================//

//方法二
function deepClone(obj) {
  if (obj == null) return null;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeof obj !== 'object') return obj;
  let t = new obj.constructor
  for (let key in obj) {
    t[key] = deepClone(obj[key])
  }
  return t;
}
  • 深拷贝的实现方式 :

方法一 :JSON.parse(JSON.stringify())

原理 :JSON.stringify 将对象转成 JSON 字符串,再用 JSON.parse() 把字符串解析成 对象。一去一来,新的对象就产生了,而且对象会开辟新的栈,实现深拷贝。

let arr = [1,3,{username:'wuhao'}];
let newArr = JSON.parse(JSON.stringify(arr));
newArr[2].username = 'hello';
console.log(arr[2].username);//'wuhao'

注意 :这种方法只能实现 数组和对象的深拷贝,会忽略 undefined,symbol,不能序列化函数,不能解决循环引用的对象。

let arr = [1,3,{username:'wuhao'},function(){}];
let newArr = JSON.parse(JSON.stringify(arr));
newArr[2].username = 'hello';
console.log(arr,newArr);//[1,3,{username:'wuhao'},function(){}],[1,3,{username:'hello'}]
//这是因为JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数

方法二 : 手写递归函数

原理 :递归方式 实现深度克隆原理:遍历对象,数组直到里面都是基本数据类型,然后再去复制,就是深度拷贝。

function deepClone(obj, cloneObj) {
    var cloneObj = cloneObj || {};
    for(var i in obj) {
    // 通过遍历判断属性是否为引用类型,此处注意null因为历史遗留bug通过typeof输出为object
       if(typeof obj[i] === 'object' && typeof obj[i] !== null) {
       // 判断引用值是否为数据 obj[i] instanceof Array
       cloneObj[i] = (obj[i].constructor === Array) ? [] : {};
       // 进行递归
       deepClone(obj[i], cloneObj[i]);
       }else {
              cloneObj[i] = obj[i];
            }
       }
     return cloneObj;
}

方法三 : 函数库 lodash

函数库 lodash :该函数库中提供  _.cloneDeep 用来做深拷贝。

let _ = require('lodash');
let obj = {
    a:1,
    b:{f:{g:1}},
    c:[1,2,3]
};
let newObj = _cloneDeep(obj);
console.log(obj.b.f === newObj.b.f);//true

方法四 :JQ的 extend 方法

let a=[0,1,[2,3],4],
b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

补充 :hasOwnProperty 方法 :

hasOwnProperty 表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。

var obj = {
    a: 1,
    fn: function(){},
    c:{d: 5}
};
console.log(obj.hasOwnProperty('a'));  // true
console.log(obj.hasOwnProperty('fn'));  // true
console.log(obj.hasOwnProperty('c'));  // true
console.log(obj.c.hasOwnProperty('d'));  // true
console.log(obj.hasOwnProperty('d'));  // false, obj对象没有d属性
 
var str = new String();
// split方法是String这个对象的方法,str对象本身是没有这个split这个属性的
console.log(str.hasOwnProperty('split'));  // false
console.log(String.prototype.hasOwnProperty('split'));  // true

Symbol :

【Symbol是一种数据类型】,表示独一无二的值!

Symbol 值是通过Symbol( )函数生成的,也就是说从现在起,对象的属性名称不只是字符串了,多了一种Symbol值,并且保证是独一无二的。

具体详情请点击链接查看:详情链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值