如何正确学习JavaScript

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何正确学习JavaScript相关的知识,希望对你有一定的参考价值。

JavaScript作为网站前端的核心知识是非常重要的,但是如何学习JavaScript是很多初学者面对的一个难题,下面我说一下个人对于如何学习JavaScript的一点心得,希望能帮到大家。

1、首先学习基础,尤其是JavaScript与其他语言不同的基础知识。

学习每个编程语言可能都要从基础的变量函数结构等学起,JavaScript相对于其他的编程语言在基础上有一些比较有特点的地方,这些东西是尤其需要我们注意的,比如说自调函数、比如说变量值为空不是null而是undefined的等,这些东西都是和主流的编程的语言如c++、java等是有区别的,所以想要学好JavaScript,这些基础的知识一定要把握好学习好才行。

2、关于学习JavaScript的进阶

JavaScript进阶知识更是有很多难理解的东西和不同于其他语言的东西,这些东西都是深入学习JavaScript的难点,简单的说一下JavaScript是基于对象的浏览器脚本语言,但是为了使JavaScript也能够支持面向对象编程,人们就使用了很多的方法来模拟面向对象的的特点,这些就变成了JavaScript的一些难点,比如说JavaScript的继承要借助原型、比如JavaScript的类需要使用构造函数来构造一个对象等,这些东西都是和其他支持面向对象的语言是不同的,所以就导致了一些难点。关于进阶学习JavaScript,还有JavaScript一些深入的知识需要去理解比如原型链和闭包、比如收函数的作用域执行流程和上下文,这些东西都是JavaScript比较深入的知识、

最后说明,JavaScript作为前端的核心语言绝对没有没有想象的那么简单,要想学好JavaScript除了以上提到的知识和注意点,还需要的就是多动手和实践,我们都知道网页上的所有特效都是借助JavaScript来实现,那么我们就需要在学习JavaScript的过程中多使用原生的JavaScript代码实现一些特效,当然学习使用jQuery等JavaScript类库进行开发也十分的重要,但是这绝对不能是取代我们学习JavaScript的方法。

参考技术A 如果要我来说,学习编程重要的是钻研和实践。比如我自己,虽然不能称为大神,但JS方面能够难倒我的不多,但从来没买过实体书,没买过任何参考资料和学习资料。你说你正在做的是网页制作,那么在这个过程中,你一定接触了许许多多的JS代码,就像你所描述的那样,各种网页特效等等。但是,这些网页特效在你的手中,仅仅是下载之、复制之、嵌入之、发布之,这怎么可以呢?从我开始学编程以来,只买过一本PHP开发王,还一真陈列着,好不容易看一看,却感觉看书并没有太大的意义,唯一的意义就是书里的东西系统一些,全面一些,但对建立编程思想,没有太多的益处,其它的书我没看过,不敢妄加评论,但我觉得,重要的并不是书,而在于写。平时总听到一些人说:“要做一个有思想的人。”那么,这个有思想,不仅仅体现在平时的为人处事,举手投足,还包括编程。从最小做起,自己独立写出一段实现以前下载回来的那些特效代码所拥有的功能,写出来之后,你会领悟到代码中包含的各种思路、实现手段以及编程思想。从易到难,从简到繁,一步步踏实走过,你就发现,不过如此。尤其现在的搜索引擎,它的强大给我们程序员带来的好处不言而喻,这也正是我不买任何资料的原因所在,而通过搜索得到的学习资料,集众家之所长,网上形形色色的各种博客博文,技术文章,很多都是程序员们的得意之作,他们炫耀之,我们学习之,看得多了,汇集到你一个人的脑海中,高手,自然就练成了。另外,我本人最鄙视的就是那些所谓的笔试,拿出一些生僻的函数,让你写神马功能,这是延伸了应试教育的陋习,没有太多现实意义,一个人脑海中有了编程思想,一些特殊函数会不会用又有什么关系,如今发达的网络,有什么信息是你不能随手得到的,把所有复杂的大量函数记忆在脑海中,而忽视了编程思想本身,就是一个极大的错误。而有人就会觉得,你说的生僻函数,却是有许多人都知道的啊,没错,这一点我承认,这也正是积累带来的好处之一,当你代码写多了,所见所闻自然越来越多,不限功能地开发各种应用,甚至开创性地开发许多程序、应用、功能、库,慢慢的,那些琐碎的知识,自然会记在脑中,抛之不去了。也不要去刻意记什么函数、语法,这些没有什么实际意义,高手们不是通过考查记下了多少函数来体现他们能力的,而是给你一个项目,你是否能在最短的时间里,完成程序的实现方案。能把所有函数记在脑中的,那不是程序员,是神仙。而编程思想的建立,只可意会,不可言传,不是不传,是没法传,只有你开发过大量的程序,编写数万、数十万、数百万行的代码之后,编程思想自然水到渠成。另外,学习编程最重要的并不是求教,当然,如果身边有这样的好条件,自然不能放过,也会节省你许多时间,但是,如果一个难题,在你百折不挠的努力下,经历若干坎坷完成了,你得到的,会!比如像百度之知道上,许多编程高手们,你看他们的提问数和回答数,完全不成比例,他们的积分用不完,花不掉,而一些人们,你看那点可怜的财富值,省着用啊,怕明天没有了,有问题就惨了,这个对比下,你有没有发现一个有趣的现象?我回答问题的时候,喜欢像你这样慷慨给分的,因为能够这样给分的,都在一定程度上说明有独立解决问题的能力的,也是有些思想的,而那些匿名的,不给分的,只有我实在找不到可以回答的问题的时候,才会最低优先级地考虑他们,而且不会在这些人身上花费太多的时间。而我,只有第一个提问,因为没有什么分,给的分数低于100分,其它的,全部200分,但慢慢的,我感觉没什么问题可问了,不知道问什么,财富值都用来兑换奖品和娃娃了。这是一个习惯,一个自我处理问题能力的良好习惯,一些人,一旦遇到棘手的问题,首先想到的是问,而不是想法自己解决,很可怕知道么。今天说得有点多了,也有点词不搭意,也没有给你提供具体的学习流程,但我觉得,这个学习流程可要可不要,每个人都是独立的个体,有自己不同的行走路线和人生轨迹,完全模仿,没个性!所以,从现在开始,着手编写代码吧,这才是实实在在,踏上高手之路的第一步。 参考技术B 如今使用Javascript框架和插件构建的Web应用越来越多,并且已经能够实现3D动画特效、可交互的信息图等很有趣又实用的效果,如果想要做Web开发,Javascript是必需品。
目前自己的知识库中只对HTML和CSS这类非编程类的语言比较熟悉且能熟练应用。曾经有一段时间下了不少功夫在Processing这门基于Java再编译的语法相对简单的编程语言,但因为是基于Java,若想要将用Processing直接引用于Web,要么需要用户装个Java的web控件,要么,就需要把它转城Javascript来实现Web的无缝链接。如果Objective-C占尽了移动开发的风头,那么跟得上潮流的Web开发一定少不了Javascript。但是,作为一个有初步编程背景的人,如何自学,才能正确有效地掌握Javascript这门语言呢?
今天,终于在JavascriptIsSexy这个网站上的“How to Learn JavaScript Properly”这篇文章中找到了我比较认同的答案。
我目前接触和学习JavaScript的几个途径,一是Codecademy这个在线编程学习网站。正如JavaScriptIsSexy这篇文章的作者所言,Codecademy给出的案例任务大都是相对简单单一的小任务,即使完成了课程,也很难开始实战一款真正的Web App。
曾经有工程师向我推荐阅读“Javascript: The Good Parts”一书,但在这篇文章中作者则建议初学者不要阅读此书,等基础扎实了再读。
很开心的是,这篇文章推荐的入门书目之一是《JavaScript权威指南》,恰巧我已经在O’Reilly上购入了正版。虽然有些Web工程师觉得此书适合当参考书,但是经由JacaScriptIsSexy上的自学计划来看,如果合理地阅读权威指南上的部分章节,并亲手把书中配套的案例代码敲出来,对Javascipt初学者来说是非常重要的!
另一本推荐书目是《JavaScript高级程序设计》,不过如果有了《JavaScript权威指南》,没有这本书也没有关系。
“How to Learn JavaScript Properly”这篇文章给JavaScript初学者列出了6~8周的学习计划,除了阅读指定书目以及根据书中的内容敲代码。作者还建议初学者开通“StackExchange”的帐号以及JSFiddle的帐号,因为StackExchange如今以积累了大量技术开发问题和解答。而工程师们往往会使用JSFiddle这个在线的IDE来分享JS代码。
Codecademy不宜作为唯一的JavaScript学习平台,但在阅读作者推荐的书目的同时,配合Codecademy上的案例学习也是很有帮助的。
如果你想要自学JavaScript,又或者你已经有一定的JavaScript编程基础却觉得自己学艺不精,不妨读读“How to Learn JavaScript Properly”(英文),相信它会给你带去一些共鸣与收货的。本回答被提问者和网友采纳
参考技术C 首先要说明的是,咱现在不是高手,最多还是一个半桶水,算是入了JS的门。
  谈不上经验,都是一些教训。
  这个时候有人要说,“靠,你丫半桶水,凭啥教我们”。您先别急着骂,先听我说。
  你叫一个大学生去教小学数学,不见得比一个初中生教得好。因为大学生早已经过了那个阶段,都忘记自己怎么走过来的了。而对于初中生,刚好走过那个阶段,对自己怎么走过来的还记忆犹新,或者还有一些自己的总结。比如,很多高手觉得那本犀牛书入门很好,他们觉得太简单了,但以我的经验来看,它不是入门的最好选择。
  先说说学js的条件
  论条件,咱是IT男;有人说英语,读了几年大学,很遗憾,咱还2级没混过;就咱这条件都学得乐呵呵的,您还等啥。
  当然学习JS也是有门槛的,就是你的html和css至少还比较熟练,您不能连<body>这东东是干啥的都不知道就开始上JS了,学乘除前,学好加减法总是有益无害的。
  再说几点忠告
  1,不要着急看一些复杂网页效果的代码,这样除了打击你自信心,什么也学不到。没达到一定的武功水平,割了小JJ也学不会葵花宝典的。
  2,别急着加技术交流QQ群,加牛人QQ。如果你找张三丰交流武功,你上去第一句问“丰哥,where is 丹田?”,你会被他一掌劈死的。
  3,看网上什么多少天精通JS,啥啥啥从入门到精通,这种教程直接跳过吧,太多的事实证明,以一种浮躁的心态去做任何事都会以失败而告终。
  4,千万别去弄啥电脑培训,花了钱和时间不说,关键是学不到东西。本来你买两本好书自学3个月能学会的,他们硬是能折腾你两年。
  推荐几本好书
  “超毛,你丫吹了半天牛B,还是没说怎么学啊”
  呵呵,我也没啥特别的办法,只是推荐几本好书。推荐的书,得按先后顺序看。别第一本没看完,就急着上第二本,并不是每次“穿越”都能成功的
  第一阶段:《JavaScript DOM编程艺术》
  看这本书之前,请先确认您对Javascript有个基本的了解,应该知道if else之类的语法,如果不懂,先去看看我第二阶段推荐的《Javascript高级程序设计》的前三章,记住看三章就别往下看了,回到《JavaScript DOM编程艺术》这本书上来。
  学习Javascript用《JavaScript DOM编程艺术》来入门最好不过了,老老实实看两遍,看完了你就会对JS有一个大概的了解,整本书都围绕着一个网页效果例子展开,你跟着老老实实敲一篇,敲完之后,你会发现这个效果不是常在网页中看到么,发现自己也能做出来网上的效果了,嘿嘿,小有成就感吧。
  第二阶段:《JavaScript高级程序设计》
  有的书是用来成为经典的,比如犀牛书;还有些书是用来超越经典的,显然这本书就是这种。书中章章经典,由浅入深,其中第6章,关于JS面向对象的解说,没有教程出其右。
  如果有一场满分100分的JS考试,看了《JavaScript DOM编程艺术》能让你拿到20分,那么看完这本书,你就能拿到60分以上了。学完后,你会成就感倍增的,相信我(至少看两遍,推荐三篇,跟着书上的代码一行行的敲)。
  这本书强烈推荐购买,写的太TMD牛逼了,给你带来的价值超过百倍千倍。
  这本书最新的是第三版,貌似就是前些日子出来的,我看的是第二版,第三版相对第二版变动不大,添加了几章内容,价格目前相差10元左右。
  接下来,恭喜你可以下山了,这个时候可以自己做一些事情了
