Vượt qua các biến bằng cách tham chiếu trong Javascript


285

Làm cách nào để chuyển các biến bằng tham chiếu trong JavaScript? Tôi có 3 biến mà tôi muốn thực hiện một số thao tác, vì vậy tôi muốn đặt chúng vào một vòng lặp for và thực hiện các thao tác cho từng biến.

mã giả:

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

Cách tốt nhất để làm việc này là gì?


31
Bạn đang nói về 'vượt qua tham chiếu', nhưng bạn không có chức năng gọi trong ví dụ của mình nên không có thông qua nào trong ví dụ của bạn. Vui lòng làm rõ những gì bạn đang cố gắng làm.
jfriend00

1
Xin lỗi vì sự nhầm lẫn. Tôi đặc biệt không cần phải viết một hàm vì vậy 'vượt qua tham chiếu' là một lựa chọn từ kém. Tôi chỉ muốn có thể thực hiện một số thao tác với các biến mà không cần viết makePretty(var1); makePretty(var2); makePretty(var3); ...
BFTrick

dựa trên nhận xét của bạn: arr = [var1, var2, var3]; for (var i = 0, len = arr.length; i < len; i++) { arr[i] = makePretty(arr[i]); }- bạn chỉ cần lưu trữ giá trị được trả về bằng cách makePrettyquay lại vị trí trong mảng.
dylnmc

Câu trả lời:


415

Không có "vượt qua tham chiếu" có sẵn trong JavaScript. Bạn có thể truyền một đối tượng (có nghĩa là, bạn có thể truyền từng giá trị một tham chiếu đến một đối tượng) và sau đó có một hàm sửa đổi nội dung của đối tượng:

function alterObject(obj) {
  obj.foo = "goodbye";
}

var myObj = { foo: "hello world" };

alterObject(myObj);

alert(myObj.foo); // "goodbye" instead of "hello world"

Bạn có thể lặp lại các thuộc tính của một mảng với một chỉ mục số và sửa đổi từng ô của mảng, nếu bạn muốn.

var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) { 
    arr[i] = arr[i] + 1; 
}

Điều quan trọng cần lưu ý là "chuyển qua tham chiếu" là một thuật ngữ rất cụ thể. Điều đó không có nghĩa đơn giản là nó có thể chuyển một tham chiếu đến một đối tượng có thể sửa đổi. Thay vào đó, điều đó có nghĩa là có thể truyền một biến đơn giản theo cách cho phép một hàm sửa đổi giá trị đó trong ngữ cảnh gọi . Vì thế:

 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"

Trong một ngôn ngữ như C ++, bạn có thể làm điều đó bởi vì ngôn ngữ đó (sắp xếp) có tham chiếu qua.

chỉnh sửa - điều này gần đây (tháng 3 năm 2015) đã xuất hiện trở lại trên Reddit qua một bài đăng blog tương tự như của tôi được đề cập dưới đây, mặc dù trong trường hợp này là về Java. Nó xảy ra với tôi khi đọc qua lại trong các bình luận của Reddit rằng một phần lớn của sự nhầm lẫn bắt nguồn từ vụ va chạm đáng tiếc liên quan đến từ "tham chiếu". Thuật ngữ "truyền bằng tham chiếu" và "truyền theo giá trị" có trước khái niệm có "đối tượng" để làm việc với các ngôn ngữ lập trình. Nó thực sự không phải là về các đối tượng; đó là về các tham số chức năng và cụ thể cách các tham số chức năng được "kết nối" (hoặc không) với môi trường gọi. Đặc biệt,và nó trông khá giống với JavaScript. Tuy nhiên, người ta cũng có thể sửa đổi tham chiếu đối tượng trong môi trường gọi và đó là điều quan trọng mà bạn không thể làm trong JavaScript. Một ngôn ngữ tham chiếu qua sẽ không vượt qua chính tham chiếu, mà là một tham chiếu đến tham chiếu .

chỉnh sửa - đây là một bài viết trên blog về chủ đề này. (Lưu ý nhận xét cho bài đăng đó giải thích rằng C ++ không thực sự có tham chiếu qua. Điều đó đúng. Tuy nhiên, điều C ++ có là khả năng tạo tham chiếu đến các biến đơn giản, hoặc rõ ràng tại điểm chức năng gọi để tạo một con trỏ hoặc ngầm định khi gọi các hàm có chữ ký loại đối số yêu cầu thực hiện. Đó là những điều quan trọng mà JavaScript không hỗ trợ.)


8
Bạn có thể chuyển tham chiếu đến một đối tượng hoặc một mảng cho phép bạn thay đổi đối tượng hoặc mảng ban đầu mà tôi tin là những gì OP thực sự đang hỏi về.
jfriend00

