美文网首页
JavaScript深入理解系列:深拷贝与浅拷贝

JavaScript深入理解系列:深拷贝与浅拷贝

作者: 程序员米粉 | 来源:发表于2022-04-17 21:38 被阅读0次

导读

在平时工作中或者面试中都会接触到对数据的复制,下面来实现一个比较完整的功能,提供全部思路,下面直接进入正文。

定义

回顾一下JavaScript中存在两大数据类型:
  • 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6 引入了一种新的原始数据类型,表示独一无二的值)

  • 引用数据类型:Object(Array、Function、RegExp、Date...)

浅拷贝.jpg
  • 浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝.png
  • 深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,并且在修改新对象不会影响原对象。

浅拷贝

浅拷贝常见简单用法:

  • Object.assign
  • Array.prototype.slice、Array.prototype.concat()
  • ES6拓展运算符
  • ...

Object.assign

const obj = {
    age: 20,
    array: ['one', 'two'],
    objs: {
        obj1: 'obj1',
        obj2: 'obj2'
    },
    demo: function () {
        console.log('This is demo!')
    }
}
const newObj = Object.assign({}, obj);

Array.prototype.slice

const array = [ 1, 2, 3 ];
const copyArray = array.slice(0);
copyArray[1] = "改变新数组";
console.log(copyArray) // [ 1, '改变新数组', 3 ]
console.log(array) // [ 1, 2, 3 ] 

Array.prototype.concat()

const array = [ 1, 2, 3 ];
const copyArray = array.concat();
copyArray[1] = "改变新数组";
console.log(copyArray) // [ 1, '改变新数组', 3 ]
console.log(array) // [ 1, 2, 3 ] 

ES6拓展运算符

const array = [ 1, 2, 3 ];
const copyArray = [...array];
copyArray[1] = "改变新数组";
console.log(copyArray) // [ 1, '改变新数组', 3 ]
console.log(array) // [ 1, 2, 3 ] 

深拷贝

常见的深拷贝:

  • JSON.parse(JSON.stringify())
  • _.cloneDeep() (lodash库中的方法,lodash是一套工具库,内部封装了很多字符串、数组、对象等常见数据类型的处理函数等等)
  • jQuery.extend() (jQuery中的方法)
  • 循环递归克隆数据

JSON.parse(JSON.stringify())

const obj = {
    age: 20,
    array: [ 'one', 'two' ],
    objs: {
        obj1: 'obj1',
        obj2: 'obj2'
    },
    demo: function() {
        console.log('This is demo!');
    },
    undefinedTest: undefined,
    symbolTest:  Symbol('A')
};
const newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// {
//     age: 20,
//     array: [ 'one', 'two' ],
//     objs: { obj1: 'obj1', obj2: 'obj2' }
//  }
// 缺点没有把demo方法/undefinedTest/symbolTest复制,忽略undefined、symbol和函数,所以这个克隆对象不安全,不完善

_.cloneDeep()

const _ = require('lodash'); 
const obj1 = {
    a: 1,
    b: { 
        f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
// 等于false表示新对象和原对象不是同一个内存引用地址

jQuery.extend()

const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
// 等于false表示新对象和原对象不是同一个内存引用地址

循环递归

既然是深度克隆,那就建一个新对象,然后把需要被克隆对象的每一个值都复制给新对象。

简单版本

const obj = {
    test: '我是文字',
    age: 20,
    name: undefined,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj1: 'obj1',
        obj2: {
            obj2_1: 'obj2_1'
        }
    }
};

function deepClone(data) {
    let cloneData = {};
    for (const key in data) {
        cloneData[key] = data[key];
    }
    return cloneData;
}
const newObj1 = deepClone(obj);
console.log(newObj1);
// {
//   test: '我是文字',
//   age: 20,
//   name: undefined,
//   array: [ 'one', 'two', [ 1, 2 ] ],
//   objs: { obj1: 'obj1', obj2: { obj2_1: 'obj2_1' } }
// }

简单版本版本就完成了。表面上可以打印新对象跟原来的对象一样了。但是我们上述说了,深度克隆取决于新对象和原来的对象是否共用同一个内存的地址。所以我改一下原对象某一个值,是否影响了新对象。

const obj = {
    test: '我是文字',
    age: 20,
    name: undefined,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj1: 'obj1',
        obj2: {
            obj2_1: 'obj2_1'
        }
    }
};

