原始类型(例如数字、字符串等)通过值传递,但对象不同,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(当我们认为变量对对象本身持有该对象时)。
虽然最终并不重要,但我想知道呈现参数传递约定的正确方式是什么。是否有来自JavaScript规范的节选,定义了关于此事应该是什么语义?
原始类型(例如数字、字符串等)通过值传递,但对象不同,因为它们既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(当我们认为变量对对象本身持有该对象时)。
虽然最终并不重要,但我想知道呈现参数传递约定的正确方式是什么。是否有来自JavaScript规范的节选,定义了关于此事应该是什么语义?
该部分还列举了参数列表有0或>1个参数的情况。产生AssignmentExpression的评估结果ref。
- 让ref成为评估AssignmentExpression的结果。
- 让arg成为GetValue(ref)。
- 返回一个仅包含arg的列表。
因此,对象的属性始终可以作为引用使用。产生MemberExpression : MemberExpression [ Expression ]的评估结果:
- 让baseReference成为评估MemberExpression的结果。
- 让baseValue成为GetValue(baseReference)。
- 让propertyNameReference成为评估Expression的结果。
- 让propertyNameValue成为GetValue(propertyNameReference)。
- 调用CheckObjectCoercible(baseValue)。
- 让propertyNameString成为ToString(propertyNameValue)。
- 如果正在评估的语法产生包含在严格模式代码中,则让strict为true,否则让strict为false。
- 返回类型为Reference的值,其基值为baseValue,引用名称为propertyNameString,其严格模式标志为strict。
a.b = 1
能够知道在哪个对象(a
)上设置属性(b
)(因为 a.b
会被求值为 Reference { a, "b" }
)。 - Jonas Wilms我找到的最简洁的解释在AirBNB样式指南中:
基元类型:当您访问基元类型时,您直接使用其值
E.g.:
var foo = 1,
bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
复杂类型:当您访问复杂类型时,您是在引用其值上进行操作的
E.g.:
var foo = [1, 2],
bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
我已经多次阅读了这些答案,但直到我了解了由芭芭拉·利斯科夫(Barbara Liskov)所称的"共享调用"的技术定义后,才真正理解它。
共享调用的语义与按引用调用不同,因为函数内部对函数参数的赋值对调用者不可见(不像按引用调用语义)[需要引证],因此,例如,如果传递了一个变量,则无法在调用者的范围内模拟对该变量的赋值。然而,由于函数可以访问与调用者相同的对象(不进行复制),因此如果对象是可变的,则函数内部对这些对象的更改对调用者是可见的,这可能看起来与按值调用语义不同。在函数内部对可变对象的更改对调用者是可见的,因为对象没有被复制或克隆-它是共享的。
也就是说,如果您访问参数值本身,则可以更改参数引用。另一方面,对参数的赋值将在评估后消失,并且对函数调用者不可访问。
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})
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)
}
alter
有一种方法可以使第二个输出false
,那么我们可以说是按引用传递,因为arg
将成为a
的别名。但在JavaScript中,这是不可能的。原始类型的概念与此无关:在JavaScript中不存在按引用传递。我发现Underscore.js库的扩展方法非常有用,当我想将一个对象作为参数传递时,该对象可能会被修改或完全替换。
function replaceOrModify(aObj) {
if (modify) {
aObj.setNewValue('foo');
} else {
var newObj = new MyObject();
// _.extend(destination, *sources)
_.extend(newObj, aObj);
}
}
int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
*age = *age + 1;
}
&age
是对 myAge
的引用,但如果你想要值,你必须使用 *age
来转换引用。
JavaScript 是一种高级语言,它会为你执行这个转换。
因此,尽管对象是按引用传递的,但语言会将引用参数转换为值。你不需要在函数定义上使用 &
来按引用传递它,也不需要在函数体上使用 *
来将引用转换为值,JavaScript 会为你执行这些操作。
这就是为什么当你尝试在函数内部更改对象时,通过替换它的值(例如 age = {value:5}
),更改不会持久化,但如果你更改它的属性(例如 age.value = 5
),它会。
myAge
的值。在 JavaScript 中你做不到这一点。你可以更改 myAge
引用的对象的属性,但你不能更改变量本身 myAge
。这就是 "按引用传递" 的含义,即能够更改函数外部变量的值。 - gmanfunction 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();
我认为这是传递副本 -
考虑到参数和变量对象是在函数调用开始时创建的执行上下文中创建的对象 - 您实际传递给函数的值/引用只是存储在这些参数+变量对象中。
简单地说,对于原始类型,值在函数调用开始时被复制,对于对象类型,引用被复制。
var x=3, y=x; f(x); alert(y === x);
,那么函数f()
可以使得alert
报告false
而不是true
。在JavaScript中,这是不可能的,所以它不是“按引用传递”。能够传递可修改对象的引用是好的,但这并不是“按引用传递”的意思。就像我说的,术语混淆真是令人遗憾。 - Pointy