JavaScript 是一种按引用传递还是按值传递的语言?

1756

原始类型(例如数字、字符串等)通过值传递,但对象不同,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(当我们认为变量对对象本身持有该对象时)。

虽然最终并不重要,但我想知道呈现参数传递约定的正确方式是什么。是否有来自JavaScript规范的节选,定义了关于此事应该是什么语义?


8
我认为你不小心颠倒了传值和传引用的定义... "传值(如果我们认为一个变量持有一个对象实际上是指向该对象的引用),传引用(当我们认为变量持有对象本身时)" - Niko Bellic
11
是的。无论在任何编程语言中的函数调用中语法如何,传递引用都表示传递的变量所关联的数据在传递到函数时不会被复制,因此函数对传递的变量所做的任何修改将在函数调用终止后保留在程序中。传值则意味着将变量相关的数据实际上复制到函数中传递,当变量超出函数体作用域并且函数返回时,该函数对变量所做的任何修改都将丢失。 - John Sonderson
9
这个老问题有点棘手,因为它的得票最高的答案是错误的。JavaScript 严格来说是“按值传递”。 - Pointy
9
术语令人遗憾地令人困惑。事实上,“传值”和“传引用”这些术语比许多更现代的编程语言特性还要早。 “值”和“引用”这两个词 具体 指函数调用表达式中的参数。JavaScript始终在调用函数之前计算函数调用参数列表中的每个表达式,因此参数始终是值。令人困惑的是,对象的引用是常见的JavaScript值。然而,这并不意味着它是一种“传引用”的语言。 - Pointy
4
“按引用传递”特指以下情况:如果你有var x=3, y=x; f(x); alert(y === x);,那么函数f()可以使得alert报告false而不是true。在JavaScript中,这是不可能的,所以它不是“按引用传递”。能够传递可修改对象的引用是好的,但这并不是“按引用传递”的意思。就像我说的,术语混淆真是令人遗憾。 - Pointy
显示剩余12条评论
35个回答

18

这篇文章讲的是编程语言中的“性能”、“速度”和简单来说就是“内存管理”。在javascript中,我们可以将值分为两种类型:类型1——对象类型2——所有其他类型的值,例如字符串布尔类型等。

如果你将内存想象成下面这个方格图,其中每个方格只能保存一个类型2的值:

enter image description here

每个类型2的值(绿色)都是一个单独的方格,而类型1的值(蓝色)则是由一组方格组成的:

enter image description here

重点是,如果要表示类型2的值,地址是明确的,但如果要对类型1的值做同样的事情,则非常困难!:

enter image description here

更复杂的情况是:

enter image description here

所以在这里引用可以拯救我们:
enter image description here

虽然这里的绿色箭头是一个典型的变量,而紫色箭头是一个对象变量,但因为绿色箭头(典型变量)只有一个任务(即指示典型值),所以我们不需要将其值与箭头分开,而是将其与值一起移动到任何位置,并在所有赋值、函数等中使用。

但是我们不能对紫色箭头做同样的事情,我们可能想要移动“约翰”单元格或许多其他东西,所以紫色箭头将固定在其位置上,只有被分配给它的典型箭头会移动...

一个非常令人困惑的情况是你无法意识到你的引用变量如何改变,让我们看一个很好的例子:

let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating it
let obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr obj
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // first pic below - making a new hand for the blue circle to point the 6
//obj2 = [1, 2, 3, 4, 5, 6]
//arr = [1, 2, 3, 4, 5, 6]
//we changed the blue circle object value (type1-value) and due to arr and obj2 are indicating that so both of them changed
obj2 = obj3; //next pic below - changing the direction of obj2 array from blue circle to orange circle so obj2 is no more [1,2,3,4,5,6] and it's no more about changing anything in it but we completely changed its direction and now obj2 is pointing to obj3
//obj2 = ['a', 'b', 'c'];
//obj3 = ['a', 'b', 'c'];

enter image description here enter image description here


我在任何图片中都找不到值为“6”的数值。 - user31782
@user31782,那是我犯的那种打字错误,请原谅 :) - TechDogLover OR kiaNasirzadeh
我无法接受的是,这里假装字符串“john”可以像数字21一样完美地适合一个小内存方块中。在底层,John仍然是一个字符数组,因此您拥有的任何引用仍然指向同一个位置。如果您修改了字符串,那么您将获得一个副本,但严格来说,这是因为JavaScript中没有可变字符串操作。 - Ethan Standel

10

