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

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

虽然最后这并不重要,但我想知道如何正确地呈现传递约定的参数。是否有JavaScript规范的摘录,它定义了对此应该是什么语义学?

505982 次浏览

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

对象通过引用自动传递,无需具体说明它

(来自上面提到的文章。

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

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

JavaScript总是按值传递;一切都是值类型。

对象是值,对象的成员函数本身就是值(记住函数在JavaScript中是一等对象)。此外,关于JavaScript中的一切都是对象的概念;这是错误的。字符串、符号、数字、布尔值、空值和未定义的都是原语

有时它们可以利用从基本原型继承的一些成员函数和属性,但这只是为了方便。这并不意味着它们本身就是对象。尝试以下参考:

x = "test";console.log(x.foo);x.foo = 12;console.log(x.foo);

console.log中,你会发现值是undefined

确定某物是否“引用传递”的一个简单方法是您是否可以编写“交换”函数。例如,在C中,您可以执行:

void swap(int *i, int *j){int t;t = *i;*i = *j;*j = t;}

如果你不能在JavaScript中做到这一点,那就不是“引用传递”。

原语通过值传递,对象通过引用传递。这与C、Visual Basic或Delphi等其他语言有很大不同。我不能确切地说他们如何处理对象和原语,但我知道Visual Basic和Delphi可以(也应该)指定它。

自版本5以来,PHP做了类似的事情:所有对象都通过引用传递,但所有原语可能都通过引用传递,如果前面有与号(&)。否则原语按值传递。

因此,在JavaScript中,如果我通过参数将对象X传递给函数,它仍然是X。如果您正在更改函数(或任何其他对象,但这并不重要)的数据里面,则新值也可在函数之外使用。

这在JavaScript中很有趣。考虑这个例子:

function changeStuff(a, b, c){a = a * 10;b.item = "changed";c = {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);

这将产生输出:

10changedunchanged
  • 如果obj1根本不是引用,那么更改obj1.item对函数外部的obj1没有影响。
  • 如果参数是正确的引用,那么一切都会改变。num将是100obj2.item将是"changed"。相反,num保持10obj2.item保持"unchanged”。

相反,情况是传入的项目是按值传递的。但按值传递的项目是本身引用。从技术上讲,这称为共享呼叫

实际上,这意味着如果您更改参数本身(与numobj2一样),这不会影响输入参数的项目。但是如果您更改参数的内部,它会传播回来(与obj1一样)。

它总是值传递,但对于对象来说,变量的值是一个引用。正因为如此,当你传递一个对象并更改它的成员时,这些更改会在函数之外持续存在。这使得它像引用传递一样。但是如果你实际上更改了对象变量的值,你会看到更改不会持续存在,证明它真的是值传递。

示例:

function changeObject(x) {x = { member: "bar" };console.log("in changeObject: " + x.member);}
function changeMember(x) {x.member = "bar";console.log("in changeMember: " + x.member);}
var x = { member: "foo" };
console.log("before changeObject: " + x.member);changeObject(x);console.log("after changeObject: " + x.member); /* change did not persist */
console.log("before changeMember: " + x.member);changeMember(x);console.log("after changeMember: " + x.member); /* change persists */

输出:

before changeObject: fooin changeObject: barafter changeObject: foo
before changeMember: fooin changeMember: barafter changeMember: bar

关于按值和引用复制、传递和比较的非常详细的解释在“JavaScript:权威指南”书的本章中。

在我们离开之前操作对象和数组参考文献,我们需要弄清楚一点的命名法。

经过引用”可以有几个含义。对一些读者来说,这个短语指的是一种函数调用技术允许函数分配新值它的论点,并有那些外可见的修改值功能。这不是术语的方式在这本书中使用。

这里的意思是简单地说,对一个对象的引用或数组——而不是对象本身——传递给一个函数。一个函数可以使用引用修改对象或元素的属性的数组。但如果函数用a覆盖引用引用一个新的对象或数组,修改是不可见的在函数之外。

读者熟悉的其他含义这个术语可能更愿意说对象和数组被传递值,但传递的值是实际上是一个参考而不是对象本身。

函数外部的对象通过引用外部对象传递给函数。

当你使用该引用来操作它的对象时,外部的对象会受到影响。然而,如果在函数内部你决定将引用指向其他东西,你根本不会影响外部的对象,因为你所做的只是将引用重新定向到其他东西。

我发现Underscore.js图书馆扩展法非常有用,当我想传递一个对象作为参数,它可以被修改或完全替换。

function replaceOrModify(aObj) {if (modify) {
aObj.setNewValue('foo');
} else {
var newObj = new MyObject();// _.extend(destination, *sources)_.extend(newObj, aObj);}}
  1. 原语(数字、布尔值等)按值传递。
    • 字符串是不可变的,所以对它们来说并不重要。
  2. 对象通过引用传递(引用通过值传递)。

函数内部的简单值不会改变函数外部的值(它们通过值传递),而复杂的值会(它们通过引用传递)。

function willNotChange(x) {
x = 1;}
var x = 1000;
willNotChange(x);
document.write('After function call, x = ' + x + '<br>'); // Still 1000
function willChange(y) {
y.num = 2;}
var y = {num: 2000};
willChange(y);document.write('After function call y.num = ' + y.num + '<br>'); // Now 2, not 2000

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

  • 原语:当您访问原始类型时,您直接在其上工作值

    • 字符串
    • 数量
    • 布尔
    • null
    • 未定义

例如:

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

即基本类型通过值传递,复杂类型通过引用传递。

我的两分钱…这是我理解的方式。(如果我错了,请随时纠正我)

是时候扔掉你所知道的关于值传递/引用的一切了。

因为在JavaScript中,它是按值传递还是按引用传递都无关紧要。重要的是传递给函数的参数的突变与赋值。

好的,让我尽力解释一下我的意思。假设你有一些物品。

var object1 = {};var object2 = {};

我们所做的是“赋值”……我们将两个单独的空对象赋值给变量“对象1”和“对象2”。

现在,假设我们更喜欢对象1……所以,我们“分配”了一个新变量。

var favoriteObject = object1;

接下来,不管出于什么原因,我们决定更喜欢对象2。所以,我们做了一点重新赋值。

favoriteObject = object2;

对象1和对象2什么也没发生。我们根本没有改变任何数据。我们所做的只是重新分配我们最喜欢的对象。重要的是要知道对象2和收藏对象都被分配给同一个对象。我们可以通过这些变量中的任何一个改变那个对象。

object2.name = 'Fred';console.log(favoriteObject.name) // Logs FredfavoriteObject.name = 'Joe';console.log(object2.name); // Logs Joe

好的,现在让我们看看原语,例如字符串

var string1 = 'Hello world';var string2 = 'Goodbye world';

再一次,我们选择了一个最喜欢的。

var favoriteString = string1;

我们最喜欢的字符串和string1变量都被分配给'Hello world'。现在,如果我们想更改我们最喜欢的字符串怎么办???会发生什么???

favoriteString = 'Hello everyone';console.log(favoriteString); // Logs 'Hello everyone'console.log(string1); // Logs 'Hello world'

呃哦…发生了什么事。我们无法通过更改收藏夹字符串来更改string1…为什么?因为我们没有改变我们的字符串对象。我们所做的只是将收藏夹字符串变量“RE ASSIGN”转换为一个新字符串。这基本上断开了它与string1的连接。在前面的例子中,当我们重命名对象时,我们没有分配任何东西。(嗯,不是变量本身,…但是,我们确实将name属性分配给了一个新字符串。)相反,我们改变了保持2个变量和底层对象之间连接的对象。(即使我们想修改字符串对象突变本身,我们也不能,因为字符串在JavaScript中实际上是不可变的。)

现在,关于函数和传递参数……当你调用一个函数并传递一个参数时,你本质上所做的是对一个新变量的“赋值”,它的工作方式与你使用等号(=)赋值完全相同。

看看这些例子。

var myString = 'hello';
// Assign to a new variable (just like when you pass to a function)var param1 = myString;param1 = 'world'; // Re assignment
console.log(myString); // Logs 'hello'console.log(param1);   // Logs 'world'

现在,同样的事情,但有一个功能

function myFunc(param1) {param1 = 'world';
console.log(param1);   // Logs 'world'}
var myString = 'hello';// Calls myFunc and assigns param1 to myString just like param1 = myStringmyFunc(myString);
console.log(myString); // logs 'hello'

好的,现在让我们举几个使用对象的例子……首先,没有函数。

var myObject = {firstName: 'Joe',lastName: 'Smith'};
// Assign to a new variable (just like when you pass to a function)var otherObj = myObject;
// Let's mutate our objectotherObj.firstName = 'Sue'; // I guess Joe decided to be a girl
console.log(myObject.firstName); // Logs 'Sue'console.log(otherObj.firstName); // Logs 'Sue'
// Now, let's reassign the variableotherObj = {firstName: 'Jack',lastName: 'Frost'};
// Now, otherObj and myObject are assigned to 2 very different objects// And mutating one object has no influence on the otherconsole.log(myObject.firstName); // Logs 'Sue'console.log(otherObj.firstName); // Logs 'Jack';

现在,同样的事情,但有一个函数调用

function myFunc(otherObj) {
// Let's mutate our objectotherObj.firstName = 'Sue';console.log(otherObj.firstName); // Logs 'Sue'
// Now let's re-assignotherObj = {firstName: 'Jack',lastName: 'Frost'};console.log(otherObj.firstName); // Logs 'Jack'
// Again, otherObj and myObject are assigned to 2 very different objects// And mutating one object doesn't magically mutate the other}
var myObject = {firstName: 'Joe',lastName: 'Smith'};
// Calls myFunc and assigns otherObj to myObject just like otherObj = myObjectmyFunc(myObject);
console.log(myObject.firstName); // Logs 'Sue', just like before

好吧,如果你通读了这篇文章,也许你现在对JavaScript中函数调用的工作原理有了更好的理解。通过引用还是通过值传递并不重要…重要的是赋值与突变。

每次将变量传递给函数时,您都在“分配”参数变量的名称,就像您使用等号(=)一样。

永远记住,等于号(=)意味着赋值。永远记住,将参数传递给函数在javascript也意味着赋值。它们是相同的,并且两个变量以完全相同的方式连接(也就是说它们不是,除非您将它们分配给同一个对象)。

“修改变量”影响不同变量的唯一时间是底层对象发生突变时(在这种情况下,您没有修改变量,而是修改了对象本身。

区分对象和原语是没有意义的,因为它的工作方式与您没有函数并且只是使用等号分配给新变量的方式完全相同。

唯一的陷阱是当您传递给函数的变量名称与函数参数的名称相同时。发生这种情况时,您必须将函数内的参数视为函数私有的全新变量(因为它是)

function myFunc(myString) {// myString is private and does not affect the outer variablemyString = 'hello';}
var myString = 'test';myString = myString; // Does nothing, myString is still 'test';
myFunc(myString);console.log(myString); // Logs 'test'

我理解这个的简单方法…

  • 调用函数时,您传递的内容(引用或值)的参数变量,而不是变量本身。

    var var1 = 13;var var2 = { prop: 2 };
    //13 and var2's content (reference) are being passed herefoo(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 outsideinVar1 = 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;}
  1. 原始类型变量,如字符串,数字总是作为传递传递按值。
  2. 数组和对象根据这两个条件作为引用传递或值传递。

    • 如果您使用new Object或Array更改该对象或数组的值,则它由Value传递。

      对象1={项目:"汽车"};数组1=[1,2,3];

    在这里,您将新对象或数组分配给旧one.you不会更改属性的值object.so值传递

    • 如果要更改对象或数组的属性值,则通过Reference传递。

      object1.key1="car";数组1[0]=9;

    在这里,您正在更改旧的属性值object.you没有将新对象或数组分配给旧的one.so它是引用传递的。

代码

    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: -laptopbike10

在JavaScript中向函数传递参数类似于传递C中指针值的参数:

/*The following C program demonstrates how argumentsto JavaScript functions are passed in a way analogousto pass-by-pointer-value in C. The original JavaScripttest case by @Shog9 follows with the translation ofthe code into C. This should make things clear tothose 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:
10changedunchanged*/
#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 valueint *old_num = num;num = malloc(sizeof(int));*num = *old_num * 10;// make property of structure pointed to by pointer// point to the new valueobj1->item = "changed";// make pointer point to a new memory location// holding the new structure valueobj2 = malloc(sizeof(struct obj));obj2->item = "changed";free(num); // end of scopefree(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 pointerschangeStuff(&num, &obj1, &obj2);printf("%d\n", num);puts(obj1.item);puts(obj2.item);return 0;}

我会说它是逐个复制的-

考虑参数和变量对象是在函数调用开始时创建的执行上下文中创建的对象-并且传递到函数中的实际值/引用仅存储在此参数+变量对象中。

简单地说,对于基本类型,值在函数调用的开头被复制,对于对象类型,引用被复制。

可以这样想:它总是值传递的。然而,对象的值不是对象本身,而是对该对象的引用。

下面是一个例子,传递一个数字(基本类型)

function changePrimitive(val) {// At this point there are two '10's in memory.// Changing one won't affect the otherval = val * 10;}var x = 10;changePrimitive(x);// x === 10

对一个对象重复此操作会产生不同的结果:

function changeObject(obj) {// At this point there are two references (x and obj) in memory,// but these both point to the same object.// changing the object will change the underlying object that// x and obj both hold a reference to.obj.val = obj.val * 10;}var x = { val: 10 };changeObject(x);// x === { val: 100 }

再举一个例子:

function changeObject(obj) {// Again there are two references (x and obj) in memory,// these both point to the same object.// now we create a completely new object and assign it.// obj's reference now points to the new object.// x's reference doesn't change.obj = { val: 100 };}var x = { val: 10 };changeObject(x);// x === { val: 10}

这些短语/概念最初是在JS创建之前很久定义的,它们并不能准确地描述javascript的语义学。我认为试图将它们应用于JS会导致更多的混乱。

所以不要挂在“引用传递/值”上。

考虑以下几点:

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

所以如果我必须给它一个名字,我会说"指针传递"——我们不处理JS中的指针,但底层引擎处理。


// codevar obj = {name: 'Fred',num: 1};
// illustration'Fred'//(obj) ---- {}\\1

// codeobj.name = 'George';

// illustration'Fred'

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

// codeobj = {};
// illustration'Fred'

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

// codevar 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'

一些最后的评论:

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

var a = [1, 2];var b = a;
a = [];console.log(b); // [1,2]// doesn't work because `b` is still pointing at the original array

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

基元值总是由value复制分配/传递

  • null
  • undefined
  • 字符串
  • 数量
  • 布尔
  • ES6中的符号

复合值总是由引用复制分配/传递

  • 对象
  • 数组
  • 函数

例如

var a = 2;var b = a; // `b` is always a copy of the value in `a`b++;a; // 2b; // 3
var c = [1,2,3];var d = c; // `d` is a reference to the shared `[1,2,3]` valued.push( 4 );c; // [1,2,3,4]d; // [1,2,3,4]

在上面的代码片段中,因为2是一个标量基元,所以a保存该值的一个初始副本,b被分配该值的另一个副本。当更改b时,你绝不会更改a中的值。

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

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

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

function foo(x) {x.push( 4 );x; // [1,2,3,4]
// laterx = [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时,它将a引用的副本分配给xxa是指向相同[1,2,3]值的单独引用。现在,在函数内部,我们可以使用该引用来改变值本身(push(4))。但是当我们进行x = [4,5,6]赋值时,这丝毫不影响初始引用a指向的位置——仍然指向(现在修改的)[1,2,3,4]值。

要有效地通过value副本传递复合值(如array),您需要手动制作它的副本,以便传递的引用不会仍然指向原始引用。例如:

foo( a.slice() );

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

function foo(wrapper) {wrapper.a = 42;}
var obj = {a: 2};
foo( obj );
obj.a; // 42

在这里,obj充当标量基元属性a的包装器。当传递给foo(..)时,obj引用的副本被传入并设置为wrapper参数。我们现在可以使用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),它会持久化。

了解更多

我已经多次阅读这些答案,但直到我了解了Barbara Liskov所说的“通过分享打电话”的技术定义才真正理解它

通过共享调用的语义学与通过引用调用的不同之处在于,函数内函数参数的赋值对调用者不可见(与引用语义学不同)[需要引用],因此例如,如果传递了一个变量,就不可能在调用者的范围内模拟对该变量的赋值。然而,由于函数可以访问与调用者相同的对象(没有复制),这些对象的突变,如果对象是可变的,函数内的对象对调用者是可见的,这可能看起来不同于按值语义学的调用。函数中可变对象的突变对调用者是可见的,因为该对象没有被复制或克隆-它是共享的。

也就是说,如果你去访问参数值本身,参数引用是可以改变的。另一方面,对参数的赋值在评估后将消失,并且函数调用者无法访问。

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

首先,有多个层次的抽象并不是每个人都能掌握的。学习过第四代或第五代语言的新程序员可能很难理解汇编或C程序员熟悉的概念,而不是通过指向指针的指针来分阶段。按引用传递不仅仅意味着使用函数参数变量更改引用对象的能力。

变量:引用内存中特定位置的值的符号的组合概念。这个术语通常太繁琐,无法单独用于讨论细节。

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

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

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

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

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

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

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

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

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

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

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

对象克隆/复制:创建一个新对象并复制原始对象的数据。这可以是深拷贝或浅拷贝,但关键是创建了一个新对象。创建对象的副本是与按值传递分开的概念。一些语言区分类对象和结构(或类似的东西),并且在传递不同类型的变量时可能有不同的行为。但JavaScript在传递对象变量时不会自动做这样的事情。但没有自动对象克隆并不能转化为按引用传递。

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

太长别读:所有东西都是按值传递的,但是Object的属性是引用,并且标准中缺少Object的定义。

论据列表的构造

第11.2.4节“参数列表”在生成仅包含1个参数的参数列表时说了以下内容:

生产ArgumentList: AsSignmentExpress的计算如下:

  1. 假设ref是评估AsSignmentExpress的结果。
  2. 设arg为GetValue(ref)。
  3. 返回其唯一项为arg的List。

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

因此,一切都是通过引用传递的。

对象属性的访问

第11.2.1节“财产访问者”

生产MemberExpress: MemberExpress[Express]的计算如下:

  1. 假设base Reference是计算MemberExpress的结果。
  2. 设basValue为GetValue(basReference)。
  3. 假设属性名称引用是计算Ex表达式的结果。
  4. 设属性名称值为GetValue(属性名称引用)。
  5. 调用CheckObjectCoercible(base Value)。
  6. 设属性名称字符串为ToString(属性名称值)。
  7. 如果正在计算的语法产生式包含在严格模式代码中,则让严格为true,否则让严格为假。
  8. 返回Reference类型的值,其基值为base Value,引用的名称为属性名称字符串,其严格模式标志为严格。

因此,对象的属性始终可用作引用。

关于参考

在第8.7节“引用规范类型”中描述了引用不是语言中的实际类型-它们仅用于描述删除,类型和赋值操作符的行为。

“对象”的定义

在5.1版本中定义“一个对象是属性的集合”。因此,我们可以推断,对象的值就是集合,但至于集合的值是什么在规范中定义得很差,需要一点0来理解。

分享我对JavaScript中引用的了解

在JavaScript中,当将对象分配给变量时,分配给变量的值是对对象的引用:

var a = {a: 1,b: 2,c: 3};var b = a;
// b.c is referencing to a.c valueconsole.log(b.c) // Output: 3// Changing value of b.cb.c = 4// Also changes the value of a.cconsole.log(a.c) // Output: 4

JavaScript通过值传递基本类型,通过引用传递对象类型

现在,人们喜欢无休止地争论是否"引用传递"是描述Java等人实际做什么的正确方法是这样的:

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

在我的书中,这叫做参考传递。

-brianbi-引用传递哪些编程语言?


更新

以下是对此的反驳:

JavaScript中没有引用传递。

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

按值传递(基本类型)

var a = 3;var b = a;
console.log(a); // a = 3console.log(b); // b = 3
a=4;console.log(a); // a = 4console.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" }
  • 等于(=)运算符设置新的内存空间或地址

MDN的文档解释得很清楚,但没有太冗长:

函数调用的参数是函数的参数。参数被传递给函数按价值。如果函数发生变化一个论点的价值,这种变化没有反映在全球或调用函数。但是,对象引用也是值,并且它们是特殊的:如果函数改变了引用对象的属性,该更改在函数外部可见,(…)

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

嗯,它是关于“性能”和“速度”,以及编程语言中简单的“内存管理”。

在javascript中,我们可以将值放在两层:type1-objectstype2-所有其他类型的值,例如stringboolean

如果你把内存想象成下面的方块,其中每一个方块中只有一个type2-value可以保存:

输入图片描述

每个type2-value(绿色)都是单一广场,而type1-value(蓝色)是其中一组

输入图片描述

关键是,如果你想指示一个type2-value,地址是简单的,但如果你想对type1-value做同样的事情,那就不容易了!:

输入图片描述

还有一个更复杂的故事:

输入图片描述

所以这里参考文献可以拯救我们:
输入图片描述

虽然这里的绿色箭头是一个典型的变量,紫色的是一个对象变量,所以因为绿色箭头(典型变量)只有一个任务(这表示一个典型的值)我们不需要将它的值与它分开,所以我们移动绿色箭头的值,无论它走到哪里,在所有的赋值,函数等中……

但是我们不能用紫色箭头做同样的事情,我们可能想在这里移动“约翰”单元格或许多其他东西……所以紫色箭头将坚持它的位置,只是分配给它的典型箭头会移动……

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

let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating itlet obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr objlet 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 changedobj2 = 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'];

输入图片描述输入图片描述

观察:如果观察者无法检查引擎的底层内存,则无法确定是否复制了不可变值或传递了引用。

JavaScript或多或少与底层内存模型无关。没有参考²这样的东西。JavaScript有。两个变量可以保存相同的(或更准确:两个环境记录可以绑定相同的值)。唯一可以突变的值类型是通过抽象[[Get]]和[[Set]]操作的对象。如果您忘记了计算机和内存,这就是描述JavaScript行为所需的全部内容,它允许您理解规范。

 let a = { prop: 1 };let b = a; // a and b hold the same valuea.prop = "test"; // The object gets mutated, can be observed through both a and bb = { prop: 2 }; // b holds now a different value

现在你可能会问自己,为什么两个变量在一台计算机上可以保存相同的值,然后你可能会查看JavaScript引擎的源代码,你很可能会发现一些引擎所用语言的程序员会称之为引用的东西。

所以实际上你可以说JavaScript是“值传递”,而值是可以共享的,你可以说JavaScript是“引用传递”,这对低级语言的程序员来说可能是一个有用的逻辑抽象,或者你可以称这种行为为“通过共享调用”。

由于JavaScript中没有引用这样的东西,所有这些都既没有错误也没有问题。因此,我不认为搜索答案特别有用。

²规范中的术语参考不是传统意义上的引用。它是对象和属性名称的容器,是中间值(例如,a.b的计算结果为Reference { value = a, name = "b" })。术语参考有时也会出现在规范中不相关的部分。

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

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();

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

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

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

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