js 对象深拷贝_这一次,彻底理解JavaScript深拷贝

1a89d8c500950541afc79f5f3c8a3820.png

导语

这一次,通过本文彻底理解JavaScript深拷贝!

阅读本文前可以先思考三个问题:

  • JS世界里,数据是如何存储的?
  • 深拷贝和浅拷贝的区别是什么?
  • 如何写出一个真正合格的深拷贝?

本文会一步步解答这三个问题

数据是如何存储的

先看一个问题,下面这段代码的输出结果是什么:

function foo(){
    let a = {name:"dellyoung"}
    let b = a
    a.name = "dell" 
    console.log(a)
    console.log(b)
}
foo()

JS的内存空间

要解答这个问题就要先了解,JS中数据是如何存储的。

要理解JS中数据是如何存储的,就要先明白其内存空间的种类。下图就是JS的内存空间模型。

5355455bdf3331ae95721a220bfd46c0.png

从模型中我们可以看出JS内存空间分为:代码空间、栈空间、堆空间。

「代码空间」:代码空间主要是存储可执行代码的。

「栈空间」:栈(call stack)指的就是调用栈,用来存储执行上下文的。(每个执行上下文包括了:变量环境、词法环境)

「堆空间」:堆(Heap)空间,一般用来存储对象的。

JS的数据类型

现在我们已经了解JS内存空间了。接下来我们了解一下JS中的数据类型 :

8b07ddbbd975a0c18f1c85c624100304.png

JS中一共有8中数据类型:Number、BigInt、String、Boolean、Symble、Null、Undefined、Object。

前7种称为「原始类型」,最后一种Object称为「引用类型」,之所以把它们区分成两种类型,是因为「它们在内存中存放的位置不同」

原始类型存放在栈空间中,具体点到执行上下文来说就是:用var定义的变量会存放在变量环境中,而用let、const定义的变量会存放在词法环境中。并且对原始类型来说存放的是值,而引用类型存放的是指针,指针指向堆内存中存放的真正内容。

好啦,现在我们就明白JS中数据是如何存储的了:「原始类型存放在栈空间中,引用类型存放在堆空间中」

深拷贝和浅拷贝的区别

我们先来明确一下深拷贝和浅拷贝的定义:

浅拷贝

❝ 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以修改新拷贝的对象会影响原对象。

26c2034735464836c73e5b4f2a870301.png

深拷贝

❝ 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

dd4fb2d4dc7d163204f9576f34a5a135.png

接下来我们就开始逐步实现一个深拷贝

自带版

一般情况下如果不使用loadsh的深拷贝函数,我们可能会这样写一个深拷贝函数

JSON.parse(JSON.stringify());

但是这个方法局限性比较大:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

显然这绝对不是我们想要的一个合格的深拷贝函数

基本版

手动实现的话我们很容易写出如下函数

const clone = (target) => {
    let cloneTarget = {};
    Object.keys(target).forEach((item) => {
        cloneTarget[item] = target[item]
    });
    return cloneTarget
}

先看下这个函数做了什么:创建一个新对象,遍历原对象,并且将需要拷贝的对象依次添加到新对象上,返回新对象。

既然是深拷贝的话,对于引用了类型我们不知道对象属性的深度,我们可以通过递归来解决这个问题,接下来我们修改一下上面的代码:

  • 判断是否是引用类型,如果是原始类型的话直接返回就可以了。
  • 如果是原始类型,那么我们需要创建一个对象,遍历原对象,将需要拷贝的对象「执行深拷贝后」再依次添加到新对象上。
  • 另外如果对象有更深层次的对象,我们就可以通过递归来解决。

这样我们就实现了一个最基本的深拷贝函数:

// 是否是引用类型
const isObject = (target) => {
    return typeof target === 'object';
};

const clone = (target) => {
    // 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
    if (!isObject(target)) {
        return target;
    }
    let cloneTarget = {};
    Object.keys(target).forEach((item) => {
        cloneTarget[item] = clone(target[item])
    });
    return cloneTarget
}

