在JavaScript中通过引用传递变量

387

如何在JavaScript中通过引用传递变量?

我有三个变量,想对它们执行一些操作,因此希望将它们放入for循环中,并对每个变量执行操作。

伪代码:

myArray = new Array(var1, var2, var3);
for (var x = 0; x < myArray.length; x++){
    // Do stuff to the array
    makePretty(myArray[x]);
}
// Now do stuff to the updated variables

什么是最佳的做法?


37
你提到了“按引用传递”,但在你的例子中并没有函数调用,因此在你的例子中根本没有传递。请澄清你想要做什么。 - jfriend00
1
抱歉让您感到困惑。我并不需要特别编写一个函数,所以“传递引用”是一个不太恰当的措辞。我只是想对变量执行一些操作而无需编写:makePretty(var1); makePretty(var2); makePretty(var3); ... - BFTrick
1
根据您的评论:arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); } -- 您只需要将 makePretty 返回的值存储回数组中的槽位即可。 - dylnmc
1
对于那些在谷歌搜索“Javascript中的按引用传递”或“按值传递”的人,这篇来自Medium的好文章是:https://medium.com/@TK_CodeBear/javascript-arrays-pass-by-value-and-thinking-about-memory-fffb7b0bf43 - Erenor Paz
1
@ErenorPaz。Medium的文章已经成为了死鹦鹉的命运! - Patanjali
谢谢@Patanjali(喜欢你的比喻),我想建议一些更加“实在”的东西(希望如此,考虑到互联网资源非常不稳定(就像一只鹦鹉,我们可以这么说)),来自Geeks4geeks:https://www.geeksforgeeks.org/pass-by-value-and-pass-by-reference-in-javascript/。但是,我相信现在已经有很多参考资料了,Google比这个评论更能帮助我们 :-) :-) - Erenor Paz
17个回答

532

在JavaScript中不存在所谓的“按引用传递”。您可以传递一个对象(也就是说,您可以传递指向对象的值),然后让函数修改该对象的内容:

function alterObject(obj) {
  obj.foo = "goodbye";
}

var myObj = { foo: "hello world" };

alterObject(myObj);

alert(myObj.foo); // "goodbye" instead of "hello world"
你可以通过数值索引迭代数组的属性,并修改数组的每个元素,如果你愿意的话。
var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) { 
    arr[i] = arr[i] + 1; 
}

需要注意的是,“按引用传递”是一个非常具体的术语。它并不仅仅意味着可以传递到可修改对象的引用。相反,它意味着可以以这样一种方式传递简单变量,使得函数能够在调用上下文中修改该值。因此:

 function swap(a, b) {
   var tmp = a;
   a = b;
   b = tmp; //assign tmp to b
 }

 var x = 1, y = 2;
 swap(x, y);

 alert("x is " + x + ", y is " + y); // "x is 1, y is 2"
在像C++这样的语言中,这是可能的,因为该语言(类似于)按参考传递的方式。 编辑 - 这最近(2015年3月)再次在Reddit上被热议,因为有一篇类似于我的博客文章,但是讨论的是Java。在阅读Reddit评论中来回争论时,我意识到混淆的一大部分源于不幸的“引用”一词的碰撞。 “按引用传递”和“按值传递”的术语早于在编程语言中使用“对象”的概念。它实际上与函数参数有关,特别是函数参数如何与调用环境“连接”(或不连接)。特别是要注意,在真正的按引用传递语言中-涉及对象的语言中-人们仍然可以修改对象内容,并且它看起来几乎与JavaScript完全相同。但是,人们还可以修改调用环境中的对象引用,这是你无法在JavaScript中执行的关键事情。 按引用传递的语言将不会传递参考本身,而是传递一个指向参考的指针编辑 - 这是有关此主题的博客文章。(请注意该帖子的评论,解释了C ++实际上没有按引用传递。那是真的。但是,C ++确实具有创建对普通变量的引用的能力,可以在函数调用点显式地创建指针,或者在调用需要进行该操作的参数类型签名的函数时隐式创建。这些是JavaScript不支持的关键点。)

