Các mảng được lưu trữ trong bộ nhớ javascript như thế nào?

Hầu hết thời gian, bạn có thể không biết gì về quản lý bộ nhớ với tư cách là nhà phát triển JavaScript. Rốt cuộc, công cụ JavaScript xử lý việc này cho bạn

Tuy nhiên, lúc này hay lúc khác, bạn sẽ gặp phải các sự cố, chẳng hạn như rò rỉ bộ nhớ, mà bạn chỉ có thể giải quyết nếu biết cách cấp phát bộ nhớ hoạt động

Trong bài viết này, tôi sẽ giới thiệu cho bạn cách hoạt động của cấp phát bộ nhớ và thu gom rác cũng như cách bạn có thể tránh một số lỗi rò rỉ bộ nhớ phổ biến

JavaScript hoạt động như thế nào trong trình duyệt?

Bài viết này là phần thứ hai trong loạt bài viết của tôi, nơi tôi giải thích cách JavaScript hoạt động trong trình duyệt. Để nhận các bài viết mới nhất của tôi vào hộp thư đến của bạn, hãy đăng ký nhận bản tin của tôi

  • Phần 1. Giải thích về vòng lặp sự kiện JavaScript và ngăn xếp cuộc gọi
  • Phần 2. Quản lý bộ nhớ của JavaScript. Giải thích về bộ sưu tập đống và rác

Vòng đời bộ nhớ

Trong JavaScript, khi chúng ta tạo các biến, hàm hoặc bất kỳ thứ gì bạn có thể nghĩ đến, công cụ JS sẽ phân bổ bộ nhớ cho việc này và giải phóng nó khi không cần thiết nữa

Cấp phát bộ nhớ là quá trình dự trữ không gian trong bộ nhớ, trong khi giải phóng bộ nhớ sẽ giải phóng không gian, sẵn sàng để sử dụng cho mục đích khác

Mỗi khi chúng ta gán một biến hoặc tạo một hàm, bộ nhớ cho biến đó luôn trải qua các giai đoạn sau

  • Cấp phát bộ nhớ

    JavaScript đảm nhận việc này cho chúng tôi. Nó phân bổ bộ nhớ mà chúng ta sẽ cần cho đối tượng mà chúng ta đã tạo

  • Sử dụng bộ nhớ

    Sử dụng bộ nhớ là điều chúng tôi làm rõ ràng trong mã của mình. Đọc và ghi vào bộ nhớ không gì khác hơn là đọc hoặc ghi từ hoặc tới một biến

  • Giải phóng bộ nhớ

    Bước này cũng được xử lý bởi công cụ JavaScript. Khi bộ nhớ được cấp phát được giải phóng, nó có thể được sử dụng cho mục đích mới

"Đối tượng" trong ngữ cảnh quản lý bộ nhớ không chỉ bao gồm các đối tượng JS mà còn bao gồm các chức năng và phạm vi chức năng

Bộ nhớ heap và stack

Bây giờ chúng tôi biết rằng đối với mọi thứ chúng tôi xác định trong JavaScript, công cụ sẽ phân bổ bộ nhớ và giải phóng nó khi chúng tôi không cần nó nữa

Câu hỏi tiếp theo nảy ra trong đầu tôi là. Cái này sẽ được lưu trữ ở đâu?

Công cụ JavaScript có hai nơi để lưu trữ dữ liệu. Bộ nhớ heap và stack

Heaps và stacks là hai cấu trúc dữ liệu mà engine sử dụng cho các mục đích khác nhau

Cây rơm. Cấp phát bộ nhớ tĩnh

Bạn có thể biết ngăn xếp từ phần đầu tiên của loạt bài này về ngăn xếp cuộc gọi và vòng lặp sự kiện, nơi tôi tập trung vào cách nó được sử dụng để theo dõi các chức năng mà trình thông dịch JS cần gọi

Tất cả các giá trị được lưu trữ trong ngăn xếp vì tất cả chúng đều chứa các giá trị nguyên thủy

Ngăn xếp là cấu trúc dữ liệu mà JavaScript sử dụng để lưu trữ dữ liệu tĩnh. Dữ liệu tĩnh là dữ liệu mà công cụ biết kích thước tại thời điểm biên dịch. Trong JavaScript, điều này bao gồm các giá trị nguyên thủy [chuỗi, số, booleans, không xác định và null] và các tham chiếu trỏ đến các đối tượng và hàm

