Передайте змінні за посиланням у 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
Ви можете передати посилання на об'єкт або масив, який дозволяє вам змінити оригінальний об'єкт або масив, про який я вважаю, про що насправді запитує ОП.
jfriend00

2
Що ж, ОП використовував термінологію, але фактичний код взагалі не передбачає "проходження" :-) Я не дуже впевнений, що він намагається зробити.
Pointy

5
Передача посилання за значенням не є такою ж, як передача посилання , хоча це може відображатися в деяких сценаріях, таких як цей.
Тревіс Вебб

5
Ваш блогер помиляється з приводу C ++, який робить посилання за посиланням, і його публікація не має сенсу. Не має значення, що ви не можете змінити значення посилання після ініціалізації; це не має нічого спільного з "пройти через посилання". Його твердження про те, що "Семантичний" прохідний посилання "можна простежити через C #", повинен був бити дзвоном тривоги.
EML

7
@Pointy Це жахлива відповідь. Якщо мені потрібна допомога, останнє, чого я хотів би, - це хтось, хто навчає мене на семантичному. "Пройти посилання" просто означає, що функція може певною мірою змінити значення змінної, переданої як аргумент. " (в контексті питання) Це насправді не так складно, як ви це робите.
коронка

109
  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
Це не значення "пройти через посилання". Термін насправді не має нічого спільного з об'єктами, а з співвідношенням між параметрами в викликаній функції та змінними в середовищі виклику.
Pointy

12
Все завжди передається цінністю. Для примітивів значення, яке передається, є еталонним. Призначення посилання змінює це посилання - природно, оскільки це значення - і, отже, не може змінити інший об'єкт, на який було посилатися спочатку. Мутація об'єкта, на який вказує посилання, діє точно так, як ви і очікували, мутуючи об'єкт, що вказує на точку. Обидва сценарії цілком узгоджуються, і це нічим магічним чином не змінює роботу JavaScript, повертаючись у часі і змінюючи те, як вони були передані у функцію.
Матвій

9
Ця відповідь уточнила попередню, яка, хоч і мала більше голосів (287 на даний момент написання, і це також прийнято), не була достатньо зрозумілою, як насправді передати її посиланням в JS. Коротше кажучи, код у цій відповіді спрацював, відповідь на який набрала більше голосів - не.
Пап

25

Обхід для передачі змінної на зразок посилання:

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

inc('a');

alert(a); // 2


EDIT

так, насправді ви можете зробити це без доступу глобально

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

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

    return init;
})();

inc();

@Phil добре бути обережним щодо глобальних / віконних значень, але в якийсь момент все, що ми робимо в браузері, це дитина або нащадок об’єкта вікна. У nodejs все є нащадком GLOBAL. У компільованих мовах об'єктів це неявний, якщо не явний батьківський об'єкт, оскільки це робить інакше ускладнення управління купою (і для чого?).
dkloke

1
@dkloke: Так, з часом об'єкт вікна потрібно торкатися, як jQuery використовує вікно. $ / 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

Мені особисто не подобається функціонал "пройти посилання", який пропонують різні мови програмування. Можливо, це тому, що я просто відкриваю поняття функціонального програмування, але я завжди отримую goosebumps, коли бачу функції, що викликають побічні ефекти (наприклад, маніпулювання параметрами, що передаються посиланням). Я особисто сильно приймаю принцип "єдиної відповідальності".

IMHO, функція повинна повертати лише один результат / значення, використовуючи ключове слово 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} '. Ніде в цьому сценарії немає нового, невідомого об'єкта, бажаного або потрібного.
Elise van Looij

@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,

це те саме, що ставити аргументи в глобальну / масштабну змінну, але краще ...

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.