你可以去Ferris这个教程看看他写的这些效果,看看源代码,怎么样,是不是觉得有一部分很简单了,尝试着跟着他写一写这些效果吧。
学技术闭门造车是行不通的,适当的加一两个QQ群交流(注重质量),常去论坛逛逛,你会经常有些小收获的。
再有就是看看前辈这些牛人前辈们分享的文章,它会让你的学习事半功倍的,这里是热心人收集的国内一些牛人的博客、个人网站,点这里。
  第三阶段:《JavaScript语言精粹》和《高性能JavaScript》
  接下来两本书《JavaScript语言精粹》和《高性能JavaScript》算是JS高级教程的补充,里面有一些内容和JS高级教程重复了,两本书可以同时看,都不厚,可以对前面所学的有一个很好的加强和巩固。
  第四阶段:《JavaScript DOM高级程序设计》和《JavaScript设计模式》
  在吃透了前面所说的书之后,接下来两本书的顺序已经无关紧要了,《JavaScript DOM高级程序设计》(注意和《JavaScript 高级程序设计》相区别)和《JavaScript设计模式》,这两本都是重量级的书,能让你的JS技术上一个新的台阶;这两本书前者主修炼外功,后者主修炼内功,有点想乾坤大挪移和九阳神功的关系。
  《JavaScript DOM高级程序设计》 首先教你搭建一个类似JQuery的额工具函数库,然后通过讲解几个实际中经常遇到的几个应用例子,会让初学者受益匪浅。
  《JavaScript设计模式》主要讲Javascript的设计模式,说实话,翻译的质量很一般,有些生硬,但已经基本不影响你的学习,看代码完全可以理解出自己的意思。
  这两本书出来一段时间了,可能买不着了,提供下载地址:
  《JavaScript DOM高级程序设计》下载地址,注意有三部分需要下载。
  《JavaScript设计模式》下载地址
  最后想说的
不安逸,不浮躁。任何学习都不是一蹴而就的,牛B就是一个学习积累的过程,别指望两三个月,你的水平就多么厉害。倚天屠龙记里面的武功最牛B的是张三丰,而不是张无忌。
任何工作都需要多种技能,别忽略了html, css等其他知识的学习。
参考技术D 由于javascript在很多时候会和html、css联系紧密,所以建议亲学习javascript之前先学习一下html和css的相关知识。javascript中涉及到ajax的那一节的话懂后端编程,例如php的话会更顺利、更容易理解。

