您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

JavaScript的深拷贝实现

时间:2020-08-03 14:33:56  来源:  作者:

在实际开发当中,我们经常会遇到要对对象进行深拷贝的情况。而且深拷贝这个问题在面试过程中也经常会遇到,下面就对本人在学习过程中的收获,做以简单的总结。

什么是浅拷贝,什么是深拷贝?

什么是浅拷贝

关于浅拷贝的概念,我在网上看到一种说法,直接上代码。

var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var person1 = person;       //他们认为这是浅拷贝

但是我个人认为,上面这个根本不涉及拷贝,只是一个简单的引用赋值。以我的理解,浅拷贝应该是不考虑对象的引用类型的属性,只对当前对象的所有成员进行拷贝,代码如下:

function copy(obj){
    var objCopy = {};
    for(var key in obj){
        objCopy[key] = obj[key];
    }
    return objCopy;
}

var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var personCopy = copy(person);

上面这段代码中,person对象拥有两个基本类型的属性name和age,一个引用类型的属性car,当使用如上方法进行拷贝的时候,name和age属性会被正常的拷贝,但是car属性,只会进行引用的拷贝,这样会导致拷贝出来的对象personCopy和person会共用一个car对象。这样就是所谓的浅拷贝。

什么是深拷贝

深拷贝的就是在拷贝的时候,需要将当前要拷贝的对象内的所有引用类型的属性进行完整的拷贝,也就是说拷贝出来的对象和原对象之间没有任何数据是共享的,所有的东西都是自己独占的一份。

如何实现深拷贝

实现深拷贝需要考虑的问题

实现深拷贝需要考虑如下几个因素:

  • 传入的对象是使用对象字面量{}创建的对象还是由构造函数生成的对象
  • 如果对象是由构造函数创建出来的,那么是否要拷贝原型链上的属性
  • 如果要拷贝原型链上的属性,那么如果原型链上存在多个同名的属性,保留哪个
  • 处理循环引用的问题

第三方库实现深拷贝

jQuery的$.extend()

我们可以通过$.extend()方法来完成深复制。值得庆幸的是,我们在jQuery中可以通过添加一个参数来实现递归extend。调用$.extend(true, {}, ...)就可以实现深复制,参考下面的例子:

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var y = $.extend({}, x),          //shallow copy
    z = $.extend(true, {}, x);    //deep copy

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false

但是jQuery的这个$.extend()方法,有弊端,什么弊端呢?我们看下面的例子:

var objA = {};
var objB = {};

objA.b = objB;
objB.a = objA;

$.extend(true,{},a);

//这个时候就出现异常了
//Uncaught RangeError: Maximum call stack size exceeded(…)

也就是说,jQuery中的$.extend()并没有处理循环引用的问题。

使用JSON对象实现深拷贝

使用JSON全局对象的parse和stringify方法来实现深复制也算是一个简单讨巧的方法。

function jsonClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
var clone = jsonClone({ a:1 });

然而使用这种方法会有一些隐藏的坑,它能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。

自己造轮子

下面我们给出一个简单的解决方案,当然这个方案是参考别人的方式来实现的。希望对大家有用。

var clone = (function() {
    //这个方法用来获取对象的类型 返回值为字符串类型 "Object RegExp Date Array..."
    var classof = function(o) {
        if (o === null) {
            return "null";
        }
        if (o === undefined) {
            return "undefined";
        }
        // 这里的Object.prototype.toString很可能用的就是Object.prototype.constructor.name
        // 这里使用Object.prototype.toString来生成类型字符串
        var className = Object.prototype.toString.call(o).slice(8, -1);
        return className;
    };

    //这里这个变量我们用来存储已经保存过的属性,目的在于处理循环引用的问题
    var references = null;

    //遇到不同类型的对象的处理方式
    var handlers = {
        //正则表达式的处理
        'RegExp': function(reg) {
            var flags = '';
            flags += reg.global ? 'g' : '';
            flags += reg.multiline ? 'm' : '';
            flags += reg.ignoreCase ? 'i' : '';
            return new RegExp(reg.source, flags);
        },
        //时间对象处理
        'Date': function(date) {
            return new Date(+date);
        },
        //数组处理 第二个参数为是否做浅拷贝
        'Array': function(arr, shallow) {
            var newArr = [],
            i;
            for (i = 0; i < arr.length; i++) {
                if (shallow) {
                    newArr[i] = arr[i];
                } else {
                    //这里我们通过reference数组来处理循环引用问题
                    if (references.indexOf(arr[i]) !== -1) {
                        continue;
                    }
                    var handler = handlers[classof(arr[i])];
                    if (handler) {
                        references.push(arr[i]);
                        newArr[i] = handler(arr[i], false);
                    } else {
                        newArr[i] = arr[i];
                    }
                }
            }
            return newArr;
        },
        //正常对象的处理 第二个参数为是否做浅拷贝
        'Object': function(obj, shallow) {
            var newObj = {}, prop, handler;
            for (prop in obj) {
                //关于原型中属性的处理太过复杂,我们这里暂时不做处理
                //所以只对对象本身的属性做拷贝
                if (obj.hasOwnProperty(prop)) {
                    if (shallow) {
                        newObj[prop] = obj[prop];
                    } else {
                        //这里还是处理循环引用的问题
                        if (references.indexOf(obj[prop]) !== -1) {
                            continue;
                        }

                        handler = handlers[classof(obj[prop])];
                        //如果没有对应的处理方式,那么就直接复制
                        if (handler) {
                            references.push(obj[prop]);
                            newObj[prop] = handler(obj[prop], false);
                        } else {
                            newObj[prop] = obj[prop];
                        }
                    }
                }
            }
            return newObj;
        }
    };

    return function(obj, shallow) {
        //首先重置我们用来处理循环引用的这个变量
        references = [];
        //我们默认处理为浅拷贝
        shallow = shallow === undefined ? true : false;
        var handler = handlers[classof(obj)];
        return handler ? handler(obj, shallow) : obj;
    };
}());