显然这个深拷贝函数还有很多缺陷,比如:没有考虑包含数组的情况

考虑数组

上面代码中,我们只考虑了是object的情况,并没有考虑存在数组的情况。改成兼容数组也非常简单:

  • 判断传入的对象是数组还是对象,我们分别对它们进行处理
  • 判断类型的方法有很多比如 type of、instanceof,但是这两种方法缺陷都比较多,这里我使用的是Object.prototype.toString.call()的方法,它可以精准的判断各种类型
  • 当判断出是数组时,那么我们需要创建一个新数组,遍历原数组,将需要数组中的每个值「执行深拷贝后」再依次添加到新的数组上,返回新数组。

代码如下:

const typeObject = '[object Object]';
const typeArray = '[object Array]';
// 是否是引用类型
const isObject = (target) => {
    return typeof target === 'object';
};

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

const clone = (target) => {
    // 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
    if (!isObject(target)) {
        return target;
    }
    const type = getType(target);
    let cloneTarget;
    switch (type) {
        case typeArray:
            // 数组
            cloneTarget = [];
            target.forEach((item, index) => {
                cloneTarget[index] = clone(item)
            });
            return cloneTarget;
        case typeObject:
            // 对象
            cloneTarget = {};
            Object.keys(target).forEach((item) => {
                cloneTarget[item] = clone(target[item])
            });
            return cloneTarget;
        default:
            return target;
    }
    return cloneTarget
}

OK,这样我们的深拷贝函数就兼容了最常用的数组和对象的情况。

循环引用

但是如果出现下面这种情况

const target = {
    field1: 1,
    field2: {
        child: 'dellyoung'
    },
    field3: [2, 4, 8]
};
target.target = target;

我们来拷贝这个target对象的话,就会发现会出现报错:循环引用导致了栈溢出。

解决循环引用问题,我们需要额外有一个空间,来专门存储已经被拷贝过的对象。当需要拷贝对象时,我们先从这个空间里找是否已经拷贝过,如果拷贝过了就直接返回这个对象,没有拷贝过就进行接下来的拷贝。需要注意的是只有可遍历的引用类型才会出现循环引用的情况。

很显然这种情况下我们使用Map,以key-value来存储就非常的合适:

  • 用has方法检查Map中有无克隆过的对象
  • 有的话就获取Map存入的值后直接返回
  • 没有的话以当前对象为key,以拷贝得到的值为value存储到Map中
  • 继续进行克隆
const typeObject = '[object Object]';
const typeArray = '[object Array]';
// 是否是引用类型
const isObject = (target) => {
    return typeof target === 'object';
};

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

const clone = (target, map = new Map()) => {
    // 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
    if (!isObject(target)) {
        return target;
    }
    const type = getType(target);
    // 用于返回
    let cloneTarget;

    // 处理循环引用
    if (map.get(target)) {
        // 已经放入过map的直接返回
        return map.get(target)
    }
    
    switch (type) {
        case typeArray:
            // 数组
            cloneTarget = [];
            map.set(target, cloneTarget);
            target.forEach((item, index) => {
                cloneTarget[index] = clone(item, map)
            });
            return cloneTarget;
        case typeObject:
            // 对象
            cloneTarget = {};
            map.set(target, cloneTarget);
            Object.keys(target).forEach((item) => {
                cloneTarget[item] = clone(target[item], map)
            });
            return cloneTarget;
        default:
            return target;
    }
    
    return cloneTarget
}

性能优化

「循环性能优化:」

其实我们写代码的时候已经考虑到了性能优化了,比如:循环没有使用 for in 循环而是使用的forEach循环,使用forEach或while循环会比for in循环快上不少的

「WeakMap性能优化:」

我们可以使用WeakMap来替代Map,提高性能。

const clone = (target, map = new WeakMap()) => {
    // ...
};