如何正确克隆 JavaScript 对象?

【中文标题】如何正确克隆 JavaScript 对象?【英文标题】:How do I correctly clone a JavaScript object? 【发布时间】:2010-10-18 05:17:24 【问题描述】:

我有一个对象x。我想将它复制为对象y,这样对y 的更改就不会修改x。我意识到复制从内置 JavaScript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。

【问题讨论】:

看到这个问题:***.com/questions/122102/… 对于 JSON,我使用 mObj=JSON.parse(JSON.stringify(jsonObject)); 我真的不明白为什么没有人建议Object.create(o),它可以满足作者的所有要求? var x = deep: key: 1 ; var y = Object.create(x); x.deep.key = 2; 执行此操作后,y.deep.key 也将为 2,因此 Object.create 不能用于克隆... @r3wt 这不起作用...请仅在对解决方案进行基本测试后发布.. 【参考方案1】:

对 JavaScript 中的任何对象执行此操作都不会简单或直接。您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。例如,如果您要向Object.prototype 添加clone 方法,正如某些答案所描述的那样,您将需要显式跳过该属性。但是,如果在Object.prototype 或其他中间原型中添加了您不知道的其他附加方法怎么办?在这种情况下,您将复制不应该复制的属性,因此您需要使用 hasOwnProperty 方法检测不可预见的非本地属性。

除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您还会遇到更棘手的问题。例如,prototype 是函数的隐藏属性。此外,对象的原型由属性__proto__ 引用,该属性也是隐藏的,并且不会被迭代源对象属性的for/in 循环复制。我认为__proto__ 可能特定于 Firefox 的 JavaScript 解释器,在其他浏览器中可能有所不同,但你明白了。并非所有事物都是可枚举的。如果您知道它的名称,您可以复制隐藏的属性,但我不知道有什么方法可以自动发现它。

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是Object,那么只需使用 创建一个新的通用对象即可,但如果源的原型是Object 的某个后代,那么您将丢失该原型中的其他成员您使用hasOwnProperty 过滤器跳过了哪些,或者哪些在原型中,但一开始就无法枚举。一种解决方案可能是调用源对象的constructor 属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如,Date 对象将其数据存储为隐藏成员:

function clone(obj) 
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) 
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    
    return copy;


var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function()
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
, 5000);

d1 的日期字符串将比 d2 的日期字符串晚 5 秒。一种使Date 与另一个相同的方法是调用setTime 方法,但这是特定于Date 类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!

当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制一个普通的 ObjectArrayDateStringNumberBoolean .最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设ObjectArray 中包含的任何元素也将是该列表中的6 个简单类型之一。这可以通过如下代码来完成:

function clone(obj) 
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) 
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    

    // Handle Array
    if (obj instanceof Array) 
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) 
            copy[i] = clone(obj[i]);
        
        return copy;
    

    // Handle Object
    if (obj instanceof Object) 
        copy = ;
        for (var attr in obj) 
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        
        return copy;
    

    throw new Error("Unable to copy obj! Its type isn't supported.");

只要对象和数组中的数据形成树形结构,上面的函数就可以很好地适用于我提到的 6 种简单类型。也就是说,对象中对相同数据的引用不超过一个。例如:

// This would be cloneable:
var tree = 
    "left"  :  "left" : null, "right" : null, "data" : 3 ,
    "right" : null,
    "data"  : 8
;

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = 
    "left"  :  "left" : null, "right" : null, "data" : 3 ,
    "data"  : 8
;
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = 
    "left"  :  "left" : null, "right" : null, "data" : 3 ,
    "data"  : 8
;
cyclicGraph["right"] = cyclicGraph;

它将无法处理任何 JavaScript 对象,但它可能足以满足多种用途,只要您不认为它只适用于您扔给它的任何东西。

【讨论】:

缺少符号键和符号值。现在,使用Object.getOwnPropertyDescriptors 更好。【参考方案2】:

如果你不在你的对象中使用Dates、functions、undefined、regExp 或 Infinity,一个非常简单的衬线是JSON.parse(JSON.stringify(object))

const a = 
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'

console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。

另请参阅this article about the structured clone algorithm of browsers,它在向工作人员发送消息或从工作人员发送消息时使用。它还包含一个深度克隆功能。

【讨论】:

有时最好的答案是最简单的。天才。【参考方案3】:

使用jQuery,你可以浅拷贝 extend:

var copiedObject = jQuery.extend(, originalObject)

copiedObject 的后续更改不会影响originalObject,反之亦然。

或者制作一个深拷贝

var copiedObject = jQuery.extend(true, , originalObject)

【讨论】:

甚至:var copiedObject = jQuery.extend(,originalObject); 将 true 指定为深层复制的第一个参数也很有用:jQuery.extend(true, , originalObject);【参考方案4】:

在 ECMAScript 6 中有 Object.assign 方法,它将所有可枚举自身属性的值从一个对象复制到另一个对象。例如:

var x = myProp: "value";
var y = Object.assign(, x); 

但请注意这是一个浅拷贝 - 嵌套对象仍作为引用进行复制。

【讨论】:

【参考方案5】:

每MDN:

如果你想要浅拷贝,使用Object.assign(, a) 对于“深”复制,使用JSON.parse(JSON.stringify(a))

不需要外部库但需要检查browser compatibility first。

【讨论】:

【参考方案6】:

有很多答案,但没有一个提到 ECMAScript 5 中的Object.create,它诚然没有给你一个精确的副本,但将源设置为新对象的原型。

因此,这不是问题的确切答案,但它是一种单行解决方案,因此很优雅。它最适用于 2 种情况:

    这种继承在哪里有用(呵呵!) 源对象不会被修改,因此这两个对象之间的关系不成问题。

例子:

var foo =  a : 1 ;
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为这个解决方案更好?它是原生的,因此没有循环,没有递归。但是,较旧的浏览器将需要一个 polyfill。

【讨论】:

这是原型继承,不是克隆。这些是完全不同的事情。新对象没有任何自己的属性,它只是指向原型的属性。克隆的重点是创建一个不引用另一个对象中任何属性的全新对象。【参考方案7】:

在一行代码中克隆 Javascript 对象的优雅方法

Object.assign 方法是 ECMAScript 2015 (ES6) 标准的一部分,可以满足您的需求。

var clone = Object.assign(, obj);

Object.assign() 方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象。

Read more...

polyfill 支持旧版浏览器:

if (!Object.assign) 
  Object.defineProperty(Object, 'assign', 
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) 
      'use strict';
      if (target === undefined || target === null) 
        throw new TypeError('Cannot convert first argument to object');
      

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) 
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) 
          continue;
        
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) 
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) 
            to[nextKey] = nextSource[nextKey];
          
        
      
      return to;
    
  );

【讨论】:

这只会执行浅“克隆”【参考方案8】:

互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,其中包括为什么不应该接受已接受的答案。

开始情况

我想深度复制 Javascript Object 及其所有子级及其子级等等。但由于我不是一个普通的开发者,我的Object正常 propertiescircular structures 甚至nested objects

所以让我们先创建一个circular structure 和一个nested object

function Circ() 
    this.me = this;


function Nested(y) 
    this.y = y;

让我们将所有内容放在一个名为 aObject 中。

var a = 
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
;

接下来,我们要将a 复制到名为b 的变量中并对其进行变异。

var b = a;

b.x = 'b';
b.nested.y = 'b';

您知道这里发生了什么,否则您甚至不会想到这个好问题。

console.log(a, b);

a --> Object 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


b --> Object 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    

现在让我们找到解决方案。

JSON