12
你可以传递对象或数组的引用,这样就可以更改原始对象或数组,我相信这正是作者实际询问的内容。 - jfriend00
9
按值传递引用与按引用传递并不相同,尽管在某些情况下可能看起来是这样的。 - Travis Webb
6
您的博主关于C ++的错误,它确实是通过引用传递,并且他的帖子毫无意义。无法在初始化后更改引用的值与'按引用传递'无关。他的声明“'按引用传递'语义可以追溯到C#”应该引起警惕。 - EML
32
@Pointy 这个回答太糟糕了。如果我需要帮助,我最不想要的就是有人在语义上教训我。 “按引用传递”只是意味着,“函数在某种程度上可以改变作为参数传入的变量的值。”(在问题的背景下) 它并没有你描述的那么复杂。 - crownlessking
5
我是唯一一个认为“按引用传递”问题的最佳答案是由名叫Pointy的人回答,这太巧合了吗? :) - Artur Carvalho
显示剩余19条评论

179
  1. 像字符串和数字这样的原始类型变量总是按值传递。
  2. 数组和对象根据以下条件传递引用或值:
  • if you are setting the value of an object or array it is Pass by Value.

     object1 = { prop: "car" };
     array1 = [1,2,3];
    
  • if you are changing a property value of an object or array then it is Pass by Reference.

     object1.prop = "car";
     array1[0] = 9;
    

代码

function passVar(obj1, obj2, num) {
    obj1.prop = "laptop"; // will CHANGE original
    obj2 = { prop: "computer" }; //will NOT affect original
    num = num + 1; // will NOT affect original
}

var object1 = {
    prop: "car"
};
var object2 = {
    prop: "bike"
};
var number1 = 10;

passVar(object1, object2, number1);
console.log(object1); // output: Object { prop: "laptop" }
console.log(object2); // output: Object { prop: "bike" }
console.log(number1); // ouput: 10


37
“按引用传递”并不是指对象的含义,而是指调用函数中参数与调用环境中变量之间的关系。请注意,这两者是没有直接联系的。 - Pointy
19
一切都是按值传递的。对于非基本数据类型,传递的是引用值。将一个引用赋值给另一个引用会改变后者所指向的对象,这是很自然的,因为它是一个值,但是它不会改变最初所指向的其他对象。通过引用指向的对象进行修改会正常地改变指向的对象。这两种情况都是完全一致的,并且没有通过回溯时间并更改传递到函数中的参数来神奇地改变JavaScript的工作方式。 - Matthew Read
19
这个回答澄清了之前的回答,即使前一个回答得到更多的投票(目前为止有287票,并且也是被接受的),但它并没有说明如何在JS中实际通过引用传递参数。简而言之,这个回答中的代码可行,获得更多投票的那个回答不可行。 - Pap
2
那些回答真正针对可以做什么和不能做什么来回答OP的需求。与其争论“按引用传递”是什么意思,我认为我们都可以同意这个术语很糟糕,并希望在不久的将来有一个更好的术语被创造出来。虽然@Pointy的答案让我更了解概念,但那个答案帮助我完成了工作。 - Rafiki
3
很棒的答案。避免了在语义、术语和“OP的意思是什么”方面的纠缠不清(这些问题现在存在于一些SO答案中,为了使它们完整并帮助寻找upvotes等),并且给出了非常清晰的可行和不可行的示例。在这种情况下,“为什么”问题和一种方式或另一种方式之间的语义差异不那么重要,因为由于Javascript的编写方式,你首先无法以另一种方式来处理它。 - Yin Cognyto

30

传递变量的引用的解决方法:

var a = 1;
inc = function(variableName) {
  window[variableName] += 1;
};

inc('a');

alert(a); // 2

没错,其实你可以不访问全局变量就做到这一点:

inc = (function () {
    var variableName = 0;

    var init = function () {
        variableName += 1;
        alert(variableName);
    }

    return init;
})();

inc();