function deepClone(data) {
    let cloneData = {};
    for (const key in data) {
        cloneData[key] = data[key];
    }
    return cloneData;
}
const newObj1 = deepClone(obj);
console.log(newObj1);
// {
//   test: '我是文字',
//   age: 20,
//   name: undefined,
//   array: [ 'one', 'two', [ 1, 2 ] ],
//   objs: { obj1: 'obj1', obj2: { obj2_1: 'obj2_1' } }
// }
obj.age = 18;
obj.objs.obj2 = null; // 改原对象某一个值,看看是否影响新对象。
console.log(newObj1)
// {
//   test: '我是文字',
//   age: 20,
//   name: undefined,
//   array: [ 'one', 'two', [ 1, 2 ] ],
//   objs: { obj1: 'obj1', obj2: null } // obj2: null 数据被改变了
// }

上述我们也看到了,修改了原对象数据【obj.age和obj.objs.obj2】,其中只有obj.objs.obj2数据变了,影响了新对象的数据。表示obj.objs.obj2这个数据,新对象和原对象在内存中还是共用了同一个地址。对于这种对象中还有多层数据的,我们使用递归遍历克隆数据到新对象。总结一下:

  • 基本数据类型,不用继续拷贝,直接返回
  • 引用数据类型,创建新对象,遍历需要克隆的数据添加到新对象,如果有多层对象的,并将需要克隆的对象继续深度克隆后添加给新对象,这里就使用递归实现。

那就继续完善一下代码

const obj = {
    test: '我是文字',
    age: 20,
    name: undefined,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj2: {
            obj2_1: 'obj2_1'
        }
    }
};
function deepClone(data) {
    // 非对象直接返回对应的值(可能是普通的值、函数、正则等等)
    if (typeof data !== 'object') return data;
    let cloneData = {};
    for (const key in data) {
        cloneData[key] = deepClone(data[key]);
    }
    return cloneData;
}
const newObj1 = deepClone(obj);
console.log(newObj1);
// {
//     test: '我是文字',
//     age: 20,
//     name: undefined,
//     array: { '0': 'one', '1': 'two', '2': { '0': 1, '1': 2 } },
//     objs: { obj2: { obj2_1: 'obj2_1' } }
//  }
obj.age = 18;
obj.objs.obj2 = null;
console.log(newObj1);
// {
//     test: '我是文字',
//     age: 20,
//     name: undefined,
//     array: { '0': 'one', '1': 'two', '2': { '0': 1, '1': 2 } },
//     objs: { obj2: { obj2_1: 'obj2_1' } }
//   }

原对象改动了数据【obj.objs.obj2】,新对象 newObj1 不受影响。说明方法对象之间没有共用同一个内存地址,但是方法还有很多不完善,例如数组还没完善,那么我们下面继续完善。

兼容数组

上述方法只考虑了数据是object的情况下,在js中数组用typeof方法来判断数组,也是显示object,那么我们继续来完善一下方法:

function deepClone(data) {
    // 非对象直接返回对应的值(可能是普通的值、函数、正则等等)
    if (typeof data !== 'object') return data;
    // Object.prototype.toString.call(data) 和 Array.isArray 都可以用来判断数据是否数组
    // let cloneData = Array.isArray(data) === '[object Array]' ? [] : {};
    let cloneData = Object.prototype.toString.call(data) === '[object Array]' ? [] : {};
    for (const key in data) {
        cloneData[key] = deepClone(data[key]);
    }
    return cloneData;
}

循环引用

我们来看一个例子:

