JavaScript 中对象的深拷贝


Posted in Javascript onDecember 04, 2016

对象的深拷贝与浅拷贝的区别如下:

浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。

一. 浅拷贝的实现

浅拷贝的实现方法比较简单,只要使用是简单的复制语句即可。

1.1 方法一:简单的复制语句

/* ================ 浅拷贝 ================ */
function simpleClone(initalObj) {
    var obj = {};
    for ( var i in initalObj) {
        obj[i] = initalObj[i];
    }
    return obj;
}

客户端调用

/* ================ 客户端调用 ================ */
var obj = {
    a: "hello",
    b: {
        a: "world",
        b: 21
    },
    c: ["Bob", "Tom", "Jenny"],
    d: function() {
        alert("hello world");
    }
}
var cloneObj = simpleClone(obj); // 对象拷贝
 
console.log(cloneObj.b); // {a: "world", b: 21}
console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"]
console.log(cloneObj.d); // function() { alert("hello world"); }
 
// 修改拷贝后的对象
cloneObj.b.a = "changed";
cloneObj.c = [1, 2, 3];
cloneObj.d = function() { alert("changed"); };
 
console.log(obj.b); // {a: "changed", b: 21} // // 原对象所引用的对象被修改了
 
console.log(obj.c); // ["Bob", "Tom", "Jenny"] // 原对象所引用的对象未被修改
console.log(obj.d); // function() { alert("hello world"); } // 原对象所引用的函数未被修改

1.2 方法二:Object.assign()

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

var obj = { a: {a: "hello", b: 21} }; 
var initalObj = Object.assign({}, obj); 
initalObj.a.a = "changed"; 
console.log(obj.a.a); // "changed"

二. 深拷贝的实现

要实现深拷贝有很多办法,有最简单的 JSON.parse() 方法,也有常用的递归拷贝方法,和ES5中的 Object.create() 方法。

2.1 方法一:使用 JSON.parse() 方法

要实现深拷贝有很多办法,比如最简单的办法是使用 JSON.parse():

/* ================ 深拷贝 ================ */
function deepClone(initalObj) {
    var obj = {};
    try {
        obj = JSON.parse(JSON.stringify(initalObj));
    }
    return obj;
}
/* ================ 客户端调用 ================ */
var obj = {
    a: {
        a: "world",
        b: 21
    }
}
var cloneObj = deepClone(obj);
cloneObj.a.a = "changed";
 
console.log(obj.a.a); // "world"

这种方法简单易用。

但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。

2.2 方法二:递归拷贝

代码如下:

/* ================ 深拷贝 ================ */
function deepClone(initalObj, finalObj) {
    var obj = finalObj || {};
    for (var i in initalObj) {
        if (typeof initalObj[i] === 'object') {
            obj[i] = (initalObj[i].constructor === Array) ? [] : {};
            arguments.callee(initalObj[i], obj[i]);
        } else {
            obj[i] = initalObj[i];
        }
    }
    return obj;
}

上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。

为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

改进版代码如下:

/* ================ 深拷贝 ================ */
function deepClone(initalObj, finalObj) {
    var obj = finalObj || {};
    for (var i in initalObj) {
        var prop = initalObj[i];
 
        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
        if(prop === obj) {
            continue;
        }
 
        if (typeof prop === 'object') {
            obj[i] = (prop.constructor === Array) ? [] : {};
            arguments.callee(prop, obj[i]);
        } else {
            obj[i] = prop;
        }
    }
    return obj;
}

2.3 方法三:使用Object.create()方法

直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

/* ================ 深拷贝 ================ */
function deepClone(initalObj, finalObj) {
    var obj = finalObj || {};
    for (var i in initalObj) {
        var prop = initalObj[i];
 
        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
        if(prop === obj) {
            continue;
        }
 
        if (typeof prop === 'object') {
            obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
        } else {
            obj[i] = prop;
        }
    }
    return obj;
}

三. 参考:jQuery.extend()方法的实现

jQuery.js的jQuery.extend()也实现了对象的深拷贝。下面将官方代码贴出来,以供参考。

官方链接地址:https://github.com/jquery/jquery/blob/master/src/core.js。

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        i = 1,
        length = arguments.length,
        deep = false;
 
    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
        deep = target;
 
        // Skip the boolean and the target
        target = arguments[ i ] || {};
        i++;
    }
 
    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
        target = {};
    }
 
    // Extend jQuery itself if only one argument is passed
    if ( i === length ) {
        target = this;
        i--;
    }
 
    for ( ; i < length; i++ ) {
 
        // Only deal with non-null/undefined values
        if ( ( options = arguments[ i ] ) != null ) {
 
            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];
 
                // Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }
 
                // Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {
 
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray( src ) ? src : [];
 
                    } else {
                        clone = src && jQuery.isPlainObject( src ) ? src : {};
                    }
 
                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );
 
                // Don't bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }
 
    // Return the modified object
    return target;
};

