448

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

13 回答 13

816

基元通过值传递,对象通过“引用的副本”传递。

具体来说,当您传递一个对象(或数组)时,您(无形地)传递了对该对象的引用,并且可以修改该对象的内容,但是如果您尝试覆盖引用,则不会影响调用者持有的引用 - 即引用本身是按值传递的:

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
于 2012-10-27T21:51:24.643 回答
71

可以这样想:

每当您在 ECMAscript 中创建一个对象时,该对象就会在一个神秘的ECMAscript 通用位置形成,任何人都无法获得。你得到的只是对这个神秘地方的那个对象的引用。

var obj = { };

Evenobj只是对对象(位于那个特别美妙的地方)的引用,因此,您只能传递此引用。实际上,任何访问obj的代码都会修改很远很远的对象。

于 2012-10-27T21:55:52.213 回答
61

我的两分钱...... 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'

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

永远记住,等号=意味着赋值。将参数传递给函数也意味着赋值。它们是相同的,并且两个变量以完全相同的方式连接。

修改变量影响不同变量的唯一时间是底层对象发生突变时。

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

于 2014-08-07T07:49:40.603 回答
33

函数参数通过值或共享传递,但在 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);

函数范围内的重新分配在周围范围内不可见。

这也适用于Strings,它是一种复合数据类型,但不可变:

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

变异意味着修改现有的某些属性Object。变量绑定到的引用副本以及引用此对象的引用副本保持不变。因此,突变在调用者的范围内是可见的。

重新分配意味着替换绑定到变量的引用副本。由于它只是一个副本,因此持有相同引用副本的其他变量不受影响。因此,重新分配在调用者的范围内是不可见的,就像使用引用调用评估策略一样。

有关ECMAScript中评估策略的更多信息。

于 2016-07-22T19:05:25.660 回答
22

与 C 一样,最终,一切都是按值传递的。与 C 不同,您实际上不能备份和传递变量的位置,因为它没有指针,只有引用。

它的引用都是对象,而不是变量。有几种方法可以实现相同的结果,但它们必须手动完成,而不仅仅是在调用或声明站点添加关键字。

于 2012-10-28T01:43:21.067 回答
9

JavaScript 是按值传递的。

对于原语,传递原语的值。对于对象,传递对象的引用“值”。

对象示例:

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。

于 2014-03-08T08:48:30.900 回答
5

为了创建一个使用 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
于 2017-08-22T19:12:25.960 回答
4

实际上,Alnitak 是正确的并且易于理解,但最终在 JavaScript 中,一切都是按值传递的。

对象的“价值”是什么?它是对象引用。

当你传入一个对象时,你会得到这个值的副本(因此是 Alnitak 描述的“引用的副本”)。如果更改此值,则不会更改原始对象;您正在更改该参考的副本。

于 2013-11-22T19:39:28.250 回答
3

“全局”JavaScript 变量是窗口对象的成员。您可以将引用作为窗口对象的成员来访问。

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

请注意,上面的示例不适用于在函数中声明的变量。

于 2014-07-24T21:49:18.057 回答
2

如果没有纯粹主义,我认为在 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.

★★★</p>

另一种更具可读性的方法:

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上面那样创建新的虚拟名称。

于 2018-03-09T17:46:53.403 回答
2

在人们试图证明这一点的示例中,我看不到传递引用。我只看到pass-by-value

对于持有对对象的引用的变量,引用是这些变量的,因此引用被传递,然后是按值传递

在这样的声明中,

var a = {
  b: "foo",
  c: "bar"
};

'a' 的值不是对象,而是(到目前为止)对它的引用。换句话说,对象不在变量中a——对它的引用。我认为这对于主要只熟悉 JavaScript 的程序员来说似乎很难。但是对于也了解 Java、C# 和 C 的人来说,这很容易。

于 2019-01-29T20:07:35.517 回答
2

对象总是按引用传递,基元按值传递。只需将该参数保持在对象的相同地址即可。

这是一些代码来说明我的意思(在 JavaScript 沙箱中尝试,例如https://js.do/)。

不幸的是,您不能只保留参数的地址;您也保留所有原始成员值。

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 在函数通过引用传递之后通过引用传递

于 2019-05-31T01:48:39.433 回答
1

基元按值传递。但是,如果您只需要读取原始值的值(并且在调用函数时值未知),您可以传递在您需要时检索值的函数。

function test(value) {
  console.log('retrieve value');
  console.log(value());
}

// call the function like this
var value = 1;
test(() => value);
于 2019-01-24T12:08:55.410 回答