Vì công cụ biết rằng kích thước sẽ không thay đổi nên nó sẽ phân bổ một lượng bộ nhớ cố định cho mỗi giá trị

Quá trình cấp phát bộ nhớ ngay trước khi thực thi được gọi là cấp phát bộ nhớ tĩnh

Vì công cụ phân bổ một lượng bộ nhớ cố định cho các giá trị này, nên có giới hạn về mức độ lớn của các giá trị nguyên thủy.

Giới hạn của các giá trị này và toàn bộ ngăn xếp khác nhau tùy thuộc vào trình duyệt

đống. Cấp phát bộ nhớ động

Heap là một không gian khác để lưu trữ dữ liệu nơi JavaScript lưu trữ các đối tượng và chức năng

Không giống như ngăn xếp, công cụ không phân bổ lượng bộ nhớ cố định cho các đối tượng này. Thay vào đó, nhiều không gian hơn sẽ được phân bổ khi cần thiết

Cấp phát bộ nhớ theo cách này còn được gọi là cấp phát bộ nhớ động

Để có cái nhìn tổng quan, dưới đây là các tính năng của hai kho được so sánh cạnh nhau

StackHeap Các giá trị và tham chiếu nguyên thủyCác đối tượng và hàmKích thước được biết tại thời điểm biên dịchKích thước được biết tại thời điểm chạyPhân bổ một lượng bộ nhớ cố địnhKhông giới hạn cho mỗi đối tượng

ví dụ

Chúng ta hãy xem xét một vài mã ví dụ. Trong chú thích tôi đề cập đến những gì đang được phân bổ

const person = {
  name: 'John',
  age: 24,
};

JS cấp phát bộ nhớ cho đối tượng này trong heap. Các giá trị thực tế vẫn còn nguyên thủy, đó là lý do tại sao chúng được lưu trữ trong ngăn xếp

const hobbies = ['hiking', 'reading'];

Mảng cũng là đối tượng, đó là lý do tại sao chúng được lưu trữ trong heap

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string

Các giá trị nguyên thủy là bất biến, có nghĩa là thay vì thay đổi giá trị ban đầu, JavaScript sẽ tạo một giá trị mới

Tài liệu tham khảo trong JavaScript

Tất cả các biến đầu tiên trỏ đến ngăn xếp. Trong trường hợp đó là giá trị không nguyên thủy, ngăn xếp chứa tham chiếu đến đối tượng trong heap

Bộ nhớ của heap không được sắp xếp theo bất kỳ cách cụ thể nào, đó là lý do tại sao chúng ta cần giữ một tham chiếu đến nó trong ngăn xếp. Bạn có thể coi các tham chiếu là địa chỉ và các đối tượng trong heap là những ngôi nhà mà các địa chỉ này thuộc về.

Hãy nhớ rằng JavaScript lưu trữ các đối tượng và hàm trong heap. Các giá trị và tham chiếu nguyên thủy được lưu trữ trong ngăn xếp

Trong hình này, chúng ta có thể quan sát cách các giá trị khác nhau được lưu trữ. Lưu ý cách cả

const hobbies = ['hiking', 'reading'];
2 và
const hobbies = ['hiking', 'reading'];
3 đều trỏ đến cùng một đối tượng

ví dụ

const person = {
  name: 'John',
  age: 24,
};

Điều này tạo ra một đối tượng mới trong đống và một tham chiếu đến nó trong ngăn xếp

Tài liệu tham khảo là một khái niệm cốt lõi về cách thức hoạt động của JavaScript. Đi sâu hơn vào chi tiết ở đây sẽ nằm ngoài phạm vi của bài viết này, nhưng nếu bạn muốn tìm hiểu thêm về nó, hãy cho tôi biết trong các nhận xét và đăng ký nhận bản tin của tôi

Thu gom rác thải

Bây giờ chúng ta biết cách JavaScript phân bổ bộ nhớ cho tất cả các loại đối tượng, nhưng nếu chúng ta nhớ vòng đời của bộ nhớ, thì còn thiếu một bước cuối cùng. giải phóng bộ nhớ

Giống như cấp phát bộ nhớ, công cụ JavaScript cũng xử lý bước này cho chúng tôi. Cụ thể hơn, bộ thu gom rác đảm nhiệm việc này

