JavaScript:如何按值传递对象?


96
  • 当将对象作为参数传递时,JavaScript通过引用传递它们,这使得创建对象的本地副本变得困难。

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    o将有.foo.bar

  • 可以通过克隆解决此问题。简单的例子:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    o不会.foo.bar


  1. 除了创建本地副本/克隆之外,还有没有更好的方法可以按值传递对象?

3
您需要什么用例?
jondavidjohn 2011年

2
编程的乐趣。查看新的JS引擎是否已解决此问题(从技术上讲,它正在按值传递引用),但主要是出于乐趣。
vol7ron 2011年

1
请参阅JavaScript是按引用传递还是按值传递语言?-JavaScript不通过引用传递。与Java一样,将对象传递给函数时,它按值传递,但该值是引用。另请参见Java是否通过引用传递?
理查德JP Le Guen

1
据我所知,您不能按值传递对象。即使存在,实际上也会进行您上面所指的克隆,因此我看不到任何收获。除了节省3行代码外。
Thor84no 2011年

1
@ vol7ron是的,他们已经通过正确实现语言设计特征来解决此问题。
jondavidjohn 2011年

Answers:


61

并不是的。

根据你的实际需要,一个可能性可能是集o作为一个新的对象的原型。

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

因此,您添加到的任何属性obj都不会添加到中oobj使用与其中相同的属性名称添加到的任何属性o都将阴影该o属性。

当然,如果没有阴影,添加到的任何属性o都将可用obj,并且o原型链中所有具有相同属性的对象都将看到相同的更新o

另外,如果obj具有引用另一个对象的属性(例如Array),则需要确保在将对象添加到对象之前将其遮蔽,否则,这些成员将添加到中obj,并将在所有该对象之间共享有obj原型链。

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

在这里,您可以看到,因为您没有使用bazon obaz属性将on 遮蔽在Array 上obj,所以对o.bazArray进行了修改。

因此,您需要先对其进行阴影处理:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined

3
+1,我也打算Object.create在答案中提出建议,但我不想延伸解释副本的浅薄;-)
Andy E

+1,我忘了这个。我试过了var obj = new Object(x)。对我来说,我认为new Object(x)将执行Object.create(x)默认-有趣
vol7ron

1
@ vol7ron:是的,new Object(x)只是吐出相同的对象(您可能已经注意到了)。如果有一种本地的克隆方法,那就太好了。不确定是否有任何作品。
user113716 2011年

43

看看这个答案https://stackoverflow.com/a/5344074/746491

简而言之,JSON.parse(JSON.stringify(obj))如果您的对象可以序列化为json ,这是一种复制对象的快速方法。


2
我倾向于避免这种情况,因为对象不能总是被序列化,因为在IE7 / 8之类的中年浏览器中,JSON不包括在内并且需要进行定义-最好使用这样的名称来描述Clone。但是,我怀疑我的看法将来可能会改变,因为JSON已被更广泛地集成到基于非浏览器的软件中,例如数据库和IDE
vol7ron 2014年

1
如果没有函数和日期对象,则JSON.parse解决方案似乎是最好的。stackoverflow.com/questions/122102/...
Herr_Hansen

真?!?序列化为JSON只是为了创建对象副本?讨厌这种荒唐语言的另一个原因。
瑞安·纳德(RyanNerd)'18年

1
序列化JSON真的是复制它的快速方法吗?根据NodeJS文档,JSON操作是最占用CPU的任务。nodejs.org/en/docs/guides/dont-block-the-event-loop/…–
harshad

38

这是克隆函数,将执行对象的深层复制:

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;
}

现在,您可以像这样使用:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)

3
这是一个很深的答案。
弥敦道

美丽!最佳答案IMO。
甘德斯

23

Object.assign()

例:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

一个执行相同操作的更干净的示例:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


4
更好:var b = Object.assign({}, a);
Bergi

同意 相应地更新答案。另外,并非每个浏览器都支持Assign()方法。根据您的兼容性要求,可能需要使用一种类似MDN的填料。
布兰登

3
@Brandon,在您提供的链接中,它警告您进行深度克隆:“对于深度克隆,我们需要使用其他替代方法,因为Object.assign()复制属性值。如果源值是对对象的引用,它只会复制该参考值。”
pwilcox

2
此方法有效,但请记住,它不会执行深度复制,其中包含的任何对象a都将被复制为参考。
Stoinov

15

您对对象在JavaScript中的工作方式有些困惑。对象的引用变量的值。没有未序列化的值。创建对象时,其结构存储在内存中,分配给该对象的变量保留对该结构的引用。

即使您要问的内容是通过某种简单的本地语言结构提供的,从技术上讲,它仍会被克隆。

JavaScript实际上只是按值传递...只是传递的值可能是对某些东西的引用。


1
嘿,AndyE!我对此并不感到困惑,我希望JS默认进行克隆。自我克隆的效率很低,所以如果引擎做到了,我肯定会更好。我怀疑需求/需求是否会带来好处。
vol7ron 2011年

12
在JS中,当人们说引用而不是值时,它们表示值就像指针,而不是重复值。这是行之有效的行话。
埃里克·雷彭

4
@Erik:以我的拙见,我认为最好对其进行更清晰的定义,以免使新手对这种语言感到困惑。仅仅因为已经建立了某些东西并不意味着它是适当的,甚至在技术上是正确的(因为它不正确)。人们之所以说“参考”,是因为说起来比解释它的实际工作要容易。
Andy E

