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

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


当前回答

这些短语/概念最初是在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”仍指向原始数组

其他回答

我理解这一点的简单方法。。。

调用函数时,您将传递内容(引用或值),而不是变量本身。var var1=13;var var2={prop:2};//13和var2的内容(引用)正在此处传递foo(var1,var2);在函数内部,参数变量inVar1和inVar2接收传递的内容。函数foo(inVar1,inVar2){//更改inVar1和inVar2的内容不会影响外部变量inVar1=20;inVar2={prop:7};}由于inVar2收到了{prop:2}的引用,因此可以更改对象属性的值。函数foo(inVar1,inVar2){inVar2.prop=7;}

“JavaScript:最终指南”一书的这一章对复制、传递和比较值和引用进行了非常详细的解释。

在我们离开主题之前操作对象和阵列参考,我们需要澄清一点术语。短语“路过”“引用”可以有几种含义。对一些读者来说,这个短语指的是一种函数调用技术允许函数分配新值它的论点,并拥有这些外部可见的修改值作用这不是术语的方式本书中使用了。这里,我们的意思是简单地说,对对象的引用或数组--而不是对象本身--传递给函数。A函数可以使用引用修改对象或元素的财产阵列的。但如果函数用对新对象或阵列的引用,该修改不可见在功能之外。读者熟悉这个词可能更喜欢这样说对象和数组值,但传递的值为实际上是一个参考,而不是对象本身。

语义!!设置具体的定义必然会使一些答案和注释不兼容,因为即使使用相同的单词和短语,它们也不会描述相同的内容,但克服这种困惑至关重要(尤其是对于新程序员)。

首先,并非每个人都能理解抽象的多个层次。学习过第四代或第五代语言的较新程序员可能很难围绕汇编语言或C语言程序员熟悉的概念进行思考,而不是通过指向指针的指针来实现。通过引用传递不仅仅意味着能够使用函数参数变量更改被引用对象。

变量:引用内存中特定位置值的符号的组合概念。在讨论细节时,这个术语通常太过沉重,不能单独使用。

符号:用于引用变量(即变量名称)的文本字符串。

值:存储在内存中并使用变量符号引用的特定位。

内存位置:存储变量值的位置。(位置本身由与存储在该位置的值不同的数字表示。)

函数参数:在函数定义中声明的变量,用于引用传递给函数的变量。

函数参数:调用方传递给函数的函数外部的变量。

对象变量:其基本基本值不是“对象”本身,而是指向内存中存储对象实际数据的另一个位置的指针(内存位置值)。在大多数更高一代语言中,“指针”方面通过在各种上下文中自动取消引用而被有效隐藏。

原始变量:其值为实际值的变量。即使这个概念也会因为各种语言的自动装箱和类似对象的上下文而变得复杂,但一般的想法是变量的值是由变量符号表示的实际值,而不是指向另一个内存位置的指针。

函数参数和参数不是一回事。此外,变量的值不是变量的对象(正如许多人已经指出的,但显然被忽略了)。这些区别对于正确理解至关重要。

通过值传递或通过共享调用(对于对象):函数参数的值被COPIED到另一个由函数参数符号引用的内存位置(无论它是在堆栈上还是在堆上)。换句话说,函数参数收到了传递参数值的副本。。。AND(关键)参数的值从不被调用函数更新/更改/更改。记住,对象变量的值不是对象本身,而是指向对象的指针,因此逐个传递对象变量会将指针复制到函数参数变量。函数参数的值指向内存中完全相同的对象。对象数据本身可以通过函数参数直接更改,但函数参数的值从未更新,因此在整个函数调用过程中,甚至在函数调用之后,它都将继续指向同一对象(即使其对象的数据已更改,或者如果函数参数被完全分配了不同的对象)。仅仅因为被引用的对象可通过函数参数变量更新,就断定函数参数是通过引用传递的,这是不正确的。

通过引用调用/传递:函数参数的值可以/将由相应的函数参数直接更新。如果有帮助,函数参数将成为参数的有效“别名”——它们有效地引用同一内存位置的相同值。如果函数参数是一个对象变量,则更改对象数据的能力与传递值情况没有区别,因为函数参数仍然指向与参数相同的对象。但是在对象变量的情况下,如果函数参数设置为完全不同的对象,那么参数同样也会指向不同的对象——这在传递值的情况下不会发生。

JavaScript不通过引用传递。如果仔细阅读,你会发现所有相反的意见都误解了传递值的含义,他们错误地得出结论:通过函数参数更新对象数据的能力与“传递值”同义。

对象克隆/复制:创建新对象并复制原始对象的数据。这可以是深层副本或浅层副本,但重点是创建了一个新对象。创建对象副本是与传递值不同的概念。有些语言区分类对象和结构(或类似的),并且可能有不同的行为来传递不同类型的变量。但是JavaScript在传递对象变量时不会自动执行类似的操作。但是,缺少自动对象克隆并不意味着通过引用传递。

一切都是通过价值传递的。

基本类型按值传递(即,实际变量值的新副本传递给函数)。

复杂类型(对象)作为“对象指针”传递。因此,您传递的实际内容是一个指针,它是按值传递的(它是一个地址,一个像任何其他值一样的数值)。显然,如果您试图在函数内修改对象的属性,那么即使在该函数外,修改也会反映出来。这是因为您通过指向属性唯一副本的指针访问属性。

这里的混淆出现在“通过值传递指针”和“通过引用传递对象”上。

MDN文档对其进行了清晰的解释,但并不过于冗长:

函数调用的参数是函数的参数。参数按值传递给函数。如果功能发生变化参数的值,此更改不会在全局或调用函数。但是,对象引用也是值,并且它们是特殊的:如果函数更改引用对象的财产,该更改在函数外部可见,(…)

资料来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description