基元类型(数字、字符串等)是通过值传递的,但对象是未知的,因为它们既可以通过值传递(如果我们认为持有对象的变量实际上是对对象的引用),也可以通过引用传递(当我们认为对象的变量持有对象本身)。

虽然最后这并不重要,但我想知道通过约定来表达论点的正确方式是什么。是否有JavaScript规范的摘录,该规范定义了与此相关的语义?


当前回答

在JavaScript中,值的类型仅控制该值是由值副本分配还是由引用副本分配。

基本值始终由值副本分配/传递:

无效的未定义一串数字布尔型ES6中的符号

复合值始终由引用副本分配/传递

物体阵列作用

例如

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3

var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

在上面的代码段中,因为2是标量原语,所以a保存该值的一个初始副本,而b被分配了该值的另一个副本。更改b时,绝对不能更改a中的值。

但c和d都是对同一共享值[1,2]的单独引用,这是一个复合值。需要注意的是,c和d都没有“拥有”[1,2,3]值——两者都只是对该值的对等引用。因此,当使用任意一个引用来修改(.push(4))实际的共享数组值本身时,它只影响一个共享值,并且两个引用都将引用新修改的值[1,2,3,4]。

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]

// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

当我们赋值b=[4,5,6]时,我们没有做任何事情来影响a仍然引用的位置([1,2,3])。要做到这一点,b必须是指向a的指针,而不是对数组的引用——但JS中不存在这样的功能!

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}

var a = [1,2,3];

foo( a );

a; // [1,2,3,4]  not  [4,5,6,7]

当我们传入参数a时,它将一个引用的副本分配给x。x和a是指向相同[1,2,3]值的独立引用。现在,在函数内部,我们可以使用该引用来改变值本身(push(4))。但是当我们赋值x=[4,5,6]时,这不会影响初始引用a指向的位置——仍然指向(现在已修改)[1,2,3,4]值。

要有效地通过值副本传递复合值(如数组),需要手动复制它,这样传递的引用就不会指向原始值。例如:

foo( a.slice() );

可以通过引用副本传递的复合值(对象、数组等)

function foo(wrapper) {
    wrapper.a = 42;
}

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

这里,obj充当标量基元属性a的包装器。当传递给foo(..)时,将传入obj引用的副本,并将其设置为wrapper参数。我们现在可以使用包装器引用来访问共享对象,并更新其属性。函数完成后,obj.a将看到更新的值42。

来源

其他回答

在低级语言中,如果要通过引用传递变量,则必须在创建函数时使用特定语法:

int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
  *age = *age + 1;
}

&age是对myAge的引用,但如果您需要该值,则必须使用*age转换引用。

JavaScript是一种高级语言,可以为您进行这种转换。

因此,尽管对象是通过引用传递的,但语言将引用参数转换为值。您不需要在函数定义上使用&来通过引用传递它,也不需要在功能体上使用*来将引用转换为值,JavaScript会为您执行此操作。

这就是为什么当您试图通过替换函数中的对象的值(即age={value:5})来更改该对象时,更改不会持久,但如果您更改了它的财产(即age.value=5),更改就会持久。

了解更多信息

JavaScript按值传递原语类型,按引用传递对象类型

现在,人们喜欢没完没了地争论是否“通过引用”是描述Java等实际操作的正确方式这是:传递对象不会复制对象。传递给函数的对象可以由该函数修改其成员。传递给函数的基元值不能由该函数修改。复制完成。在我的书中,这叫做通过引用传递。

-Brian Bi-哪些编程语言是通过引用传递的?


使现代化

以下是对此的反驳:

JavaScript中没有可用的“通过引用传递”。

我已经多次阅读了这些答案,但直到我了解了Barbara Liskov所称的“通过共享呼叫”的技术定义,我才真正理解

通过共享调用的语义与通过引用调用的语义不同,因为对函数内函数参数的赋值对调用方不可见(与引用语义不同)[需要引用],因此例如,如果传递了变量,则无法在调用方的范围内模拟对该变量的赋值。然而,由于函数可以访问与调用方相同的对象(不进行复制),因此如果对象是可变的,则调用方可以看到函数中这些对象的突变,这可能与逐值调用语义不同。调用方可以看到函数中可变对象的变体,因为该对象未被复制或克隆,而是共享的。

也就是说,如果您访问参数值本身,参数引用是可变的。另一方面,对参数的赋值将在求值后消失,函数调用方无法访问。

这是对值传递和引用传递(JavaScript)的更多解释。在这个概念中,他们讨论的是通过引用传递变量和通过引用传递该变量。

传递值(基本类型)

var a = 3;
var b = a;

console.log(a); // a = 3
console.log(b); // b = 3

a=4;
console.log(a); // a = 4
console.log(b); // b = 3

应用于JavaScript中的所有基元类型(字符串、数字、布尔值、未定义和null)。a被分配一个存储器(例如0x001),b在存储器中创建该值的拷贝(例如0x002)。因此,更改一个变量的值不会影响另一个变量,因为它们都位于两个不同的位置。


通过引用(对象)

var c = { "name" : "john" };
var d = c;

console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }

c.name = "doe";

console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }

JavaScript引擎将对象分配给变量c,并指向某个内存,例如(0x012)。当d=c时,在该步骤中,d指向相同的位置(0x012)。更改任何变量的值都会更改这两个变量的值。函数是对象


特殊情况,通过引用传递(对象)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }

等号(=)运算符设置新的内存空间或地址

这里有一些关于JavaScript中使用术语“通过引用传递”的讨论,但要回答您的问题:

对象通过引用自动传递,无需特别声明

(摘自上述文章。)