【问题标题】:Pass variables by reference in JavaScript在 JavaScript 中通过引用传递变量
【发布时间】:2011-12-06 08:58:44
【问题描述】:

如何在 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

最好的方法是什么?

【问题讨论】:

  • 您说的是“通过引用传递”,但您的示例中没有函数调用,因此您的示例中根本没有传递。请说明您要做什么。
  • 很抱歉给您带来了困惑。我没有特别需要编写一个函数,所以“通过引用传递”是一个糟糕的词选择。我只是希望能够在不写makePretty(var1); makePretty(var2); makePretty(var3); ... 的情况下对变量执行一些操作
  • 根据您的评论:arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i &lt; len; i++) { arr[i] = makePretty(arr[i]); } -- 您只需将makePretty 返回的值存储回数组中的槽中。
  • 对于那些在 Javascript 中搜索“pass-by-reference”或“pass-by-value”时来自 Google 的人,一篇来自 Medium 的好文章:medium.com/@TK_CodeBear/…

标签: javascript variables pass-by-reference


【解决方案1】:

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++ 这样的语言中,这样做是可能的,因为该语言确实(某种意义上)具有传递引用。

edit——最近(2015 年 3 月)在 Reddit 上再次引爆了一篇与我下面提到的类似的博客文章,尽管在本例中是关于 Java。在阅读 Reddit cmets 的来回阅读时,我突然想到,很大一部分混乱源于涉及“参考”一词的不幸碰撞。术语“按引用传递”和“按值传递”早于在编程语言中使用“对象”的概念。这根本不是关于对象的。它是关于函数参数的,特别是函数参数如何“连接”(或不连接)到调用环境。特别要注意的是,在真正的按引用传递语言中——确实涉及对象的语言——仍然可以修改对象内容,并且看起来几乎和 JavaScript 中的一样。但是, 可以在调用环境中修改对象引用,这是您在 JavaScript 中不能做的关键事情。传递引用的语言不会传递引用本身,而是对引用的引用

edithere is a blog post on the topic.(请注意该帖子的评论解释说 C++ 并没有真正的传递引用。这是真的。但是,C++ 确实有能够创建对普通变量的引用,或者在函数调用点显式创建指针,或者在调用参数类型签名要求完成的函数时隐式。这些是 JavaScript 的关键不支持。)

【讨论】:

  • 您可以传递对对象或数组的引用,它允许您更改原始对象或数组,我认为这是 OP 实际询问的内容。
  • 好吧,OP 使用了术语,但实际代码似乎根本不涉及任何“传递”:-) 我不太确定他想做什么。
  • 传递引用按值与传递按引用不同,但在某些情况下可能会出现这种情况。跨度>
  • 您的博主对 C++ 的看法是错误的,C++确实通过引用传递,他的帖子毫无意义。初始化后不能更改引用的值是无关紧要的;这与“通过引用”无关。他所说的“可以通过 C# 追溯“通过引用传递”语义”应该敲响了警钟。
  • @Pointy 这是一个可怕的答案。如果我需要帮助,我最不想要的就是有人教我语义学。 “按引用传递”仅表示“函数在某种程度上可以更改作为参数传入的变量的值。” (在问题的背景下)它真的没有你想象的那么复杂。
【解决方案2】:

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

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 最好注意全局/窗口值,但有时我们在浏览器中所做的一切都是窗口对象的子对象或后代。在 nodejs 中,一切都是 GLOBAL 的后代。在编译对象语言中,如果不是显式的父对象,那也是隐式的父对象,因为这样做会使堆管理更加复杂(为了什么?)。
  • @dkloke:是的,最终窗口对象需要被触摸——就像 jQuery 使用 window.$/window.jQuery 和其他方法一样。我说的是全局命名空间的污染,你在其中添加了很多变量,而不是将它们放在统一的命名空间下。
  • 我找不到好的词来表达这种方法有多糟糕;(
  • 我喜欢最后一个解决方案(编辑版),即使它没有通过引用传递变量。这是一个优势,因为您可以直接访问变量。
【解决方案3】:

简单对象

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
  • @Ant_222 代码已修复。
  • 感谢您的修复。现在它起作用了,但我对你的答案投票感到谨慎,因为我咨询过的专业 JavaScript 程序员认为你的解决方案令人憎恶。我自己意识到这是相当危险的,因为它使变量不是它们看起来的样子。尽管如此,您的rvar 解决方案无疑具有教育价值。
【解决方案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);
});