@Phil,小心全局/窗口值是好的,但在某些情况下,我们在浏览器中所做的一切都是窗口对象的子代或后代。在Node.js中,所有内容都是GLOBAL的后代。在编译的对象语言中,这是一个隐式的父对象,如果不是显式的话,那么堆管理会更加复杂(为什么要这样做呢?)。 - dkloke
1
@dkloke:是的,最终需要涉及到window对象- 就像jQuery使用window.$/window.jQuery以及其他方法都在其下面。我谈论的是全局命名空间的污染,你不应该将很多变量添加到其中,而是应该将它们放在一个统一的命名空间之下。 - Phil
9
我找不到合适的词来表达那种方法有多糟糕。 ;( - SOReader
我喜欢最后的解决方案(编辑版本),即使它不通过引用传递变量。这是一个优势,因为你可以直接访问变量。 - John Boe

19

简单对象

function foo(x) {
  // Function with other context
  // Modify `x` property, increasing the value
  x.value++;
}

// Initialize `ref` as object
var ref = {
  // The `value` is inside `ref` variable object
  // The initial value is `1`
  value: 1
};

// Call function with object value
foo(ref);
// Call function with object value again
foo(ref);

console.log(ref.value); // Prints "3"


自定义对象

对象rvar

/**
 * Aux function to create by-references variables
 */
function rvar(name, value, context) {
  // If `this` is a `rvar` instance
  if (this instanceof rvar) {
    // Inside `rvar` context...

    // Internal object value
    this.value = value;

    // Object `name` property
    Object.defineProperty(this, 'name', { value: name });

    // Object `hasValue` property
    Object.defineProperty(this, 'hasValue', {
      get: function () {
        // If the internal object value is not `undefined`
        return this.value !== undefined;
      }
    });

    // Copy value constructor for type-check
    if ((value !== undefined) && (value !== null)) {
      this.constructor = value.constructor;
    }

    // To String method
    this.toString = function () {
      // Convert the internal value to string
      return this.value + '';
    };
  } else {
    // Outside `rvar` context...

    // Initialice `rvar` object
    if (!rvar.refs) {
      rvar.refs = {};
    }

    // Initialize context if it is not defined
    if (!context) {
      context = this;
    }

    // Store variable
    rvar.refs[name] = new rvar(name, value, context);

    // Define variable at context
    Object.defineProperty(context, name, {
      // Getter
      get: function () { return rvar.refs[name]; },
      // Setter
      set: function (v) { rvar.refs[name].value = v; },
      // Can be overrided?
      configurable: true
    });

    // Return object reference
    return context[name];
  }
}

// Variable Declaration

// Declare `test_ref` variable
rvar('test_ref_1');

// Assign value `5`
test_ref_1 = 5;
// Or
test_ref_1.value = 5;

// Or declare and initialize with `5`:
rvar('test_ref_2', 5);

// ------------------------------
// Test Code

// Test Function
function Fn1(v) { v.value = 100; }

// Test
function test(fn) { console.log(fn.toString()); console.info(fn()); }

// Declare
rvar('test_ref_number');

// First assign
test_ref_number = 5;
test(() => test_ref_number.value === 5);

// Call function with reference
Fn1(test_ref_number);
test(() => test_ref_number.value === 100);

// Increase value
test_ref_number++;
test(() => test_ref_number.value === 101);

// Update value
test_ref_number = test_ref_number - 10;
test(() => test_ref_number.value === 91);


你的示例似乎做了一些你没有说明的假设。当我在一个在线的 JavaScript 解释器中运行它时,我得到一个错误:/workspace/Main.js:43 context = window; ReferenceError: window is not defined - Anton Shepelev
1
@Ant_222 代码已修复。 - Eduardo Cuomo
1
感谢您的修复。现在它可以工作了,但我对投票支持您的答案感到犹豫,因为我咨询过的专业JavaScript程序员认为您的解决方案是可憎的。我自己也意识到它相当危险,因为它使变量不像它们看起来那样。尽管如此,您的rvar解决方案无疑具有教育价值。 - Anton Shepelev

6

另一种传递任何(本地的、原始的)变量的引用的方法是通过使用 eval 在“飞行中”包装变量的闭包。这也适用于“use strict”。(注意:请注意,eval 对 JavaScript 优化器不友好,并且在变量名周围缺少引号可能会导致不可预测的结果)

"use strict"

// Return text that will reference variable by name (by capturing that variable to closure)
function byRef(varName){
    return "({get value(){return "+varName+";}, set value(v){"+varName+"=v;}})";
}

// Demo

// Assign argument by reference
function modifyArgument(argRef, multiplier){
    argRef.value = argRef.value * multiplier;
}

(function(){
    var x = 10;

    alert("x before: " + x);
    modifyArgument(eval(byRef("x")), 42);
    alert("x after: " + x);
})()

实时示例:https://jsfiddle.net/t3k4403w/


好主意,但我决定不这样做,因为对于仅输出的变量,传递箭头函数x=>value=x实际上更短,而双向输入输出参数并不经常需要。请参见我的答案中对重复问题的另一种基于eval的方法,其中您可以传递变量名称并评估整个函数的结果。 - Anton Shepelev

5
我个人不太喜欢各种编程语言中提供的“引用传递”功能。或许是因为我刚刚开始了解函数式编程的概念,但每当我看到会引起副作用(例如操作通过引用传递的参数)的函数时,就会浑身起鸡皮疙瘩。我个人强烈支持“单一职责”原则。
在我看来,一个函数应该只使用返回关键字返回一个结果/值。与其修改参数/变量,我宁愿返回修改后的参数/变量值,并让调用代码自行处理任何需要重新赋值的情况。
但有时候(但愿很少),需要从同一个函数中返回两个或更多的结果值。这种情况下,我会选择将所有的结果值放在一个单一的结构体或对象中。同样,任何需要重新赋值的处理也应该由调用代码完成。
例如:
假设使用特殊关键字,如参数列表中的“ref”,来支持传递参数。我的代码可能如下所示:
//The Function
function doSomething(ref value) {
    value = "Bar";
}

//The Calling Code
var value = "Foo";
doSomething(value);
console.log(value); //Bar

相反,我更愿意像这样做:

//The Function
function doSomething(value) {
    value = "Bar";
    return value;
}

//The Calling Code:
var value = "Foo";
value = doSomething(value); //Reassignment
console.log(value); //Bar

如果我需要编写一个返回多个值的函数,我也不会使用参数传递引用。因此,我会避免这样的代码:

//The Function
function doSomething(ref value) {
    value = "Bar";

    //Do other work
    var otherValue = "Something else";

    return otherValue;
}

//The Calling Code
var value = "Foo";
var otherValue = doSomething(value);
console.log(value); //Bar
console.log(otherValue); //Something else

相反,我更希望将新的值作为一个对象返回,像这样:
//The Function
function doSomething(value) {
    value = "Bar";

    //Do more work
    var otherValue = "Something else";

    return {
        value: value,
        otherValue: otherValue
    };
}

//The Calling Code:
var value = "Foo";
var result = doSomething(value);
value = result.value; //Reassignment
console.log(value); //Bar
console.log(result.otherValue);

这些代码示例相当简化,但大致演示了我个人如何处理此类内容。它帮助我将各种责任放在正确的位置。
编码愉快。 :)