这是关于按值传递和按引用传递(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 中的所有原始类型(字符串、数值、布尔、未定义和空值)。
  • 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" }
  • 等号(=)操作符会设置新的内存空间或地址。

在你所谓的特殊情况下,引起内存空间分配的不是赋值运算符,而是对象字面量本身。花括号表示法会创建一个新对象。属性c被设置为新对象引用的副本。 - georgeawg
这不是传递引用,而是传递值,其值恰好是一个引用。 - gman

7

所有东西都是按值传递的。

基本类型是按值传递的(即实际变量值的新副本被传递到函数中)。

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

“按值传递指针”和“按引用传递对象”是相同的。


6
语义学!! 确定具体的定义将必然使一些答案和评论不兼容,因为即使使用相同的单词和短语,它们也没有描述相同的东西,但是对于新程序员来说,摆脱混乱至关重要。
首先,有多个抽象级别并非每个人都能掌握。在第四代或第五代语言上学习的新程序员可能会很难理解对汇编或C程序员来说很熟悉的概念,这些概念不受指针到指针到指针的影响。传递引用不仅仅意味着使用函数参数变量更改引用对象的能力。
变量: 一个符号的组合概念,该符号引用内存中特定位置的值。 这个术语通常太过于复杂以至于不能单独讨论细节。
符号: 用于引用变量的文本字符串(即变量的名称)。
值: 存储在内存中的特定位,并使用变量的符号进行引用。
内存位置: 存储变量值的位置。 (位置本身由与存储在该位置的值分开的数字表示。)
函数参数: 在函数定义中声明的变量,用于引用传递给函数的变量。
函数参数: 外部变量,由调用者传递给函数的变量。
对象变量: 其基本底层值不是“对象”本身,而是一个指针(内存位置值),该指针指向另一个内存位置,其中存储着对象的实际数据。 在大多数高一代语言中,“指针”方面都被自动解除引用在各种上下文中隐藏起来。
原始变量: 变量其值即为实际值。 甚至这个概念也可以通过各种语言的自动装箱和类似对象的上下文变得复杂,但总体思想是变量的值是由变量的符号表示的实际值,而不是指向另一个内存位置的指针。
函数参数和参数不是相同的东西。 此外,变量的值不是变量的对象(已经被各种人指出,但似乎被忽略了)。 这些区别对于正确理解至关重要。

按值传递或按共享调用(针对对象):函数参数的值被复制到另一个内存位置,该位置由函数的参数符号引用(无论它是在堆栈还是堆上)。换句话说,函数参数接收了传递参数值的副本...并且(关键是)调用函数永远不会更新/更改/改变参数的值。请记住,对象变量的值不是对象本身,而是指向对象的指针,因此通过按值传递对象变量会将指针复制到函数参数变量中。函数参数的值指向完全相同的内存中的对象。可以直接通过函数参数修改对象数据,但是函数参数的值永远不会更新,因此即使在函数调用期间和之后(即使其对象数据已更改或如果函数参数分配了完全不同的对象),它仍将继续指向相同的对象。错误的结论是函数参数是按引用传递的,只是因为通过函数参数变量可以更新所引用的对象。

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

JavaScript不是按引用传递的。如果您仔细阅读,您将意识到所有相反的观点都误解了按值传递的含义,并错误地得出结论,通过函数参数变量更新对象数据的能力等同于“按值传递”。

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


6

分享我对JavaScript中引用的了解

在JavaScript中,当将一个对象赋给一个变量时,被赋值的变量实际上是指向该对象的引用:

var a = {
  a: 1,
  b: 2,
  c: 3
};
var b = a;

// b.c is referencing to a.c value
console.log(b.c) // Output: 3
// Changing value of b.c
b.c = 4
// Also changes the value of a.c
console.log(a.c) // Output: 4


1
这是一个过于简单化的回答,没有比之前的回答更好地解释。我对你为什么将数组作为特例感到困惑。 - Quentin
1
对象被存储为引用”是具有误导性的。我认为你的意思是,当将一个对象赋值给一个变量时,分配给该变量的值是指向该对象的引用。 - RobG
@amaster 感谢您指出这一点!您能提出修改建议吗? - Zameer Ansari
哈哈,我尝试了...我的建议编辑改变了太多,因此没有被允许。 - amaster
@amaster,我无法追踪你的建议啊!(建议链接:https://stackoverflow.com/posts/45614077/revisions) - Zameer Ansari
显示剩余2条评论

6
观察:如果没有一种方法让观察者检查引擎的底层内存,就无法确定是复制了不可变值还是传递了引用。
JavaScript 在很大程度上对底层内存模型无所谓。它没有所谓的“引用”²。JavaScript 有“值”。两个变量可以持有相同的“值”(或更准确地说,两个“环境记录”可以“绑定”相同的值)。唯一可以改变的值类型是对象,通过它们的抽象 [[Get]] 和 [[Set]] 操作进行改变。 如果忘记计算机和内存,这就是描述 JavaScript 行为的全部内容,并且它使您能够理解规范。
 let a = { prop: 1 };
 let b = a; // a and b hold the same value
 a.prop = "test"; // The object gets mutated, can be observed through both a and b
 b = { prop: 2 }; // b holds now a different value

现在你可能会问,在计算机上如何让两个变量持有相同的值。你可能会查看JavaScript引擎的源代码,最有可能找到的是程序员在编写该语言时称之为“引用”的东西。
因此,实际上可以说JavaScript是“传值”,而该值可以被共享;也可以说JavaScript是“传引用”,这对于来自低级语言的程序员可能是一个有用的逻辑抽象,或者您可以将其称为“共享调用”。
由于JavaScript中没有引用这样的东西,所有这些都不是正确的也不是错误的。因此,我认为寻找答案并不特别有用。
规范中的术语“引用”并不是传统意义上的引用。它是一个对象的容器和属性的名称,并且它是一个中间值(例如,“a.b”评估为“Reference { value = a, name = 'b' }”)。术语“引用”有时也出现在规范中的无关部分。

5
MDN文档清晰地解释了它,没有过多的冗长:
函数调用的参数是函数的“参数”。参数通过值传递给函数。如果函数更改参数的值,则不会在全局或调用函数中反映出此更改。但是,对象引用也是值,并且它们很特殊:如果函数更改所引用对象的属性,则该更改在函数外部可见,(...)
来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description

4

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

现在,人们经常无休止地争论Java等语言实际上是否正确描述了"按引用传递"。重点在于:

  1. 传递对象不会复制该对象。
  2. 传递给函数的对象可以被函数修改其成员。
  3. 传递给函数的原始值不能被函数修改,需要进行复制。

在我的书里,这被称为按引用传递。

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


更新

这里有一篇反驳文章:

JavaScript中不存在"按引用传递"。


@Amy 因为这描述的是按值传递,而不是按引用传递。这个答案很好地展示了区别:https://dev59.com/5nRB5IYBdhLWcg3wxZ7Y#3638034 - nasch
@nasch 我明白其中的区别。#1和#2描述了传引用语义,而#3则描述了传值语义。 - user47589
@Amy 1、2和3都是按值传递的一致的。要使用按引用传递,您还需要4:在函数内部将引用分配给新值(使用=运算符)也会重新分配函数外部的引用。这在Javascript中不是这种情况,使其专门按值传递。当传递对象时,您传递指向对象的指针,并通过值传递该指针。 - nasch
这通常不是“按引用传递”的意思。你已经满足了我的查询,但我不同意你的观点。谢谢。 - user47589
在我的书里,这被称为通过引用传递。但在编译器、解释器、编程语言理论和计算机科学的所有书籍中,都并非如此。 - Jörg W Mittag

4

我理解这个的简单方法是...

  • When calling a function, you are passing the content (reference or value) of the argument variables, not the the variables themselves.

    var var1 = 13;
    var var2 = { prop: 2 };
    
    //13 and var2's content (reference) are being passed here
    foo(var1, var2); 
    
  • Inside the function, parameter variables, inVar1 and inVar2, receive the contents being passed.

    function foo(inVar1, inVar2){
        //changing contents of inVar1 and inVar2 won't affect variables outside
        inVar1 = 20;
        inVar2 = { prop: 7 };
    }
    
  • Since inVar2 received the reference of { prop: 2 }, you can change the value of the object's property.

    function foo(inVar1, inVar2){
        inVar2.prop = 7; 
    }
    

你清楚地表达了我的理解。需要注意的主要事项是我们正在传递内容(引用或值) - KJ Sudarshan

3
在JavaScript中,将参数传递给函数类似于在C中通过指针值传递参数。
/*
The following C program demonstrates how arguments
to JavaScript functions are passed in a way analogous
to pass-by-pointer-value in C. The original JavaScript
test case by @Shog9 follows with the translation of
the code into C. This should make things clear to
those transitioning from C to JavaScript.

function changeStuff(num, obj1, obj2)
{
    num = num * 10;
    obj1.item = "changed";
    obj2 = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);    
console.log(obj2.item);

This produces the output:

10
changed
unchanged
*/

#include <stdio.h>
#include <stdlib.h>

struct obj {
    char *item;
};

void changeStuff(int *num, struct obj *obj1, struct obj *obj2)
{
    // make pointer point to a new memory location
    // holding the new integer value
    int *old_num = num;
    num = malloc(sizeof(int));
    *num = *old_num * 10;
    // make property of structure pointed to by pointer
    // point to the new value
    obj1->item = "changed";
    // make pointer point to a new memory location
    // holding the new structure value
    obj2 = malloc(sizeof(struct obj));
    obj2->item = "changed";
    free(num); // end of scope
    free(obj2); // end of scope
}

int num = 10;
struct obj obj1 = { "unchanged" };
struct obj obj2 = { "unchanged" };

int main()
{
    // pass pointers by value: the pointers
    // will be copied into the argument list
    // of the called function and the copied
    // pointers will point to the same values
    // as the original pointers
    changeStuff(&num, &obj1, &obj2);
    printf("%d\n", num);
    puts(obj1.item);
    puts(obj2.item);
    return 0;
}

2
我不认为JavaScript是这种情况: var num = 5; - Danail Nachev
1
@DanailNachev:虽然从技术上讲这可能是正确的,但这种差异只能在可变对象中观察到,而ECMAScript原语并不是可变对象。 - Jörg W Mittag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接