6
该值是对内存中位置的引用。最终,您仍在处理内存中的同一项目,而不是使用副本。如何使这种模棱两可的歧义帮助新移民?这是很多技术书籍(包括《 O'Reilly的权威指南》)中对此解释的方法,该指南目前是第五版(旧版,并且更新很少)。
埃里克·雷彭

1
@AndyE我做了更多阅读,以了解大惊小怪。IMO,这有点像JS是否具有“真正的” OOP,或者与其他语言相比,我们可以正确地称呼某种“方法”。我实际上不确定将vars直接写入内存中的同一对象,而不是用赋值运算符行为的性能降低分支以基于闭包的语言覆盖其复制的指针,这是不可行的。在JS和其他语言中,我们所指的唯一真正的行为区别是使用赋值运算符时发生的情况。
艾里克·雷彭

15

用这个

x = Object.create(x1);

x并且x1将是两个不同的对象,改变x不会改变x1


我最终使用了Object.assign()。对于简单的测试用例,.create()可以工作,但对于更复杂的(我还无法找到模式),它仍然引用相同的地址。
Coisox

如果对象的值不是通过引用存储的,即它们是诸如number或boolean之类的原始数据类型,则不仅会更改。例如,考虑下面的代码a = {x:[12],y:[]}; b = Object.create(a); bxpush(12); console.log(ax);
Jjagwe Dennis

8

JavaScript总是按值传递。在这种情况下,它将引用的副本传递给o匿名函数。该代码使用的是引用的副本,但它正在更改单个对象。除了值之外,没有其他方法可以使javascript通过。

在这种情况下,您想要的是传递基础对象的副本。克隆对象是唯一的手段。您的克隆方法需要一些更新

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}

1
+1对于“总是按值传递”,尽管我更喜欢使用“按对象共享 ”调用(而不是“按[引用]的按值调用”)并将“值”提升为“对象本身”(注意该对象不会被复制/克隆/复制),因为引用只是实现细节,不会暴露给JavaScript(或在规范中直接提及!)。

我在从远程连接接收的对象中添加属性时遇到了问题(我认为这可能是我无法向该对象添加任何属性的问题),所以我尝试了该浅拷贝函数并收到此错误-对象原型在var copy = Object.create(o)处只能是一个Object或为null;任何想法如何解决我的问题
Harshit Laddha 2014年

这不是深复制吗?

@RiverTam不,它不是深层副本,因为它不会在对象内克隆对象。但是,只需在每个子对象上递归调用它,就可以轻松地使其成为深层副本。
ABPerson

7

考虑到jQuery用户,也有一种使用框架以简单方式执行此操作的方法。jQuery使我们的生活更轻松的另一种方式。

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

参考资料:


啊-您发现了这个老问题:)我认为最初的问题更多是对JS语言的探索。当时我的用语似乎还很薄弱,因为我的示例是深复制而不是克隆。但是看来对象/哈希只能通过引用传递,这在脚本语言中很常见
vol7ron 2014年

1
:)穿过它寻找确切的功能。我有一个数据对象,在保留原始对象的同时提交之前需要复制以进行编辑。我更喜欢本机JS解决方案,并将在我自己的基础库中使用它,但是jQuery解决方案是我的临时解决方案。接受的答案是太棒了。:D
Brett Weber

6

实际上,JavaScript总是按值传递。但是由于对象引用是,所以对象的行为就像通过引用传递一样。

因此,为了解决此问题,请使用JSON 对对象进行字符串化处理并将其解析回去。请参见下面的代码示例:

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects

2
是的,这是简单转换/重新转换的选项。通常,使用字符串效率很低。但是,我有一段时间没有评估JSON解析的性能。请记住,虽然这适用于简单对象,但包含功能作为值的对象将丢失内容。
vol7ron

4

使用obj2 = {... obj1}现在两个对象的值相同,但引用不同


2
javascript的新功能。Spread运算符创建新值,而不是通过引用复制?那么,为什么这被否决呢?
jo3birdtalk

2
乍一看,它对我有用。但是我发现它仅在变量的第一层。例如,计算出何时let a = {value: "old"} let b = [...a] b.value = "new",a将是{value: "old"},b将是{value: "new"}。但是当let a = [{value: "old"}] let b = [...a] b[0].value = "new"a将会是[{value: "new"}]b将会是什么时候却不能算出来[{value: "new"}]
Brady Huang

是的,它会进行浅表复制。如果您嵌套了对象,则需要进行深层复制。为简单起见,您可以使用lodash的clone和deepClone函数
Geetanshu Gulati

3

我需要按值(而不是引用)复制对象,并且发现此页面有帮助:

在JavaScript中深度克隆对象的最有效方法是什么?。特别是,使用John Resig的以下代码克隆对象:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

问题是关于JavaScript,而不是jQuery库。
j08691

1
没错,对于无法使用Jquery的人,无论出于何种原因,这都不会对他们有帮助。但是,由于Jquery是一个javascript库,我认为它仍然可以帮助大多数javascript用户。
安东尼·哈菲

它不会帮助任何试图在服务器上执行此操作的程序员。所以不行。这不是有效答案。
TomaszGałkowski17年


1

归根结底,这只是一个花哨的过于复杂的代理,但也许Catch-All Proxies可以做到吗?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}

理查德(Richard),我回应的时间有点晚了:),但我认为出于以下原因我从未这样做:我在寻找语法糖,这使JS能够以不同方式传递对象;您的回答看起来很长,我对您使用“代理”感到挂念。3年后,我仍然挂断电话。也许我在课堂上没有给予足够的重视,但是像网络一样,我认为CS中的代理应该充当中介。也许它也在这里,我没有正确考虑它之间的中介作用。
vol7ron

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.