为什么要这样做呢?,先来看看WeakMap的作用:

❝ WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

那什么是弱引用呢?

❝ 在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。

我们默认创建一个对象:const obj = {},就默认创建了一个强引用的对象,我们只有手动将obj = null,它才会被垃圾回收机制进行回收,如果是弱引用对象,垃圾回收机制会自动帮我们回收。

我们来举个例子:

let obj = { name : 'dellyoung'}
const target = new Map();
target.set(obj,'dell');
obj = null;

虽然我们手动将obj赋值为null,进行释放,然是target依然对obj存在强引用关系,所以这部分内存依然无法被释放。

基于此我们再来看WeakMap:

let obj = { name : 'dellyoung'}
const target = new WeakMap();
target.set(obj,'dell');
obj = null;

如果是WeakMap的话,target和obj存在的就是弱引用关系,当下一次垃圾回收机制执行的时候,这块内存就会被释放掉了。

如果我们要拷贝的对象非常庞大时,使用Map会对内存造成非常大的额外消耗,而且我们需要手动delete Map的key才能释放这块内存,而WeakMap会帮我们解决这个问题。

更多的数据类型

到现在其实我们已经解决了Number BigInt String Boolean Symbol Undefined Null Object Array,这9种情况了,但是引用类型中我们其实只考虑了Object和Array两种数据类型,但是实际上所有的引用类型远远不止这两个。

判断引用类型

判断是否是引用类型还需要考虑null和function两种类型。

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

获取数据类型

获取类型,我们可以使用toString来获取准确的引用类型:

❝ 每一个引用类型都有toString方法,默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。

但是由于大部分引用类型比如Array、Date、RegExp等都重写了toString方法,所以我们可以直接调用Object原型上未被覆盖的toString()方法,使用call来改变this指向来达到我们想要的效果

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

87236112172d72750b62a8c57f202a5d.png

类型非常多,本文先考虑大部分常用的类型,其他类型就等小伙伴来探索啦

// 可遍历类型 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]';

可继续遍历类型

上面我们已经考虑的Object、Array都属于可以继续遍历的类型,因为它们内存都还可以存储其他数据类型的数据,另外还有Map,Set等都是可以继续遍历的类型,这里我们只考虑这四种常用的,其他类型等你来探索咯。

下面,我们改写clone函数,使其对可继续遍历的数据类型进行处理:

// 可遍历类型 Map Set Object Array
const typeMap = '[object Map]';
const typeSet = '[object Set]';
const typeObject = '[object Object]';
const typeArray = '[object Array]';

// 是否是引用类型
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、处理循环引用情况 WeakMap
* 3、处理可遍历类型 Set Map Array Object
* */
const clone = (target, map = new WeakMap()) => {
    // 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
    if (!isObject(target)) {
        return target;
    }

    // 用于返回
    let cloneTarget;

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

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

这样我们就完成了对Set和Map的兼容

考虑对象键名为Symbol类型

对于对象键名为Symbol类型时,用Object.keys(target)是获取不到的,这时候就需要用到Object.getOwnPropertySymbols(target)方法。

case typeObject:
    // 对象
    cloneTarget = {};
    map.set(target, cloneTarget);
    [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].forEach((item) => {
        cloneTarget[item] = clone(target[item], map)
    });
    return cloneTarget;

这样就实现了对于对象键名为Symbol类型的兼容。

不可继续遍历类型

不可遍历的类型有Number BigInt String Boolean Symbol Undefined Null Date RegExp Function 等等,但是前7中已经被isObject拦截了,于是我们先对后面Date RegExp Function进行处理,其实后面不止有这几种,其他类型等你来探索咯。

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

 const isFunc = typeof value == 'function'
 if (isFunc || !cloneableTags[tag]) {
        return object ? value : {}
 }

显然如果发现是函数的话就会直接返回了,没有做特殊的处理,这里我们暂时也这样处理,以后有时间我会把拷贝函数的部分给补上。

// 可遍历类型 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 simpleType = [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 (simpleType.includes(type)) {
        switch (type) {
            case typeDate:
                // 日期
                return new Date(target);
            case typeRegExp:
                // 正则
                const reg = /w*$/;
                const result = new RegExp(target.source, reg.exec(target)[0]);
                result.lastIndex = target.lastIndex; // lastIndex 表示每次匹配时的开始位置
                return result;
            case typeFunction:
                // 函数
                return target;
            default:
                return target;
        }
    }

    // 用于返回
    let cloneTarget;

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

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