const obj = {
    test: '我是文字',
    age: 20,
    name: undefined,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj2: {
            obj2_1: 'obj2_1'
        }
    }
};
obj.objs = obj;
// 我们把方法改一下这样引用。然后执行方法
const newObj1 = deepClone(obj);
console.log(newObj1);
// 控制台看到
// RangeError: Maximum call stack size exceeded // 代表堆栈溢出

运行一下可以看到下图:

error.png

一般看到这种情况是堆栈溢出,原因是对象属性引用了自身的地址,循环引用了。由于直接和间接引用了自身,在克隆对象的时候,就不断的循环创建一块内存地址来存放数据,导致堆栈溢出。

解决这种情况,其实我们可以首先创建一个公共的内存空间来放这个数据,存储当前的对象和引用对象之间的关系,在需要拷贝的时候,首先去存储的公共内存空间找一下,这个公共空间有没拷贝过数据,有的话不用拷贝了,没有的话再拷贝。这样就避免了,一直循环引用,不断创建新的内存空间,导致堆栈溢出。

根据上述描述,这个公共的存储空间,就需要用到 key - value 这种数据结构形式,obj[objData] = objData; 其中objData是一个对象,由于JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。所以我们可以考虑ES6中的 map 数据结构

map数据结构:它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

Map结构有以下2种方法:

  • Map:

    • 1、本质上是键值对的集合,类似集合。
    • 2、可以遍历,方法很多可以跟各种数据格式转换。
  • WeakMap:

    • 1、只接受对象作为键名(null除外),不接受其他类型的值作为键名。
    • 2、键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的。
    • 3、不能遍历,方法有get、set、has、delete。

Map与WeakMap:WeakMap相对Map是弱引用(在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收), 可以看出来WeakMap在性能方面比Map更胜一筹,在浏览器中垃圾回收机制用来清理不会再使用的对象,在某个时刻不用的时候自动被回收了,从而降低内存泄露和数据损坏的可能性。

举个例子:Map

const goodBoy = { 
    name: '程序员米粉', 
    age: 18 
};
const mapObj = new Map();
mapObj.set(goodBoy, '程序员米粉');
goodBoy = null;

上述使用 Map 创建对象,并引用了对象 goodBoy作为键,存在一个强关联关系,虽然把goodBoy置为null,但是mapObj依然对goodBoy还是一个强引用关系,内存还是一直没有释放。

举个例子:WeakMap

const goodBoy = { 
    name: '程序员米粉', 
    age: 18 
};
const mapObj = new WeakMap();
mapObj.set(goodBoy, '程序员米粉');
goodBoy = null;

上述使用 WeakMap 创建对象,并引用了对象 goodBoy作为键,存在弱引用关系,不需要使用的时候,在下一次垃圾回收机制,浏览器会自动回收,内存得到释放。

继续完善一下代码

const obj = {
    test: '我是文字',
    age: 20,
    name: undefined,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj2: {
            obj2_1: 'obj2_1'
        }
    }
};
obj.objs = obj;
function deepClone(data, map = new WeakMap()) {
    // 非对象直接返回对应的值
    if (typeof data !== 'object') return data;
    // Object.prototype.toString.call(data) 和 Array.isArray 都可以用来判断数据是否数组
    // let cloneData = Array.isArray(data) === '[object Array]' ? [] : {};
    let cloneData = Object.prototype.toString.call(data) === '[object Array]' ? [] : {};
    // 内存公共空间是否有复制过的数据,有就直接返回
    if (map.get(data)) return map.get(data);
    // 存在公共空间对象数据
    map.set(data, cloneData);
    for (const key in data) {
        cloneData[key] = deepClone(data[key], map); // 这里记得存 map
    }
    return cloneData;
}
const newObj1 = deepClone(obj);
结论:假设数据量很大的情况下,在考虑性能方面上,优先选用WeakMap,避免过多的消耗,毕竟这也算JavaScript性能优化的一部分,能优化则优化的原则,让我们写的程序达到最优。

兼容各种数据类型

