Передать переменные по ссылке в 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

1
Извините за путаницу. Мне не нужно было специально писать функцию, поэтому «передача по ссылке» - плохой выбор слов. Я просто хочу иметь возможность выполнять некоторые операции с переменными без записи makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick

на основе вашего комментария: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- вам просто нужно сохранить значение, возвращаемое makePrettyобратно в слот в массиве.
dylnmc

Ответы:


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) снова взорвалось на Reddit из-за сообщения в блоге, похожего на мое, упомянутое ниже, хотя в данном случае о Java. При чтении взад-вперед в комментариях Reddit мне пришло в голову, что большая часть путаницы проистекает из неудачного столкновения, связанного со словом «ссылка». Термины «передача по ссылке» и «передача по значению» предшествуют концепции наличия «объектов» для работы в языках программирования. Это действительно не об объектах вообще; речь идет о параметрах функции, и, в частности, о том, как параметры функции «связаны» (или нет) с вызывающей средой. В частности,и это будет выглядеть почти так же, как в JavaScript. Однако можно было бы также изменить ссылку на объект в вызывающей среде, и это ключевая вещь, которую вы не можете сделать в JavaScript. Язык передачи по ссылке будет передавать не саму ссылку, а ссылку на ссылку .

редактировать - вот блог в теме. (Обратите внимание на комментарий к этому посту, который объясняет, что C ++ на самом деле не имеет передачи по ссылке. Это правда. Однако C ++ имеет возможность создавать ссылки на простые переменные, либо явно в точке функции вызов для создания указателя или неявно при вызове функций, чья сигнатура типа аргумента требует этого. Это ключевые вещи, которые JavaScript не поддерживает.)


8
Вы можете передать ссылку на объект или массив, который позволяет вам изменить исходный объект или массив, который, как я полагаю, является тем, о чем фактически спрашивает OP.
jfriend00

2
Что ж, ОП использовал терминологию, но реальный код, по-видимому, вообще не включает в себя «передачу» :-) Я не совсем уверен, что он пытается сделать.
Заостренный

5
Передача ссылки по значению - это не то же самое, что передача по ссылке , хотя в некоторых сценариях, таких как этот, это может выглядеть так.
Трэвис Уэбб

5
Ваш блогер ошибается насчет C ++, который делает переход по ссылке, и его пост не имеет смысла. Неважно, что вы не можете изменить значение ссылки после инициализации; это не имеет ничего общего с «передать по ссылке». Его утверждение о том, что «семантика« передача по ссылке »может быть прослежена до C #» », должно было прозвенеть.
EML

7
@Pointy Это ужасный ответ. Если бы мне понадобилась помощь, последнее, что я бы хотел, это чтобы кто-то научил меня семантике «Передача по ссылке» просто означает, что «функция в некоторой степени может изменить значение переменной, переданной в качестве аргумента». (в контексте вопроса) Это на самом деле не так сложно, как кажется.
короны

110
  1. переменные примитивного типа, такие как строки и числа, всегда передаются по значению.
  2. Массивы и объекты передаются по ссылке или по значению на основе следующих условий:

    • если вы устанавливаете значение объекта или массива, это Pass by Value.

      object1 = {prop: "car"}; array1 = [1,2,3];

    • если вы изменяете значение свойства объекта или массива, то это 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 {item:"laptop"}
console.log(object2); //output: Object {item:"bike"}
console.log(number1); //ouput: 10


21
Это не означает «передать по ссылке». Термин на самом деле не имеет ничего общего с объектами, но имеет отношение между параметрами в вызываемой функции и переменными в вызывающей среде.
Заостренный

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 и другие методы находятся под этим. Я говорил о загрязнении глобального пространства имен, когда вы добавляете в него множество переменных, а не объединяете их.
Фил

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

Еще один подход к передаче любых (локальных, примитивных) переменных по ссылке заключается в переносе переменной с замыканием «на лету» 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"]

3

Мне лично не нравится функциональность «передача по ссылке», предлагаемая различными языками программирования. Возможно, это потому, что я просто открываю концепции функционального программирования, но я всегда получаю мурашки по коже, когда вижу функции, вызывающие побочные эффекты (например, манипулирование параметрами, передаваемыми по ссылке). Я лично твердо придерживаюсь принципа «единой ответственности».

ИМХО, функция должна возвращать только один результат / значение, используя ключевое слово 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 > 0) { /* process foundArray */ }. Это 1) сделает вызывающий код более читабельным / понятным, и 2) значительно упростит внутреннюю функциональность (и, следовательно, также тестируемость) findStuffметода, сделав его на самом деле намного более гибким в различных (пере) вариантах использования / сценариях.
Барт Хофланд

@ElisevanLooij Тем не менее, я согласен с тем, что переназначения (как в моем ответе) действительно являются «запахом кода», и я на самом деле хотел бы избежать их как можно больше. Я подумаю о том, чтобы отредактировать (или даже переформулировать) мой ответ таким образом, чтобы он лучше отражал мое реальное мнение по этому вопросу. Спасибо за вашу реакцию. :)
Барт Хофланд

2

Я играл с синтаксисом, чтобы делать подобные вещи, но для этого нужны некоторые немного необычные помощники. Он начинается с того, что вообще не использует 'var', а представляет собой простой помощник 'DECLARE', который создает локальную переменную и определяет область ее действия посредством анонимного обратного вызова. Управляя тем, как переменные объявляются, мы можем выбрать их оборачивание в объекты, чтобы они всегда могли быть переданы по ссылке. Это похоже на один из ответов Эдуардо Куомо выше, но решение ниже не требует использования строк в качестве идентификаторов переменных. Вот некоторый минимальный код, чтобы показать концепцию.

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,

это то же самое, что поместить аргументы в переменную global / scoped, но лучше ...

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экземпляром. Это не работает.
Эдуардо Куомо

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 не является сильным типом, он позволяет решать проблемы различными способами, как показано в этом шаге.

Однако, с точки зрения ремонтопригодности, я бы согласился с Бартом Хофландом. Функция должна получить аргументы, чтобы что-то сделать, и вернуть результат. Делая их легко многоразовыми.

Если вы чувствуете, что переменные должны передаваться по ссылке, вам, возможно, лучше обслужить их встраивание в объекты IMHO.


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
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.