在JavaScript中通过引用传递变量

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

最好的方法是什么?

543065 次浏览

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++这样的语言中,这样做是可能的,因为该语言 (sort-of)具有引用传递。

编辑本;最近(2015年3月),Reddit上又出现了一篇与我下面提到的类似的博客文章,不过这次是关于Java的。在阅读Reddit上的评论时,我突然意识到,很大一部分混淆源于“reference”一词的不幸冲突。术语“按引用传递”和“按值传递”早于在编程语言中使用“对象”的概念。这与对象无关;它是关于函数参数的,特别是函数参数如何“连接”(或不连接)到调用环境。特别要注意的是,在真正的引用传递语言—一个涉及对象—仍然可以修改对象内容,并且它看起来和JavaScript中非常相似。然而,能够在调用环境中修改对象引用,这是你在JavaScript中不能所做的关键事情。引用传递语言传递的不是引用本身,而是引用引用

编辑本;这里有一篇关于这个话题的博客文章。(注意那篇文章的注释,解释了c++并没有真正的引用传递。这是真的。然而,c++所拥有的是创建普通变量引用的能力,可以显式地在函数调用时创建指针,也可以在调用其参数类型签名要求这样做的函数时调用隐式地。这些都是JavaScript不支持的关键。)

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

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

简单的对象

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

我一直在用语法来做这类事情,但它需要一些不寻常的帮助程序。它一开始根本不使用“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);
});
  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

其实很简单。问题在于,一旦传递了经典参数,你就被定义为另一个只读区。

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

另一种通过引用传递任何(局部的,基本的)变量的方法是用“on the fly”闭包包装变量。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/

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

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

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

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

这些代码示例非常简化,但它大致演示了我个人如何处理这些东西。它帮助我把各种责任放在正确的位置。

快乐的编码。:)

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

原语是按值传递的,但在迭代时var i的值没有复制到匿名函数中,这一事实至少令人惊讶。

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

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]

撇开“参考资料传递”的讨论不谈,那些仍然在寻找上述问题的解决方案的人可以使用:

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

我喜欢解决JavaScript中缺少通过引用的问题,就像这个例子所示。

这样做的本质是你不会试着创建通过引用。相反,您可以使用返回功能,并使其能够返回多个值。因此,不需要在数组或对象中插入值。

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>'
}
<div id='msg'></div>

如果你想通过引用传递变量,更好的方法是在对象中传递参数,然后使用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

这里使用解构是一个例子,其中我有3个变量,并且对每个变量执行多个操作:

  • 如果value小于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

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

所以

"makePretty(myArray[x]);"

应该是

"myArray[x] = makePretty(myArray[x]);"

这是在函数内部需要赋值的情况下,如果只需要突变,那么传递对象并突变它就足够了