提问人:Danail Nachev 提问时间:2/6/2009 最后编辑:Dave MackeyDanail Nachev 更新时间:9/10/2023 访问量:547509
JavaScript 是按引用传递还是按值传递语言?
Is JavaScript a pass-by-reference or pass-by-value language?
问:
基元类型(数字、字符串等)是按值传递的,但对象是未知的,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对对象的引用)也可以是按引用传递的(当我们认为对象的变量包含对象本身时)。
虽然最后并不重要,但我想知道什么是通过约定来呈现参数的正确方式。是否有 JavaScript 规范的摘录,它定义了与此相关的语义应该是什么?
答:
变量不“保存”对象;它有一个参考。您可以将该引用分配给另一个变量,现在两者都引用同一个对象。它总是按值传递(即使该值是引用...
无法更改作为参数传递的变量所持有的值,如果 JavaScript 支持通过引用传递,这将是可能的。
评论
JavaScript 始终是按值传递的;一切都是价值类型的。
对象是值,对象的成员函数本身就是值(请记住,函数是 JavaScript 中的第一类对象)。此外,关于 JavaScript 中的所有内容都是对象的概念;这是错误的。字符串、符号、数字、布尔值、null 值和未定义是基元。
有时,它们可以利用从其基本原型继承的一些成员函数和属性,但这只是为了方便起见。这并不意味着它们本身就是对象。请尝试以下操作以供参考:
x = "test";
console.log(x.foo);
x.foo = 12;
console.log(x.foo);
在两者中,您都会发现值为 。console.log
undefined
评论
x = "teste"; x.foo = 12;
确定某些内容是否为“通过引用传递”的一种简单方法是是否可以编写“交换”函数。例如,在 C 语言中,您可以执行以下操作:
void swap(int *i, int *j)
{
int t;
t = *i;
*i = *j;
*j = t;
}
如果你不能在 JavaScript 中做与此等效的事情,它就不是“通过引用传递”。
评论
这在 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);
这将产生输出:
10
changed
unchanged
- 如果根本不是引用,则更改不会对函数的外部产生影响。
obj1
obj1.item
obj1
- 如果这个论点是一个适当的参考,那么一切都会改变。 将是 ,并且会读作 。相反,留下来并保持”。
num
100
obj2.item
"changed"
num
10
obj2.item
"unchanged
相反,情况是传入的项是按值传递的。但是按值传递的项本身就是一个引用。 从技术上讲,这称为共享呼叫。
实际上,这意味着如果更改参数本身(如 和 ),则不会影响输入到参数中的项。但是,如果您更改参数的内部结构,它将传播回去(如)。num
obj2
obj1
评论
item
ref
foo = GetNewFoo();
GetNewFoo(foo);
var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';
它始终按值传递,但对于对象,变量的值是引用。因此,当您传递对象并更改其成员时,这些更改将保留在函数之外。这使它看起来像通过引用传递。但是,如果你真的更改了对象变量的值,你会看到更改不会持续存在,证明它确实是按值传递的。
例:
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: foo
in changeObject: bar
after changeObject: foo
before changeMember: foo
in changeMember: bar
after changeMember: bar
评论
changeObject
x
x = {member:"bar"};
x = new Object(); x.member = "bar";
ref
new Object()
关于按值和引用进行复制、传递和比较的非常详细的解释在“JavaScript:权威指南”一书的这一章中。
在我们离开这个话题之前 通过以下方式操作对象和数组 参考,我们需要澄清一点 的命名法。
短语“路过 reference“可以有多种含义。 对于一些读者来说,这句话指的是 一种函数调用技术,它 允许函数分配新值 到它的论点,并拥有那些 修改后的值在 功能。这不是术语的方式 在本书中使用。
在这里,我们的意思是 简单地说,就是对对象的引用 或数组 -- 不是对象本身 -- 被传递给函数。一个函数 可以使用引用进行修改 对象或元素的属性 数组。但是如果函数 用 对新对象或数组的引用, 该修改是不可见的 在函数之外。
读者 熟悉 的其它含义 这个术语可能更愿意说 对象和数组被传递 value,但传递的值是 实际上是一个参考,而不是 对象本身。
评论
函数外部的对象通过提供对外部对象的引用来传递到函数中。
当您使用该引用来操作其对象时,外部的对象会因此受到影响。但是,如果在函数内部,您决定将引用指向其他内容,则完全不会影响外部对象,因为您所做的只是将引用重定向到其他内容。
我发现 Underscore.js 库的 extend 方法非常有用,当我想将对象作为参数传入时,该参数可以被修改或完全替换。
function replaceOrModify(aObj) {
if (modify) {
aObj.setNewValue('foo');
} else {
var newObj = new MyObject();
// _.extend(destination, *sources)
_.extend(newObj, aObj);
}
}
我发现的最简洁的解释是在 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
也就是说,实际上基元类型是通过值传递的,而复杂类型是通过引用传递的。
评论
我的两分钱......这就是我的理解。(如果我错了,请随时纠正我)
是时候抛弃你所知道的关于按值/引用传递的所有信息了。
因为在 JavaScript 中,它是按值传递还是按引用或其他方式传递并不重要。 重要的是传递给函数的参数的突变与分配。
好吧,让我尽力解释我的意思。假设您有几个对象。
var object1 = {};
var object2 = {};
我们所做的是“分配”......我们为变量“object1”和“object2”分配了 2 个单独的空对象。
现在,假设我们更喜欢 object1...因此,我们“分配”一个新变量。
var favoriteObject = object1;
接下来,无论出于何种原因,我们决定我们更喜欢对象 2。所以,我们做了一些重新分配。
favoriteObject = object2;
object1 或 object2 未发生任何反应。我们根本没有更改任何数据。我们所做的只是重新分配我们最喜欢的对象是什么。重要的是要知道 object2 和 favoriteObject 都分配给同一个对象。我们可以通过这些变量中的任何一个来更改该对象。
object2.name = 'Fred';
console.log(favoriteObject.name) // Logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // Logs Joe
好了,现在让我们看一下像字符串这样的基元
var string1 = 'Hello world';
var string2 = 'Goodbye world';
同样,我们选择一个最喜欢的。
var favoriteString = string1;
我们的 favoriteString 和 string1 变量都分配给了 'Hello world'。现在,如果我们想更改我们的 favoriteString 怎么办???会发生什么???
favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'
呃噢......发生了什么。我们无法通过更改 favoriteString 来更改 string1...为什么??因为我们没有更改字符串对象。我们所做的只是将 favoriteString 变量“重新分配”给一个新字符串。这实质上断开了它与 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 = myString
myFunc(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 object
otherObj.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 variable
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';
现在,同样的事情,但使用函数调用
function myFunc(otherObj) {
// Let's mutate our object
otherObj.firstName = 'Sue';
console.log(otherObj.firstName); // Logs 'Sue'
// Now let's re-assign
otherObj = {
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 = myObject
myFunc(myObject);
console.log(myObject.firstName); // Logs 'Sue', just like before
好的,如果你通读了整篇文章,也许你现在对函数调用在 JavaScript 中的工作方式有了更好的理解。无论某些东西是通过引用还是按值传递,都无关紧要......重要的是分配与突变。
每次将变量传递给函数时,您都是在“分配”参数变量的名称,就像使用等号 (=) 一样。
永远记住,等号 (=) 表示赋值。 永远记住,在 JavaScript 中将参数传递给函数也意味着赋值。 它们是相同的,并且 2 个变量以完全相同的方式连接(也就是说它们不是,除非您计算它们被分配给同一个对象)。
“修改变量”影响其他变量的唯一情况是基础对象发生突变时(在这种情况下,您没有修改变量,而是修改对象本身。
区分对象和基元是没有意义的,因为它的工作方式与你没有函数而只是使用等号分配给新变量的方式完全相同。
唯一的问题是,当您传递到函数中的变量的名称与函数参数的名称相同时。当这种情况发生时,你必须将函数内部的参数视为函数私有的全新变量(因为它是)
function myFunc(myString) {
// myString is private and does not affect the outer variable
myString = 'hello';
}
var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';
myFunc(myString);
console.log(myString); // Logs 'test'
评论
foo(char *a){a="hello";}
foo(char *a){a[0]='h';a[1]='i';a[2]=0;}
a
source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2"
我理解这一点的简单方法......
调用函数时,您正在传递内容(引用或 value),而不是变量本身。
var var1 = 13; var var2 = { prop: 2 }; //13 and var2's content (reference) are being passed here foo(var1, var2);
在函数内部,参数变量和 接收正在传递的内容。
inVar1
inVar2
function foo(inVar1, inVar2){ //changing contents of inVar1 and inVar2 won't affect variables outside inVar1 = 20; inVar2 = { prop: 7 }; }
由于收到 的引用,您可以更改对象属性的值。
inVar2
{ prop: 2 }
function foo(inVar1, inVar2){ inVar2.prop = 7; }
评论
在 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;
}
评论
我会说它是逐个传递的——
考虑参数和变量对象是在函数调用开始时创建的执行上下文中创建的对象 - 传递到函数中的实际值/引用只是存储在此参数 + 变量对象中。
简单地说,对于基元类型,值在函数调用的开头被复制,对于对象类型,引用被复制。
评论
可以这样想:它总是按价值传递的。但是,对象的值不是对象本身,而是对该对象的引用。
下面是一个示例,传递一个数字(基元类型)
function changePrimitive(val) {
// At this point there are two '10's in memory.
// Changing one won't affect the other
val = 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 会引起更多的混乱。
因此,不要纠结于“按引用/值传递”。
请考虑以下几点:
- 变量是指向值的指针。
- 重新赋值变量只是将该指针指向一个新值。
- 重新赋值变量永远不会影响指向同一对象的其他变量,因为每个变量都有自己的指针。
因此,如果我必须给它起一个名字,我会说“传递指针”——我们在 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'
最后的一些评论:
- 短语“按值/引用传递”仅用于描述语言的行为,而不一定是实际的底层实现。由于这种抽象,丢失了对体面解释至关重要的关键细节,这不可避免地导致了当前的情况,即单个术语在没有额外信息的情况下无法充分描述实际行为。
- 人们很容易认为基元是由特殊规则强制执行的,而对象则不是,但基元只是指针链的末端。
- 作为最后一个示例,请考虑为什么清除数组的常见尝试无法按预期工作。
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
评论
{'George', 1}
var myExistingVar = {"blah", 42}; var obj = myExistingVar;
obj
{"blah", 42}
myExistingVar
1)
2)
var a = b
var a = b
a
在 JavaScript 中,值的类型仅控制该值是由 value-copy 还是 reference-copy 分配的。
基元值始终由 value-copy 分配/传递:
null
undefined
- 字符串
- 数
- 布尔
- 符号 in
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]
在上面的代码片段中,because 是一个标量基元,保存该值的一个初始副本,并为该值分配另一个副本。更改 时,绝不会更改 中的值。2
a
b
b
a
但两者都是对同一共享值的单独引用,这是一个复合值。需要注意的是,两者都不是“拥有”该值的,两者都只是对该值的平等对等引用。因此,当使用任一引用来修改()实际共享值本身时,它只影响一个共享值,并且两个引用都将引用新修改的值。c
d
[1,2,3]
c
d
[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]
// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
当我们进行赋值时,我们绝对不会做任何事情来影响仍然引用()的位置。要做到这一点,必须是一个指向而不是引用 -- 但 JS 中不存在这样的功能!b = [4,5,6]
a
[1,2,3]
b
a
array
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
a
x
x
a
[1,2,3]
push(4)
x = [4,5,6]
a
[1,2,3,4]
若要通过值复制有效地传递复合值(如 ),需要手动创建该值的副本,以便传递的引用不会仍指向原始值。例如: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
评论
myAge
myAge
myAge
我已经多次阅读了这些答案,但直到我了解了芭芭拉·里斯科夫(Barbara Liskov)所说的“共享呼叫”的技术定义,我才真正理解它
通过共享调用的语义与通过引用调用的不同之处在于,调用者看不到函数中函数参数的赋值(与引用语义不同)[需要引用],因此,例如,如果传递了一个变量,则不可能在调用方的作用域中模拟该变量的赋值。但是,由于函数可以访问与调用方相同的对象(不进行复制),因此调用方可以看到函数中这些对象的突变(如果对象是可变的),这可能与按值语义调用不同。函数中可变对象的突变对调用者是可见的,因为该对象不是复制或克隆的,而是共享的。
也就是说,如果您访问参数值本身,则参数引用是可更改的。另一方面,对参数的赋值将在计算后消失,并且函数调用者无法访问。
评论
语义学!!设定具体的定义必然会使一些答案和评论不兼容,因为即使使用相同的单词和短语,它们也不会描述相同的事物,但克服混淆至关重要(尤其是对于新程序员)。
首先,似乎不是每个人都能掌握多个抽象层次。学习过第 4 代或第 5 代语言的新程序员可能很难将他们的思想集中在汇编或 C 程序员熟悉的概念上,而不是通过指针到指针到指针进行分阶段。按引用传递并不仅仅意味着使用函数参数变量更改引用对象的能力。
变量:符号的组合概念,它引用内存中特定位置的值。这个术语通常太重了,不能单独用于讨论细节。
符号:用于引用变量(即变量名称)的文本字符串。
值:存储在内存中并使用变量符号引用的特定位。
内存位置:存储变量值的位置。(位置本身由一个与存储在该位置的值分开的数字表示。
函数参数:在函数定义中声明的变量,用于引用传递给函数的变量。
函数参数:函数外部的变量,由调用者传递给函数。
对象变量:其基本基础值不是“对象”本身的变量,而是其值是指向内存中存储对象实际数据的另一个位置的指针(内存位置值)。在大多数高级语言中,“指针”方面通过各种上下文中的自动取消引用有效地隐藏。
原始变量:值为实际值的变量。甚至这个概念也会因各种语言的自动装箱和类似对象的上下文而变得复杂,但一般的想法是变量的值是由变量的符号表示的实际值,而不是指向另一个内存位置的指针。
函数参数和参数不是一回事。此外,变量的值不是变量的对象(正如许多人已经指出的那样,但显然被忽略了)。这些区别对于正确理解至关重要。
按值传递或按共享调用(对于对象):函数参数的值被复制到另一个内存位置,该位置由函数的参数符号引用(无论它是在堆栈上还是在堆上)。换句话说,函数参数接收了传递的参数值的副本......并且(关键)参数的值永远不会被调用函数更新/更改/更改。请记住,对象变量的值不是对象本身,而是指向对象的指针,因此按值传递对象变量会复制指向函数参数变量的指针。函数参数的值指向内存中完全相同的对象。对象数据本身可以通过函数参数直接更改,但函数参数的值永远不会更新,因此它将在整个函数调用过程中甚至在函数调用之后继续指向同一个对象(即使其对象的数据被更改或函数参数被分配了完全不同的对象)。仅仅因为引用的对象可通过函数参数变量进行更新,就断定函数参数是通过引用传递的,这是不正确的。
调用/引用传递:函数参数的值可以/将直接由相应的函数参数更新。如果有帮助,函数参数将成为参数的有效“别名”——它们有效地引用同一内存位置的相同值。如果函数参数是对象变量,则更改对象数据的能力与按值传递的情况没有什么不同,因为函数参数仍将指向与参数相同的对象。但是在对象变量的情况下,如果函数参数被设置为一个完全不同的对象,那么参数同样也会指向不同的对象——这在按值传递的情况下不会发生。
JavaScript 不通过引用传递。如果你仔细阅读,你会发现所有相反的观点都误解了按值传递的含义,他们错误地得出结论,通过函数参数更新对象数据的能力是“按值传递”的同义词。
对象克隆/复制:创建一个新对象并复制原始对象的数据。这可以是深拷贝或浅拷贝,但关键是要创建一个新对象。创建对象的副本是与按值传递不同的概念。某些语言区分类对象和结构(或类似语言),并且可能具有不同的传递不同类型的变量的行为。但是 JavaScript 在传递对象变量时不会自动执行此类操作。但是,没有自动对象克隆并不意味着通过引用传递。
对于编程语言律师,我已经阅读了 ECMAScript 5.1 的以下部分(它比最新版本更容易阅读),甚至在 ECMAScript 邮件列表上询问它。
TL的;DR:一切都是按值传递的,但 Object 的属性是引用,而 Object 的定义在标准中非常缺乏。
参数列表的构建
第 11.2.4 节 “参数列表” 在生成仅包含 1 个参数的参数列表时说了以下内容:
生产 ArgumentList : AssignmentExpression 的计算方式如下:
- 设 ref 是计算 AssignmentExpression 的结果。
- 设 arg 为 GetValue(ref)。
- 返回一个 List,其唯一项是 arg。
本节还列举了参数列表具有 0 或 >1 参数的情况。
因此,一切都是通过引用传递的。
对象属性的访问
第 11.2.1 节 “属性访问器”
生产 MemberExpression : MemberExpression [ Expression ] 的计算方式如下:
- 设 baseReference 是计算 MemberExpression 的结果。
- 设 baseValue 为 GetValue(baseReference)。
- 设 propertyNameReference 为计算 Expression 的结果。
- 设 propertyNameValue 为 GetValue(propertyNameReference)。
- 调用 CheckObjectCoercible(baseValue)。
- 设 propertyNameString 为 ToString(propertyNameValue)。
- 如果正在计算的语法生成包含在 strict 模式代码中,则 keep 为 true,否则让 strict 为 false。
- 返回一个 Reference 类型的值,其基值为 baseValue,引用的名称为 propertyNameString,其 strict 模式标志为 strict。
因此,对象的属性始终可用作参考。
参考
在第 8.7 节 “引用规范类型” 中进行了描述,引用在语言中不是实际类型 - 它们仅用于描述 delete、typeof 和赋值运算符的行为。
“对象”的定义
在 5.1 版中定义“对象是属性的集合”。因此,我们可以推断,对象的值是集合,但至于集合的值是什么,在规范中定义得很差,需要一些努力才能理解。
评论
a.b = 1
a
b
a.b
Reference { a, "b" }
分享我对 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
评论
JavaScript 按值传递基元类型,按引用传递对象类型
现在,人们喜欢无休止地争吵是否“通过引用” 是描述 Java 等人实际操作的正确方式。重点 是这样的:
- 传递对象不会复制该对象。
- 传递给函数的对象可以由函数修改其成员。
- 传递给函数的基元值不能由函数修改。制作副本。
在我的书中,这叫做“引用传递”。
更新
这是对这一点的反驳:
评论
这是对按值传递和按引用传递 (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 中的所有基元类型(字符串、数字、布尔值、未定义和 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 引擎将对象分配给变量 ,它指向一些内存,比如 (0x012)。
c
- 当 d=c 时,在此步骤中指向相同的位置 (0x012)。
d
- 更改任何变量的值都会更改两个变量的值。
- 函数是对象
特殊情况,通过引用传递(对象)
c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
- equal(=) 运算符设置新的内存空间或地址
评论
MDN 文档对此进行了清晰的解释,但不会太冗长:
函数调用的参数是函数的参数。 参数按值传递给函数。如果功能更改 参数的值,此更改不会全局反映在全局或 调用函数。但是,对象引用也是值,并且 它们很特殊:如果函数更改了被引用对象的 属性,该更改在函数外部可见,(...)
来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description
嗯,这是关于“性能”和“速度”,用编程语言中的简单词“内存管理”来说。
在JavaScript中,我们可以将值放在两层中:type1-和type2-所有其他类型的值,如&&等objects
string
boolean
如果您将内存想象为下面的正方形,其中每个正方形中只能保存一个 type2 值:
每个 type2 值(绿色)都是一个正方形,而 type1 值(蓝色)是一组正方形:
关键是,如果你想指示一个 type2-value,地址是普通的,但如果你想对 type1-value 做同样的事情,这并不容易!:
在一个更复杂的故事中:
虽然这里的绿色箭头是一个典型的变量,但紫色的箭头是一个对象变量,所以因为绿色箭头(典型变量)只有一个任务(即表示一个典型值),我们不需要将它的值与它分开,所以我们将绿色箭头与它的值一起移动到任何地方和所有赋值中, 功能等等......
但是我们不能用紫色箭头做同样的事情,我们可能想在这里移动“约翰”单元格或许多其他东西......,所以紫色箭头会粘在它的位置,只有分配给它的典型箭头才会移动......
一个非常令人困惑的情况是,你无法意识到你引用的变量是如何变化的,让我们看一个很好的例子:
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'];
评论
观察:如果观察者无法检查引擎的底层内存,则无法确定是复制不可变值还是传递引用。
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" }
如果您想要像其他语言一样的(正常)函数参数行为(传递值的副本) 然后只需在传入函数之前克隆对象:
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();
一切都是按值传递的。
基本类型按值传递(即将实际变量值的新副本传递给函数)。
复杂类型(对象)作为“指向对象的指针”传递。因此,您传递的实际内容是按值传递的指针(它是一个地址,一个像其他任何值一样的数值)。显然,如果您尝试在函数内修改对象的属性,则即使在该函数之外也会反映修改。这是因为您通过指向属性的唯一副本的指针访问该属性。
“按值传递指针”和“按引用传递对象”是一回事。
function passByCopy ([...array], {...object})
{
console .log ("copied objects", array, object)
}
passByCopy ([1,2,3], {a:1, b:2, c:3})
function passByReference (array, object)
{
console .log ("same objects", array, object)
}
passByReference ([1,2,3], {a:1, b:2, c:3})
评论
引用提问者的话:
基元类型(数字、字符串等)是按值传递的,但对象 [...$ 既可以按值传递(在这种情况下,我们认为包含对象的变量实际上是对对象的引用)也可以按引用传递(当我们认为对象的变量包含对象本身时)。
事实并非如此。只有一种方法可以将对象作为参数传递。尽管其他答案也区分了原始值和非原始值,但这是一种干扰和无关紧要。
无引用传递
考虑以下代码,它不关心是原语还是对象:a
function alter(arg) { /* some magic happening here to `arg` */ }
function main() {
var a, b;
a = b = { x: 1 }; // Example value. It can be an object or primitive: irrelevant here
console.log(a === b); // true
alter(a);
console.log(a === b); // false? (not possible)
}
如果函数有某种方式进行第二个输出,那么我们可以说是按引用传递,因为那时将是 的别名。但这在 JavaScript 中是不可能的。原语的概念与此无关:JavaScript 中没有按引用传递。alter
false
arg
a
对象
在 JavaScript 中,对象是对属性(和插槽)集合的引用。当使用对象作为参数调用函数时,这不会创建新的属性集合。函数的参数变量(即局部变量)将接收相同的值(即对属性集合的引用)。
变量赋值与对象突变
调用方的数据结构也可以通过函数调用进行更改。这显然只适用于可变值(根据定义),在 JavaScript 中,这是通过为属性赋值来实现的。要区分两件事:
- 对 objext 属性的赋值会改变调用方和被调用方都有引用的对象。
- 对参数变量的赋值不会改变调用方的数据。具体而言,如果该参数变量是对象(引用),则该引用将被覆盖,因此函数将自身与调用方的数据分离。
评论
var x=3, y=x; f(x); alert(y === x);
f()
false
true