javascript中对象的深度克隆

零、寒暄

又是一个月多月没有更新博客了,这段时间回学校处理下论文的事情,实习的生活也暂时告一段落(在公司上班,才发现学校里面的生活简直如天堂一般,相信很多已经毕业的小伙伴肯定被我说中了,说中了请给本文点个赞,哈哈!)。希望接下来自己的更新进度能加快,马上又是一年校招时,被虐也好、大牛虐别人也罢,总之祝福各位今年要找工作的小伙伴们好运。那么,今天就聊一下一个常见的笔试、面试题,js中对象的深度克隆。翻了下这个题目,在很多地方出现过,已经算一个老的题目了,但是每年的校招中总会考到,其实想想,这个题目考查的知识点还是蛮多的,尤其是对基础知识的考查。好了,闲话不多说,开始正题。

一、js中的对象

谈到对象的克隆,必定要说一下对象的概念。

js中的数据类型分为两大类:原始类型和对象类型。(1)原始类型包括:数值、字符串、布尔值、null、undefined(后两个是特殊的原始值,这里不做详细的说明,我的上一篇博客有谈到过一些)(2)对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象----函数(js中的一等对象)、数组(键值的有序集合)。

好了既然对象分为这两类,这两种类型在复制克隆的时候是有很大区别的。原始类型存储的是对象的实际数据,而对象类型存储的是对象的引用地址(对象的实际内容单独存放,为了减少数据开销通常存放在内存中)。ps:说到这里,大家要知道,对象的原型也是引用对象,它把原型的方法和属性放在内存当中,通过原型链的方式来指向这个内存地址。

二、克隆的概念

浅度克隆:原始类型为值传递,对象类型仍为引用传递。

深度克隆:所有元素或属性均完全复制,与原对象完全脱离,也就是说所有对于新对象的修改都不会反映到原对象中。

三、浅克隆的表现

1,原始类型

看下面一段代码:

//数值克隆的表现
var a="1";
var b=a;
b="2";
console.log(a);// "1"
console.log(b);// "2"

//字符串克隆的表现
var c="1";
var d=c;
d="2";
console.log(c);// "1"
console.log(d);// "2"

//字符串克隆的表现
var x=true;
var y=x;
y=false;
console.log(x);// true
console.log(y);// false

从上面的代码大家可以看出,原始类型即使我们采用普通的克隆方式仍能得到正确的结果,原因就是原始类型存储的是对象的实际数据。

2.对象类型

前面说过,函数式一等对象,当然也是对象类型,但是函数的克隆通过浅克隆即可实现

var m=function(){alert(1);};
var n=m;
n=function(){alert(2);};

console.log(m());//1
console.log(n());//2

大家能看到,我们直接通过普通赋值的方式,就实现了函数的克隆,并且不会影响之前的对象。原因就是函数的克隆会在内存单独开辟一块空间,互不影响。

好了,说了这个特殊的”关系户“以后,我们来说说普通的”选手“。为了方便后续的代码表现,我这里定义一个复杂的对象类型oPerson。下面看一下对象类型的浅复制有什么危害:

var oPerson={
    oName:"rookiebob",
    oAge:"18",
    oAddress:{
        province:"beijing"
    },
    ofavorite:[
        "swimming",
        {reading:"history book"}
    ],
    skill:function(){
        console.log("bob is coding");
    }
};
function clone(obj){
    var result={};
    for(key in obj){
        result[key]=obj[key];
    }
    return result;
}
var oNew=clone(oPerson);
console.log(oPerson.oAddress.province);//beijing
oNew.oAddress.province="shanghai";
console.log(oPerson.oAddress.province);//shanghai

通过上面的代码,大家能看到,经过对象克隆以后,我修改oNew的地址,发现原对象oPerson也被修改了。这说明对象的克隆不够彻底,那也就是说深度克隆失败!

四、深克隆的实现