我尝试的第一次尝试是使用JSON

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

不要在上面浪费太多时间,你会得到TypeError: Converting circular structure to JSON

递归复制(接受的“答案”)

让我们看看接受的答案。

function cloneSO(obj) 
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) 
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    

    // Handle Array
    if (obj instanceof Array) 
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) 
            copy[i] = cloneSO(obj[i]);
        
        return copy;
    

    // Handle Object
    if (obj instanceof Object) 
        var copy = ;
        for (var attr in obj) 
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        
        return copy;
    

    throw new Error("Unable to copy obj! Its type isn't supported.");

看起来不错,嗯?它是对象的递归副本,也可以处理其他类型,例如 Date,但这不是必需的。

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

递归和circular structures 不能很好地协同工作...RangeError: Maximum call stack size exceeded

原生解决方案

在与我的同事争论之后,我的老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案。它叫做Object.create

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

这个解决方案是前段时间添加到 Javascript 中的,甚至可以处理 circular structure

console.log(a, b);

a --> Object 
    x: "a",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


b --> Object 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    

...你看,它不适用于内部的嵌套结构。

原生解决方案的 polyfill

在旧版浏览器中,Object.create 有一个 polyfill,就像 IE 8 一样。这就像 Mozilla 推荐的那样,当然,它并不完美,并且会导致与 本机解决方案相同的问题.

function F() ;
function clonePF(o) 
    F.prototype = o;
    return new F();


var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

我已将F 放在范围之外,以便我们查看instanceof 告诉我们的内容。

console.log(a, b);

a --> Object 
    x: "a",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


b --> F 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

原生解决方案相同的问题,但输出稍差。

更好(但不完美)的解决方案

在四处挖掘时,我发现了一个与此问题类似的问题 (In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?),但有一个更好的解决方案。

function cloneDR(o) 
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) 
        return o; // primitive value
    

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") 
        return cache();
    
    // else
    o[gdcc] = function()  return result; ; // overwrite
    if (o instanceof Array) 
        result = [];
        for (var i=0; i<o.length; i++) 
            result[i] = cloneDR(o[i]);
        
     else 
        result = ;
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    
    if (set) 
        o[gdcc] = cache; // reset
     else 
        delete o[gdcc]; // unset again
    
    return result;


var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

让我们看看输出...

console.log(a, b);

a --> Object 
    x: "a",
    circ: Object 
        me: Object  ... 
    ,
    nested: Object 
        y: "a"
    


b --> Object 
    x: "b",
    circ: Object 
        me: Object  ... 
    ,
    nested: Object 
        y: "b"
    


console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

要求匹配,但还是有一些小问题,包括将nestedcircinstance改为Object

共享一个叶子的树的结构不会被复制,它们会成为两个独立的叶子:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

结论

使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真实深度副本。它处理简单的propertiescircular structuresnested object,但在克隆时会弄乱它们的实例。

jsfiddle

【讨论】:

所以结论是避免这个问题:) @mikus 直到有一个 真正的 规范,它涵盖的不仅仅是基本用例,是的。 对上面提供的解决方案进行了分析,但作者得出的结论表明该问题没有解决方案。 遗憾的是 JS 不包含原生克隆功能。 在所有最热门的答案中,我觉得这是接近正确的答案。【参考方案9】:

如果你对浅拷贝没问题,underscore.js 库有一个clone 方法。

y = _.clone(x);

或者你可以像这样扩展它

copiedObject = _.extend(,originalObject);

【讨论】:

谢谢。在 Meteor 服务器上使用这种技术。 要快速开始使用 lodash,我建议学习 npm、Browserify 以及 lodash。我让克隆与 'npm i --save lodash.clone' 一起工作,然后是 'var clone = require('lodash.clone');'要让 require 工作,你需要类似 browserify 的东西。安装并了解它的工作原理后,每次运行代码时都将使用“browserify yourfile.js > bundle.js;start chrome index.html”(而不是直接进入 Chrome)。这会将您的文件和 npm 模块所需的所有文件收集到 bundle.js 中。不过,您可能可以使用 Gulp 节省时间并自动执行此步骤。【参考方案10】:

好的,假设您在下面有这个对象并且您想要克隆它:

let obj = a:1, b:2, c:3; //ES6

var obj = a:1, b:2, c:3; //ES5

答案主要取决于您使用的是哪个ECMAscript,在ES6+,您可以简单地使用Object.assign 进行克隆:

let cloned = Object.assign(, obj); //new a:1, b:2, c:3;

或像这样使用扩展运算符:

let cloned = ...obj; //new a:1, b:2, c:3;

但是如果你使用ES5,你可以使用一些方法,但是JSON.stringify,只要确保你没有使用大量数据来复制,但在很多情况下它可能是一个方便的方法,像这样:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new a:1, b:2, c:3;, can be handy, but avoid using on big chunk of data over and over

【讨论】:

您能否举例说明big chunk of data 的含义? 100KB? 100MB?谢谢! 是的,@user1063287,基本上更大的数据,性能更差......所以这真的取决于,而不是 kb、mb 或 gb,更多的是关于你想要做多少次。 ..它也不适用于函数和其他东西...... Object.assign 进行浅拷贝(就像传播一样,@Alizera) 你不能在 es5 中使用 let :^) @Alireza【参考方案11】:

2020 年 7 月 6 日更新

在 JavaScript 中有三 (3) 种方法可以克隆对象。由于 JavaScript 中的对象是引用值,因此不能简单地使用 = 进行复制。

方法有:

const food =  food: 'apple', drink: 'milk' 


// 1. Using the "Spread"
// ------------------

 ...food 


// 2. Using "Object.assign"
// ------------------