至此这个深拷贝函数已经能处理大部分的类型了:Number String Boolean Symbol Null Undefined Date RegExp Function Set Map Array Object,并且也能优秀的处理循环引用情况了

参考

  • 浏览器工作原理与实践
  • ConardLi深拷贝文章
  • MDN-WeakMap
  • Loadsh

总结

现在我们应该能理清楚写一个合格深拷贝的思路了:

  • 处理原始类型 如: Number String Boolean Symbol Null Undefined
  • 处理不可遍历类型 如: Date RegExp Function
  • 处理循环引用情况 使用:WeakMap
  • 处理可遍历类型 如: Set Map Array Object

看完两件事

  • 欢迎加我微信(iamyyymmm),拉你进技术群,长期交流学习
  • 关注微信公众号「呆鹅实验室」,和呆鹅一起学前端,提高技术认知

ff9399da9b5f9ac35b105cfa13070a7c.png
weixin_39687192
关注 关注
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
详解JavaScript对象深拷贝
大菜鸟的博客
04-04 2397
详解JavaScript对象深拷贝 在几乎所有编程语言中,对象都以引用形式保存给变量、复制给其他变量。JavaScript语言也是如此。因此简单的进行赋值操作进行复制仅仅是对对象数据的引用地址进行一个传递,并不会将对象内部的所有属性进行一个完整的复制。也就是说,当修改其中一个对象,另一个变量也会发生改变,因为他们本质上指向了同一个对象引用。 let obj={ a:1 }; let cop...
JS 数组对象深拷贝操作示例
10-15
数组对象深拷贝中,有多种方法可以实现这一目的。 一、数组深拷贝 1. `Array.from()` `Array.from()`方法可以从类数组对象或可迭代对象创建一个新的数组实例。尽管它可以用来拷贝数组,但它并不会进行...
一次彻底理解JavaScript深拷贝
piaobodewu的博客
10-04 152
导语 这一次,通过本文彻底理解JavaScript深拷贝! 阅读本文前可以先思考三个问题: JS世界里,数据是如何存储的? 深拷贝和浅拷贝的区别是什么? 如何写出一个真正合格的深拷贝? 本文会一步步解答这三个问题 数据是如何存储的 先看一个问题,下面这段代码的输出结果是什么: function foo(){ let a = {name:"dellyoung"} let b = a a.name = "dell" console.log(a) console.lo
JavaScript对象深拷贝
哲洛不闹的专栏
11-01 226
JavaScript中,对对象进行拷贝的场景比较常见。但是简单的复制语句只能对对象进行浅拷贝,即复制的是一份引用,而不是它所引用的对象。而更多的时候,我们希望对对象进行...
ES6 系列之 WeakMap
weixin_33901641的博客
07-26 105
前言 我们先从 WeakMap 的特性说起,然后聊聊 WeakMap 的一些应用场景。 特性 1. WeakMap 只接受对象作为键名 const map = new WeakMap(); map.set(1, 2); // TypeError: Invalid value used as weak map key map.set(null, 2); // TypeError: Invalid v...
JavaScript对象深拷贝
CongJiYong的博客
05-12 272
// Deep Clone obj1 = { a: 0 , b: { c: 0}}; let obj3 = JSON.parse(JSON.stringify(obj1)); obj1.a = 4; obj1.b.c = 4; log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
46_深拷贝与浅拷贝的区别1
08-03
深拷贝则是创建一个全新的、与原始对象完全独立的对象,新对象的属性和值都是原始对象的副本,它们在内存中占据不同的位置,所以修改其中一个对象不会影响另一个。 接着,我们转向C++,在C++中,浅拷贝通常由默认...
JavaScript Array对象详解
10-22
- **深度复制**:如果数组中包含对象,可能需要使用`JSON.parse(JSON.stringify(array))`来实现深拷贝,避免改变原数组。 通过这些实例和方法,我们可以灵活地创建、操作和管理JavaScript中的数组对象。了解并熟练...
深入理解JavaScript继承的多种方式和优缺点
10-19
**复制继承**通过浅拷贝深拷贝实现,适合单例模式或简单对象的继承。 总结来说,理解JavaScript的继承方式和优缺点对于编写高效、可维护的代码至关重要。在实际开发中,应根据项目需求和性能考虑,灵活运用这些...
前端必会编程题.doc
04-09
文件中的 `bigIntAdd` 函数就实现了这一功能,通过将数字转换为字符串,然后填充零以确保相同长度,再进行逐位相加。 2. **防抖(Debounce)**:防抖技术用于限制函数的执行频率,常用于输入事件监听、滚动事件等。...
4种JavaScript深拷贝的方法
web前端开发
05-16 347
来源 |https://www.fly63.com/浅拷贝深拷贝拷贝是创建一个新对象,这个对象有着原始对象属性值的拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的是内存地址 。如果不进行深拷贝,其中一个对象改变了对象的值,就会影响到另一个对象的值。深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新...
JavaScript案例:实现对象深拷贝
lyq0011的博客
08-25 277
1、创建deepCopy的方法实现传入一个对象,返回一个新的对象。2、新对象与就对象属性值完全相等,但是两个对象不共享同一块内存。3、可以通过 obj.constructor === Array 判断属性是否是数组
js对象深拷贝的实现
qq_40052237的博客
04-15 171
//使用递归的方式实现数组对象深拷贝 function deepClone(obj) { //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝对象的话进行对象拷贝 var objClone = Array.isArray(obj) ? [] : {} //进行深拷贝的不能为空,并且是对象或者是 if (obj && typeof obj === 'object') { for (key in obj) { i
javascript深拷贝
CreeSteve的博客
04-15 100
方法进行深拷贝时,会丢失对象中的函数、正则表达式和 undefined 值。如果需要保留这些值,可以使用其他方法来实现深拷贝,例如递归遍历对象并手动创建新对象JavaScript 中的深拷贝指的是创建一个新对象,该对象与原对象完全独立,即使修改新对象也不会影响到原对象。在 JavaScript 中,可以使用 JSON 库中的。
javascript 数组以及对象深拷贝方法
Jq的博客
08-14 272
javascript 数组以及对象深拷贝方法 for循环 var arr = [{ name: 'jq', old: '20' },{ name: 'aa', old: '18' }] var arr2=[] for(let i=0;i<arr.length;i++){ arr2.push({...arr[i]}) } arr[0].name='xia...
JavaScript对象引用、浅拷贝深拷贝详解
汪小穆的博客
06-06 873
前言:ECMAScript有五种简单数据类型(也称为基本数据类型),也有一种复杂数据类型,那就是object了。数组可以是数组对象,函数可以是函数对象,普通对象类型也是,这些object都存在对象引用的问题。 一、对象的引用 var arr = ['nick']; var obj = { name: 'nick', hobit: ['eat'] } var newArr =...
js 对象深拷贝_深拷贝与浅拷贝
weixin_39622562的博客
12-03 54
前言:最近在复习一些面试的知识点,刚刚好复习到了这一部分,于是就写下这篇文章记录一下。一 、值类型和引用类型 在学习深拷贝和浅拷贝之前,我们先来了解一下js的变量类型。值类型 vs引用类型值类型:值类型主要有:number,string,boolean,symbol,null,undefined 这6种。我们来看下代码:我们可以看到将a赋值给了b,然后将a的值改变,再打印...
浅谈JS深拷贝
qq_42730111的博客
12-08 209
在学习深拷贝之前,我们要先搞明白什么是深拷贝? 在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据存储在堆内存中 当我们对数据进行操作的时候,会发生两种情况 一、基本数据类型 var a = 3; var b = a; b = 5; console.log(a); // 3 console.log(b); // 5 可以看到的是对于基本类型来说,我们将一个基本类型的值赋予 a 变量,接着将
ES6 深拷贝_Javascript 经典面试之深拷贝VS浅拷贝
最新发布
06-02
深拷贝和浅拷贝是在 JavaScript 中常用的两种数据复制方式。 浅拷贝只会复制对象的引用,而不是对象本身,因此当原对象改变时,新对象也会发生改变。深拷贝则会复制整个对象,包括对象的所有属性和嵌套对象,因此新对象和原对象是完全独立的,互不影响。 在 ES6 中,可以使用 Object.assign() 方法来进行浅拷贝。例如: ``` let obj1 = { name: 'Alice', age: 18 }; let obj2 = Object.assign({}, obj1); obj1.age = 20; console.log(obj2.age); // 输出 18 ``` 在这个例子中,我们使用 Object.assign() 对 obj1 进行浅拷贝,将其复制到 obj2 中。当我们修改 obj1 中的 age 属性时,obj2 并不会受到影响。 而对于深拷贝,可以使用 JSON.parse(JSON.stringify()) 来实现。例如: ``` let obj1 = { name: 'Alice', age: 18, address: { city: 'Shanghai', country: 'China' } }; let obj2 = JSON.parse(JSON.stringify(obj1)); obj1.address.city = 'Beijing'; console.log(obj2.address.city); // 输出 Shanghai ``` 在这个例子中,我们使用 JSON.stringify() 将 obj1 转换为字符串,再使用 JSON.parse() 将其转换回对象。这样就可以实现深拷贝了。需要注意的是,使用 JSON.stringify() 和 JSON.parse() 进行深拷贝时,会自动忽略掉值为 undefined、function 和 symbol 的属性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • excel打开后灰色不显示内容_如何解决EXCEL表格打开显示空白(灰色)的问题 48653
  • 微信无法打开xlsx文件_微信电脑版接收的文件打不开怎么办?解决方法 19532
  • iphone固件降级_iphone 6s如何降级ios10.3.3【降级方法】 18688
  • 空间注意力机制_计算机视觉中attention机制的理解 8661
  • seo积分排名系统源码_SEO快速排名系统操作手法以及细节 5028

大家在看

  • 构建网络图 (JavaScript) 388
  • cgroups v1简介 462
  • 钉钉直播回放下载方法 绝对有效2024
  • 要降低对“存在感”的诉求 关注自己的成长 117
  • 一款为AI 视觉类训练定制的高功性能文件系统hpfs 591

最新文章

  • 扫描服务器的端口信息是什么,远程shell扫描服务器的端口号
  • 云服务器 ftp上传文件大小,云服务器ftp上传文件大小
  • 苹果6显示正在搜索跟无服务器,苹果4S 显示正在搜索 或无服务 求解!
2021年143篇
2020年224篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值

深圳SEO优化公司深圳网站设计盐田网页制作同乐网站推广系统西乡SEO按效果付费木棉湾英文网站建设盐田英文网站建设西乡网站优化按天收费双龙标王盐田网站排名优化坂田企业网站设计福永设计网站松岗seo网站优化沙井网站改版木棉湾企业网站建设松岗外贸网站设计东莞设计公司网站石岩网站seo优化大运网站优化推广宝安高端网站设计南山网站推广系统大浪如何制作网站罗湖网站优化推广光明企业网站建设大芬网站推广方案横岗seo优化大芬网站推广西乡百度竞价包年推广丹竹头网站推广盐田企业网站制作大芬网站优化软件歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化