这篇文章主要是介绍js关于深拷贝的内容,其它的内容可以查看三水点靠木以前发表的文章

Javascript 相关文章推荐
HTML页面如何象ASP一样接受参数
Feb 07 Javascript
js使用for循环查询数组中是否存在某个值
Aug 12 Javascript
JavaScript中实现继承的三种方式和实例
Jan 29 Javascript
Jquery Ajax Error 调试错误的技巧
Nov 20 Javascript
Node.js Streams文件读写操作详解
Jul 04 Javascript
Javascript for in的缺陷总结
Feb 03 Javascript
jQuery.ajax向后台传递数组问题的解决方法
May 12 jQuery
javascript 中事件冒泡和事件捕获机制的详解
Sep 01 Javascript
JavaScript异步加载问题总结
Feb 17 Javascript
webpack打包react项目的实现方法
Jun 21 Javascript
element跨分页操作选择详解
Jun 29 Javascript
详解vue 中 scoped 样式作用域的规则
Sep 14 Javascript
详解JavaScript模块化开发
Dec 04 #Javascript
javascript 定时器工作原理分析
Dec 03 #Javascript
JavaScript 最佳实践:帮你提升代码质量
Dec 03 #Javascript
简单理解Vue条件渲染
Dec 03 #Javascript
学习vue.js条件渲染
Dec 03 #Javascript
浅谈jQuery中Ajax事件beforesend及各参数含义
Dec 03 #Javascript
jquery 判断div show的状态实例
Dec 03 #Javascript
You might like
用PHP与XML联手进行网站编程代码实例
2008/07/10 PHP
重新认识php array_merge函数
2014/08/31 PHP
php实现字符串首字母转换成大写的方法
2015/03/17 PHP
教你php如何实现验证码
2016/01/20 PHP
Yii框架弹出窗口组件CJuiDialog用法分析
2017/01/07 PHP
PHP实现的数独求解问题示例
2017/04/18 PHP
JS效率个人经验谈(8-15更新),加入range技巧
2007/01/09 Javascript
利用JQuery+EasyDrag 实现弹出可拖动的Div,同时向Div传值,然后返回Div选中的值
2009/10/24 Javascript
JavaScript实现的经典文件树菜单效果
2015/09/08 Javascript
jQuery实现的调整表格行tr上下顺序
2016/01/10 Javascript
Javascript生成带参数的二维码示例
2016/10/10 Javascript
JQueryEasyUI之DataGrid数据显示
2016/11/23 Javascript
Vue.js实现一个todo-list的上移下移删除功能
2017/06/26 Javascript
vue cli 全面解析
2018/02/28 Javascript
Webpack中publicPath路径问题详解
2018/05/03 Javascript
使用Vue实现调用接口加载页面初始数据
2019/10/28 Javascript
OpenLayers3实现对地图的基本操作
2020/09/28 Javascript
python通过线程实现定时器timer的方法
2015/03/16 Python
使用Python的Flask框架实现视频的流媒体传输
2015/03/31 Python
详解python脚本自动生成需要文件实例代码
2017/02/04 Python
TensorFlow实现随机训练和批量训练的方法
2018/04/28 Python
Python3实现定时任务的四种方式
2019/06/03 Python
TensorFlow实现自定义Op方式
2020/02/04 Python
python第三方库学习笔记
2020/02/07 Python
Python如何实现邮件功能
2020/05/27 Python
解决Python3.8运行tornado项目报NotImplementedError错误
2020/09/02 Python
python实现发送QQ邮件(可加附件)
2020/12/23 Python
html5的新玩法——语音搜索
2013/01/03 HTML / CSS
关于期中考试的反思
2014/02/02 职场文书
给校长的建议书500字
2014/05/15 职场文书
上课迟到检讨书300字
2014/10/15 职场文书
卢旺达饭店观后感
2015/06/05 职场文书
2019入党申请书范文3篇
2019/08/21 职场文书
话题作文之成长
2019/12/09 职场文书
阿里云服务器部署mongodb的详细过程
2021/09/04 MongoDB
详细聊一聊mysql的树形结构存储以及查询
2022/04/05 MySQL