分配和重新分配东西(没有任何类型检查)让我毛骨悚然。至于责任,我希望doSomething()去做那件事情,而不是做那件事情再加上创建一个对象并将我的变量分配给属性。比如我有一个需要搜索的数组,我想把匹配的项放在第二个数组中,并且我想知道找到了多少个。一个标准的解决方案是调用这样一个函数:'var foundArray; if ((findStuff(inArray, &foundArray)) > 1) { // process foundArray}'。在这种情况下,没有新的、未知的对象是必要或者可取的。 - Elise van Looij
在你的示例案例中,我更喜欢让findStuff方法简单地返回结果数组。你也不需要再声明一个foundArray变量,而是直接将结果数组赋值给它:var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* 处理foundArray */ }。这样做可以 1)使调用代码更易读/易懂,2)大大简化findStuff方法的内部功能(因此也可以更轻松地测试),从而使它在不同的(重新)使用情况/场景中更加灵活。 - Bart Hofland
@ElisevanLooij,然而,我同意重新分配(如我的答案中所示)确实是一种“代码异味”,我也尽可能想要避免这种情况。我会考虑编辑(甚至重新表述)我的答案,以更好地反映我对该主题的实际看法。感谢您的回应。 :) - Bart Hofland
很好的答案。帮助了我所需要的,谢谢。我想避免通过字符串传递变量并传递其上下文(用于自动重新分配),但明显错过了“只需在那里直接赋值”(傻瓜)。 - Andrew