Object.assign(, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
//  food: 'apple', drink: 'milk' 

这可以作为参考总结。

【讨论】:

这为这个问题增加了哪些新的/独特的信息? JSON 方法将删除对象的任何方法 从一个对象创建一个字符串,然后将该字符串解析为另一个对象只是为了复制该对象是一种Monty Python的编程风格:-D 这仅适用于对象字面量和可以这样表示的对象,但不适用于 像您在 OO 语言中遇到的通用“对象”。这似乎是 OP 要求的,因此没关系,但它不是适用于每种对象的通用解决方案。 扩展运算符和 Object.assign 对于具有层次结构的对象失败,即。嵌套对象。 JSON.parse/stringify 有效,但如上所述不复制方法。【参考方案12】:

一个特别不优雅的解决方案是使用 JSON 编码来制作没有成员方法的对象的深层副本。该方法是对您的目标对象进行 JSON 编码,然后通过对其进行解码,您可以获得所需的副本。您可以根据需要进行多次解码,制作尽可能多的副本。

当然,函数不属于 JSON,所以这只适用于没有成员方法的对象。

这种方法非常适合我的用例,因为我将 JSON blob 存储在键值存储中,并且当它们在 JavaScript API 中作为对象公开时,每个对象实际上都包含原始状态的副本对象,这样我们就可以在调用者改变暴露的对象后计算增量。

var object1 = key:"value";
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

【讨论】:

为什么函数不属于JSON?我不止一次看到它们以 JSON 格式传输... 函数不是 JSON 规范的一部分,因为它们不是一种安全(或智能)的数据传输方式,而这正是 JSON 的用途。我知道 Firefox 中的本机 JSON 编码器只是忽略传递给它的函数,但我不确定其他人的行为。 @mark: 'foo': function() return 1; 是文字构造的对象。 @abarnert 函数不是数据。 “函数字面量”用词不当——因为函数可以包含任意代码,包括赋值和各种“不可序列化”的东西。【参考方案13】:

您可以简单地使用spread property 来复制没有引用的对象。但要小心(见 cmets),“副本”只是在最低的对象/数组级别。嵌套属性仍然是引用!


完整克隆:

let x = a: 'value1'
let x2 = ...x

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

使用二级引用克隆:

const y = a: b: 'value3'
const y2 = ...y

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript 实际上本身并不支持深度克隆。使用效用函数。例如拉姆达:

http://ramdajs.com/docs/#clone

【讨论】:

这不起作用...当 x 将是一个数组时,它可能会起作用,例如 x= [ 'ab','cd',...] 这可行,但请记住这是一个 SHALLOW 副本,因此对其他对象的任何深度引用仍然是引用! 部分克隆也可以通过这种方式发生:const first = a: 'foo', b: 'bar'; const second = ...a = first【参考方案14】:
const objClone =  ...obj ;

请注意,嵌套对象仍被复制作为参考。

【讨论】:

感谢嵌套对象仍被复制作为参考的提示!调试我的代码时我几乎发疯了,因为我修改了“克隆”上的嵌套属性,但原来的被修改了。 这是 ES2016,不是 2018,这个答案是 two years earlier。 如果我也想要嵌套属性的副本怎么办 @SunilGarg 要复制嵌套属性,您可以使用const objDeepClone = JSON.parse(JSON.stringify(obj));【参考方案15】:

对于那些使用 AngularJS 的人,也有直接的方法来克隆或扩展这个库中的对象。

var destination = angular.copy(source);

angular.copy(source, destination);

更多内容见 angular.copy documentation...

【讨论】:

这是一个深拷贝仅供参考。【参考方案16】:

来自这篇文章:How to copy arrays and objects in Javascript by Brian Huisman:

Object.prototype.clone = function() 
  var newObj = (this instanceof Array) ? [] : ;
  for (var i in this) 
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") 
      newObj[i] = this[i].clone();
     else newObj[i] = this[i]
   return newObj;
;

【讨论】:

这很接近,但不适用于任何对象。尝试用这个克隆一个 Date 对象。并非所有属性都是可枚举的,因此它们不会全部显示在 for/in 循环中。 像这样添加到对象原型对我来说破坏了 jQuery。即使我重命名为 clone2。 @iPadDeveloper2011 上面的代码有一个错误,它创建了一个名为“i”“(for i in this)”的全局变量,而不是“(for var i in this)”。我有足够的业力来编辑它并修复它,所以我做到了。 @Calvin: 这应该被创建一个不可枚举的属性,否则 'clone' 将出现在 'for' 循环中。 为什么var copiedObj = Object.create(obj); 不是一个好方法?【参考方案17】:

A.Levy 的回答差不多完成了,这是我的一点贡献:有一种方法可以处理递归引用,请看这一行

if(this[attr]==this) copy[attr] = copy;

如果对象是 XML DOM 元素,我们必须使用 cloneNode 代替

if(this.cloneNode) return this.cloneNode(true);

受 A.Levy 详尽的研究和 Calvin 的原型制作方法的启发,我提供了以下解决方案:

Object.prototype.clone = function() 
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : ;
  for(var attr in this) 
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  
  return copy;


Date.prototype.clone = function() 
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;


Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() 
  return this;

另请参阅答案中的 Andy Burke 注释。

【讨论】:

Date.prototype.clone = function() return new Date(+this);【参考方案18】:
function clone(obj) 
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;

【讨论】:

这个答案非常接近,但并不完全正确。如果您尝试克隆 Date 对象,您将不会获得相同的日期,因为对 Date 构造函数的调用会使用当前日期/时间初始化新 Date。该值不可枚举,不会被 for/in 循环复制。 并不完美,但对于那些基本情况来说很好。例如。允许对可以是基本对象、数组或字符串的参数进行简单克隆。 赞成使用 new 正确调用构造函数。接受的答案没有。 适用于其他所有节点!仍然留下参考链接 递归的想法很棒。但是如果值是数组,它会工作吗?【参考方案19】:

使用 Lodash:

var y = _.clone(x, true);

【讨论】:

天啊,重新发明克隆技术真是太疯狂了。这是唯一理智的答案。 我更喜欢_.cloneDeep(x),因为它本质上和上面一样,但是读起来更好。【参考方案20】:

在 ES-6 中,您可以简单地使用 Object.assign(...)。 例如:

let obj = person: 'Thor Odinson';
let clone = Object.assign(, obj);

这里有一个很好的参考: https://googlechrome.github.io/samples/object-assign-es6/

【讨论】:

它不会深度克隆对象。 这是一个任务,而不是副本。 clone.Title = "just a clone" 表示 obj.Title = "just a clone"。 @HoldOffHunger 你错了。在浏览器的 JS 控制台中检查 (let obj = person: 'Thor Odinson'; let clone = Object.assign(, obj); clone.title = "Whazzup";) @collapsar:这正是我检查的内容,然后 console.log(person) 将是“Whazzup”,而不是“Thor Odinson”。请参阅 August 的评论。 @HoldOffHunger 在 Chrome 60.0.3112.113 和 Edge 14.14393 中都不会发生; August 的评论不适用,因为 obj 属性的原始类型的值确实被克隆了。对象本身的属性值不会被克隆。【参考方案21】:

性能

今天 2020.04.30 我在 MacOs High Sierra v10.13.6 上对 Chrome v81.0、Safari v13.1 和 Firefox v75.0 上的所选解决方案进行了测试。

我专注于复制 DATA 的速度(具有简单类型字段的对象,而不是方法等)。解 A-I 只能做浅拷贝,解 J-U 可以做深拷贝。

浅拷贝的结果

solution ...obj (A) 在 chrome 和 firefox 上最快,在 safari 上中等速度 基于Object.assign (B) 的解决方案在所有浏览器上都很快 jQuery (E) 和 lodash (F,G,H) 解决方案中等/相当快 解决方案JSON.parse/stringify(K) 很慢 解决方案 D 和 U 在所有浏览器上都很慢

深拷贝的结果

解决方案 Q 在所有浏览器上都是最快的 jQuery (L) 和 lodash (J) 中等速度 解决方案JSON.parse/stringify(K) 很慢 解决方案 U 在所有浏览器上最慢 lodash (J) 和解决方案 U 在 Chrome 上崩溃 1000 级深度对象

详情

对于选择的解决方案: A B C(我的) D E F G H I J K L M N O P Q R S T U, 我进行了 4 次测试

浅小:具有 10 个非嵌套字段的对象 - 你可以运行它HERE 浅大:具有 1000 个非嵌套字段的对象 - 你可以运行它HERE deep-small:具有 10 个级别嵌套字段的对象 - 您可以运行它HERE deep-big: 具有 1000 个级别嵌套字段的对象 - 你可以运行它HERE

测试中使用的对象如下所示sn-p

let obj_ShallowSmall = 
  field0: false,
  field1: true,
  field2: 1,
  field3: 0,
  field4: null,
  field5: [],
  field6: ,
  field7: "text7",
  field8: "text8",


let obj_DeepSmall = 
  level0: 
   level1: 
    level2: 
     level3: 
      level4: 
       level5: 
        level6: 
         level7: 
          level8: 
           level9: [[[[[[[[[['abc']]]]]]]]]],
  ,
;

let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,);


let obj_DeepBig = genDeepObject(1000);



// ------------------
// Show objects
// ------------------

console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));




// ------------------
// HELPERS
// ------------------