Khi công cụ JavaScript nhận ra rằng một biến hoặc hàm đã cho không còn cần thiết nữa, nó sẽ giải phóng bộ nhớ mà nó đã chiếm

Vấn đề chính của vấn đề này là liệu bộ nhớ có còn cần thiết hay không là một vấn đề không thể giải quyết được, điều đó có nghĩa là không thể có một thuật toán nào có thể thu thập tất cả bộ nhớ không cần thiết nữa vào đúng thời điểm nó trở nên lỗi thời

Một số thuật toán cung cấp một xấp xỉ tốt cho vấn đề. Tôi sẽ thảo luận về những cái được sử dụng nhiều nhất trong phần này. Bộ sưu tập rác đếm tham chiếu và thuật toán đánh dấu và quét

Bộ sưu tập rác đếm tham chiếu

Đây là phép tính gần đúng dễ nhất. Nó thu thập các đối tượng không có tham chiếu trỏ đến chúng

Hãy cùng xem ví dụ sau. Các dòng đại diện cho tài liệu tham khảo

Lưu ý rằng trong khung cuối cùng chỉ có

const hobbies = ['hiking', 'reading'];
4 nằm trong đống vì nó là đối tượng có tham chiếu cuối cùng

chu kỳ

Vấn đề với thuật toán này là nó không xem xét các tham chiếu tuần hoàn. Điều này xảy ra khi một hoặc nhiều đối tượng tham chiếu lẫn nhau nhưng không thể truy cập chúng thông qua mã nữa

let son = {
  name: 'John',
};

let dad = {
  name: 'Johnson',
}

son.dad = dad;
dad.son = son;

son = null;
dad = null;

Vì các đối tượng

const hobbies = ['hiking', 'reading'];
5 và
const hobbies = ['hiking', 'reading'];
6 tham chiếu lẫn nhau nên thuật toán sẽ không giải phóng bộ nhớ đã cấp phát. Không còn cách nào để chúng tôi truy cập vào hai đối tượng nữa

Đặt chúng thành

const hobbies = ['hiking', 'reading'];
7 sẽ không làm cho thuật toán đếm tham chiếu nhận ra rằng chúng không thể được sử dụng nữa vì cả hai đều có tham chiếu đến

Thuật toán đánh dấu và quét

Thuật toán đánh dấu và quét có giải pháp cho các phụ thuộc theo chu kỳ. Thay vì chỉ đếm các tham chiếu đến một đối tượng nhất định, nó sẽ phát hiện xem chúng có thể truy cập được từ đối tượng gốc hay không

Root trong trình duyệt là đối tượng

const hobbies = ['hiking', 'reading'];
8, trong khi ở NodeJS, đây là
const hobbies = ['hiking', 'reading'];
9

Thuật toán đánh dấu các đối tượng không thể truy cập là rác và quét [thu thập] chúng sau đó. Các đối tượng gốc sẽ không bao giờ được thu thập

Bằng cách này, sự phụ thuộc theo chu kỳ không còn là vấn đề nữa. Trong ví dụ trước đó, cả đối tượng

const hobbies = ['hiking', 'reading'];
6 và
const hobbies = ['hiking', 'reading'];
5 đều không thể truy cập được từ thư mục gốc. Như vậy, cả hai sẽ được đánh dấu là rác và được thu gom

Kể từ năm 2012, thuật toán này được triển khai trong tất cả các trình duyệt hiện đại. Các cải tiến chỉ được thực hiện đối với hiệu suất và triển khai chứ không phải ý tưởng cốt lõi của thuật toán

đánh đổi

Thu gom rác tự động cho phép chúng ta tập trung vào việc xây dựng ứng dụng thay vì mất thời gian quản lý bộ nhớ. Tuy nhiên, có một số sự đánh đổi mà chúng ta cần lưu ý

Sử dụng bộ nhớ

Do các thuật toán không thể biết chính xác khi nào bộ nhớ sẽ không cần thiết nữa, các ứng dụng JavaScript có thể sử dụng nhiều bộ nhớ hơn mức thực sự cần

Mặc dù các đối tượng được đánh dấu là rác, bộ thu gom rác quyết định khi nào và liệu bộ nhớ được cấp phát có được thu thập hay không

Nếu bạn cần ứng dụng của mình có bộ nhớ hiệu quả nhất có thể, tốt hơn hết bạn nên sử dụng ngôn ngữ cấp thấp hơn. Nhưng hãy nhớ rằng điều này đi kèm với sự đánh đổi của chính nó