4

实际上有一个很好的解决方案:

function updateArray(context, targetName, callback) {
    context[targetName] = context[targetName].map(callback);
}

var myArray = ['a', 'b', 'c'];
updateArray(this, 'myArray', item => {return '_' + item});

console.log(myArray); //(3) ["_a", "_b", "_c"]

3
它是以什么方式漂亮的? - Peter Mortensen

4
我一直在尝试使用语法来完成这种操作,但需要一些不太常见的辅助工具。首先,完全不使用'var',而是使用一个简单的'DECLARE'辅助程序,通过匿名回调创建本地变量并定义其作用域。通过控制变量的声明方式,我们可以选择将它们包装成对象,以便始终可以通过引用传递。这与Eduardo Cuomo上面的一个答案类似,但以下解决方案不需要使用字符串作为变量标识符。这里是一些最小代码来展示这个概念。
function Wrapper(val){
    this.VAL = val;
}
Wrapper.prototype.toString = function(){
    return this.VAL.toString();
}

function DECLARE(val, callback){
    var valWrapped = new Wrapper(val);    
    callback(valWrapped);
}

function INC(ref){
    if(ref && ref.hasOwnProperty('VAL')){
        ref.VAL++; 
    }
    else{
        ref++;//or maybe throw here instead?
    }

    return ref;
}

DECLARE(5, function(five){ //consider this line the same as 'let five = 5'
console.log("five is now " + five);
INC(five); // increment
console.log("five is incremented to " + five);
});

这种方法的问题在于它并不等同于 let five = 5;,因为它没有在外部上下文中设置变量,只能在函数内部使用。这就是为什么你不需要使用字符串的原因。尽管如此,我还是点赞这个新颖的想法,即利用闭包来唯一地引用、存储和本地化该引用中的值。不过,这也可以通过一个 class 或对象来实现。然后,您可以存储引用对象,并将其用于比 INC(ref) 更通用的目的。我正在为我的一个项目做这件事。 - Andrew

2

实际上这很容易。问题是要理解一旦传递了经典参数,你就被限制在另一个只读区域中。

解决方案是使用JavaScript的面向对象设计来传递参数。这与将参数放入全局/作用域变量相同,但更好...

function action(){
  /* Process this.arg, modification allowed */
}

action.arg = [["empty-array"], "some string", 0x100, "last argument"];
action();

你可以使用承诺式结构来承诺一些东西,以享受著名的链式效应: 这是整个事情,带有承诺式结构。
function action(){
  /* Process this.arg, modification allowed */
  this.arg = ["a", "b"];
}

action.setArg = function(){this.arg = arguments; return this;}

action.setArg(["empty-array"], "some string", 0x100, "last argument")()

或者更好的方法是...
action.setArg(["empty-array"],"some string",0x100,"last argument").call()

1
this.arg 只能与 action 实例一起使用。这不起作用。 - Eduardo Cuomo
@EduardoCuomo,您可以简单地使用class来解决这个问题。 我发现在JS中,this关键字是class的主要功能提供目的。 - Andrew

2

JavaScript可以在函数内修改数组项(它作为对象/数组的引用传递)。

function makeAllPretty(items) {
   for (var x = 0; x < myArray.length; x++){
      // Do stuff to the array
      items[x] = makePretty(items[x]);
   }
}

myArray = new Array(var1, var2, var3);
makeAllPretty(myArray);

这里有另一个例子:
function inc(items) {
  for (let i=0; i < items.length; i++) {
    items[i]++;
  }
}

let values = [1,2,3];
inc(values);
console.log(values);
// Prints [2,3,4]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接