Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Bất kể bạn làm việc với ngôn ngữ lập trình nào, chắc chắn bạn sẽ thấy mình đặt câu hỏi sau. Hỗ trợ ngôn ngữ này có vượt qua tham chiếu không? . Đó là điều không phải lúc nào cũng được giải thích rõ ràng trên mạng. Những người mới bắt đầu thường bối rối và hiểu sai mô hình tinh thần về cách bộ nhớ hoạt động và cách ngôn ngữ lập trình thao tác dữ liệu

Cụ thể, bạn có thể đã xem qua lời giải thích này trước đây

Nguyên thủy được truyền theo giá trị;

Điều này chắc chắn nghe có vẻ đơn giản đủ để trở thành sự thật. Ngoại trừ nó không phải. JavaScript, giống như hầu hết các ngôn ngữ lập trình, sử dụng nghiêm ngặt truyền theo giá trị và không hỗ trợ truyền theo tham chiếu, mặc dù nó có cái mà chúng ta gọi là “tham chiếu” (tham chiếu đối tượng)

Để làm cho vấn đề trở nên khó hiểu hơn, có rất nhiều thông tin sai lệch về chủ đề này. Đó là điều mà những người mới bắt đầu gặp khó khăn không chỉ với JavaScript mà còn với các ngôn ngữ khác. Đây là những gì bài báo Phương tiện được liên kết ở trên nói về việc chuyển qua tham chiếu

Thay đổi đối số bên trong hàm ảnh hưởng đến biến được truyền từ bên ngoài hàm. Trong các đối tượng và mảng Javascript theo sau chuyển qua tham chiếu

Ngay cả câu trả lời được xếp hạng cao nhất trên StackOverflow cho Is JavaScript a pass-by-reference or pass-by-value language?—với số phiếu tán thành gần gấp ba lần so với câu trả lời đúng—cũng bỏ lỡ dấu ấn, cho rằng JavaScript là một trường hợp đặc biệt khi nó thực sự

Đây là sự thật. Chỉ một số ngôn ngữ lập trình thực sự hỗ trợ truyền đối số bằng cách tham chiếu. Hai ngôn ngữ như vậy là C++ và PHP. Các ngôn ngữ khác—như C, Java, JavaScript, Python và Go, kể tên một số ngôn ngữ mà tôi nghĩ đến—truyền đối số theo giá trị

Vậy tại sao nhiều người hiểu sai điều này? . Nếu bạn muốn câu trả lời ngắn gọn, vui lòng chuyển đến phần có tiêu đề Truyền con trỏ (“Tham chiếu”) theo giá trị

Bỏ qua mục lục

Mục lục

  1. thuật ngữ tiên quyết. Đối số vs. Thông số
  2. JavaScript không vượt qua tham chiếu
  3. Tài liệu tham khảo "True" là Bí danh
  4. “Tham chiếu đối tượng” là con trỏ
    1. Các đối tượng được lưu trữ trong bộ nhớ như thế nào?
  5. Vượt qua giá trị so với. Vượt qua tham khảo
    1. Pass by Reference là gì?
    2. Vượt qua giá trị là gì?
    3. Truyền con trỏ (“Tham chiếu”) theo giá trị
  6. Gọi cho họ bất cứ điều gì bạn muốn
  7. Bản tóm tắt. JavaScript không được thông qua theo tham chiếu
  8. Thuộc tính

thuật ngữ tiên quyết. Đối số vs. Thông số

Trước khi tiếp tục, chúng ta sẽ cần sơ lược thuật ngữ nhanh

function doSomething(arg) {
    // ...
}

const obj = { foo: 'bar' };
doSomething(obj);

Tham số hình thức (gọi tắt là tham số) là biến cục bộ được khai báo trong chữ ký của hàm. Ở trên, tham số chính thức của

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
2 là
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
3. Mặt khác, đối số là biến (
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
4) có giá trị được truyền vào hàm

Về mặt sơ đồ, đây là ngăn xếp sẽ trông như thế nào sau khi

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
2 được gọi

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Ngoài ra, chúng ta hãy đi sâu vào nội dung của bài đăng này

JavaScript không vượt qua tham chiếu

Nếu có một điều bạn nên rút ra từ bài đăng này, thì đó là điều này. Truyền một tham chiếu vào một hàm không giống như truyền một đối số theo tham chiếu