Màn biểu diễn

Các thuật toán thu gom rác cho chúng tôi thường chạy định kỳ để dọn dẹp các đối tượng không sử dụng

Vấn đề với điều này là chúng tôi, những nhà phát triển, không biết chính xác khi nào điều này sẽ xảy ra. Thu gom nhiều rác hoặc thu gom rác thường xuyên có thể ảnh hưởng đến hiệu suất vì nó cần một lượng sức mạnh tính toán nhất định để làm như vậy

Tuy nhiên, tác động thường không đáng chú ý đối với người dùng hoặc nhà phát triển

Rò rỉ bộ nhớ

Được trang bị tất cả những kiến ​​thức này về quản lý bộ nhớ, chúng ta hãy xem xét các rò rỉ bộ nhớ phổ biến nhất

Bạn sẽ thấy rằng những điều này có thể dễ dàng tránh được nếu một người hiểu những gì đang diễn ra đằng sau hậu trường

biến toàn cầu

Lưu trữ dữ liệu trong các biến toàn cục có lẽ là kiểu rò rỉ bộ nhớ phổ biến nhất

Ví dụ, trong trình duyệt, nếu bạn sử dụng

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
2 thay vì
let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
3 hoặc
let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
4—hoặc loại bỏ hoàn toàn từ khóa—công cụ sẽ gắn biến vào đối tượng
const hobbies = ['hiking', 'reading'];
8

Điều tương tự cũng xảy ra với các hàm được xác định bằng từ khóa

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
6

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
0

Cả 3 biến

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
7,
let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
8, và
let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
9 sẽ được gắn vào đối tượng
const hobbies = ['hiking', 'reading'];
8

Điều này chỉ áp dụng cho các biến và chức năng được xác định trong phạm vi toàn cầu. Nếu bạn muốn tìm hiểu thêm về điều này, hãy xem bài viết này giải thích phạm vi JavaScript

Tránh điều này bằng cách chạy mã của bạn ở chế độ nghiêm ngặt

Ngoài việc vô tình thêm các biến vào thư mục gốc, có nhiều trường hợp bạn có thể cố tình làm điều này

Bạn chắc chắn có thể sử dụng các biến toàn cục, nhưng hãy đảm bảo rằng bạn giải phóng dung lượng khi không cần dữ liệu nữa

Để giải phóng bộ nhớ, hãy gán biến toàn cục cho

const hobbies = ['hiking', 'reading'];
7

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
6

Tôi muốn làm cho bài viết này dễ hiểu nhất có thể. Nếu bạn có bất kỳ câu hỏi mở nào, vui lòng gửi cho tôi hoặc để lại nhận xét. Tôi sẽ cố gắng giúp bạn và cải thiện bài viết với phản hồi của bạn

Quên hẹn giờ và gọi lại

Quên bộ hẹn giờ và gọi lại có thể làm tăng mức sử dụng bộ nhớ của ứng dụng. Đặc biệt trong Ứng dụng một trang [SPA], bạn phải cẩn thận khi thêm trình xử lý sự kiện và gọi lại một cách linh hoạt

Đồng hồ hẹn giờ bị lãng quên

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
7

Đoạn mã trên chạy chức năng cứ sau 2 giây. Nếu bạn có mã như thế này trong dự án của mình, bạn có thể không cần mã này để chạy mọi lúc

Các đối tượng được tham chiếu trong khoảng thời gian sẽ không bị thu gom rác miễn là khoảng thời gian đó không bị hủy bỏ

Đảm bảo xóa khoảng thời gian khi không cần thiết nữa

let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number

name = 'John Doe'; // allocates memory for a new string
const firstName = name.slice[0,4]; // allocates memory for a new string
8

Điều này đặc biệt quan trọng trong các SPA. Ngay cả khi điều hướng khỏi trang cần khoảng thời gian này, nó vẫn sẽ chạy ở chế độ nền

cuộc gọi lại bị lãng quên

Giả sử bạn thêm một trình nghe

const person = {
  name: 'John',
  age: 24,
};
2 vào một nút, nút này sau đó sẽ bị xóa

Các trình duyệt cũ không thể thu thập trình nghe, nhưng ngày nay, đây không còn là vấn đề nữa

