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 相关文章推荐
javascript:void(0)的问题使用探讨
Apr 10 Javascript
get(0).tagName获得作用标签示例代码
Oct 08 Javascript
谈一谈javascript闭包
Jan 28 Javascript
JS中使用变量保存arguments对象的方法
Jun 03 Javascript
EasyUI创建对话框的两种方式
Aug 23 Javascript
基于chosen插件实现人员选择树搜索自动筛选功能
Sep 24 Javascript
微信公众号支付H5调用支付解析
Nov 04 Javascript
JQuery和PHP结合实现动态进度条上传显示
Nov 23 Javascript
Vue实现双向数据绑定
May 03 Javascript
JS数组操作中的经典算法实例讲解
Jul 26 Javascript
简单实现jQuery上传图片显示预览功能
Jun 29 jQuery
Vue中多元素过渡特效的解决方案
Feb 05 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 第一节 php简介
2012/04/28 PHP
php调整gif动画图片尺寸示例代码分享
2013/12/05 PHP
php指定长度分割字符串str_split函数用法示例
2017/01/30 PHP
PHP实现根据数组的值进行分组的方法
2017/04/20 PHP
Yii2.0使用阿里云OSS的SDK上传图片、下载、删除图片示例
2017/09/20 PHP
使用PHP访问RabbitMQ消息队列的方法示例
2018/06/06 PHP
JavaScript 动态创建VML的方法
2009/10/14 Javascript
JavaScript是否可实现多线程  深入理解JavaScript定时机制
2009/12/22 Javascript
jQuery增加自定义函数的方法
2015/07/18 Javascript
微信小程序 解决请求服务器手机预览请求不到数据的方法
2017/01/04 Javascript
获取今天,昨天,本周,上周,本月,上月时间(实例分享)
2017/01/04 Javascript
深入nodejs中流(stream)的理解
2017/03/27 NodeJs
Vuex和前端缓存的整合策略详解
2017/05/09 Javascript
Ionic学习日记实现验证码倒计时
2018/02/08 Javascript
jQuery扩展方法实现Form表单与Json互相转换的实例代码
2018/09/05 jQuery
[01:45]亚洲邀请赛互动指南虚拟物品介绍
2015/01/30 DOTA
[48:37]EG vs OG 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
Python内置函数bin() oct()等实现进制转换
2012/12/30 Python
收集的几个Python小技巧分享
2014/11/22 Python
python getopt详解及简单实例
2016/12/30 Python
如何打包Python Web项目实现免安装一键启动的方法
2020/05/21 Python
Python基于pillow库实现生成图片水印
2020/09/14 Python
Django2.1.7 查询数据返回json格式的实现
2020/12/29 Python
Jimmy Choo美国官网:周仰杰鞋子品牌
2018/06/08 全球购物
网络安全方面的面试题
2015/11/04 面试题
物流专业毕业生推荐信范文
2013/11/18 职场文书
骨干教师培训制度
2014/01/13 职场文书
酒店节能降耗方案
2014/05/08 职场文书
社区志愿者培训方案
2014/06/10 职场文书
小学趣味运动会加油稿
2014/09/25 职场文书
单位单身证明样本
2014/10/11 职场文书
会计工作总结范文2014
2014/12/23 职场文书
应届毕业生自荐信
2015/03/04 职场文书
团干部培训班心得体会
2016/01/06 职场文书
docker compose 部署 golang 的 Athens 私有代理问题
2022/04/28 Servers
python中validators库的使用方法详解
2022/09/23 Python