通过Javascript中的引用传递变量


285

如何在JavaScript中通过引用传递变量?我想对3个变量执行几个操作,因此我想将它们放在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 vars

做这个的最好方式是什么?


31
您正在谈论“通过引用传递”,但是您的示例中没有函数调用,因此您的示例中根本没有传递。请说明您要做什么。
jfriend00 2011年

1
对困惑感到抱歉。我不是特别需要编写函数,因此“按引用传递”是一个不好的选择。我只想能够对变量执行某些操作而无需编写 makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick 2011年

根据您的评论:arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }-您只需要将返回的值存储makePretty回数组中的插槽中即可。
dylnmc '17

Answers:


415

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中完成的关键操作。引用传递语言不会传递引用本身,而是传递对reference 的引用

编辑 - 这是有关该主题的博客文章。(请注意该帖子的注释,该注释解释了C ++确实没有传递引用。的确如此。但是,C ++确实具有创建对普通变量的引用的能力,无论是在函数点处显式创建调用以创建指针,或在调用参数类型签名要求完成的函数时隐式调用。这是JavaScript不支持的关键。)


8
您可以传递对对象或数组的引用,该引用使您可以更改原始对象或数组,我相信这是OP真正要求的。
jfriend00 2011年

2
好吧,OP使用了术语,但是实际代码似乎根本不涉及任何“传递” :-)我不确定他到底想做什么。
尖尖的

5
按值传递引用与按引用传递不同,尽管在诸如此类的某些情况下可能如此。
Travis Webb

5
您的博客作者对C ++错误,它确实通过引用传递,因此他的帖子毫无意义。初始化后不能更改引用的值是无关紧要的。这与“通过引用传递”无关。他关于“可以通过C#追溯到”引用传递”语义的声明应该敲响警钟。
EML,2014年

7
@Pointy这是一个可怕的答案。如果我需要帮助,我想要的最后一件事就是有人要教我语义。“按引用传递”仅表示“该函数在某种程度上可以更改作为参数传递的变量的值”。(在问题的上下文中)实际上并没有您想像的那么复杂。
Crownlessking

108
  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 {item:"laptop"}
console.log(object2); //output: Object {item:"bike"}
console.log(number1); //ouput: 10


21
那不是“通过引用传递”的含义。该术语实际上与对象无关,而是与被调用函数中的参数与调用环境中的变量之间的关系。
尖尖的2015年

12
一切总是通过价值传递。对于非基元,传递的值是引用。分配给引用会更改该引用-自然,因为它是一个值-因此无法更改最初引用的另一个对象。通过更改指向的对象,使引用指向的对象发生更改的行为与您期望的完全相同。两种情况都是完全一致的,并且都无法通过回溯时间和更改将它们传递到函数的方式来神奇地改变JavaScript的工作方式。

9
这个答案澄清了前一个答案,尽管它有更多的选票(在撰写本文时为287个,并且它也是被接受的)仍不清楚如何在JS中通过引用实际通过它。简而言之,此答案中的代码有效,但获得更多选票的答案却没有。
子宫颈抹片

25

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

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小心全局/窗口值是一件好事,但是在某些时候,我们在浏览器中所做的一切都是window对象的子代或后代。在nodejs中,一切都是GLOBAL的后代。在编译的对象语言中,这是一个隐式(如果不是显式的)父对象,因为否则这样做会使堆管理更加复杂(原因何在)。
dkloke

1
@dkloke:是的,最终需要触摸窗口对象-就像jQuery使用window。$ / window.jQuery和其他方法一样。我说的是全局命名空间的污染,在其中您向其中添加了很多变量,而不是将它们放在统一的命名空间下。
Phil