2
Chà, OP đã sử dụng thuật ngữ này nhưng mã thực tế dường như không liên quan đến bất kỳ sự "vượt qua" nào :-) Tôi không thực sự chắc chắn những gì anh ta cố gắng làm.
Mũi nhọn

5
Truyền tham chiếu theo giá trị không giống như chuyển qua tham chiếu , mặc dù nó có thể xuất hiện như vậy trong một số trường hợp như thế này.
Travis Webb

5
Blogger của bạn đã sai về C ++, điều này không có tham khảo, và bài đăng của anh ấy không có ý nghĩa gì. Không liên quan khi bạn không thể thay đổi giá trị của tài liệu tham khảo sau khi khởi tạo; điều đó không có gì để làm với 'vượt qua tham chiếu'. Tuyên bố của ông rằng "Ngữ nghĩa" vượt qua tham chiếu "có thể được truy ngược lại thông qua C # 'nên đã rung chuông báo động.
EML

7
@Pointy Đây là một câu trả lời khủng khiếp. Nếu tôi cần sự giúp đỡ, điều cuối cùng tôi muốn là ai đó dạy tôi về ngữ nghĩa. "Pass-by-Reference" chỉ đơn giản là "hàm, ở một mức độ nào đó, có thể thay đổi giá trị của biến được truyền vào dưới dạng đối số." (trong bối cảnh của câu hỏi) Nó thực sự không phức tạp như bạn nghĩ.
crownlessking

108
  1. các biến kiểu nguyên thủy như chuỗi và số luôn được truyền theo giá trị.
  2. Mảng và đối tượng được truyền bằng tham chiếu hoặc theo giá trị dựa trên các điều kiện sau:

    • nếu bạn đang đặt giá trị của một đối tượng hoặc mảng thì nó là Pass by Value.

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

    • nếu bạn đang thay đổi giá trị thuộc tính của một đối tượng hoặc mảng thì đó là 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
Đó không phải là ý nghĩa của "vượt qua tham chiếu". Thuật ngữ này thực sự không liên quan gì đến các đối tượng, nhưng với mối quan hệ giữa các tham số trong một hàm được gọi và các biến trong môi trường gọi.
Mũi nhọn

12
Mọi thứ luôn được thông qua bởi giá trị. Với người không nguyên thủy, giá trị được thông qua là một tham chiếu. Việc chỉ định một tham chiếu thay đổi tham chiếu đó - một cách tự nhiên, vì đó là một giá trị - và do đó không thể thay đổi đối tượng khác được tham chiếu ban đầu. Đột biến đối tượng được trỏ đến bởi một tham chiếu hoạt động chính xác như bạn mong đợi, bằng cách thay đổi đối tượng được trỏ. Cả hai kịch bản đều hoàn toàn phù hợp và không làm thay đổi một cách kỳ diệu cách JavaScript hoạt động bằng cách quay ngược thời gian và thay đổi cách chúng được truyền vào hàm.
Matthew đọc

9
Câu trả lời này đã làm rõ câu hỏi trước, mặc dù nó có nhiều phiếu hơn (287 tại thời điểm viết bài này và nó cũng là câu được chấp nhận) không đủ rõ ràng về cách thực sự vượt qua nó bằng cách tham chiếu trong JS. Nói tóm lại, mã trong câu trả lời này đã hoạt động, câu trả lời thu thập được nhiều phiếu hơn thì không.
Pap

25

Cách giải quyết để vượt qua biến như tham chiếu:

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

inc('a');

alert(a); // 2


BIÊN TẬP

yup, thực sự bạn có thể làm điều đó mà không cần truy cập toàn cầu

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

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

    return init;
})();

inc();

@Phil thật tốt khi cẩn thận với các giá trị toàn cầu / cửa sổ, nhưng tại một số điểm, mọi thứ chúng ta đang làm trong trình duyệt là một đứa trẻ hoặc hậu duệ của đối tượng cửa sổ. Trong nodejs, mọi thứ đều là hậu duệ của GLOBAL. Trong các ngôn ngữ đối tượng được biên dịch, đó là một đối tượng ẩn nếu không rõ ràng, bởi vì làm điều đó nếu không thì việc quản lý heap trở nên phức tạp hơn (và để làm gì?).
dkloke 28/03/2015

1
@dkloke: Vâng, cuối cùng, đối tượng cửa sổ cần được chạm vào - giống như jQuery sử dụng cửa sổ. $ / window.jQuery và các phương thức khác nằm dưới điều này. Tôi đã nói về sự ô nhiễm của không gian tên toàn cầu nơi bạn thêm rất nhiều biến vào đó thay vì đặt chúng dưới một không gian tên thống nhất.
Phil