function getField(k) 
  let i=k%10;
  if(i==0) return false;
  if(i==1) return true;
  if(i==2) return k;
  if(i==3) return 0;
  if(i==4) return null;
  if(i==5) return [];
  if(i==6) return ;  
  if(i>=7) return "text"+k;


function genDeepObject(N) 
  // generate: level0:level1:...levelN: end:[[[...N-times...['abc']...]]] ...
  let obj=;
  let o=obj;
  let arr = [];
  let a=arr;

  for(let i=0; i<N; i++) 
    o['level'+i]=;
    o=o['level'+i];
    let aa=[];
    a.push(aa);
    a=aa;
  

  a[0]='abc';
  o['end']=arr;
  return obj;

下面的 sn-p 展示了经过测试的解决方案并显示了它们之间的差异

function A(obj) 
  return ...obj


function B(obj) 
  return Object.assign(, obj); 


function C(obj) 
  return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), )


function D(obj) 
  let copyOfObject = ;
  Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
  return copyOfObject;


function E(obj) 
  return jQuery.extend(, obj) // shallow


function F(obj) 
  return _.clone(obj);


function G(obj) 
  return _.clone(obj,true);


function H(obj) 
  return _.extend(,obj);


function I(obj) 
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) 
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    
    return copy;


function J(obj) 
  return _.cloneDeep(obj,true);


function K(obj) 
	return JSON.parse(JSON.stringify(obj));


function L(obj) 
  return jQuery.extend(true, , obj) // deep


function M(obj) 
  if(obj == null || typeof(obj) != 'object')
    return obj;    
  var temp = new obj.constructor(); 
  for(var key in obj)
    temp[key] = M(obj[key]);    
  return temp;


function N(obj) 
  let EClone = function(obj) 
    var newObj = (obj instanceof Array) ? [] : ;
    for (var i in obj) 
      if (i == 'EClone') continue;
      if (obj[i] && typeof obj[i] == "object") 
        newObj[i] = EClone(obj[i]);
       else newObj[i] = obj[i]
     return newObj;
  ;

	return EClone(obj);
;

function O(obj) 
    if (obj == null || typeof obj != "object") return obj;
    if (obj.constructor != Object && obj.constructor != Array) return obj;
    if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
        obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
        return new obj.constructor(obj);

    let to = new obj.constructor();

    for (var name in obj)
    
        to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name];
    

    return to;


function P(obj) 
  function clone(target, source)

      for(let key in source)

          // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
          let descriptor = Object.getOwnPropertyDescriptor(source, key);
          if(descriptor.value instanceof String)
              target[key] = new String(descriptor.value);
          
          else if(descriptor.value instanceof Array)
              target[key] = clone([], descriptor.value);
          
          else if(descriptor.value instanceof Object)
              let prototype = Reflect.getPrototypeOf(descriptor.value);
              let cloneObject = clone(, descriptor.value);
              Reflect.setPrototypeOf(cloneObject, prototype);
              target[key] = cloneObject;
          
          else 
              Object.defineProperty(target, key, descriptor);
          
      
      let prototype = Reflect.getPrototypeOf(source);
      Reflect.setPrototypeOf(target, prototype);
      return target;
  
  return clone(,obj);


function Q(obj) 
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) 
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    

    // Handle Array
    if (obj instanceof Array) 
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) 
            copy[i] = Q(obj[i]);
        
        return copy;
    

    // Handle Object
    if (obj instanceof Object) 
        copy = ;
        for (var attr in obj) 
            if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]);
        
        return copy;
    

    throw new Error("Unable to copy obj! Its type isn't supported.");


function R(obj) 
    const gdcc = "__getDeepCircularCopy__";
    if (obj !== Object(obj)) 
        return obj; // primitive value
    

    var set = gdcc in obj,
        cache = obj[gdcc],
        result;
    if (set && typeof cache == "function") 
        return cache();
    
    // else
    obj[gdcc] = function()  return result; ; // overwrite
    if (obj instanceof Array) 
        result = [];
        for (var i=0; i<obj.length; i++) 
            result[i] = R(obj[i]);
        
     else 
        result = ;
        for (var prop in obj)
            if (prop != gdcc)
                result[prop] = R(obj[prop]);
            else if (set)
                result[prop] = R(cache);
    
    if (set) 
        obj[gdcc] = cache; // reset
     else 
        delete obj[gdcc]; // unset again
    
    return result;


function S(obj) 
    const cache = new WeakMap(); // Map of old - new references

    function copy(object) 
        if (typeof object !== 'object' ||
            object === null ||
            object instanceof HTMLElement
        )
            return object; // primitive value or HTMLElement

        if (object instanceof Date) 
            return new Date().setTime(object.getTime());

        if (object instanceof RegExp) 
            return new RegExp(object.source, object.flags);

        if (cache.has(object)) 
            return cache.get(object);

        const result = object instanceof Array ? [] : ;

        cache.set(object, result); // store reference to object before the recursive starts

        if (object instanceof Array) 
            for(const o of object) 
                 result.push(copy(o));
            
            return result;
        

        const keys = Object.keys(object); 

        for (const key of keys)
            result[key] = copy(object[key]);

        return result;
    

    return copy(obj);


function T(obj)
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) 
        if (obj == null) return null;
        if (obj.__obj_id == undefined)
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        
        return obj.__obj_id;
    

    function cloneRecursive(obj) 
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

        // Handle Date
        if (obj instanceof Date) 
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        

        // Handle Array
        if (obj instanceof Array) 
            var copy = [];
            for (var i = 0; i < obj.length; ++i) 
                copy[i] = cloneRecursive(obj[i]);
            
            return copy;
        

        // Handle Object
        if (obj instanceof Object) 
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function()return obj.apply(this, arguments);;
            else
                copy = ;

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
               


        throw new Error("Unable to copy obj! Its type isn't supported.");
    
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    
        delete originalObjectsArray[i].__obj_id;
    ;

    return cloneObj;


function U(obj) 
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(obj)

  function defineProp(object, key, descriptor = , copyFrom = ) 
    const  configurable: _configurable, writable: _writable 
      = Object.getOwnPropertyDescriptor(object, key)
      ||  configurable: true, writable: true 

    const test = _configurable // Can redefine property
      && (_writable === undefined || _writable) // Can assign to property

    if (!test || arguments.length <= 2) return test

    const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      ||  configurable: true, writable: true  // Custom…
      || ; // …or left to native default settings

    ["get", "set", "value", "writable", "enumerable", "configurable"]
      .forEach(attr =>
        descriptor[attr] === undefined &&
        (descriptor[attr] = basisDesc[attr])
      )

    const  get, set, value, writable, enumerable, configurable 
      = descriptor

    return Object.defineProperty(object, key, 
      enumerable, configurable, ...get || set
        ?  get, set  // Accessor descriptor
        :  value, writable  // Data descriptor
    )
  

  function clone(object) 
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) 
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        copyPropDescs(_object, object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) 
          //                              // Stem from:
          case "[object Function]":       // `class`
          case "[object Undefined]":      // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:                        // `Proxy`
            _object = object
        
    

    return _object
  


  function cloneObject(object) 
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key,  value: clone(object[key]) , object)
    )

    return _object
  


  function copyPropDescs(target, source) 
    Object.defineProperties(target,
      Object.getOwnPropertyDescriptors(source)
    )
  

 
// ------------------------
// Test properties
// ------------------------


