提问人:J Any 提问时间:10/28/2012 最后编辑:Peter MortensenJ Any 更新时间:10/16/2020 访问量:280630
JavaScript 是否通过引用传递?[复制]
Does JavaScript pass by reference? [duplicate]
问:
JavaScript 是通过引用传递还是通过值传递?
下面是 JavaScript 中的一个示例:The Good Parts。我对矩形函数的参数感到非常困惑。它实际上是 ,并在函数中重新定义。没有原始参考资料。如果我从函数参数中删除它,则内部区域函数将无法访问它。my
undefined
是关闭吗?但不返回任何函数。
var shape = function (config) {
var that = {};
that.name = config.name || "";
that.area = function () {
return 0;
};
return that;
};
var rectangle = function (config, my) {
my = my || {};
my.l = config.length || 1;
my.w = config.width || 1;
var that = shape(config);
that.area = function () {
return my.l * my.w;
};
return that;
};
myShape = shape({
name: "Unhnown"
});
myRec = rectangle({
name: "Rectangle",
length: 4,
width: 6
});
console.log(myShape.name + " area is " + myShape.area() + " " + myRec.name + " area is " + myRec.area());
答:
基元按值传递,对象按“引用副本”传递。
具体来说,当你传递一个对象(或数组)时,你(无形地)传递了对该对象的引用,并且可以修改该对象的内容,但如果你试图覆盖引用,它不会影响调用者持有的引用的副本 - 即引用本身是按值传递的:
function replace(ref) {
ref = {}; // this code does _not_ affect the object passed
}
function update(ref) {
ref.key = 'newvalue'; // this code _does_ affect the _contents_ of the object
}
var a = { key: 'value' };
replace(a); // a still has its original value - it's unmodfied
update(a); // the _contents_ of 'a' are changed
评论
可以这样想:
每当您在 ECMAscript 中创建一个对象时,这个对象都会在一个神秘的 ECMAscript 通用位置形成,没有人能够获得。你得到的只是在这个神秘的地方对那个物体的引用。
var obj = { };
Even只是对对象(位于那个特别美妙的地方)的引用,因此,您只能传递此引用。实际上,任何访问 obj 的代码段都会修改很远很远的对象。obj
评论
与 C 一样,最终,一切都是通过值传递的。与 C 语言不同,您实际上无法备份和传递变量的位置,因为它没有指针,只有引用。
它所拥有的引用都是对象,而不是变量。有几种方法可以达到相同的结果,但它们必须手动完成,而不仅仅是在调用或声明站点添加关键字。
评论
实际上,Alnitak 是正确的,并且易于理解,但最终在 JavaScript 中,一切都是通过值传递的。
对象的“价值”是什么?它是对象引用。
当你传入一个对象时,你会得到这个值的副本(因此是 Alnitak 描述的“引用副本”)。如果更改此值,则不会更改原始对象;您正在更改该引用的副本。
评论
JavaScript 是按值传递的。
对于基元,将传递基元的值。对于 Objects,将传递 Object 的引用“value”。
Object 示例:
var f1 = function(inputObject){
inputObject.a = 2;
}
var f2 = function(){
var inputObject = {"a": 1};
f1(inputObject);
console.log(inputObject.a);
}
调用 f2 会导致将“a”值打印为 2 而不是 1,因为传递了引用并更新了引用中的“a”值。
使用基元的示例:
var f1 = function(a){
a = 2;
}
var f2 = function(){
var a = 1;
f1(a);
console.log(a);
}
调用 f2 会导致将“a”值打印为 1。
“全局”JavaScript 变量是 window 对象的成员。您可以作为窗口对象的成员访问引用。
var v = "initialized";
function byref(ref) {
window[ref] = "changed by ref";
}
byref((function(){for(r in window){if(window[r]===v){return(r);}}})());
// It could also be called like... byref('v');
console.log(v); // outputs changed by ref
请注意,上面的示例不适用于函数中声明的变量。
我的两分钱......JavaScript 是通过引用还是值传递参数无关紧要。真正重要的是分配与突变。
我在这个链接中写了一个更长、更详细的解释。
当你传递任何东西(无论是对象还是原语)时,JavaScript 所做的只是在函数内部分配一个新变量......就像使用等号()一样。=
该参数在函数中的行为方式与您刚刚使用等号分配一个新变量时的行为完全相同......举个简单的例子。
var myString = 'Test string 1';
// Assignment - A link to the same place as myString
var sameString = myString;
// If I change sameString, it will not modify myString,
// it just re-assigns it to a whole new string
sameString = 'New string';
console.log(myString); // Logs 'Test string 1';
console.log(sameString); // Logs 'New string';
如果我将参数作为参数传递给函数,它的行为就好像我只是将其分配给一个新变量一样。现在,让我们做同样的事情,但使用函数而不是简单的赋值myString
function myFunc(sameString) {
// Reassignment... Again, it will not modify myString
sameString = 'New string';
}
var myString = 'Test string 1';
// This behaves the same as if we said sameString = myString
myFunc(myString);
console.log(myString); // Again, logs 'Test string 1';
在将对象传递给函数时可以修改对象的唯一原因是,您没有重新分配...相反,对象可以被改变或改变。同样,它的工作方式相同。
var myObject = { name: 'Joe'; }
// Assignment - We simply link to the same object
var sameObject = myObject;
// This time, we can mutate it. So a change to myObject affects sameObject and visa versa
myObject.name = 'Jack';
console.log(sameObject.name); // Logs 'Jack'
sameObject.name = 'Jill';
console.log(myObject.name); // Logs 'Jill'
// If we re-assign it, the link is lost
sameObject = { name: 'Howard' };
console.log(myObject.name); // Logs 'Jill'
如果我将参数作为参数传递给函数,它的行为就好像我只是将其分配给一个新变量一样。同样,同样的事情具有完全相同的行为,但具有函数。myObject
function myFunc(sameObject) {
// We mutate the object, so the myObject gets the change too... just like before.
sameObject.name = 'Jill';
// But, if we re-assign it, the link is lost
sameObject = {
name: 'Howard'
};
}
var myObject = {
name: 'Joe'
};
// This behaves the same as if we said sameObject = myObject;
myFunc(myObject);
console.log(myObject.name); // Logs 'Jill'
每次将变量传递给函数时,您都是在“赋值”参数的名称,就像使用等号一样。=
永远记住,等号表示分配。
将参数传递给函数也意味着赋值。
它们是相同的,并且两个变量以完全相同的方式连接。=
修改变量影响其他变量的唯一情况是基础对象发生突变时。
区分对象和基元是没有意义的,因为它的工作方式与你没有函数而只是使用等号分配给新变量的方式完全相同。
评论
函数参数是按值传递的,也可以是按共享传递的,但在 JavaScript 中从来不会通过引用传递!
按值调用
基元类型按值传递:
var num = 123, str = "foo";
function f(num, str) {
num += 1;
str += "bar";
console.log("inside of f:", num, str);
}
f(num, str);
console.log("outside of f:", num, str);
函数作用域内的重新分配在周围作用域中不可见。
这也适用于 s,它是一种复合数据类型,但不可变:String
var str = "foo";
function f(str) {
str[0] = "b"; // doesn't work, because strings are immutable
console.log("inside of f:", str);
}
f(str);
console.log("outside of f:", str);
共享呼叫
对象,即所有不是基元的类型,都是通过共享传递的。保存对对象的引用的变量实际上仅包含此引用的副本。如果 JavaScript 采用按引用调用的计算策略,则变量将保留原始引用。这是按共享和按引用之间的关键区别。
这种区别的实际后果是什么?
var o = {x: "foo"}, p = {y: 123};
function f(o, p) {
o.x = "bar"; // Mutation
p = {x: 456}; // Reassignment
console.log("o inside of f:", o);
console.log("p inside of f:", p);
}
f(o, p);
console.log("o outside of f:", o);
console.log("p outside of f:", p);
Mutating 意味着修改现有 .变量绑定到的引用副本以及引用此对象的引用副本保持不变。因此,在调用方的作用域中可以看到突变。Object
重新赋值意味着替换绑定到变量的引用副本。由于它只是一个副本,因此保存同一引用副本的其他变量不受影响。因此,重新分配在呼叫者的作用域中不可见,就像使用引用调用评估策略一样。
有关 ECMAScript 中评估策略的更多信息。
评论
为了创建一个使用 const 的简单示例......
const myRef = { foo: 'bar' };
const myVal = true;
function passes(r, v) {
r.foo = 'baz';
v = false;
}
passes(myRef, myVal);
console.log(myRef, myVal); // Object {foo: "baz"} true
如果没有纯粹主义,我认为在 JavaScript 中通过引用模拟标量参数的最佳方法是使用对象,就像前面的答案一样。
但是,我做的有点不同:
我已经在函数调用中进行了对象赋值,因此可以在函数调用附近看到引用参数。它提高了源的可读性。
在函数声明中,我把属性放在注释中,原因与此相同:可读性。
var r;
funcWithRefScalars(r = {amount:200, message:null} );
console.log(r.amount + " - " + r.message);
function funcWithRefScalars(o) { // o(amount, message)
o.amount *= 1.2;
o.message = "20% increase";
}
在上面的示例中,清楚地指示了输出参考参数。null
出口:
240 - 20% Increase
在客户端,应替换为 。console.log
alert
★ ★ ★
另一种可读性更强的方法:
var amount, message;
funcWithRefScalars(amount = [200], message = [null] );
console.log(amount[0] + " - " + message[0]);
function funcWithRefScalars(amount, message) { // o(amount, message)
amount[0] *= 1.2;
message[0] = "20% increase";
}
在这里,您甚至不需要创建新的虚拟名称,如上所述。r
基元按值传递。但是,如果您只需要读取原始值(并且在调用函数时不知道值),则可以传递函数,该函数会在您需要时检索值。
function test(value) {
console.log('retrieve value');
console.log(value());
}
// call the function like this
var value = 1;
test(() => value);
在人们试图证明这一点的例子中,我看不到通过引用。我只看到按值传递。
对于包含对对象的引用的变量,引用是这些变量的值,因此传递引用,然后按值传递。
在这样的声明中,
var a = {
b: "foo",
c: "bar"
};
“a”的值不是 Object,而是(到目前为止)对它的引用。换句话说,对象不在变量中 - 对它的引用在变量中。我认为这对于主要只熟悉 JavaScript 的程序员来说似乎很困难。但对于也知道 Java、C# 和 C 的人来说,这很容易。a
对象始终按引用传递,基元始终按值传递。只需将该参数保留在对象的同一地址即可。
这里有一些代码来说明我的意思(在 https://js.do/ 等 JavaScript 沙箱中尝试一下)。
不幸的是,您不能只保留参数的地址;您还将保留所有原始成员值。
a = { key: 'bevmo' };
testRetain(a);
document.write(' after function ');
document.write(a.key);
function testRetain (b)
{
document.write(' arg0 is ');
document.write(arguments[0].key);
b.key = 'passed by reference';
var retain = b; // Retaining the original address of the parameter
// Address of left set to address of right, changes address of parameter
b = {key: 'vons'}; // Right is a new object with a new address
document.write(' arg0 is ');
document.write(arguments[0].key);
// Now retrieve the original address of the parameter for pass by reference
b = retain;
document.write(' arg0 is ');
document.write(arguments[0].key);
}
结果:
arg0 是 bevmo arg0 是 vons arg0 在通过引用传递的函数之后通过引用传递
评论