Tuy nhiên, bạn nên xóa trình xử lý sự kiện khi bạn không cần chúng nữa

const hobbies = ['hiking', 'reading'];
0

Ngoài tham chiếu DOM

Rò rỉ bộ nhớ này tương tự như những lần trước. Nó xảy ra khi lưu trữ các phần tử DOM trong JavaScript

const hobbies = ['hiking', 'reading'];
0

Khi bạn xóa bất kỳ phần tử nào trong số đó, có thể bạn cũng muốn đảm bảo xóa luôn phần tử này khỏi mảng

Nếu không, các phần tử DOM này không thể được thu thập

const hobbies = ['hiking', 'reading'];
1

Xóa phần tử khỏi mảng giúp nó đồng bộ với DOM

Vì mọi phần tử DOM cũng giữ một tham chiếu đến nút cha của nó, nên bạn sẽ ngăn trình thu gom rác thu thập cha và con của phần tử

Sự kết luận

Trong bài viết này, tôi đã tóm tắt các khái niệm cốt lõi về quản lý bộ nhớ trong JavaScript

Viết bài viết này đã giúp tôi làm sáng tỏ một số khái niệm mà tôi không hiểu hoàn toàn và tôi hy vọng điều này sẽ đóng vai trò là một cái nhìn tổng quan về cách hoạt động của quản lý bộ nhớ trong JavaScript

Tôi đã học được điều này từ một số bài báo tuyệt vời khác mà tôi cũng muốn đề cập ở đây

  • Quản lý bộ nhớ - Tài liệu web MDN

    Một nguồn tài nguyên tuyệt vời để đọc lại chủ đề này

  • Quản lý bộ nhớ trong V8

    Bài viết này đi sâu hơn vào chi tiết về cách thức hoạt động của động cơ V8. Tôi thấy điều này rất thú vị

Các bài viết khác có thể bạn quan tâm

  • Giải thích về vòng lặp sự kiện JavaScript và ngăn xếp cuộc gọi

    Trong phần đầu tiên của loạt blog này, tôi giải thích lý do tại sao bạn có thể thực hiện đồng thời nhiều việc trong trình duyệt, mặc dù JavaScript là ngôn ngữ đơn luồng

  • 9 chủ đề yêu thích của tôi về "Lập trình viên thực dụng"

    Đọc là một cách tuyệt vời để cải thiện kỹ năng lập trình của bạn. Trong bài viết này, tôi chia sẻ những điểm chính của tôi từ cuốn sách lập trình yêu thích của tôi

  • Heap JavaScript hết lỗi bộ nhớ

    Bài viết này có liên quan chặt chẽ với bài viết này vì nó mô tả những gì bạn có thể làm với Node của mình. ứng dụng js hết bộ nhớ

Nếu bạn muốn có nhiều bài viết như thế này, hãy để lại tin nhắn cho tôi và đảm bảo rằng bạn đã đăng ký nhận bản tin email của tôi

Dữ liệu mảng được lưu trữ trong bộ nhớ như thế nào?

Mảng lưu trữ các phần tử của nó ở các vị trí bộ nhớ liền kề . Nếu bạn đã tạo mảng cục bộ thì nó sẽ nằm trên ngăn xếp. Nơi các phần tử được lưu trữ tùy thuộc vào đặc tả lưu trữ.

Mảng JavaScript được lưu trữ như thế nào?

Các đối tượng mảng JavaScript có thể được lưu trữ trong các biến và được xử lý giống như cách bạn xử lý với bất kỳ loại dữ liệu nào khác. Sự khác biệt là chúng ta có thể truy cập từng giá trị trong danh sách một cách riêng lẻ và thực hiện các hoạt động khác nhau, chẳng hạn như lặp qua nó.

Làm thế nào là các phần tử mảng được lưu trữ trong vị trí bộ nhớ?

Các phần tử mảng luôn được lưu trữ trong các vị trí bộ nhớ tuần tự . Do đó, câu trả lời đúng là phương án [A]

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

“Các biến trong JavaScript [và hầu hết các ngôn ngữ lập trình khác] được lưu trữ ở hai nơi. ngăn xếp và đống . Ngăn xếp thường là một vùng bộ nhớ liên tục phân bổ ngữ cảnh cục bộ cho từng chức năng thực thi. Heap là một vùng lớn hơn nhiều lưu trữ mọi thứ được phân bổ động.

Chủ Đề