为了保证对象的所有属性都被复制到,我们必须知道如果for循环以后,得到的元素仍是Object或者Array,那么需要再次循环,直到元素是原始类型或者函数为止。为了得到元素的类型,我们定义一个通用函数,用来返回传入对象的类型。

//返回传递给他的任意对象的类
function isClass(o){
    if(o===null) return "Null";
    if(o===undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8,-1);
}

PS:Object.prototype.toString.call(o)能直接返回对象的类属性,形如"[object class]"的字符串,我们通过截取class,并能知道传入的对象是什么类型。

当然这里有两个疑问需要解释下:

(1)为什么不直接用toString方法?这是为了防止对象中的toString方法被重写,为了正确的调用toString()版本,必须间接的调用Function.call()方法

(2)为什么不使用typeof来直接判断类型?因为对于Array而言,使用typeof(Array)返回的是object,所以不能得到正确的Array,这里对于后续的数组克隆将产生致命的问题。

万事俱备,只欠曹操了,下面就正儿八经的开始克隆。

//深度克隆
function deepClone(obj){
    var result,oClass=isClass(obj);
        //确定result的类型
    if(oClass==="Object"){
        result={};
    }else if(oClass==="Array"){
        result=[];
    }else{
        return obj;
    }
    for(key in obj){
        var copy=obj[key];
        if(isClass(copy)=="Object"){
            result[key]=arguments.callee(copy);//递归调用
        }else if(isClass(copy)=="Array"){
            result[key]=arguments.callee(copy);
        }else{
            result[key]=obj[key];
        }
    }
    return result;
}
//返回传递给他的任意对象的类
function isClass(o){
    if(o===null) return "Null";
    if(o===undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8,-1);
}
var oPerson={
    oName:"rookiebob",
    oAge:"18",
    oAddress:{
        province:"beijing"
    },
    ofavorite:[
        "swimming",
        {reading:"history book"}
    ],
    skill:function(){
        console.log("bob is coding");
    }
};
//深度克隆一个对象
var oNew=deepClone(oPerson);

oNew.ofavorite[1].reading="picture";
console.log(oNew.ofavorite[1].reading);//picture
console.log(oPerson.ofavorite[1].reading);//history book

oNew.oAddress.province="shanghai";
console.log(oPerson.oAddress.province);//beijing
console.log(oNew.oAddress.province);//shanghai

从上面的代码可以看到,深度克隆的对象可以完全脱离原对象,我们对新对象的任何修改都不会反映到原对象中,这样深度克隆就实现了。

这里要注意一点的就是:为什么deepClone这个函数中的result一定要判断类型?这里有一种情况,如果你的result直接是{}对象,我明明传进去的是一个数组,结果你复制完了以后,变成了一个对象了。

//深度克隆
function deepClone(obj){
    var result={},oClass=isClass(obj);
    // if(oClass==="Object"){
    //     result={};
    // }else if(oClass==="Array"){
    //     result=[];
    // }else{
    //     return obj;
    // }
    for(key in obj){
        var copy=obj[key];
        if(isClass(copy)=="Object"){
            result[key]=arguments.callee(copy);
        }else if(isClass(copy)=="Array"){
            result[key]=arguments.callee(copy);
        }else{
            result[key]=obj[key];
        }
    }
    return result;
}
function isClass(o){
    if(o===null) return "Null";
    if(o===undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8,-1);
}
//克隆一个数组
var arr=["a","b","c"];
var oNew=deepClone(arr);
console.log(oNew);//Object {0: "a", 1: "b", 2: "c"} 

五、总结

说了这么多,其实深度克隆实现的关键点主要有两个:(1)对象类型的判断,使用上面的isClass()方法;(2)递归调用实现对象的所有属性完全复制。

本人前端菜鸟,希望这篇文章能对大家有所启发,文中有误导大家的地方,希望大伙海涵并给予指正。如果各位基友和软妹子觉得文章对您有所帮助,你的留言和推荐将对我是莫大的鼓舞!各位,周末快乐!

  

时间: 2024-12-06 22:17:30

javascript中对象的深度克隆的相关文章

