为什么将其他东西分配给这个对象时它没有按引用传递?

54

我知道在JS中,对象是按引用传递的,例如:

function test(obj) {
    obj.name = 'new name';
}

var my_obj = { name: 'foo' };
test(my_obj);
alert(my_obj.name); // new name

但是为什么以下代码不起作用:

function test(obj) {
    obj = {};
}

var my_obj = { name: 'foo' };
test(my_obj);
alert(my_obj.name); // foo
我已将对象设置为空的{},但仍然显示foo。有人能解释一下这背后的逻辑吗?

7
那是因为对象不是通过引用传递的 ;) - user395760
@delnan 但是确实有。请看 currentObject 如何改变 $scope.countries -> http://jsfiddle.net/ay1wpr5L/2/ - CodyBugstein
@Imray 这并不是传递引用,正如下面多个答案所解释的那样。 - user395760
该死的JavaScript。我同时又爱又恨它... - James Harrington
4个回答

67
如果您熟悉指针,那么这是一个可以借鉴的比喻。实际上,您正在传递一个指针,因此obj.someProperty将被解引用为该属性并进行覆盖,而仅仅覆盖obj会删除指针而不是覆盖对象。

2
+1 - 做得很好,比我的答案更清晰(假设原贴作者知道指针) - Adam Rackis
@AdamRackis 我点赞你的回答,因为它使用了 JS 术语,并引出了上面的比喻 ;) - Alexander Varwijk
谢谢,这个解释很清楚 :) - Dev555
1
为什么上周我搜索时没有找到这个答案呢?!这终于回答了我的问题,非常感谢! - Campbeln
在这里需要记住的是,当我们声明一个带有某些参数(比如这里的obj)的函数时,obj就是一种新的东西。这就像是在函数内部声明了一个var obj; // undefined。因此,如果我们进行一些赋值操作,实际上是在改变函数内部的局部变量,并且我们打破了“引用”(指针)。 - Wojciech Fornal

30

因为JavaScript实际上通过复制-引用来传递对象。

当您将my_obj传递到test函数中时,会传递一个对该对象的引用的副本。因此,当您在test中重新分配对象时,您实际上只是重新分配了对原始对象的引用的副本;您的原始my_obj保持不变。


2
如果这是复制的,那么为什么它在我发布的第一个示例中有效?在我的第一个示例中,它也应该保持不变吧? - Dev555
2
@Dev555 - 这是一个指向对象的引用副本 - 我进行了编辑以使其更清晰。在你的第一种情况中,obj 是指向你真正对象的引用的副本。添加属性将会很好地工作。如果我亲自在那里,我可以画一些带箭头的盒子的图片,我认为这会帮助很多 :) - Adam Rackis
那么如何初始化对象呢?最初我有这个 instance.initGrid($(instance.mainEntityContainer), instance.mainEntityList); instance.initGrid($(instance.dependantEntityContainer), instance.dependantEntityList);,我必须将其转换为以下内容:instance.mainEntityList = instance.initGrid($(instance.mainEntityContainer)); instance.dependantEntityList = instance.initGrid($(instance.dependantEntityContainer)); 但我仍然想知道如何从先前的初始化中“释放内存”。我是否只需要在某个地方将 instance.dependantEntityList = null?我在里面使用了 "= new ..."。 - Alexander

27
因为你正在覆盖引用,而不是对象本身。
// Create a new object and assign a reference to it
// to the variable my_obj
var my_obj = { name: 'foo' };

// Pass the reference to the test function
test(my_obj);

// Assign the reference to a variable called obj
// (since that is the first argument)
function test(obj) {
// Create a new (empty) object and assign a reference to it to obj
// This replaces the existing REFERENCE
    obj = {};
}
// my_obj still has a reference to the original object, 
// because my_obj wasn't overwritten
alert(my_obj.name); // foo

你正在覆盖引用的副本,而不是引用本身。如果你覆盖了引用本身,一切都会没问题。 - Alex
3
如果您想覆盖my_obj,该怎么做? - CodyBugstein
@CodyBugstein 把它包装在另一个对象中。这个问题的另一个答案,https://dev59.com/Pmox5IYBdhLWcg3wGwlo#13452001,展示了这个方法以及其他一些可能的方法。或者,如果你不喜欢这些方法中的任何一个,我认为答案基本上是:你不能覆盖`my_obj`。 - Darren Cook

5

Javascript不支持按引用传递(尽管对象是按引用传递的,只要没有使用赋值操作符例如=覆盖引用,引用就会保持不变),但您可以使用以下技术模拟C#的ref关键字:

function test(obj) {
  obj.Value = {};
  //obj.Value = {name:"changed"};
}

var my_obj = { name: 'foo' };

(function ()
{
  my_obj = {Value: my_obj};
  var $return = test(my_obj);
  my_obj = my_obj.Value;
  return $return;
}).call(this);

alert(my_obj.name); // undefined, as expected
                    // In the question this returns "foo" because
                    // assignment causes dereference

当然,你可以使用全局变量并调用没有参数的函数,在这种情况下,引用不会被忽略,如下所示:
var obj = { name: 'foo' };
function test() {
    obj = {};
}
test();
alert(obj.name); // undefined

如果你所有的代码都在闭包中,那么像全局变量这样的东西就不会污染全局命名空间。
(function(){
    var obj = { name: 'foo' };
    function test() {
        obj = {};
    }
    test();
    alert(obj.name); // undefined
}).call(this);

以上的“闭包内全局变量”技巧很适合将具有ref参数的C#代码移植到Javascript中。例如以下的C#代码:
void MainLoop()
{
   // ...
   MyStruct pt1 = CreateMyStruct(1);
   MyStruct pt2 = CreateMyStruct(2);
   SwapPoints(ref pt1, ref pt2);
   // ...
}
void SwapPoints(ref MyStruct pt1, ref MyStruct pt2)
{
    MyStruct tmp = pt1;
    pt1 = pt2;
    pt2 = tmp;
}

可以使用类似以下的方式将其移植到Javascript:

(function(){
    var pt1, pt2;
    function CreateMyStruct(myvar)
    {
      return {"myvar":myvar}  
    }
    function MainLoop()
    {
       // ...
       pt1 = CreateMyStruct(1);
       pt2 = CreateMyStruct(2);
       console.log("ORIG:",pt1,pt2); 
       SwapPoints(); 
       console.log("SWAPPED:",pt1,pt2);
       // ...
    }
    function SwapPoints()
    {
        var tmp = pt1;
        pt1 = pt2;
        pt2 = tmp;
    }
    MainLoop();

}).call(this);

如果必须使用局部变量和函数参数,那么可以基于我答案中的第一个示例来解决问题,像这样:
(function(){
    function CreateMyStruct(myvar)
    {
      return {"myvar":myvar}  
    }
    function MainLoop()
    {
      // ...
      var pt1 = CreateMyStruct(1);
      var pt2 = CreateMyStruct(2);
      console.log("ORIG:",pt1,pt2); 

      (function ()
      {
        pt1 = {Value: pt1};
        pt2 = {Value: pt2};
        var $return = SwapPoints(pt1, pt2);
        pt1 = pt1.Value;
        pt2 = pt2.Value;
        return $return;
      }).call(this);

      console.log("SWAPPED:",pt1,pt2);
      // ...
    }
    function SwapPoints(pt1, pt2)
    {
      var tmp = pt1.Value;
      pt1.Value = pt2.Value;
      pt2.Value = tmp;
    }
    MainLoop();
}).call(this);

真的不得不说,当Javascript没有本地支持ref时,它缺少很多!代码会简单得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接