console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt`)

log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);

console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)


// ------------------------
// Helper functions
// ------------------------


function deepCompare(obj1,obj2) 
  return JSON.stringify(obj1)===JSON.stringify(obj2);


function getCase()  // pure data case
  return  
    undef: undefined,
    bool: true, num: 1, str: "txt1",    
    e1: null, e2: [], e3: , e4: 0, e5: false,
    arr: [ false, 2, "txt3", null, [], ,
      [ true,4,"txt5",null, [], ,  [true,6,"txt7",null,[], ], 
        bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3: ,e4: 0, e5: false
      ],
        bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3: ,e4: 0, e5: false
    ], 
    obj:  
        bool: true, num: 12, str: "txt13",
        e1: null, e2: [], e3: , e4: 0, e5: false,
        arr: [true,14,"txt15",null,[], ],
        obj:  
          bool: true, num: 16, str: "txt17",
          e1: null, e2: [], e3: , e4: 0, e5: false,
          arr: [true,18,"txt19",null,[], ],
          obj: bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3: ,e4: 0, e5: false
       
     
  ;


function check(org, copy, field, newValue) 
  copy[field] = newValue;
  return deepCompare(org,copy); 


function testFunc(f) 
	let o =  a:1, fun: (i,j)=> i+j ;
  let c = f(o);
  
  let val = false
  try
    val = c.fun(3,4)==7;
   catch(e)  
  return val;
 

function testCirc(f) 
	function Circ() 
    this.me = this;
  

  var o = 
      x: 'a',
      circ: new Circ(),
      obj_circ: null,
  ;
  
  o.obj_circ = o;

  let val = false;

  try
    let c = f(o);  
    val = (o.obj_circ == o) && (o.circ == o.circ.me);
   catch(e)  
  return val;
 

function testRegExp(f) 
  let o = 
    re: /a[0-9]+/,
  ;
  
  let val = false;

  try
    let c = f(o);  
    val = (String(c.re) == String(/a[0-9]+/));
   catch(e)  
  return val;


function testDate(f) 
  let o = 
    date: new Date(),
  ;
  
  let val = false;

  try
    let c = f(o);  
    val = (+new Date(c.date) == +new Date(o.date));
   catch(e)  
  return val;


function testBigInt(f) 
  let val = false;
  
  try
    let o = 
      big: 123n,
    ;
  
    let c = f(o);  
  
    val = o.big == c.big;
   catch(e)  
  
  return val;


function log(f) 
  let o = getCase();  // orginal object
  let oB = getCase(); // "backup" used for shallow valid test
  
  let c1 = f(o); // copy 1 for reference
  let c2 = f(o); // copy 2 for test shallow values
  let c3 = f(o); // copy 3 for test deep values

  let is_proper_copy = deepCompare(c1,o);  // shoud be true
  
  // shallow changes
  let testShallow = 
    [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',] ]
    .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
  
  // should be true (original object shoud not have changed shallow fields)
  let is_valid = deepCompare(o,oB); 

  // deep test (intruduce some change)
  if (c3.arr[6]) c3.arr[6][7].num = 777;
  
  let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
  let diff_deep = !deepCompare(c1,c3);    // shoud be true (deep field was copied)
  let can_copy_functions = testFunc(f);
  let can_copy_circular = testCirc(f);
  let can_copy_regexp = testRegExp(f);
  let can_copy_date = testDate(f);
  let can_copy_bigInt = testBigInt(f);
  
  let has_undefined = 'undef' in c1; // field with undefined value is copied?  
  let is_ok = is_valid && is_proper_copy;
  let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
  
  testFunc(f);
  
  if(is_ok) 
    console.log(`$f.name $b(diff_shallow)   $b(diff_deep) $b(can_copy_functions) $b(can_copy_circular) $b(has_undefined)     $b(can_copy_date) $b(can_copy_regexp)  $b(can_copy_bigInt)`)
   else 
    console.log(`$f.name: INVALID $is_valid $is_proper_copy`,c1)
  
  
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>

This snippet only presents tested solutions and show differences between them (but it no make performence tests)

下面是 Chrome 用于浅大对象的示例结果

【讨论】:

【参考方案22】:

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(json_original));

来源:How to copy JavaScript object to new variable NOT by reference?

【讨论】:

非常好 - 简单。 @MattH:这个答案已经给了in 2012。你看见了吗? Mohammed,您在复制其中一个之前检查过现有答案吗? 这是一种方式。你从来没有想过这个【参考方案23】:

您可以使用一行代码克隆一个对象并从前一个对象中删除任何引用。只需这样做:

var obj1 =  text: 'moo1' ;
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: text:'moo1', obj2: text:'moo2'

对于当前不支持 Object.create 的浏览器/引擎,您可以使用这个 polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) 
    Object.create = function (o) 
        var F = function () ;
        F.prototype = o;
        return new F();
    ;

【讨论】:

+1 Object.create(...) 似乎绝对是要走的路。 完美答案。也许您可以为Object.hasOwnProperty 添加解释?这样人们就知道如何防止搜索原型链接。 效果很好,但是 polyfill 在哪些浏览器中工作? 这是用 obj1 作为原型创建 obj2。它之所以有效,是因为您正在遮蔽 obj2 中的 text 成员。您不是在制作副本,只是在 obj2 上找不到成员时遵循原型链。 这不会在“没有引用的情况下”创建它,它只是将引用移动到原型。它仍然是一个参考。如果原始属性发生变化,“克隆”中的原型属性也会发生变化。它根本不是克隆。【参考方案24】:
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

如果您想(浅)克隆一个 类实例 而不仅仅是一个属性对象,则使用 E​​S6 解决方案。

【讨论】:

这与let cloned = Object.assign(, obj) 有何不同? @ceztko 当obj 是类实例时,Object.assign() 不会克隆例如类方法(因为它们不可枚举)。【参考方案25】:

老问题的新答案!如果您有幸将 ECMAScript 2016 (ES6) 与 Spread Syntax 一起使用,这很容易。

keepMeTheSame = first: "Me!", second: "You!";
cloned = ...keepMeTheSame

这为对象的浅拷贝提供了一种干净的方法。制作深层副本,即为每个递归嵌套对象中的每个值创建一个新副本,需要上述较重的解决方案。

JavaScript 不断发展。

【讨论】:

当你在对象上定义了函数时它不起作用 据我所知,传播运算符仅适用于迭代 - developer.mozilla.org 说:var obj = 'key1': 'value1'; var array = [...obj]; // TypeError: obj is not iterable @Oleh 所以使用 ` ... obj 而不是 [...obj];` @manikantgautam 我之前使用过 Object.assign(),但现在最新的 Chrome、Firefox(仍然不支持 Edge 和 Safari)确实支持对象传播语法。它的 ECMAScript 提案......但据我所知,Babel 确实支持它,所以它可能可以安全使用。【参考方案26】:

对于深拷贝和克隆,JSON.stringify 然后 JSON.parse 对象:

obj =  a: 0 , b:  c: 0;
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); //  a: 0, b:  c: 0

【讨论】:

非常聪明...这种方法有什么缺点吗?【参考方案27】:

我认为有一个简单而有效的答案。在深度复制中存在两个问题:

    保持属性相互独立。 并在克隆对象上保持方法有效。

所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行分配以复制函数。

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign(, source);
Object.assign(merged, deepCloned);

虽然这个问题有很多答案,但我希望这个也能有所帮助。

【讨论】:

虽然如果允许我导入 lodash,我更喜欢使用 lodash cloneDeep 我正在使用 JSON.parse(JSON.stringify(source))。一直在工作。 @Misha,这样你会错过这些功能。 “作品”一词有多种含义。 请记住,我提到的方式,只会复制第一层的功能。因此,如果我们在彼此内部有一些对象,那么唯一的方法就是逐个字段递归地复制。【参考方案28】:

(以下主要是@Maciej Bukowski,@A. Levy,@Jan Turoň,@Redu的回答和@LeviRoberts,@RobG的cmets的整合,非常感谢给他们!!!)

深拷贝? - 是的! (大部分);浅拷贝? - 不! (Proxy 除外)。

真诚欢迎大家测试clone(). 此外,defineProp() 旨在轻松快速地(重新)定义或复制任何类型的描述符。

功能

function clone(object) 
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(object)


  function clone(object) 
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) 
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        _object = copyFn(object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) 
          //                                  // Stem from:
          case "[object Function]":
            switch (object[Symbol.toStringTag]) 
              case undefined:
                _object = cloneObject(object) // `class`
                break

              case "AsyncFunction":
              case "GeneratorFunction":
              case "AsyncGeneratorFunction":
                _object = copyFn(object)
                break

              default:
                _object = object
            
            break

          case "[object Undefined]":          // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:
            _object = object                  // `Proxy`
        
    

    return _object
  


  function cloneObject(object) 
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key,  value: clone(object[key]) , object)
    )

    return _object
  



function copyPropDescs(target, source) 
  Object.defineProperties(target,
    Object.getOwnPropertyDescriptors(source)
  )



function convertFnToStr(fn) 
  let fnStr = String(fn)
  if (fn.name.startsWith("[")) // isSymbolKey
    fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
  fnStr = /^(?!(async )?(function\b|[^]+?=>))[^(]+?\(/.test(fnStr)
    ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
  return fnStr


function copyFn(fn) 
  const newFn = new Function(`return $convertFnToStr(fn)`)()
  copyPropDescs(newFn, fn)
  return newFn




function defineProp(object, key, descriptor = , copyFrom = ) 
  const  configurable: _configurable, writable: _writable 
    = Object.getOwnPropertyDescriptor(object, key)
    ||  configurable: true, writable: true 

  const test = _configurable // Can redefine property
    && (_writable === undefined || _writable) // Can assign to property

  if (!test || arguments.length <= 2) return test

  const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
    ||  configurable: true, writable: true  // Custom…
    || ; // …or left to native default settings

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(attr =>
      descriptor[attr] === undefined &&
      (descriptor[attr] = basisDesc[attr])
    )

  const  get, set, value, writable, enumerable, configurable 
    = descriptor

  return Object.defineProperty(object, key, 
    enumerable, configurable, ...get || set
      ?  get, set  // Accessor descriptor
      :  value, writable  // Data descriptor
  )

// 测试

const obj0 = 
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g()  return 0 ,
  o: 
    n: 0,
    o:  f: function (...args)   
  ,
  f: 
    getAccessorStr(object) 
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    ,
    f0: function f0()  ,
    f1: function ()  ,
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params)  return param => param + params ,
    f5: (a, b) => ( c = 0  = ) => a + b + c
  


defineProp(obj0, "s",  set(v)  this._s = v  )
defineProp(obj0.arr, "tint",  value:  is: "non-enumerable"  )
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0)  return a + b 
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s",  set(v)  this._s = v + 1  )
defineProp(obj1.re, "multiline",  value: true )

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - More kinds of functions")

const fnsForTest = 
  f(_)  return _ ,
  func: _ => _,
  aFunc: async _ => _,
  async function()  ,
  async asyncFunc()  ,
  aFn: async function ()  ,
  *gen()  ,
  async *asyncGen()  ,
  aG1: async function* ()  ,
  aG2: async function* gen()  ,
  *[Symbol.iterator]()  yield* Object.keys(this) 


console.log(Reflect.ownKeys(fnsForTest).map(k =>
  `$String(k):
  $fnsForTest[k].name-->
    $String(fnsForTest[k])`
).join("\n"))

const normedFnsStr = `
  f: function f(_)  return _ ,
  func: _ => _,
  aFunc: async _ => _,
  function: async function()  ,
  asyncFunc: async function asyncFunc()  ,
  aFn: async function ()  ,
  gen: function* gen()  ,
  asyncGen: async function* asyncGen()  ,
  aG1: async function* ()  ,
  aG2: async function* gen()  ,
  [Symbol.iterator]: function* ()  yield* Object.keys(this) 
