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

虽然最后这并不重要,但我想知道通过约定来表达论点的正确方式是什么。是否有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。

来源

其他回答

字符串、数字等基本类型变量始终作为传递按价值计算。基于这两个条件,数组和对象作为引用传递或值传递。如果要使用新的Object或array更改该Object或array的值,则它将通过value传递。object1={item:“car”};数组1=[1,2,3];此处是将新对象或数组分配给旧对象或数组。您没有更改属性的值旧对象的。因此它是按值传递的。如果要更改对象或数组的属性值,则会通过引用传递。object1.key1=“汽车”;阵列1[0]=9;此处您正在更改旧对象的属性值。您没有将新对象或数组分配给旧对象或数组。因此它是通过引用传递的。

Code

    function passVar(object1, object2, number1) {

        object1.key1= "laptop";
        object2 = {
            key2: "computer"
        };
        number1 = number1 + 1;
    }

    var object1 = {
        key1: "car"
    };
    var object2 = {
        key2: "bike"
    };
    var number1 = 10;

    passVar(object1, object2, number1);
    console.log(object1.key1);
    console.log(object2.key2);
    console.log(number1);

Output: -
    laptop
    bike
    10

如果您想要像其他语言一样的(正常)函数参数行为(传递值的副本)然后在传递到函数之前克隆对象:

function run()
{
    var test = [];
    test.push(1);

    console.log('before: '+test); // 1

    changeVariable(_.clone(test)); // (Note: I am using lodash _.clone() function)
 
    console.log('after: '+test); // 1 
}


function changeVariable(test2) {
  var test1 = test2;
  test1.push(2); 
  console.log('inside func:', test1);  // inside func: [1,2]
}   


run();    

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

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

(摘自上述文章。)

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

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

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

变量不“保存”对象;它有一个参考。您可以将该引用分配给另一个变量,现在两者都引用同一个对象。它总是按值传递(即使该值是引用…)。

无法更改作为参数传递的变量所持有的值,如果JavaScript支持通过引用传递,这是可能的。