Thuật ngữ lộn xộn này là nguồn gây nhầm lẫn chính trong việc hiểu chuyển theo giá trị so với. chuyển qua tham chiếu trong JavaScript và các ngôn ngữ khác. Đó là điều khiến nhiều nhà phát triển vấp ngã

Trong JavaScript, các biến lưu trữ đối tượng được gọi là tham chiếu đối tượng (thường gọi tắt là “tham chiếu”). Thuật ngữ tham chiếu là một cách tự nhiên để nói về các biến này trong tiếng Anh bởi vì chúng ta nói rằng một biến tham chiếu đến một đối tượng trong bộ nhớ, giống như cách một bài báo có thể tham chiếu đến một nguồn

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Về mặt kỹ thuật, không có gì sai khi sử dụng thuật ngữ “tham chiếu” để nói về các đối tượng theo cách này và tôi không muốn ngăn cản bạn làm như vậy—trong JavaScript, đây là thuật ngữ được chấp nhận và nếu bạn chọn không tuân theo thuật ngữ đó,

Nhưng thuật ngữ này cũng gây nhầm lẫn cho người mới bắt đầu. Ai có thể đổ lỗi cho họ vì đã tạo ra mối liên hệ ở trên? . Để đề nghị khác thậm chí có vẻ vô lý hoặc mô phạm

Vì vậy, để giải tỏa sự nhầm lẫn này, chúng ta cần hiểu hai điều

  1. "Tham khảo" trong "vượt qua tham chiếu" thực sự có nghĩa là gì
  2. Sự khác biệt giữa truyền theo giá trị và truyền theo tham chiếu trong JavaScript

Tài liệu tham khảo "True" là Bí danh

Hãy làm rõ một điều trước khi tiếp tục. Từ "tham chiếu" trong "vượt qua tham chiếu" có một ý nghĩa đặc biệt và nó không có nghĩa là "tham chiếu đối tượng. ” JavaScript không có các tham chiếu “đúng” này. Một lần nữa, thuật ngữ có thể gây nhầm lẫn

JavaScript đã chọn đi theo bước chân của Java và sử dụng thuật ngữ "tham chiếu" khi nói về các biến trỏ đến các đối tượng trong bộ nhớ. Nhưng như chúng tôi đã lưu ý ở trên, mặc dù điều này có ý nghĩa hoàn hảo trong tiếng Anh, nhưng nó lại khiến bạn rất bối rối khi nói về việc “chuyển tài liệu tham khảo” mà không mất trí.

Trong một lớp học về lý thuyết ngôn ngữ lập trình, bạn sẽ học được rằng “tham chiếu” trong “chuyển qua tham chiếu” thực sự là một cái gì đó hoàn toàn khác với những “tham chiếu đối tượng” mà bạn đã từng nghe đến. Trên thực tế, một tham chiếu thực sự không liên quan gì đến các đối tượng—các tham chiếu có thể chứa các giá trị nguyên thủy hoặc các đối tượng

Biến tham chiếu đúng là bí danh hoặc tên khác của đối tượng. Nếu bạn chủ yếu làm việc với các ngôn ngữ như C, Java, JavaScript, Python và các ngôn ngữ khác cho đến bây giờ, thì khái niệm bí danh này có vẻ hơi xa lạ với bạn. Và đó là bởi vì những ngôn ngữ đó không có tài liệu tham khảo. Vì lý do này, tôi sẽ sử dụng C++ để chứng minh cách hoạt động của tham chiếu thực, so sánh chúng với hành vi của tham chiếu đối tượng trong JavaScript

Bằng cách tương tự, một tài liệu tham khảo giống như biệt hiệu của một người. Tên thật của tôi là Aleksandr, nhưng đôi khi tôi cũng gọi bằng Alex. Mọi người có thể gọi tôi là một trong hai, và họ vẫn sẽ nhắc đến tôi

Đây là một số mã C++ để minh họa các biến tham chiếu thực sự đang hoạt động

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}

Trong các ngôn ngữ không hỗ trợ biến tham chiếu (e. g. , JavaScript), hai biến có thể chia sẻ một bản sao của một giá trị, nhưng đảm bảo rằng các biến đó sẽ chiếm các địa chỉ bộ nhớ khác nhau. Đây là một nỗ lực ở trên trong JavaScript

let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"