(function() {
    //下面是一些测试代码
    var date = new Date();
    var reg = /hello word/gi;
    var obj = {
        prop: 'this ia a string',
        arr: [1, 2, 3],
        o: {
            wow: 'aha'
        }
    };
    var refer1 = {
        arr: [1, 2, 3]
    };
    var refer2 = {
        refer: refer1
    };
    refer1.refer = refer2;

    var cloneDate = clone(date, false);
    var cloneReg = clone(reg, false);
    var cloneObj = clone(obj, false);
    alert((date !== cloneDate) && (date.valueOf() === cloneDate.valueOf()));
    alert((cloneReg !== reg) && (reg.toString() === cloneReg.toString()));
    alert((obj !== cloneObj) && (obj.arr !== cloneObj.arr) && (obj.o !== cloneObj.o) && (JSON.stringify(obj) === JSON.stringify(cloneObj)));

    clone(refer2, false);
    alert("I'm not dead yet!");
    // Output:
    // true
    // true
    // true
    // I'm not dead yet!
}());


Tags:JavaScript 深拷贝   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
在实际开发当中,我们经常会遇到要对对象进行深拷贝的情况。而且深拷贝这个问题在面试过程中也经常会遇到,下面就对本人在学习过程中的收获,做以简单的总结。什么是浅拷贝,什么是...【详细内容】
2020-08-03  Tags: JavaScript 深拷贝  点击:(57)  评论:(0)  加入收藏
▌简易百科推荐
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === &#39;a&#39;) { b = true} else { b = false}// goodb = a === &#39;a&#39;2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Mason程    Tags:JavaScript   点击:(5)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  未来讲IT    Tags:JavaScript   点击:(19)  评论:(0)  加入收藏
1. 检测一个对象是不是纯对象,检测数据类型// 检测数据类型的方法封装(function () { var getProto = Object.getPrototypeOf; // 获取实列的原型对象。 var class2type =...【详细内容】
2021-12-08  前端明明    Tags:js   点击:(23)  评论:(0)  加入收藏
作者:一川来源:前端万有引力 1 写在前面Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的...【详细内容】
2021-12-06  Nodejs开发    Tags:Javascript   点击:(19)  评论:(0)  加入收藏
概述DOM全称Document Object Model,即文档对象模型。是HTML和XML文档的编程接口,DOM将文档(HTML或XML)描绘成一个多节点构成的结构。使用JavaScript可以改变文档的结构、样式和...【详细内容】
2021-11-16  海人为记    Tags:DOM模型   点击:(34)  评论:(0)  加入收藏
入口函数 /*js加载完成事件*/ window.onload=function(){ console.log("页面和资源完全加载完毕"); } /*jQuery的ready函数*/ $(document).ready(function(){ co...【详细内容】
2021-11-12  codercyh的开发日记    Tags:jQuery   点击:(35)  评论:(0)  加入收藏
一、判断是否IE浏览器(支持判断IE11与edge)function IEVersion() {var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isIE = userAgent.indexOf("comp...【详细内容】
2021-11-02  V面包V    Tags:Javascript   点击:(39)  评论:(0)  加入收藏
Null、Undefined、空检查普通写法: if (username1 !== null || username1 !== undefined || username1 !== &#39;&#39;) { let username = username1; }优化后...【详细内容】
2021-10-28  前端掘金    Tags:JavaScript   点击:(50)  评论:(0)  加入收藏
今天我们将尝试下花 1 分钟的时间简单地了解下什么是 JS 代理对象(proxies)?我们可以这样理解,JS 代理就相当于在对象的外层加了一层拦截,在拦截方法里我们可以自定义一些个性化...【详细内容】
2021-10-18  前端达人    Tags:JS   点击:(51)  评论:(0)  加入收藏
带有多个条件的 if 语句把多个值放在一个数组中,然后调用数组的 includes 方法。// bad if (x === "abc" || x === "def" || x === "ghi" || x === "jkl") { //logic } // be...【详细内容】
2021-09-27  羲和时代    Tags:JS   点击:(58)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条