2
Tôi không thể tìm thấy những từ hay để diễn tả mức độ tệ hại của cách tiếp cận đó; (
SOReader

Tôi thích giải pháp cuối cùng (phiên bản chỉnh sửa) thậm chí nó không vượt qua biến bằng tham chiếu. Đó là lợi thế vì bạn có thể truy cập các biến trực tiếp.
John Boe

11

Đối tượng đơn giản

var ref = { value: 1 };

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

Foo(ref);
Foo(ref);

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

Đối tượng tùy chỉnh

Vật 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];
    }
}

Sự định nghĩa biến

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

Hoặc là:

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

Mã kiểm tra

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

Kết quả kiểm tra bảng điều khiển

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

Tuy nhiên, một cách tiếp cận khác để vượt qua bất kỳ biến (cục bộ, nguyên thủy) nào bằng cách tham chiếu là bằng cách đóng gói biến với đóng "trên đường bay" eval. Điều này cũng hoạt động với "sử dụng nghiêm ngặt". (Lưu ý: lưu ý rằng evalkhông thân thiện với trình tối ưu hóa JS, việc thiếu các trích dẫn xung quanh tên biến có thể gây ra kết quả không dự đoán được)

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

})()

Mẫu trực tiếp https://jsfiddle.net/t3k4403w/


Điều này thật tuyệt vời
hard_usiness_ant

3

Thực sự có một cách khá hay:

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

Tôi đã chơi xung quanh với cú pháp để làm điều này, nhưng nó đòi hỏi một số người trợ giúp hơi khác thường. Nó bắt đầu bằng việc không sử dụng 'var', nhưng một trình trợ giúp 'DECLARE' đơn giản tạo ra một biến cục bộ và xác định phạm vi cho nó thông qua một cuộc gọi lại ẩn danh. Bằng cách kiểm soát cách các biến được khai báo, về cơ bản chúng ta có thể chọn bọc chúng vào các đối tượng để chúng luôn có thể được chuyển qua tham chiếu. Điều này tương tự với một trong những câu trả lời của Eduardo Cuomo ở trên, nhưng giải pháp bên dưới không yêu cầu sử dụng chuỗi làm định danh biến. Đây là một số mã tối thiểu để hiển thị khái niệm.

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

thực sự nó rất dễ

vấn đề là hiểu rằng một khi vượt qua các đối số cổ điển, bạn sẽ nằm trong vùng khác, chỉ đọc.

giải pháp là vượt qua các đối số bằng cách sử dụng thiết kế hướng đối tượng của JavaScript,

nó cũng giống như đặt các đối số vào một biến toàn cục / phạm vi, nhưng tốt hơn ...

function action(){
  /* process this.arg, modification allowed */
}

action.arg = [ ["empty-array"],"some string",0x100,"last argument" ];
action();

bạn cũng có thể hứa hẹn mọi thứ để tận hưởng chuỗi nổi tiếng: đây là toàn bộ, với cấu trúc giống như lời hứa

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

hoặc tốt hơn.. action.setArg(["empty-array"],"some string",0x100,"last argument").call()


this.argchỉ hoạt động với actionví dụ. Điều này không hoạt động.
Eduardo Cuomo

2

Cá nhân tôi không thích chức năng "vượt qua tham chiếu" được cung cấp bởi các ngôn ngữ lập trình khác nhau. Có lẽ đó là vì tôi mới khám phá các khái niệm về lập trình chức năng, nhưng tôi luôn bị nổi da gà khi thấy các hàm gây ra tác dụng phụ (như thao tác các tham số được truyền qua tham chiếu). Cá nhân tôi mạnh mẽ chấp nhận nguyên tắc "trách nhiệm duy nhất".

IMHO, một hàm sẽ trả về chỉ một kết quả / giá trị bằng cách sử dụng từ khóa return. Thay vì sửa đổi một tham số / đối số, tôi sẽ chỉ trả về giá trị tham số / đối số đã sửa đổi và để lại bất kỳ sự gán lại mong muốn nào cho mã gọi.

Nhưng đôi khi (hy vọng rất hiếm khi), cần phải trả về hai hoặc nhiều giá trị kết quả từ cùng một hàm. Trong trường hợp đó, tôi sẽ chọn bao gồm tất cả các giá trị kết quả đó trong một cấu trúc hoặc đối tượng. Một lần nữa, xử lý bất kỳ sự gán lại nào sẽ tùy thuộc vào mã gọi.

Thí dụ:

Giả sử truyền tham số sẽ được hỗ trợ bằng cách sử dụng một từ khóa đặc biệt như 'ref' trong danh sách đối số. Mã của tôi có thể trông giống như thế này:

//The Function
function doSomething(ref value) {
    value = "Bar";
}

//The Calling Code
var value = "Foo";
doSomething(value);
console.log(value); //Bar

Thay vào đó, tôi thực sự thích làm một cái gì đó như thế này:

//The Function
function doSomething(value) {
    value = "Bar";
    return value;
}

//The Calling Code:
var value = "Foo";
value = doSomething(value); //Reassignment
console.log(value); //Bar

Khi tôi cần viết một hàm trả về nhiều giá trị, tôi cũng sẽ không sử dụng các tham số được truyền bởi tham chiếu. Vì vậy, tôi sẽ tránh mã như thế này:

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

Thay vào đó, tôi thực sự muốn trả về cả hai giá trị mới bên trong một đối tượng, như thế này:

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

Các ví dụ mã này khá đơn giản, nhưng nó đại diện cho thấy cá nhân tôi sẽ xử lý những thứ đó như thế nào. Nó giúp tôi giữ các trách nhiệm khác nhau ở đúng nơi.

Chúc mừng mã hóa. :)


Việc chỉ định và gán lại công cụ (không có bất kỳ kiểm tra kiểu nào) là những gì mang lại cho tôi nổi da gà. Về trách nhiệm, tôi mong muốn doS Something (), thực hiện điều đó, không làm điều đó cộng với việc tạo một đối tượng cộng và gán các biến của tôi cho các thuộc tính. Giả sử tôi có một mảng cần tìm kiếm. Tôi muốn các mục phù hợp được đặt trong một mảng thứ hai và tôi muốn biết có bao nhiêu được tìm thấy. Một giải pháp tiêu chuẩn sẽ là gọi một hàm như thế này: 'var FoundArray; if (findStuff (inArray, & FoundArray))> 1) {// process FoundArray} '. Không nơi nào trong kịch bản đó là một đối tượng mới, chưa biết mong muốn hoặc cần thiết.
Elise van Looij

@ElisevanLooij Trong trường hợp ví dụ của bạn, tôi chỉ muốn findStufftrả về mảng kết quả. Bạn cũng phải khai báo một foundArraybiến, vì vậy tôi sẽ trực tiếp gán mảng kết quả cho nó : var foundArray = findStuff(inArray); if (foundArray.length > 0) { /* process foundArray */ }. Điều này sẽ 1) làm cho mã cuộc gọi dễ đọc / dễ hiểu hơn và 2) đơn giản hóa đáng kể chức năng bên trong (và do đó cũng là khả năng kiểm tra) củafindStuff phương thức, làm cho nó thực sự linh hoạt hơn rất nhiều trong các trường hợp / tình huống sử dụng khác nhau.
Bart Hofland

@ElisevanLooij Tuy nhiên, tôi đồng ý rằng việc đánh giá lại (như trong câu trả lời của tôi) thực sự là một "mùi mã" và tôi thực sự muốn tránh những điều đó càng nhiều càng tốt. Tôi sẽ suy nghĩ về việc chỉnh sửa (hoặc thậm chí cải cách) câu trả lời của tôi theo cách nó sẽ phản ánh tốt hơn ý kiến ​​thực tế của tôi về chủ đề này. Cảm ơn phản ứng của bạn. :)
Bart Hofland

1

Javascript có thể sửa đổi các mục mảng bên trong một hàm (nó được truyền dưới dạng tham chiếu đến đối tượng / mảng).

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

Đây là một ví dụ khác:

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 không phải là loại mạnh, nó cho phép bạn giải quyết các vấn đề theo nhiều cách khác nhau, dường như trong guồng quay này.

Tuy nhiên, về quan điểm bảo trì, tôi sẽ phải đồng ý với Bart Hofland. Hàm sẽ nhận được args để làm một cái gì đó với và trả về kết quả. Làm cho chúng dễ dàng tái sử dụng.

Nếu bạn cảm thấy rằng các biến cần được chuyển qua tham chiếu, bạn có thể được phục vụ tốt hơn Xây dựng chúng thành các đối tượng IMHO.


1

Tạm gác lại cuộc thảo luận qua tham chiếu, những người vẫn đang tìm giải pháp cho câu hỏi đã nêu có thể sử dụng:

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

0

Tôi biết chính xác ý của bạn là gì. Điều tương tự trong Swift sẽ không có vấn đề. Dòng dưới là sử dụng letkhông var.

Thực tế là các nguyên thủy được truyền theo giá trị nhưng thực tế là giá trị var itại điểm lặp không được sao chép vào hàm ẩn danh là điều khá đáng ngạc nhiên để nói rằng ít nhất.

for (let i = 0; i < boxArray.length; i++) {
  boxArray[i].onclick = function() { console.log(i) }; // correctly prints the index
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.