Ở dòng thứ hai, chúng tôi đang tạo một bản sao của chuỗi

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
6 và lưu trữ nó trong một biến mới có tên là
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
7. Điều quan trọng là hai biến này chiếm các địa chỉ bộ nhớ khác nhau—việc gán giá trị mới cho một biến không ảnh hưởng đến giá trị được tham chiếu bởi biến kia

Không giống như một biến nhận một bản sao của một giá trị, một biến tham chiếu không có địa chỉ bộ nhớ riêng—nó chia sẻ cùng một địa chỉ bộ nhớ chính xác như biến ban đầu. Lạ thật phải không?

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
0

Nếu bạn chạy mã này, bạn sẽ thấy rằng cùng một địa chỉ bộ nhớ chính xác được ghi lại hai lần

Ở đây, một số người ngừng lắng nghe và tuyên bố rằng JavaScript có tham chiếu thực sự, sử dụng các đối tượng để cố gắng chứng minh quan điểm của họ

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
1

Điều này chắc chắn có vẻ hoạt động theo cách các tài liệu tham khảo hoạt động, nhưng nó không giống nhau chút nào. Những gì chúng tôi đã làm ở đây có thể được chia thành năm bước đơn giản

  1. Chúng tôi đã phân bổ không gian trong bộ nhớ để lưu trữ một đối tượng (______19)
  2. Chúng tôi đã tạo một biến có tên
    let myName = "Aleksandr";
    let myNickname = myName;
    
    myNickname = "Alex";
    
    console.log(myName); // "Aleksandr"
    console.log(myNickname); // "Alex"
    
    0 trỏ đến địa chỉ bộ nhớ nơi đối tượng này cư trú
  3. Chúng tôi đã tạo một biến khác,
    let myName = "Aleksandr";
    let myNickname = myName;
    
    myNickname = "Alex";
    
    console.log(myName); // "Aleksandr"
    console.log(myNickname); // "Alex"
    
    1, cũng trỏ đến cùng một địa chỉ bộ nhớ
  4. Chúng ta có thể sử dụng một trong hai biến để sửa đổi đối tượng tại địa chỉ bộ nhớ đó
  5. Vì cả hai biến đều trỏ đến cùng một địa chỉ bộ nhớ, mọi thay đổi đối với đối tượng tại vị trí đó sẽ được phản ánh khi chúng ta thăm dò một trong hai biến trong tương lai

Nhưng hai biến này,

let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
0 và
let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
1, không chia sẻ cùng một địa chỉ bộ nhớ trên ngăn xếp. Nói cách khác,
let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
1 không thực sự là một tài liệu tham khảo thực sự. Chúng ta có thể dễ dàng chứng minh điều này

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
8

Nếu trên thực tế,

let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
1 là một tham chiếu thực sự theo nghĩa này của thuật ngữ—một bí danh—thì sự thay đổi ở dòng ba sẽ được phản ánh trong biến ban đầu (được đặt bí danh). Nhưng nó không phải là

“Tham chiếu đối tượng” là con trỏ

Ở trên, bạn có thể nhận thấy rằng tôi đã sử dụng thuật ngữ “điểm” khá nhiều. Có chuyện gì vậy?

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?
Nguồn. xkcd

Cho đến nay, chúng ta đã thấy rằng JavaScript không có các tham chiếu “đúng”—loại có trong “chuyển qua tham chiếu”, như chúng ta sẽ sớm tìm hiểu—mặc dù đó là cách viết tắt được chấp nhận khi đề cập đến “các tham chiếu đối tượng. ” Vậy chính xác tham chiếu đối tượng trong JavaScript là gì?

“Tham chiếu đối tượng” trong JavaScript thực sự là con trỏ. Con trỏ là một biến—thay vì lưu trữ trực tiếp một giá trị nguyên thủy như

let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
6,
let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
7,
let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
8 hoặc
let myName = "Aleksandr";
let myNickname = myName;

myNickname = "Alex";

console.log(myName); // "Aleksandr"
console.log(myNickname); // "Alex"
9—lưu trữ địa chỉ bộ nhớ chứa một số dữ liệu. Con trỏ đề cập đến dữ liệu của chúng một cách gián tiếp, sử dụng địa chỉ nơi có thể tìm thấy những dữ liệu đó