2
我找不到合适的词来表达这种方法有多糟糕;(
SOReader

我喜欢最后一个解决方案(编辑版本),即使它没有通过引用传递变量。这是一个优势,因为您可以直接访问变量。
约翰·波

11

简单对象

var ref = { value: 1 };

function Foo(x) {
    x.value++;
}

Foo(ref);
Foo(ref);

alert(ref.value); // Alert: 3

自订物件

宾语 rvar

function rvar (name, value, context) {
    if (this instanceof rvar) {
        this.value = value;
        Object.defineProperty(this, 'name', { value: name });
        Object.defineProperty(this, 'hasValue', { get: function () { return this.value !== undefined; } });
        if ((value !== undefined) && (value !== null))
            this.constructor = value.constructor;
        this.toString = function () { return this.value + ''; };
    } else {
        if (!rvar.refs)
            rvar.refs = {};
        if (!context)
            context = window;
        // Private
        rvar.refs[name] = new rvar(name, value);
        // Public
        Object.defineProperty(context, name, {
            get: function () { return rvar.refs[name]; },
            set: function (v) { rvar.refs[name].value = v; },
            configurable: true
        });

        return context[name];
    }
}

变量声明

rvar('test_ref');
test_ref = 5; // test_ref.value = 5

要么:

rvar('test_ref', 5); // test_ref.value = 5

测试代码

rvar('test_ref_number');
test_ref_number = 5;
function Fn1 (v) { v.value = 100; }
console.log("rvar('test_ref_number');");
console.log("test_ref_number = 5;");
console.log("function Fn1 (v) { v.value = 100; }");
console.log('test_ref_number.value === 5', test_ref_number.value === 5);
console.log(" ");

Fn1(test_ref_number);
console.log("Fn1(test_ref_number);");
console.log('test_ref_number.value === 100', test_ref_number.value === 100);
console.log(" ");

test_ref_number++;
console.log("test_ref_number++;");
console.log('test_ref_number.value === 101', test_ref_number.value === 101);
console.log(" ");

test_ref_number = test_ref_number - 10;
console.log("test_ref_number = test_ref_number - 10;");
console.log('test_ref_number.value === 91', test_ref_number.value === 91);

console.log(" ");
console.log("---------");
console.log(" ");

rvar('test_ref_str', 'a');
console.log("rvar('test_ref_str', 'a');");
console.log('test_ref_str.value === "a"', test_ref_str.value === 'a');
console.log(" ");

test_ref_str += 'bc';
console.log("test_ref_str += 'bc';");
console.log('test_ref_str.value === "abc"', test_ref_str.value === 'abc');

测试控制台结果

rvar('test_ref_number');
test_ref_number = 5;
function Fn1 (v) { v.value = 100; }
test_ref_number.value === 5 true

Fn1(test_ref_number);
test_ref_number.value === 100 true

test_ref_number++;
test_ref_number.value === 101 true

test_ref_number = test_ref_number - 10;
test_ref_number.value === 91 true

---------

rvar('test_ref_str', 'a');
test_ref_str.value === "a" true

test_ref_str += 'bc';
test_ref_str.value === "abc" true 

5

通过引用传递任何(局部,原始)变量的另一种方法是通过将变量用闭包“ by the fly”包装eval。这也适用于“严格使用”。(注意:请注意,这eval对JS优化器并不友好,变量名周围的引号也可能导致不可预测的结果)

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


这太棒了
hard_working_ant

3

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

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

2

我一直在尝试使用语法来完成这种事情,但是它需要一些帮手,这有点不寻常。它从根本不使用'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);
});

2

其实真的很容易

问题在于要理解,一旦传递经典参数,您就会陷入另一个只读区域。

解决方案是使用JavaScript的面向对象设计传递参数,

这与将args放入全局/作用域变量相同,但是更好...

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实例。这是行不通的。
爱德华多·库莫

2

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

恕我直言,一个函数应该使用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);

这些代码示例已经相当简化,但是粗略地展示了我个人如何处理这些东西。这有助于我将各种职责保持在正确的位置。

快乐的编码。:)


分配和重新分配内容(也无需进行任何类型检查)就是我的鸡皮s。至于责任,我希望doSomething()能做到这一点,而不是做到那件事,再加上创建一个对象,然后将我的变量分配给属性。假设我有一个需要搜索的数组,我想将匹配项放在第二个数组中,我想知道找到了多少个。一个标准的解决方案是调用这样的函数:'var foundArray; if((findStuff(inArray,&foundArray))> 1){//处理foundArray}'。在这种情况下,没有一个新的未知对象是理想的或不需要的。
Elise van Looij,

@ElisevanLooij在您的示例情况下,我希望findStuff仅返回结果数组。您也必须声明一个foundArray变量,所以我将直接将结果数组分配给它:var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }。这将1)使调用代码更具可读性/可理解性,并且2)大大简化了该方法的内部功能(从而也简化了可测试性)findStuff,从而使该方法实际上在不同的(重新)用例/场景中更加灵活。
巴特霍夫兰

@ElisevanLooij但是,我确实同意重新分配(确实像我的回答一样)确实是一种“代码味道”,我实际上也想尽可能避免这种情况。我将考虑以某种方式编辑(甚至重新制定)我的答案,以使其更好地反映出我对该主题的实际看法。感谢您的反应。:)
巴特霍夫兰

1

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]

1

JS不是强类型,它可以让您以许多不同的方式解决问题,如本教程所述。

但是,从可维护性的角度来看,我必须同意Bart Hofland的观点。函数应该让args做一些事情并返回结果。使它们易于重用。

如果您认为需要通过引用传递变量,则最好将其构建为对象恕我直言。


1

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

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

0

我确切地知道你的意思。Swift中的同一件事将没有问题。底线是let不使用var

var i至少可以说,原语是通过值传递的,但是迭代点的值没有复制到匿名函数中的事实。

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.