目前只对普通object和array进行了拷贝。还有其它详细的数据类型没有拷贝,我们详细的列出来,一个一个分别归类进行拷贝。归类的前提需要对数据进行类型判断,我们使用 Object.prototype.toString.call 进行判断。

// Boolean 类型,tag 为 "Boolean"
Object.prototype.toString.call(true);            // => "[object Boolean]"

// Number 类型,tag 为 "Number"
Object.prototype.toString.call(1);               // => "[object Boolean]"

// String 类型,tag 为 "String"
Object.prototype.toString.call("");              // => "[object String]"

// Array 类型,tag 为 "String"
Object.prototype.toString.call([]);              // => "[object Array]"

// Arguments 类型,tag 为 "Arguments"
Object.prototype.toString.call((function() {
return arguments;
})());                                           // => "[object Arguments]"

// Function 类型, tag 为 "Function"
Object.prototype.toString.call(function(){});    // => "[object Function]"

// Error 类型(包含子类型),tag 为 "Error"
Object.prototype.toString.call(new Error());     // => "[object Error]"

// RegExp 类型,tag 为 "RegExp"
Object.prototype.toString.call(/\d+/);           // => "[object RegExp]"

// Date 类型,tag 为 "Date"
Object.prototype.toString.call(new Date());      // => "[object Date]"

// 其他类型,tag 为 "Object"
Object.prototype.toString.call(new class {});    // => "[object Object]

...

// 获取数据类型
function getType(data) {
    return Object.prototype.toString.call(data);
}

那我整理一下标识,这些都是平常用,其实还有更多的,有兴趣的话,自己再查详细点文档,这里不展开

// 可遍历的类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

// 不可遍历的类型
const booleanTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
...

上述判断数据类型,出现可遍历属性和不可遍历属性,主要是可遍历属性我们需要用到这些对象原型prototype方法和构造函数constructor,需要遍历这些对象原型上和构造函数上的方法。下面我们就用 constructor这种方式来获取。

function getInit(data) {
    const newCtor = data.constructor;
    return new newCtor();
}

继续改造

const map = new Map();
map.set('flag', '程序员米粉');
const set = new Set();
set.add('程序员米粉');

const obj = {
    test: '我是文字',
    age: 20,
    name: undefined,
    nullFlag: null,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj2: {
            a: 1
        }
    },
    map,
    set
};
// 可遍历类型
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const mapTag = '[object Map]';
const setTag = '[object Set]';
const argsTag = '[object Arguments]';


// 不可遍历类型
const numberTag = '[object Number]';
const stringTag = '[object String]';
const booleanTag = '[object Boolean]';
const dateTag = '[object Date]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
// ....
// 可遍历类型数据标识
const deeptTagList = [ mapTag, setTag, arrayTag, objectTag, argsTag ];
// 获取数据类型
function getType(data) {
    return Object.prototype.toString.call(data);
}
// 初始类型
function getInit(data) {
    const NewCtor = data.constructor;
    return new NewCtor();
}
// 克隆正则
function deepCloneReg(obj) {
    const data = new obj.constructor(obj.source, /\w*$/.exec(targe));
    data.lastIndex = obj.lastIndex;
    return data;
}
// 其它类型
function otherType(obj, type) {
    const NewCtor = obj.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case symbolTag:
        case dateTag:
            return new NewCtor(obj);
        case regexpTag:
            return deepCloneReg(obj);
        default:
            return null;
    }
}
function deepClone(data, map = new WeakMap()) {
    // 非对象直接返回对应的值
    if (typeof data !== 'object' || !data) return data;
    let cloneData = null;
    // 判断数据类型
    const type = getType(data);
    // 判断是否可遍历类型
    if (deeptTagList.includes(type)) {
        cloneData = getInit(data, type);
    }
    // 克隆map || 克隆set
    if (type === mapTag || type === setTag) {
        data.forEach((val, key) => {
            if (type === mapTag) {
                // 克隆map
                cloneData.set(key, deepClone(val, map));
            } else {
                // 克隆set
                cloneData.add(key, deepClone(val, map));
            }
        });
        return cloneData;
    }
    // Object.prototype.toString.call(data) 和 Array.isArray 都可以用来判断数据是否数组
    // let cloneData = Array.isArray(data) === '[object Array]' ? [] : {};
    cloneData = Object.prototype.toString.call(data) === '[object Array]' ? [] : {};
    // 内存公共空间是否有复制过的数据,有就直接返回
    if (map.get(data)) return map.get(data);
    // 存在公共空间对象数据
    map.set(data, cloneData);
    for (const key in data) {
        cloneData[key] = deepClone(data[key], map); // 这里记得存 map
    }
    return cloneData;
}
const newObj1 = deepClone(obj);
console.log(newObj1);
// {
//     test: '我是文字',
//     age: 20,
//     name: undefined,
//     nullFlag: null,
//     array: [ 'one', 'two', [ 1, 2 ] ],
//     objs: { obj2: { a: 1 } },
//     map: Map(1) { 'flag' => '程序员米粉' },
//     set: Set(1) { '程序员米粉' }
//   }