Một phép loại suy có thể giúp minh họa điều này. Một con trỏ giống như ghi lại địa chỉ nhà của một người thay vì ghi trực tiếp thông tin của người đó vào một nơi nào đó. Nếu sau này bạn truy cập địa chỉ đó, bạn có thể tìm thấy dữ liệu ở đó. một người có tên, tuổi, v.v. Người ở địa chỉ đó có thể thay đổi tên, già đi hoặc quyết định rời khỏi tài sản và được thay thế bởi một người thuê nhà khác với tên, tuổi hoàn toàn khác, v.v. Địa chỉ thậm chí có thể trống và trên thị trường. Miễn là chúng tôi trỏ đến cùng một địa chỉ, chúng tôi có thể truy cập địa chỉ đó để tìm hiểu dữ liệu (người) sống ở đó. Một ngày nào đó, chúng tôi cũng có thể quyết định chỉ đến một địa chỉ nhà hoàn toàn khác với những cư dân khác hoặc, nếu chúng tôi là chủ nhà, đuổi ai đó ra khỏi nhà của họ

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Có thể không rõ ràng ngay tại sao chúng ta cần con trỏ ngay từ đầu và chúng thậm chí có liên quan như thế nào trong JavaScript. Điều này xứng đáng được xây dựng. Nếu bạn đã quen thuộc với con trỏ, vui lòng bỏ qua phần truyền theo giá trị so với. vượt qua tham chiếu

Các đối tượng được lưu trữ trong bộ nhớ như thế nào?

Máy tính chỉ giỏi một thứ. Lưu trữ, truy xuất và thao tác số. Đó là nó

Vậy điều gì sẽ xảy ra khi chúng ta muốn tạo dữ liệu như đối tượng, mảng hoặc chuỗi bằng ngôn ngữ lập trình mà chúng ta chọn?

Hãy coi mã JavaScript này là một ví dụ

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
4

Ở cấp độ phần cứng, chúng ta có thể lưu trữ số nguyên nguyên thủy

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
00 trong thanh ghi CPU và lưu trực tiếp vào ngăn xếp, thay vì phải nhớ địa chỉ bộ nhớ nơi chúng ta đã phân bổ không gian cho số nguyên này. Nhớ lại. Con số là ngôn ngữ của máy tính. Khi nó xảy ra,
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
00 là một số. Mọi thứ hoạt động và chúng tôi lưu trữ số này trực tiếp

Nhưng nếu biến của chúng ta đề cập đến một đối tượng phức tạp hơn (nghĩ là “dữ liệu tổng hợp”), như một đối tượng, mảng hoặc chuỗi thì sao? . Bởi vì ở cấp độ CPU, chúng tôi không có khái niệm về “đối tượng”, “mảng” hoặc “chuỗi”—chúng tôi chỉ có thể phân bổ các khối bộ nhớ và lưu trữ số nguyên trong các địa chỉ đó. Nếu bạn muốn lưu trữ dữ liệu tổng hợp trong bộ nhớ, bạn cần sử dụng một con trỏ để ghi nhớ vị trí bắt đầu của đoạn dữ liệu tổng hợp đó

Ví dụ: giả sử rằng mọi người trong thế giới của chúng ta có nhiều thuộc tính hơn

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
7

Làm cách nào để có thể nhồi nhét tất cả dữ liệu này vào một địa chỉ bộ nhớ duy nhất? . Vì vậy, thay vào đó, chúng tôi bắt đầu tại một địa chỉ bộ nhớ cụ thể—được gọi là địa chỉ bộ nhớ cơ sở—và bắt đầu lưu trữ các mẩu thông tin này trong các khối liên tiếp. Sau đó,

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
04 trỏ đến địa chỉ bộ nhớ cơ sở đó, để sau này chúng ta có thể làm việc theo cách của mình và “tập hợp lại” các mẩu thông tin mà chúng ta cần, có thể nói như vậy

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Điều đó quan tâm đến các đối tượng, nhưng còn chuỗi thì sao? . Nhưng hãy nhớ. Máy tính không hiểu tiếng Anh, tiếng Pháp hoặc tiếng Trung; . Trong thực tế, một chuỗi bao gồm các ký tự được lưu trữ trong các địa chỉ bộ nhớ liên tiếp và được mã hóa thành các số sử dụng tiêu chuẩn Unicode. Trong các ngôn ngữ lập trình, một chuỗi được cài đặt như một con trỏ tới địa chỉ bộ nhớ của ký tự đầu tiên của nó. Nếu chúng ta biết mã hóa ký tự cụ thể mà một chuỗi sử dụng (e. g. , UTF-16 trong JavaScript), chúng ta có thể sử dụng số học con trỏ để tìm ra chuỗi là gì vì chúng ta đã có địa chỉ bộ nhớ cơ sở và số lượng ký tự được lưu trữ

