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

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


当前回答

对于编程语言律师,我已经阅读了ECMAScript 5.1的以下部分(它比最新版本更容易阅读),并在ECMASript邮件列表中询问了它。

TL;DR:一切都是通过值传递的,但Objects的财产是引用,标准中令人毛骨悚然地缺少Object的定义。

参数列表的构造

第11.2.4节“参数列表”说明了生成仅由1个参数组成的参数列表的以下内容:

生产ArgumentList:AssignmentExpression的计算如下:让ref是赋值表达式的结果。让arg为GetValue(ref)。返回唯一项为arg的列表。

本节还列举了参数列表包含0或>1个参数的情况。

因此,所有的都是通过引用传递的。

对象财产的访问

第11.2.1节“财产访问人”

生产MemberExpression:MemberExpression[Expression]的计算如下:让baseReference作为计算MemberExpression的结果。让baseValue为GetValue(baseReference)。让propertyNameReference作为表达式求值的结果。让propertyNameValue为GetValue(propertyNameReference)。调用CheckObject强制(baseValue)。让propertyNameString为ToString(propertyNameValue)。如果正在计算的语法生成包含在严格模式代码中,则让strict为true,否则让严格是假的。返回一个Reference类型的值,其基值为baseValue,其引用名称为propertyNameString,其严格模式标志为strict。

因此,对象的财产始终可用作参考。

关于参考

第8.7节“引用规范类型”中描述了引用在语言中不是真正的类型——它们仅用于描述删除行为、类型和赋值运算符。

“对象”的定义

5.1版中定义了“对象是财产的集合”。因此,我们可以推断,对象的值是集合,但是关于集合的值是什么,在规范中定义得很差,需要一些努力才能理解。

其他回答

这些短语/概念最初是在JS创建之前很久定义的,它们没有准确描述javascript的语义。我认为尝试将它们应用于JS会导致更多的困惑。

所以不要被“通过引用/值传递”挂断。

考虑以下事项:

变量是指向值的指针。重新分配变量只会将指针指向新值。重新分配变量不会影响指向同一对象的其他变量,因为每个变量都有自己的指针。

所以如果我必须给它起个名字,我会说“passbypointer”——我们不处理JS中的指针,但底层引擎会处理。


// code
var obj = {
    name: 'Fred',
    num: 1
};

// illustration
               'Fred'
              /
             /
(obj) ---- {}
             \
              \
               1

// code
obj.name = 'George';


// illustration
                 'Fred'


(obj) ---- {} ----- 'George'
             \
              \
               1

// code
obj = {};

// illustration
                 'Fred'


(obj)      {} ----- 'George'
  |          \
  |           \
 { }            1

// code
var obj = {
    text: 'Hello world!'
};

/* function parameters get their own pointer to 
 * the arguments that are passed in, just like any other variable */
someFunc(obj);


// illustration
(caller scope)        (someFunc scope)
           \             /
            \           /
             \         /
              \       /
               \     /
                 { }
                  |
                  |
                  |
            'Hello world'

一些最终意见:

短语“传递值/引用”仅用于描述语言的行为,不一定是实际的底层实现。由于这种抽象,对于正确解释至关重要的关键细节丢失了,这不可避免地导致了当前的情况,在没有额外信息的情况下,单个术语无法充分描述实际行为。人们很容易认为原语是由特殊规则强制执行的,而对象不是,但原语只是指针链的末端。作为最后一个示例,请考虑为什么清除数组的常见尝试不能按预期工作。

变量a=[1,2];变量b=a;a=[];console.log(b);//[1,2]//不起作用,因为“b”仍指向原始数组

我在AirBNB风格指南中找到了最简洁的解释:

基本体:访问基本体类型时,直接处理其价值一串数字布尔型无效的未定义

例如。:

var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

复杂:访问复杂类型时,需要处理对其值的引用对象大堆作用

例如。:

var foo = [1, 2],
    bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

也就是说,基本类型是通过值传递的,复杂类型是通过引用传递的。

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

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

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

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

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

(摘自上述文章。)

通过引用外部对象,将函数外部的对象传递到函数中。

当使用该引用操纵其对象时,外部的对象将受到影响。但是,如果在函数内部决定将引用指向其他对象,则根本不会影响外部对象,因为所做的只是将引用重新指向其他对象。