为传递给JavaScript中的函数的对象分配新值

ogg*_*ter 9 javascript pass-by-reference pass-by-value

我是JavaScript的新手(虽然在C++方面经验丰富),今天我写了这样的东西:

function foo(bar) {
    bar = "something else";
}
var x = "blah";
foo(x);
alert(x); // Alerts with "blah", but I was expecting it to alert with "something else"
Run Code Online (Sandbox Code Playgroud)

这让我很困惑,因为我一直在观看道格拉斯·克罗克福德的一些JavaScript视频,并记得他说像"JavaScript总是通过引用传递".

我可以解释这种情况的方式是JavaScript传递对象的引用,但这些引用被复制.这意味着在foo函数中,我正在分配一个新的引用bar,然后超出范围,留下x未触及的引用.基本上我们从:

x   ---->"blah"
Run Code Online (Sandbox Code Playgroud)

然后在foo调用时,bar引用相同的数据:

x   ---->"blah"
bar -----^
Run Code Online (Sandbox Code Playgroud)

因此,当指定"其他东西"时bar,会发生这种情况:

x   ---->"blah"
bar ---->"something else"
Run Code Online (Sandbox Code Playgroud)

这是JavaScript中实际发生的事情的准确模型,还是我错过了其他什么?

作为一个额外的问题,有没有办法说,改变这个变量引用的数据?这是经常出现的情况,还是可以轻易避免?

编辑:

道格拉斯·克罗克福德在我看过视频中说"对象总是通过引用传递它们不是通过值传递",这是正确的,但函数的参数是通过值传递的,它只是通过值传递引用.

Mik*_*sen 7

你的解释是现实的.

首先,您有一个名为变量的变量x,它是对字符串对象的引用.假设内存为0x100. x指向0x100,其中包含字节blah:

var x = "blah"; // x is 0x100 which references a string in memory
Run Code Online (Sandbox Code Playgroud)

接下来,您将0x100进入函数foo:

function foo(bar) {
    bar = "something else";
}
Run Code Online (Sandbox Code Playgroud)

由于JavaScript中的所有内容都是按值传递的,即使是引用,JavaScript 也会在内存中创建此引用的副本,现在bar在该函数中调用它:

foo(x); // Copies the value of x (a reference) to bar
Run Code Online (Sandbox Code Playgroud)

此时,我们有两个独立的变量. xbar.两者都碰巧具有相同的价值0x100.因此,如果您要更改其中任何一个引用的对象的属性,它将同时影响xbar.

但是,你正在做的是分配bar指向别的东西:

bar = "something else"; // Now references some other string we just created
Run Code Online (Sandbox Code Playgroud)

现在,bar重新分配引用我们刚为其分配内存的新字符串. bar不再具有值0x100,它现在具有某个其他地址的值(比方说0x500). x当然还有一个值,0x100因为bar它只是一个副本 x,而不是一个参考x.

出于这个原因,当你:

alert(x);
Run Code Online (Sandbox Code Playgroud)

您仍将获得原始值,因为这x是指向的.

第二个问题:

有没有办法说,改变这个变量引用的数据?这是经常出现的情况,还是可以轻易避免?

是的,只需将其包装在另一个对象中.例如:

var x = {Value: "blah"};
foo(x);
Run Code Online (Sandbox Code Playgroud)

现在,我们引用了一个带有被调用属性的对象,该对象Value包含对某个内存中某个字符串的引用.

foo,我们可以做:

bar.Value = "something else";
Run Code Online (Sandbox Code Playgroud)

这将影响到Value财产x,因为两者barx引用同一个对象,你永远不会改变其中任何价值.

换句话说,您不能重新分配您传递给函数的引用,因为您只是重新分配副本.但是,您可以更改被引用对象的属性,因为该引用的其他副本都指向您正在更改的数据.


use*_*740 -2

“JavaScript 总是通过引用传递”是一个[善意的]谎言并且是术语的混淆。虽然存在一些“灰色地带”,但我还是遵循评估策略的这些定义

\n\n

以下是我的论点和推理。如果您持有不同的观点,请确保您至少可以支持它。

\n\n

通过参考调用

\n\n

通过引用调用(对于很多人来说)意味着分配给参数会影响调用者中的绑定。

\n\n
\n

在按引用调用计算(也称为按引用传递)中,函数接收对用作参数的变量的隐式引用,而不是其值的副本。这通常意味着该函数可以修改(即分配给)用作参数的变量\xe2\x80\x94,该变量将被其调用者看到。

\n
\n\n

JavaScript 中的情况并非如此,正如原始帖子在观察到的行为中指出的那样。重新分配参数(可以将其视为具有动态提供值的局部变量)不会影响任何提供的参数。

\n\n

有时,“通过引用调用”[令人困惑地]被用来表示“通过共享调用”或“通过[引用的]值调用”,如下所述;真正的“按引用调用”存在于 C++ 和 VB 等语言中,但 JavaScript 中却没有。

\n\n

通过[对象]共享调用

\n\n

JavaScript 的调用约定完全可以通过Call By [Object]sharing来讨论语义来讨论。

\n\n

所有 JavaScript 对象都是值;所有原始值(所有值的子集)都是不可变的。

\n\n
\n

通过共享调用的语义与通过引用调用不同,因为调用者看不到函数内函数参数的赋值,因此,例如,如果传递了一个变量,则不可能在函数中模拟对该变量的赋值。调用者的范围。然而由于函数可以访问与调用者相同的对象(不进行复制),因此如果对象是可变的,则函数内这些对象的突变对调用者来说是可见的,这可能与按值调用语义不同。

\n
\n\n

ultrayoshi 的答案中提供了这些共享突变的示例,并且可以简单地解释:当表达式(例如变量访问)计算为对象并且所述对象被传递给函数时,进行复制/克隆。

\n\n

[参考文献] 按值调用

\n\n

虽然术语“按引用的值调用”经常用于描述该行为,但应该注意的是,JavaScript没有Java/C# 意义上的“引用”(或“非引用”值),因此这个术语有微妙的误导性——至少它不是说Call By Reference,它有各种各样的含义,而且很多人理解的解释很浅薄。

\n\n
\n

在按值调用中,对参数表达式进行求值,并将结果值绑定到函数中相应的变量。如果函数或过程能够为其参数赋值,则仅为其本地副本分配 \xe2 \x80\x94 也就是说,当函数返回时,传递给函数调用的[任何变量]在调用者的作用域中不会发生变化。

\n
\n\n

因为仅传递对对象的“引用”(而不是所述对象的副本/克隆),所以语义仅仅是“通过共享调用”的语义。然而,我在 JavaScript 中避免使用这个术语,因为它会带来不必要的实现细节,并且还会在实现传递对象与原始值的方式上引入分歧。

\n\n
\n

“按值调用,其中值是引用”的描述很常见(但不应理解为按引用调用);另一个术语是共享呼叫。

\n
\n\n
\n\n

因此,当我谈论 JavaScript 中的调用约定时,

\n\n

更喜欢使用通过共享调用来讨论行为,并且避免通过[值/引用]调用,因为它们有太多不同的“含义”并拖入不必要的实现细节。

\n