Điểm mấu chốt ở đây là con trỏ là một công cụ cơ bản trong lập trình tồn tại trong quá trình triển khai của hầu hết mọi ngôn ngữ. Cho dù chúng bị ẩn khỏi tầm nhìn rõ ràng hay được làm rõ bằng một cú pháp đặc biệt đều không quan trọng. Điều quan trọng là bạn hiểu cách hoạt động của bộ nhớ và hầu hết các ngôn ngữ không có “tham chiếu. ”

Vượt qua giá trị so với. Vượt qua tham khảo

Được rồi, chúng ta đã xác định rằng “các tham chiếu đối tượng” trong JavaScript thực sự là các con trỏ và các con trỏ đó chỉ giỏi một thứ. lưu trữ địa chỉ bộ nhớ. Các đối tượng cư trú tại các địa chỉ bộ nhớ này. Chúng tôi có thể truy cập các địa chỉ đó và sửa đổi các đối tượng sống ở đó hoặc chúng tôi có thể có một điểm biến đối với một đối tượng hoàn toàn mới tại một số vị trí khác trong bộ nhớ

Với nền tảng này, chúng tôi đã sẵn sàng để hiểu tại sao chuyển qua tham chiếu không tồn tại trong JavaScript

Pass by Reference là gì?

Chúng tôi đã học được tài liệu tham khảo thực sự là gì. bí danh. Và hóa ra, đây chính xác là kiểu quy chiếu mà chúng ta đang nói đến khi chúng ta nói một ngôn ngữ có “chuyển qua tham chiếu”. ” Đó là tất cả về các đối số bí danh đã được thông qua trong

Trước tiên, hãy hiểu điều gì vượt qua tham chiếu không có nghĩa là. Truyền theo tham chiếu không có nghĩa là “Chúng ta có thể truyền một đối tượng vào một hàm, sửa đổi đối tượng đó và sau đó quan sát kết quả đã sửa đổi ngay cả sau khi hàm trả về. ”

Thông qua tham chiếu, tham số chính thức là một biến tham chiếu (bí danh) cho đối số. Nó gần giống như hai chức năng đã tạm thời đồng ý gọi một đối tượng bằng hai tên (một tên gốc và một bí danh) và giả vờ rằng hai tên đó tương đương nhau. Hoặc, nói một cách kém chính xác hơn, bạn có thể coi nó giống như một hàm “mượn” một biến từ một khung ngăn xếp khác nhưng gọi nó là một thứ khác

Có một bài kiểm tra giấy quỳ cổ điển để kiểm tra xem một ngôn ngữ có hỗ trợ chuyển qua tham chiếu không. liệu bạn có thể hoán đổi hai số hay không (đoạn mã sau được viết bằng JavaScript)

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
1

Nếu bạn chạy cái này trong trình duyệt của mình, bạn sẽ thấy rằng

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
05 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
06 không hoán đổi, trong khi
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
07 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
08 thực hiện cục bộ. Điều này chứng tỏ rằng JavaScript không hỗ trợ chuyển qua tham chiếu. Nếu nó hỗ trợ cơ chế này, thì
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
07 sẽ là bí danh của
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
05 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
08 sẽ là bí danh của
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
06. Mọi thay đổi đối với
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
07 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
08 sẽ được phản ánh trở lại
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
05 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
06, tương ứng. Nhưng họ không

Vì vậy, những gì đã xảy ra ở đây? . Điều này có nghĩa là các giá trị của

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
80 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
81 trùng nhau vì chúng là bản sao chứ không phải vì chúng được đặt bí danh

Ngược lại, đây là một ví dụ về hoán đổi hai số nguyên theo tham chiếu trong C++

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
9

