原始类型(例如数字、字符串等)通过值传递,但对象不同,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(当我们认为变量对对象本身持有该对象时)。
虽然最终并不重要,但我想知道呈现参数传递约定的正确方式是什么。是否有来自JavaScript规范的节选,定义了关于此事应该是什么语义?
原始类型(例如数字、字符串等)通过值传递,但对象不同,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(当我们认为变量对对象本身持有该对象时)。
虽然最终并不重要,但我想知道呈现参数传递约定的正确方式是什么。是否有来自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
)。
foo = GetNewFoo();
而不是 GetNewFoo(foo);
。 - Tim Goodmanvar obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';
来简单复制变量,并观察到与您的示例中相同的效果。因此,我个人更喜欢Tim Goodman的答案。 - chiccodoro它始终是按值传递,但对于对象,变量的值是一个引用。因此,当您传递一个对象并更改其成员时,这些更改会在函数外部保持不变。这使得它看起来像是按引用传递。但是,如果您实际上更改了对象变量的值,您将看到更改不会持久,证明它实际上是按值传递。
示例:
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";
顺便说一下,C# 也是如此。 - Tim Goodmanref
关键字,就可以通过引用传递(而非默认的值传递)来传递引用。这样,将其更改为指向new Object()
将会持久保留。 - Tim Goodman变量并不“持有”对象,它持有一个引用。可以将该引用分配给另一个变量,现在两者引用同一对象。它总是按值传递(即使该值是引用…)。
如果JavaScript支持按引用传递,则无法修改作为参数传递的变量所持有的值。
void swapNums(int &x, int &y)
或在C#中的void increment(ref int arg)
。 - wlnirvana我的两分钱:这是我理解的方式。(如果我错了,请随意纠正)
现在是时候抛弃你所知道的关于按值/引用传递的一切了。
因为在JavaScript中,无论是按值传递还是按引用传递或其他什么方式都不重要。重要的是对传入函数的参数进行变异还是赋值。
好的,让我尽力解释一下我的意思。假设你有几个对象。
var object1 = {};
var object2 = {};
var favoriteObject = object1;
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';
Again, we pick a favorite.
var favoriteString = string1;
我们的favoriteString和string1变量都被赋值为“Hello world”。现在,如果我们想要更改我们的favoriteString,会发生什么?
favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'
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
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
是通过值传递的内存位置,它引用了一个字符串(char数组)。在C中,通过值传递结构体(类似于js对象)是被允许的,但不推荐。JavaScript只是强制执行这些最佳实践,并隐藏不必要和通常不需要的冗余代码...这确实使阅读更容易。 - technosaurussource = { "id":"1"}; copy = source /*这是错误的*/; copy.id="2"
时,source 仍然是 {"id":"1"} 呢? - Machtyn这些短语/概念在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
? - Michael Hoffmann1)
我在浏览器开发工具中运行了一个类似于你描述的循环函数的内存分析,并在整个循环过程中看到了内存使用量的波动。这似乎表明在每次迭代中确实创建了新的相同对象。当这些峰值突然下降时,垃圾收集器刚刚清理了一组这些未使用的对象。 - geg2)
关于类似 var a = b
的语句,JavaScript 并没有提供使用指针的机制,因此变量永远不能指向指针(就像在 C 中一样),尽管底层的 JavaScript 引擎无疑会使用它们。所以...var a = b
将把 a
指向“右操作数的指针”。 - geg一个函数之外的对象通过给予该对象的引用被传递到函数中。
当您使用该引用来操作其对象时,外部对象就会受到影响。然而,如果在函数内部决定将引用指向其他内容,则根本不会影响外部对象,因为您所做的只是将引用重新指向其他内容。
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}
console.log(typeof "hello world"); // "string"
console.log(typeof 3); // "number"
console.log(typeof true); // "boolean"
x = "test";
console.log(x.foo);
x.foo = 12;
console.log(x.foo);
在两个console.log
中,你会发现值为undefined
。
在JavaScript中,一个值的类型独立地决定了这个值是通过值复制还是通过引用复制来赋值的。
原始值始终是通过值复制进行赋值/传递的:
null
undefined
复合值始终通过引用复制进行赋值/传递
例如:
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]
在上面的代码片段中,因为2
是一个标量原始值,a
保存了该值的一个初始副本,而b
则被分配了该值的另一个副本。当更改b
时,您并没有以任何方式更改a
中的值。c
和d
都是指向同一共享值[1,2,3]
的单独引用,这是一个复合值。重要的是要注意,c
和d
都不“拥有”[1,2,3]
值--它们只是相等的对等引用。因此,当使用任一引用来修改实际共享的array
值本身(例如使用.push(4)
),它只会影响一个共享值,而两个引用将引用新修改的值[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]
当我们执行赋值操作b=[4,5,6]
时,并没有对变量a
所引用的[1,2,3]
数组产生任何影响。要达到这个效果,b
必须是指向a
的指针而不是数组的引用--但是在JS中并不存在这样的能力!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]
值。数组
),您需要手动复制它,以便传递的引用不仍然指向原始值。例如: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
。
var x=3, y=x; f(x); alert(y === x);
,那么函数f()
可以使得alert
报告false
而不是true
。在JavaScript中,这是不可能的,所以它不是“按引用传递”。能够传递可修改对象的引用是好的,但这并不是“按引用传递”的意思。就像我说的,术语混淆真是令人遗憾。 - Pointy