Hướng dẫn javascript return by reference - javascript trả về theo tham chiếu
Show Ngôn ngữ lập trình đầu tiên mà tôi học đó là C++, và trong khi học ngôn ngữ này, tôi được học khái niệm về pass-by-value và pass-by-reference; C++ mặc định sử dụng pass-by-value cho việc truyền argument vào function. Bạn cũng có thể chỉ định việc sử dụng pass-by-reference bằng cách đặt một dấu & giữa kiểu dữ liệu và tên param trong danh sách params đầu vào của function. Ngược lại, sau này, khi làm việc với Javascript, tôi đã phát hiện ra rằng ngôn ngữ này không cho bạn lựa chọn việc đó: Tự bản thân Javascript đã mặc định kiểu truyền dữ liệu của mình và cách các variable được gán với bộ nhớ. Trong quá trình làm việc với các project của mình, tôi đã gặp một số vấn đề liên quan tới khái niệm này, vì vậy tôi đã tìm hiểu và viết một bài dưới đây về cách mà Javascript thực hiện mọi thứ phía sau khi ta truyền argument vào function. Ôn lại kiến thứcĐầu tiên, hãy cũng nhau nhớ lại một số khái niệm cơ bản mà ta đã được học từ hồi Đại học. Đầu tiên, Javascript là một ngôn ngữ hướng đối tượng: điều này có nghĩa là hầu hết mọi thứ trong Javascript là Object. Ví dụ: function là Objects. Chỉ có 1 vài thứ trong Javascript không phải Object - chúng được gọi là các kiểu dữ liệu nguyên thủy (Primitive Data Types): string, number, boolean, null và undefined. Các Types này cũng có một đặc tính đó là immutable: chúng chỉ có thể được tạo ra mà không thể bị modify.Object. Ví dụ: function là Objects. Chỉ có 1 vài thứ trong Javascript không phải Object - chúng được gọi là các kiểu dữ liệu nguyên thủy (Primitive Data Types): string, number, boolean, null và undefined. Các Types này cũng có một đặc tính đó là immutable: chúng chỉ có thể được tạo ra mà không thể bị modify. Tiếp theo, cùng nhớ lại các khái niệm về pass-by-value và pass-by-reference.Variable giữ data của nó trong bộ nhớ (memory), nhưng bản thân variable nó chỉ là một con trỏ trỏ tới vùng nhớ trên memory. Bằng cách khởi tạo 1 variable, ta đã nói với chương trình "hãy chọn lấy một vùng trong bộ nhớ máy tính, lưu địa chỉ lại để sau này có thể gán giá trị hay lôi giá trị ra". Trong Pass-By-Reference , reference là để ám chỉ tới vùng bộ nhớ; trong khi với Pass-By-Value, value là để nói tới giá trị thực sự được lưu trong vùng bộ nhớ đó. Để dễ hiểu, ta có thể hình dùng 2 khái niệm này qua ví dụ dưới.
Như vậy, khi nói tới khái niệm pass-by-value tức là ta muốn nói rằng "việc truyền một argument vào 1 function thực chất là copy giá trị (value) của nó và chuyển cho parameter bên trong function". Điều này có nghĩa parameter bên trong function hoàn toàn không phải là variable mà ta truyền vào qua argument của function đó - 2 thứ này có chung giá trị, nhưng các giá trị đó được tạo trên các vùng bộ nhớ khác nhau, cho nên khi ta thay đổi giá trị của 1 trong các parameter đó trong function thì cũng không ảnh hưởng tới variable bên ngoài function. Không có mối liên kết nào giữa variable bên ngoài function và parameter bên trong function vì tất cả những gì ta đã làm là truyền giá trị.
Trong ví dụ trên: variable 2 vẫn có giá trị là 5 cho dù function 3 đã đổi nó thành 7. Đó là bởi vì vùng bộ nhớ mà 2 trỏ tới không được truyền vào cho function, thứ được truyền vào là giá trị 5. Parameter 6 được khởi tạo bên trong function trỏ tới một vùng bộ nhớ hoàn toàn mới và giữ giá trị 5, mà sau đó được thay đổi lại bằng 7.Ngược lại, với 7, thứ ta truyền vào function không phải là giá trị của biến mà là địa chỉ của vùng nhớ mà biến đó trỏ tới. Trong ví dụ tiếp theo, địa chỉ vùng nhớ của 2 được truyền vào function do đó 6 lúc này thực sự là 1 reference tới cùng một vùng nhớ với 2 ! Rõ ràng lúc này mọi thứ ta làm với 6 sẽ ảnh hưởng ngược lại tới 2.
Javascript function funny(number) { number+= 7; } let num = 5; funny(num); console.log(num); // 5 3Javascript tuân theo 1 quy tắc trong việc truyền argument vào function. Đó là Javascript luôn luôn sử dụng pass-by-value. Thứ được truyền vào argument list luôn luôn là value của biến bên ngoài.luôn luôn sử dụng pass-by-value. Thứ được truyền vào argument list luôn luôn là value của biến bên ngoài. Mặc dù vậy, khi mà kiểu của argument được truyền vào không phải là một primitive type, lúc này 4 được truyền vào lúc này thực chất lại là địa chỉ vùng nhớ (reference) ! Vì vậy, với 5 kiểu dữ liệu primitive (string, number, boolean, null và undefined) , chúng được pass by value như ta mong đọi. Nhưng với bất kì kiểu dữ liệu phức tạp nào khác (object hay array ...) , chúng được 5 với value chính là reference, vì vậy từ bên ngoài nhìn vào, chúng có vẻ như là được 6 !thực chất lại là địa chỉ vùng nhớ (reference) ! Vì vậy, với 5 kiểu dữ liệu primitive (string, number, boolean, null và undefined) , chúng được pass by value như ta mong đọi. Nhưng với bất kì kiểu dữ liệu phức tạp nào khác (object hay array ...) , chúng được 5 với value chính là reference, vì vậy từ bên ngoài nhìn vào, chúng có vẻ như là được 6 !(Với bất kì bài phỏng vấn nào, bạn nên nhớ lại sự khác biệt căn bản trên và không nên nhầm lẫn nó). Dưới đây là ví dụ về cách mà Javascript thực hiện với các argument kiểu primitive:
Và đây là với các argument có kiểu non-primitive:
Trong ví dụ trên, ta có thể thấy object và array trông như là được pass-by-reference , bởi vì 7 và 8 được thay đổi bên trong function. Đó là do địa chỉ bộ nhớ của 7 và 8 được truyền vào function thay vì giá trị thực sự của các array hay object này.Pass-by-reference rất tuyệt bởi vì khi ta truyền vào nhiều object hay array vào một function, ta không cần phải trả về tương tự từng đó thứ. Tuy nhiên , nếu như bạn đang luyện tập code theo chuẩn no-side effect trong functional programming , hãy cẩn thận bởi vì function của bạn - bằng cách thay đổi giá trị value bên ngoài nó - sẽ gây ra thay đổi state của chương trình. Tuy nhiên, điều này nằm ngoài phạm vi bài viết hiện tại, vì vậy hãy bỏ qua nó. Tricks of memory:Cần lưu ý lại rằng, không phải ta muốn làm gì với params kiểu object hay array cũng được và vẫn có sự thay đổi tương ứng đối với variable gốc được truyền vào. Nguyên do ở đây là các vùng bộ nhớ. Nếu như bạn chỉ cập nhật hay thay đổi các element nhỏ lẻ của biến (item của array, property của object ...) vậy thì reference tới variable vẫn giữ nguyên, hay nói cách khác là mối liên hệ giữa argument và variable bên ngoài vẫn còn khi cả 2 vẫn trỏ tới cùng 1 vùng bộ nhớ. Tuy nhiên, trong javascript, ngay khi ta assign (gán) một giá trị mới cho 1 biến, một vùng bộ nhớ mới sẽ được gọi tới , gán cho biến đó với giá trị được gán. Vì vậy khi ta assign nguyên một array cho một array parameter của function, ta sẽ khiến parameter đó trỏ tới một vùng bộ nhớ hoàn toàn mới, trong khi array gốc bên ngoài function vẫn trỏ tới vùng bộ nhớ gốc. Điều tương tự cũng xảy ra khi ta assign một object hoàn toàn mới cho một parameter kiểu object của function .Lúc này, mối liên kết (địa chỉ bộ nhớ được chia sử lúc trước) bị gãy , đồng thời việc thay đổi bên trong function không còn ảnh hưởng tới variable bên ngoài function nữa. Ở ví dụ cuối cùng phía trên kia , reference được giữ lại bởi vì function đã trực tiếp thay đổi giá trị bên trong array và object bằng các method push của array để thêm item cho mảng, cũng như trực tiếp update giá trị cho 1 property cho object. Thay vào đó, ở ví dụ dưới đây, tôi sẽ demo cho bạn thấy cách để bẻ gãy mối liên kết giữa variable bên ngoài với argument bên trong fuction bằng cách assign hẳn một giá trị mới cho params:
Trong ví dụ trên, khi [4,5,6] được assign cho 1, Javascript sẽ tạo một vùng bộ nhớ mới cho [4,5,6] và assign địa chỉ vùng nhớ đó cho 1. 2 địa chỉ của 1 và 7 lúc này đã khác nhau nên 1 không còn thay đổi ngược lại cho 7 nữa. Điều tương tự xả ra với 7 và 8Vấn đề trong bài toán của tôi.Lý do mà tôi viết bài này vởi vì trước đó tôi đã gặp phải một vấn đề trong project mà tôi đang làm việc. Khi mà tôi mong đợi rằng array mà tôi truyền vào 1 function sẽ được update dựa theo những gì tôi làm bên trong function, tôi nhanh chóng nhận ra nó không thực hiện đúng như tôi muốn. Hãy cùng tìm hiểu xem tôi đã sai ở đâu. Tôi có một mảng các id của user và một mảng tương đương chứa tọa độ GPS của các user đó. Điều tôi muốn là truyền cả 2 array này vào 1 function, cùng với khoảng cách xa nhất và 1 cặp tọa độ GPS, sau đó tính ra khoảng cách giữa mỗi cặp tọa độ trong mảng. Function sẽ lọc lấy các user nào đứng xa hơn khoảng cách max distance kia, và tạo một mảng các distances tương ứng với các user còn lại trong mảng. Mảng các distance này được trả lại bởi function và assign cho mảng bên ngoài function để thể hiện khoảng cách tới mỗi user từ cặp tọa độ độc lập. Điều quan trọng với function này đó là mảng user bên ngoài truyền vào có thể được filter bên trong function. Mặc dù vậy, khi tôi sử dụng method 9 của Javascript - 0 - nó lại trả về cho tôi một array hoàn toàn mới. Vì vậy, bên trong function, khi tôi assigin kết quả của hàm filter cho users array, mảng này đã được assign một vùng bộ nhớ hoàn toàn mới, đồng nghĩa với việc users array bên ngoài function không còn kết nối tới thứ bên trong function nữa.Code của tôi hiện tại như sau:
1 bên ngoài và bên trong function bị chia cắt khi tôi assign mảng đã filter cho 2Giải phápTrước khi tôi thực sự nghĩ tới vấn đề về bộ nhớ của Javascript, tôi đã thử vài cách khác, và đương nhiên là không có cách nào thực sự chạy ổn cả. Đối với Javascript thuần, tôi có thể sử dụng vòng lặp for-loop thay cho 0. Trong vòng lặp for, nếu 4 ít hơn hoặc bằng 5, thì tôi sẽ push 4 vào mảng 7 và sử dụng 8 để loại bỏ đi những user không mong muốn. Bằng cách này tôi đã trực tiếp thay đổi array của users thay vì gán một array mới cho nó. Tuy nhiên, vì hàm 9 thay đổi độ dài của array, thế nên dẫn đến việc tôi lại phải đảm bảo rằng biến couter của vòng lặp 0 được đồng bộ bằng với độ dài của array, và điều này cũng lại dẫn tới tôi cũng cần phải cắt bỏ các element ở vị trí tương đương phía trên mảng 1 để đảm bảo mảng 1 tương đương với mảng 2
Hàm này sẽ chạy, và sẽ chạy nhanh hơn là sử dụng 0! Vì thế khi ứng dụng của tôi trở nên nổi tiếng và có nhiều user sử dụng hơn, tôi có thể sẽ muốn optimize lại hàm này bằng việc sử dụng giải pháp này.Tuy nhiên, vì hiện tại tôi đã code project này bằng Angular, tồn tại một giải pháp khác cho tôi đó là sử dụng 5 với cú pháp 6Method này thực hiện một quy tình deep-copy từ array nguồn tới array đích, có nghĩa rằng nó copy từng element từ bên này sang bên kia, đồng nghĩa với địa chỉ bộ nhớ không thay đổi. Điều này khác với việc trỏ array đích tới vị trí ô nhớ của array nguồn, hay copy luôn toàn bộ array nguồn và trỏ array đích tới địa chỉ của ô nhớ vừa copy (như những gì hàm 7 sẽ làm khi không có argument nào) 8 mô tả chính xác những gì nó làm : nó clear sạch sẽ array đích và copy từng element từ array nguồn qua:
Lúc này, đối với vấn đề bên trên, tôi sử dụng 5, truyền vào array đã filter như là array đích và parameter 2 như là arrray nguồn.
IT WORKS ! Kết luậnTổng kết lại, Javascript truyền các 1 bằng value và có thể coi như truyền các kiểu dữ liệu khác (object, arrray ...) bằng reference. Nhưng bạn cần cẩn thận với việc re-assign một object hay array bên trong một function bởi việc đó đồng nghĩa với tạo ra 1 đối tượng hoàn toàn mới, không còn liên quan gì tới argument gốc nữa. Hiểu đơn giản, khi ta dùng operator 2 - với một array hay object hoàn toàn mới - ta đã vừa gán cả một vùng bộ nhớ mới cho variable. Thay vào đó, nếu ta cần giữ lại reference của variable trong function, ta có thể cần phải trực tiếp thay đổi từng property hay element của variable, với các hàm như 3, 4, ... thay vì các hàm sẽ trả về object mới (như là 5, 6, 7 ...)Nguồn dịch:https://medium.com/@TK_CodeBear/javascript-arrays-pass-by-value-and-thinking-about-memory-fffb7b0bf43 https://hackernoon.com/grasp-by-value-and-by-reference-in-javascript-7ed75efa1293 |