Hãy phá vỡ điều này.

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
05 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
06 là các đối số mà chúng tôi chuyển vào
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
19. Chữ ký phương thức của
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
19 xác định hai tham số tham chiếu,
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
07 và
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
08. Vì đây là các biến tham chiếu nên chúng đóng vai trò là bí danh cho bất kỳ hai biến nào được truyền vào dưới dạng đối số (lần lượt là ___105 và _______106). Như vậy là hoán đổi thành công

Vượt qua giá trị là gì?

Nói một cách đơn giản nhất, chuyển theo giá trị là sao chép. Chúng tôi đã thấy điều này ở trên với hoán đổi JavaScript không thành công

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
0

Khi một đối số được truyền theo giá trị, tham số chính thức sẽ nhận được một bản sao giá trị của đối số. Tham số chính thức và đối số là hai biến khác nhau tình cờ chia sẻ cùng một giá trị. Tham số chính thức là một biến cục bộ trên khung ngăn xếp của hàm. Đối số mà chúng tôi đã truyền vào là một biến cư trú tại một địa chỉ bộ nhớ hoàn toàn khác, trong khung ngăn xếp của người gọi

Truyền con trỏ (“Tham chiếu”) theo giá trị

Bây giờ đến phần thảo luận quan trọng nhất và là chìa khóa để hiểu tại sao JavaScript không được chuyển qua tham chiếu. Điều gì xảy ra khi chúng ta chuyển “tham chiếu đối tượng” (con trỏ) theo giá trị?

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
1

Trước tiên, hãy lưu ý một lần nữa rằng khi chúng ta chuyển một giá trị nào đó theo giá trị, tham số hình thức sẽ nhận được một bản sao giá trị của đối số

Nếu chúng ta chuyển một con trỏ vào một hàm dưới dạng đối số, thì tham số hình thức nhận được “giá trị” nào? . Con trỏ không có gì lạ mắt—chúng chỉ là các biến lưu trữ địa chỉ bộ nhớ của một số dữ liệu

Vì vậy, khi chúng ta chuyển một con trỏ vào một hàm dưới dạng đối số, tham số chính thức sẽ nhận được một bản sao của địa chỉ bộ nhớ mà đối số đang trỏ tới. Về cơ bản, cuối cùng chúng ta có hai biến khác nhau, trên hai khung ngăn xếp khác nhau (người gọi và người gọi), trỏ đến cùng một vị trí trong bộ nhớ

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Tại địa chỉ bộ nhớ đó là một đối tượng. Chúng ta có thể theo con trỏ tham số hình thức hoặc con trỏ đối số và truy cập địa chỉ bộ nhớ đó để sửa đổi đối tượng nằm ở đó. Trong trường hợp mã ở trên, chúng tôi đang đọc con trỏ tham số chính thức

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Những gì chúng ta không thể làm là có tham số chính thức trỏ đến một vị trí khác trong bộ nhớ và mong đợi đối số trỏ đến vị trí mới đó sau khi hàm trả về. Đây sẽ là trường hợp nếu đối số được truyền vào bằng tham chiếu. Nhưng JavaScript được truyền theo giá trị

Đối tượng javascript có được chuyển theo tham chiếu hoặc giá trị không?

Lưu ý rằng trong phạm vi của hàm

#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
40, chúng ta thực sự có thể quan sát thấy rằng
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
41 trỏ đến đối tượng này.
#include <iostream>
#include <string>

int main()
{
    // An ordinary variable
    std::string myName = "Aleksandr";

    // A reference variable that aliases myName
    std::string &myNickname = myName;

    // let's change myNickname to store a different string
    myNickname = "Alex";

    // the change is reflected in both variables!
    std::cout << myName << std::endl; // "Alex"
    std::cout << myNickname << std::endl; // "Alex"
}
42. Nhưng tất nhiên, điều này không ảnh hưởng đến đối số con trỏ—nó vẫn trỏ đến vị trí ban đầu trong bộ nhớ và đối tượng ở đó không bị ảnh hưởng

Gọi cho họ bất cứ điều gì bạn muốn

Tham chiếu đối tượng, tham chiếu, con trỏ—đây đều là các thuật ngữ hợp lệ. Vấn đề không phải là tham gia vào các cuộc chiến thuật ngữ với mọi người trực tuyến về việc vượt qua giá trị so với. vượt qua tham chiếu. Vào cuối ngày, tất cả các bạn có thể đang nói về cùng một điều, chỉ với cách hiểu hơi khác nhau