JavaScript实现对象的深度克隆【简洁】【分享】

JavaScript实现对象的深度克隆 代码实现如下: 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>clone</title> 6 7 <script type="text/javascript"> 8 //对象深度克隆方法 9 10 11 ////

javascript对象的深度克隆

在做项目的时候需要向对象里面添加新属性,又不想修改原对象.于是就写: var newObj = oldObj,但是新对象属性改变后就对象也会跟着改变,这是因为无论是新对象还是旧对象,指向的内存地址都是一样的,改变了谁都改变了 内存中的数据. 于是找到了一个取巧的方法就是先把旧对象转化为字符串 然后 在转化为对象给新对象,虽然可以达到效果,但是总感觉有点不正规.于是想到了深度克隆 function cloneObjectFn (obj){ // 对象复制 return JSON.parse(JSO

JAVA对象的深度克隆

有时候,我们需要把对象A的所有值复制给对象B(B = A),但是这样用等号给赋值你会发现,当B中的某个对象值改变时,同时也会修改到A中相应对象的值! 也许你会说,用clone()不就行了?!你的想法只对了一半,因为用clone()时,除了基础数据和String类型的不受影响外,其他复杂类型(如集合.对象等)还是会受到影响的!除非你对每个对象里的复杂类型又进行了clone(),但是如果一个对象的层次非常深,那么clone()起来非常复杂,还有可能出现遗漏! 既然用等号和clone()复制对象都会对

JavaScript中对象类型的转换小结

对象到字符串和对象到数字类型的转换涉及到两个重要的方法,最终的转换结果会受到这两个方法返回结果的影响,这两个方法就是toString和valueOf.所有的对象都会从Object对象中继承到这两个方法.toString方法 用于返回对象的字符串表示(但是其实也可以不返回字符串).对于默认从Object继承而来的toString方法并不会返回太多有意义的内容.而valueOf方法目的是返回一个可以表示对象的原始类型值,但是由于对象的复杂性,大多数情况下根本不可能用一个原始类型值来表示,所以默认的v

JavaScript中对象属性的添加和删除

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script type="text/javascript"> /* * 用.为对象添加属性 用关键字delete删除属性 用[]添加属性 和.的不同 r.name==r["name"]; r.

JAVA对象任意深度克隆clone工具类分享

原文:JAVA对象任意深度克隆clone工具类分享 源代码下载地址:http://www.zuidaima.com/share/1550463408114688.htm package com.zuidaima.n_app.util; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import jav

私人定制javascript中对象小知识点(Only For Me)

废话不多讲,先上笑话,然后再,.看懂这个的说明你的节操已经不再了. 晚饭后去理发店理发...割了吧...老板问我怎么剪,我悠悠的来一句往帅了剪...高潮往往令人想不到....旁边一在焗油烫头发的大妈说到 别这样为难老板,人家赚点钱不容易...首先如果你是高手那么请出门右转,如果你是菜鸟那么恭喜你,go on吧 全局对象在javascript程序中任何位置,都可以拈来就用的这种东西,是全局对象的属性.那么属性所在的对象也就是全局对象了.当javascript解释器启动时(或者任何Web浏览器加载新

JavaScript对象之深度克隆

也不知道从什么时候开始,前端圈冒出了个新词:对象深度克隆.看起来好像很高大上的样子,实际上并不新鲜,在我们的实际项目开发中,你可能早已用到,只不过由于汉字的博大精深,有些原本很简单的事物被一些看似专业的词汇稍加修饰,就变得神秘起来了. 首先为什么要将一个对象进行深克隆?请允许我进行一个猜测:你有时一定会认为js的内置对象document太长,那么你可能会这样做: var d = document; d.by = function(id){ return d.getElementById(id);

实现对象的深度克隆

1.方式一 实现Cloneable接口并重写 Object类中的 clone()方法 2. 方式二 (实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆) 2.1 User类和Car的准备 package cn.qdm.ceshi; import java.io.Serializable; public class User implements Serializable{ private String name; private Integer age