我们拷贝已经完成了80%了,下面我们继续完成没有完成的类型:String、Number、Boolean、Date、Error

// 其它类型
function otherType(data, type) {
    const NewCtor = data.constructor;
    switch (type) {
        case booleanTag:
            return new Boolean(data);
        case numberTag:
            return new Number(data);
        case stringTag:
            return new String(data);
        case errorTag:
            return new Error(data);
        case symbolTag:
            return new Symbol(data); // 这个我偷懒了,感兴趣可以查一下文档,这里不详细写了
        case dateTag:
            return new NewCtor(data);
        case regexpTag:
            return deepCloneReg(data);
        // 还有函数、等等 可以到loadsh工具库看一下
        default:
            return null;
    }
}
// 克隆正则
function deepCloneReg(obj) {
    const data = new obj.constructor(obj.source, /\w*$/.exec(targe));
    data.lastIndex = obj.lastIndex;
    return data;
}

完整版

const map = new Map();
map.set('flag', '程序员米粉');
const set = new Set();
set.add('程序员米粉');

const obj = {
    name: undefined,
    nullFlag: null,
    array: [ 'one', 'two', [ 1, 2 ] ],
    objs: {
        obj2: {
            a: 1
        }
    },
    map,
    set,
    str: '程序员米粉',
    boolean_: true,
    num: 20,
    symbol: Object(Symbol('程序员米粉')),
    date: new Date(),
    reg: /\d+/,
    error: new Error('错误信息'),
    fn: function(a) {
        return a + b;
    }
};
// 可遍历类型
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const mapTag = '[object Map]';
const setTag = '[object Set]';
const argsTag = '[object Arguments]';

// 不可遍历类型
const numberTag = '[object Number]';
const stringTag = '[object String]';
const booleanTag = '[object Boolean]';
const dateTag = '[object Date]';

const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
// ....
// 可遍历类型数据标识
const deeptTagList = [ mapTag, setTag, arrayTag, objectTag, argsTag ];
// 获取数据类型
function getType(data) {
    return Object.prototype.toString.call(data);
}
// 初始类型
function getInit(data) {
    const NewCtor = data.constructor;
    return new NewCtor();
}