Lý do tôi khuyên bạn nên sử dụng các thuật ngữ như con trỏ hoặc điểm là vì đó là một thuật ngữ rất xúc giác. Bạn gần như có thể hình dung một biến trỏ đến một cái gì đó trong bộ nhớ. Điều này có thể giúp dễ dàng hình dung các mô hình bộ nhớ và hiểu điều gì đang diễn ra. Ngoài ra, như chúng ta đã thấy, thật khó hiểu khi nói về chuyển qua tham chiếu khi thực sự có hai định nghĩa riêng biệt cho thuật ngữ tham chiếu

Nhưng bây giờ bạn đã hiểu tham chiếu thực sự là gì trong JavaScript, bạn có thể tiếp tục sử dụng thuật ngữ này giống như những người khác vẫn làm để tránh gây nhầm lẫn cho người khác

Bản tóm tắt. JavaScript không được thông qua theo tham chiếu

Hãy tóm tắt lại những gì chúng tôi đề cập trong bài đăng này

  1. Cái mà một số ngôn ngữ gọi là “tham chiếu đối tượng” có thể được coi là con trỏ. Sẽ rất hữu ích khi coi chúng như các con trỏ để chúng ta không nhầm lẫn giữa thuật ngữ tham chiếu khi nó được sử dụng trong “tham chiếu đối tượng” với cách nó được sử dụng trong “chuyển qua tham chiếu. ”
  2. Tài liệu tham khảo thực sự là bí danh. "Tham chiếu" trong "chuyển qua tham chiếu" là về bí danh, không phải "tham chiếu đối tượng. ”
  3. Con trỏ là biến lưu trữ dữ liệu gián tiếp, thông qua địa chỉ bộ nhớ
  4. Khi truyền giá trị, tham số chính thức nhận một bản sao giá trị của đối số
  5. Khi truyền theo tham chiếu, tham số chính thức là bí danh cho đối số được truyền trong
  6. Khi chúng ta chuyển một con trỏ theo giá trị, tham số chính thức sẽ nhận được một bản sao của địa chỉ bộ nhớ mà đối số đang trỏ tới. Điều này cho phép chúng tôi sửa đổi đối tượng cơ bản được trỏ tới

Và đó là về tổng hợp nó lên

Thuộc tính

Xem trước phương tiện truyền thông xã hội. Ảnh của Olesya Grichina (Bapt)

Bình luận

Bình luận trên GitHub

Hệ thống nhận xét được cung cấp bởi API vấn đề GitHub. Bạn có thể tìm hiểu thêm về cách tôi xây dựng nó hoặc đăng nhận xét trên GitHub và nó sẽ hiển thị bên dưới sau khi bạn tải lại trang này

JavaScript có chuyển các đối tượng theo giá trị không?

Javascript luôn chuyển theo giá trị nên việc thay đổi giá trị của biến không bao giờ thay đổi giá trị nguyên thủy bên dưới (Chuỗi hoặc số). Tuy nhiên, khi một biến đề cập đến một đối tượng bao gồm mảng, giá trị là tham chiếu đến đối tượng.

Các đối tượng có được truyền theo tham chiếu hoặc giá trị không?

Tham chiếu đối tượng được truyền theo giá trị . Do đó, mặc dù Java truyền tham số cho phương thức theo giá trị, nhưng nếu biến trỏ đến tham chiếu đối tượng thì đối tượng thực cũng sẽ bị thay đổi.

JavaScript theo tham chiếu hay theo giá trị?

Javascript luôn được truyền theo giá trị , nhưng khi một biến tham chiếu đến một đối tượng (bao gồm cả mảng), thì "giá trị" là một tham chiếu đến đối tượng. Việc thay đổi giá trị của một biến không bao giờ thay đổi nguyên hàm hoặc đối tượng cơ bản, nó chỉ trỏ biến đó tới một nguyên hàm hoặc đối tượng mới.

Làm thế nào các đối tượng được thông qua trong JavaScript?

Các đối tượng được truyền theo tham chiếu . Do đó, các đối tượng sẽ hoạt động giống như chúng được truyền qua tham chiếu. Nếu một chức năng thay đổi một thuộc tính đối tượng, nó sẽ thay đổi giá trị ban đầu. Các thay đổi đối với thuộc tính đối tượng được hiển thị (được phản ánh) bên ngoài chức năng.