【讨论】:

    【解决方案5】:
    1. 字符串和数字等原始类型变量始终按值传递。
    2. 数组和对象根据以下条件通过引用或值传递:
    • 如果您正在设置对象或数组的值,则它是按值传递。

       object1 = { prop: "car" };
       array1 = [1,2,3];
      
    • 如果您要更改对象或数组的属性值,则它是通过引用传递。

       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

    【讨论】:

    • 这不是“通过引用传递”的意思。该术语实际上与对象无关,而是与被调用函数中的参数和调用环境中的变量之间的关系有关。
    • Everything 总是按值传递。对于非基元,传递的值是一个引用。分配给一个引用会更改该引用——自然,因为它是一个值——因此不能更改最初引用的另一个对象。通过改变指向的对象,改变引用指向的对象的行为完全符合您的预期。这两种情况是完全一致的,都没有神奇地改变 JavaScript 的工作方式,因为它们会通过时光倒流和改变它们被传递到函数的方式。
    • 这个答案澄清了前一个,尽管它有更多的选票(在撰写本文时有 287 票,它也是被接受的),但对于如何实际通过它还不够清楚JS中的参考。简而言之,此答案中的代码有效,而获得更多选票的答案无效。
    【解决方案6】:

    其实很简单。问题在于,一旦传递了经典参数,您就会被限制在另一个只读区域。

    解决方案是使用 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()
    

    【讨论】:

    • this.arg 仅适用于 action 实例。这不起作用。
    【解决方案7】:

    另一种通过引用传递任何(本地、原始)变量的方法是通过eval“即时”使用闭包包装变量。这也适用于“使用严格”。 (注意:请注意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=&gt;value=x 实际上更短,而双向输入输出参数并不经常需要。请参阅我的 answer 到另一个基于 eval 的方法的重复问题,您可以在其中传递变量名称并评估整个函数的结果。
    【解决方案8】:

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

    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"]
    

    【讨论】:

    • 美在什么方面?
    【解决方案9】:

    我个人不喜欢各种编程语言提供的“通过引用”功能。也许那是因为我只是在发现函数式编程的概念,但是当我看到会导致副作用的函数时(比如操作通过引用传递的参数),我总是会起鸡皮疙瘩。我个人强烈拥护“单一责任”原则。

    恕我直言,一个函数应该使用 return 关键字只返回一个结果/值。我不会修改参数/参数,而是返回修改后的参数/参数值,并将任何所需的重新分配留给调用代码。

    但有时(希望很少),有必要从同一个函数返回两个或多个结果值。在这种情况下,我会选择将所有这些结果值包含在单个结构或对象中。同样,处理任何重新分配都应取决于调用代码。

    例子:

    假设通过在参数列表中使用像“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) { // 处理 foundArray}'.在这种情况下,任何地方都不需要或需要一个新的、未知的对象。
    • @ElisevanLooij 在您的示例中,我希望 findStuff 简单地返回结果数组。你也必须声明一个foundArray 变量,所以我会直接将结果数组分配给它:var foundArray = findStuff(inArray); if (foundArray.length &gt; 0) { /* process foundArray */ }。这将 1) 使调用代码更具可读性/可理解性,以及 2) 大大简化 findStuff 方法的内部功能(以及可测试性),使其在不同(重新)用例中实际上更加灵活/ 场景。
    • @ElisevanLooij 但是,我同意重新分配(就像我的回答一样)确实是一种“代码气味”,我实际上也希望尽可能避免这些。我会考虑编辑(甚至重新制定)我的答案,以便更好地反映我对该主题的实际看法。谢谢你的反应。 :)
    【解决方案10】:

    我完全明白你的意思。在 Swift 中同样的事情不会有问题。底线是使用let,而不是var

    原语是按值传递的,但 var i 在迭代时的值没有被复制到匿名函数中,这至少可以说是相当令人惊讶的。

    for (let i = 0; i < boxArray.length; i++) {
      boxArray[i].onclick = function() { console.log(i) }; // Correctly prints the index
    }
    

    【讨论】:

      【解决方案11】:

      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]
      

      【讨论】:

        【解决方案12】:

        JavaScript 不是强类型。它允许您以许多不同的方式解决问题,就像在这个问题中显示的那样。

        但是,从可维护性的角度来看,我不得不同意 Bart Hofland 的观点。一个函数应该获取参数来做某事并返回结果。使它们易于重复使用。

        如果您觉得需要通过引用传递变量,您最好将它们构建到对象中,恕我直言。

        【讨论】:

          【解决方案13】:

          抛开通过引用的讨论,那些仍在寻找所述问题的解决方案的人可以使用:

          const myArray = new Array(var1, var2, var3);
          myArray.forEach(var => var = makePretty(var));
          

          【讨论】:

          • 解释是什么?它与其他答案有何不同/更好?
          【解决方案14】:

          我喜欢解决 JavaScript 中缺少 by reference 的问题,就像这个例子所示。

          这样做的本质是您不要尝试通过引用来创建。您改为使用返回功能并使其能够返回多个值。因此,无需将值插入数组或对象中。

          var x = "First";
          var y = "Second";
          var z = "Third";
          
          log('Before call:',x,y,z);
          with (myFunc(x, y, z)) {x = a; y = b; z = c;} // <-- Way to call it
          log('After call :',x,y,z);
          
          
          function myFunc(a, b, c) {
            a = "Changed first parameter";
            b = "Changed second parameter";
            c = "Changed third parameter";
            return {a:a, b:b, c:c}; // <-- Return multiple values
          }
          
          function log(txt,p1,p2,p3) {
            document.getElementById('msg').innerHTML += txt + '<br>' + p1 + '<br>' + p2 + '<br>' + p3 + '<br><br>'
          }
          &lt;div id='msg'&gt;&lt;/div&gt;

          【讨论】:

            【解决方案15】:

            如果你想通过引用传递变量,一个更好的方法是在一个对象中传递你的参数,然后使用window开始改变值:

            window["varName"] = value;
            

            例子:

            // Variables with first values
            var x = 1, b = 0, f = 15;
            
            
            function asByReference (
                argumentHasVars = {},   // Passing variables in object
                newValues = [])         // Pass new values in array
            {
                let VarsNames = [];
            
                // Getting variables names one by one
                for(let name in argumentHasVars)
                    VarsNames.push(name);
            
                // Accessing variables by using window one by one
                for(let i = 0; i < VarsNames.length; i += 1)
                    window[VarsNames[i]] = newValues[i]; // Set new value
            }
            
            console.log(x, b, f); // Output with first values
            
            asByReference({x, b, f}, [5, 5, 5]); // Passing as by reference
            
            console.log(x, b, f); // Output after changing values
            

            【讨论】:

            • window 是全局(别名?)对象(?)(在网络浏览器上下文中)。如此有效地在全局变量中传回信息(?)。
            【解决方案16】:

            在这里使用Destructuring 是一个示例,其中我有 3 个变量,并且在每个变量上我执行多个操作:

            • 如果值小于0则改为0,
            • 如果大于 255 则改为 1,
            • 否则将数字减少 255 以从 0-255 范围转换为 0-1 范围。
            let a = 52.4, b = -25.1, c = 534.5;
            [a, b, c] = [a, b, c].map(n => n < 0 ? 0 : n > 255 ? 1 : n / 255);
            console.log(a, b, c); // 0.20549019607843136 0 1
            

            【讨论】:

              【解决方案17】:

              由于我们没有 javascript 通过引用传递功能,唯一的方法是让函数返回值并让调用者分配它:

              所以“makePretty(myArray[x]);”应该是“myArray[x] = makePretty(myArray[x]);”

              (如果您需要在函数内部进行赋值,如果只需要突变,那么传递对象并对其进行突变就足够了)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-08-26
                • 2011-10-19
                • 2018-01-18
                相关资源
                最近更新 更多