// 其它类型
function otherType(data, type) {
    const NewCtor = data.constructor;
    switch (type) {
        case booleanTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case symbolTag:
            return data;
        case dateTag:
            return new NewCtor(data);
        case regexpTag:
            return deepCloneReg(data);
        // 还有函数、等等 可以到loadsh工具库看一下
        default:
            return null;
    }
}
// 克隆正则
function deepCloneReg(obj) {
    const data = new obj.constructor(obj.source, /\w*$/.exec(obj));
    data.lastIndex = obj.lastIndex;
    return data;
}
function deepClone(data, map = new WeakMap()) {
    // 非对象直接返回对应的值
    if (typeof data !== 'object' || !data) return data;
    let cloneData = null;
    // 判断数据类型
    const type = getType(data);
    // 判断是否可遍历类型
    if (deeptTagList.includes(type)) {
        cloneData = getInit(data, type);
    } else {
        // 不可遍历 
        return otherType(data, type);
    }
    // 克隆map || 克隆set
    if (type === mapTag || type === setTag) {
        data.forEach((val, key) => {
            if (type === mapTag) {
                // 克隆map
                cloneData.set(key, deepClone(val, map));
            } else {
                // 克隆set
                cloneData.add(key, deepClone(val, map));
            }
        });
        return cloneData;
    }
    // Object.prototype.toString.call(data) 和 Array.isArray 都可以用来判断数据是否数组
    // let cloneData = Array.isArray(data) === '[object Array]' ? [] : {};
    cloneData = Object.prototype.toString.call(data) === '[object Array]' ? [] : {};
    // 内存公共空间是否有复制过的数据,有就直接返回
    if (map.get(data)) return map.get(data);
    // 存在公共空间对象数据
    map.set(data, cloneData);
    for (const key in data) {
        cloneData[key] = deepClone(data[key], map); // 这里记得存 map
    }
    return cloneData;
}
const newObj1 = deepClone(obj);

最后输出

错误信息.png

基本的全部功能基本实现,有部分更细节的地方,自己可以查阅相关文档或者lodash工具库,例如:克隆Symbol类型、
克隆Function类型、等等,我这边没有添加上。

参考

结语

希望看完这篇文章对你有帮助:

  • 理解深浅拷贝背后拷贝的原理
  • 手写一个比较完整的深拷贝方法
  • 理解实践这个功能的思路

文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞和关注,后续会输出更好的分享。

欢迎关注公众号:【程序员米粉】
公众号分享开发编程、职场晋升、大厂面试经验

相关文章

  • 浅拷贝和深拷贝

    本文参考:JavaScript中的浅拷贝和深拷贝js 深拷贝 vs 浅拷贝深入剖析 JavaScript 的深复制...

  • JavaScript深入理解系列:深拷贝与浅拷贝

    导读 定义 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol(...

  • 深入理解JavaScript中的堆与栈 、浅拷贝与深拷贝

    JavaScript中的浅拷贝与深拷贝 学了这么长时间的JavaScript想必大家对浅拷贝和深拷贝还不太熟悉吧,...

  • iOS之深拷贝与浅拷贝

    深拷贝与浅拷贝是在内存管理中非常重要的概念,理解好深拷贝和浅拷贝也有助于加深对iOS的内存管理的理解。 深拷贝与浅...

  • 理解JavaScript中浅拷贝和深拷贝的区别

    要理解 JavaScript中浅拷贝和深拷贝的区别,首先要明白JavaScript的数据类型 JavaScript...

  • iOS 深浅拷贝

    iOS深拷贝与浅拷贝的区别 深拷贝与浅拷贝的概念:(我的理解,望不吝赐教) 浅拷贝:只copy一份对象的指针,指向...

  • iOS面试题-第二页

    11.深拷贝和浅拷贝的理解. 深拷贝;拷贝的内容. 浅拷贝:拷贝的指针. 深拷贝如: NSMutableDicti...

  • 深拷贝 浅拷贝

    深入认识深拷贝 浅拷贝 公共方法 // 深拷贝 浅拷贝 对于基本类型来说 对于赋值操作 就是 深拷贝 因为值...

  • 深浅拷贝,面向对象,Symbol,Set,Map

    一:浅拷贝与深拷贝对象的浅拷贝:浅拷贝是对象共用的一个内存地址,对象的变化相互影响。对象的深拷贝:简单理解深拷贝是...

  • js浅拷贝和深拷贝

    javaScript的变量类型 javaScript的变量类型基本类型:引用类型: 浅拷贝和深拷贝的区分 浅拷贝浅...

网友评论

      本文标题:JavaScript深入理解系列:深拷贝与浅拷贝

      本文链接:https://www.haomeiwen.com/subject/bacqertx.html