`

const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`($normedFnsStr)`))
console.log("Comparison of fnsForTest and its clone:",
  Reflect.ownKeys(fnsForTest).map(k =>
    [k, fnsForTest[k] === copiedFnsForTest[k]]
  )
)

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = 
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person 
  constructor(name) 
    this.name = name
  


class Boy extends Person  
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby =  sport: "spaceflight" 

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

参考文献

    Object.create() | MDN Object.defineProperties() | MDN Enumerability and ownership of properties | MDN TypeError: cyclic object value | MDN

使用的语言技巧

    Conditionally add prop to object

【讨论】:

既然Symbol("a") === Symbol("a")false,那么clone(Symbol("a")) 不应该使用Symbol(object.description) 创建一个新符号吗?或者这会对知名符号产生太奇怪的影响?【参考方案29】:

Use lodash _.cloneDeep().

浅拷贝:lodash _.clone()

可以通过简单地复制引用来进行浅拷贝。

let obj1 = 
    a: 0,
    b: 
        c: 0,
        e: 
            f: 0
        
    
;
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
//"a":4,"b":"c":4,"e":"f":100

console.log(JSON.stringify(obj3));
//"a":0,"b":"c":4,"e":"f":100

深度复制:lodash _.cloneDeep()

字段被取消引用:而不是对被复制对象的引用

let obj1 = 
    a: 0,
    b: 
        c: 0,
        e: 
            f: 0
        
    
;
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
"a":100,"b":"c":100,"e":"f":100

console.log(JSON.stringify(obj3));
"a":0,"b":"c":0,"e":"f":0

【讨论】:

【参考方案30】:

这是对 A. Levy 的代码的改编,也可以处理函数的克隆和多个/循环引用 - 这意味着如果克隆的树中的两个属性是同一对象的引用,则克隆的对象树将使这些属性指向被引用对象的同一个克隆。这也解决了循环依赖的情况,如果不加以处理,就会导致无限循环。算法复杂度为O(n)

function clone(obj)
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) 
        if (obj == null) return null;
        if (obj.__obj_id == undefined)
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        
        return obj.__obj_id;
    

    function cloneRecursive(obj) 
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

        // Handle Date
        if (obj instanceof Date) 
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        

        // Handle Array
        if (obj instanceof Array) 
            var copy = [];
            for (var i = 0; i < obj.length; ++i) 
                copy[i] = cloneRecursive(obj[i]);
            
            return copy;
        

        // Handle Object
        if (obj instanceof Object) 
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function()return obj.apply(this, arguments);;
            else
                copy = ;

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
               


        throw new Error("Unable to copy obj! Its type isn't supported.");
    
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    
        delete originalObjectsArray[i].__obj_id;
    ;

    return cloneObj;

一些快速测试

var auxobj = 
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    ;

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function ()
    this.prop1 = "prop1 val changed by f1";
;

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

【讨论】:

截至 2016 年 9 月,这是该问题的唯一正确解决方案。

以上是关于如何正确学习JavaScript的主要内容,如果未能解决你的问题,请参考以下文章

如何正确学习JavaScript

最全解析如何正确学习JavaScript指南,必看!

如何正确的学习Javascript?

学习笔记 第十五章 JavaScript基础

想学Java,也一直在自学,但始终没有掌握正确的学习方法~

正确的Java学习线路图原来是这样,教你如何成为优秀的程序员