Văn hay trong hiện tại, chữ tốt ở tương lai

Giải mã bản chất cốt lõi của JavaScript | Chương 3.2

Giải mã bản chất cốt lõi của JavaScript (Phần 3, chương 2) là nội dung chuyển ngữ Việt ngữ từ tác phẩm kinh điển You Don't Know JS Yet của Kyle Simpson.

53 phút đọc.

0 lượt xem.

Giải mã bản chất cốt lõi của JavaScript (Phần 3, chương 2) là nội dung chuyển ngữ Việt ngữ từ tác phẩm kinh điển You Don’t Know JS Yet của Kyle Simpson.

Mở đầu

Đối tượng không chỉ đơn thuần là những vật chứa thụ động dùng để lưu trữ nhiều giá trị dữ liệu khác nhau, mặc dù rõ ràng đó chính là bối cảnh phổ biến nhất cho phần lớn các tương tác thực tiễn với đối tượng trong quá trình phát triển phần mềm. Để có thể thấu hiểu một cách trọn vẹn và sâu sắc nhất về cơ chế vận hành của đối tượng trong ngôn ngữ JavaScript, cũng như khai thác tối đa sức mạnh của việc sử dụng các đối tượng trong các chương trình máy tính, chúng ta bắt buộc phải tiến hành soi xét kỹ lưỡng hơn vào hàng loạt các đặc điểm nội tại của đối tượng cùng với các thuộc tính của chúng, những thứ có khả năng tác động mạnh mẽ đến hành vi của đối tượng khi hệ thống hoặc lập trình viên tương tác với chúng. Toàn bộ những đặc điểm tinh vi này, vốn làm nhiệm vụ định nghĩa nên hành vi nền tảng nằm sâu bên dưới của các đối tượng, được giới hàn lâm khoa học máy tính gọi chung bằng một thuật ngữ kỹ thuật mang tính chính thức là giao thức siêu đối tượng… Giao thức siêu đối tượng mang lại giá trị vô cùng to lớn không chỉ trong việc giúp chúng ta giải mã và dự đoán xem các đối tượng sẽ phản ứng và hành xử như thế nào trong các kịch bản thực thi, mà nó còn cung cấp chìa khóa để chúng ta có thể chủ động ghi đè lên những hành vi mặc định của đối tượng, qua đó uốn nắn và nhào nặn ngôn ngữ lập trình này sao cho nó khớp nối một cách hoàn hảo nhất với những nhu cầu kiến trúc phức tạp của chương trình.

Bộ mô tả thuộc tính và cơ chế kiểm soát

Việc quản lý và điều phối các thuộc tính bên trong một đối tượng không diễn ra một cách ngẫu nhiên, mà tuân thủ theo một hệ thống quy tắc siêu dữ liệu cực kỳ chặt chẽ, cho phép các kiến trúc sư phần mềm can thiệp sâu vào vòng đời của từng mảnh dữ liệu.

Khái niệm và cấu trúc của bộ mô tả thuộc tính

Mỗi một thuộc tính đang hiện diện trên một đối tượng đều được hệ thống mô tả và theo dõi ở tầng nội bộ thông qua một cơ cấu dữ liệu đặc thù được biết đến với tên gọi là bộ mô tả thuộc tính… Xét về mặt bản chất cấu trúc, bản thân bộ mô tả này cũng chính là một đối tượng vật lý thực thụ trong không gian bộ nhớ, hay còn được giới chuyên môn gọi bằng thuật ngữ siêu đối tượng, mang trên mình một vài thuộc tính nội tại riêng biệt, hay còn gọi là các thuộc tính mô tả, đóng vai trò như những mệnh lệnh điều khiển độc đoán nhằm ra lệnh xem cái thuộc tính mục tiêu đó sẽ phải hành xử như thế nào trong suốt vòng đời của nó. Kể từ thời kỳ Tiêu chuẩn ngôn ngữ kịch bản châu Âu phiên bản thứ năm, các nhà phát triển đã được trao quyền năng để có thể truy xuất và lôi ra ánh sáng bộ mô tả thuộc tính của bất kỳ một thuộc tính nào đang sờ sờ tồn tại bằng cách triệu hồi phương thức tiện ích tích hợp sẵn là phương thức lấy bộ mô tả thuộc tính sở hữu của đối tượng. Phương thức này khi được kích hoạt sẽ tiến hành soi chiếu vào bảng băm nội bộ của đối tượng, trích xuất cấu trúc dữ liệu ẩn và nôn ra cho chúng ta một đối tượng mô tả hoàn chỉnh chứa đựng các cờ trạng thái như giá trị thực tế, khả năng liệt kê, khả năng ghi đè, và khả năng cấu hình lại, tạo tiền đề cho việc phân tích và gỡ lỗi các hành vi dữ liệu bất thường ở cấp độ vi mô.

Vượt xa hơn cả khả năng chỉ thuần túy đọc và truy xuất thông tin, chúng ta thậm chí còn có thể tận dụng chính cấu trúc của một bộ mô tả như vậy để chủ động định nghĩa và đắp thêm một thuộc tính hoàn toàn mới lên trên một đối tượng, bằng cách viện đến sức mạnh của phương thức định nghĩa thuộc tính của đối tượng, một thứ vũ khí cũng được trình làng từ Tiêu chuẩn ngôn ngữ kịch bản châu Âu phiên bản thứ năm. Khi sử dụng phương thức quyền năng này, lập trình viên có thể truyền vào một đối tượng mô tả chi tiết, trong đó bao gồm giá trị cụ thể và thiết lập các cờ trạng thái; nếu những cờ trạng thái như khả năng liệt kê, khả năng ghi đè, hay khả năng cấu hình bị bỏ trống, cỗ máy thực thi ngôn ngữ JavaScript sẽ tự động áp dụng các giá trị mặc định cho chúng. Tuy nhiên, cần phải gióng lên một hồi chuông cảnh báo về mặt khái niệm: trong vô số các cuộc thảo luận kỹ thuật về việc sao chép hay nhân bản các thuộc tính, một người bình thường rất dễ rơi vào ảo tưởng rằng quá trình sao chép đó sẽ được thực thi một cách toàn diện ở cấp độ bộ mô tả thuộc tính. Sự thật phũ phàng là hoàn toàn không có bất kỳ một thao tác sao chép thông thường nào thực sự vận hành theo cái cơ chế tinh vi đó; tất cả chúng đều chỉ đơn thuần tiến hành một thao tác truy cập và gán giá trị mang phong cách của toán tử bằng mộc mạc, dẫn đến một hệ lụy tất yếu là hệ thống sẽ nhắm mắt làm ngơ và phớt lờ hoàn toàn mọi tiểu tiết phức tạp trong cái cách mà bộ mô tả nền tảng của một thuộc tính được định nghĩa ban đầu.

Mặc dù có vẻ như đây là một kỹ thuật hiếm khi vác mặt ra trong các đoạn mã nghiệp vụ thông thường ngoài thế giới thực, thế nhưng nền tảng ngôn ngữ vẫn cung cấp cho chúng ta một đặc quyền để có thể định nghĩa hàng tá các thuộc tính cùng một lúc trong một thao tác duy nhất, mỗi thuộc tính sẽ đi kèm với một bộ mô tả mang tính cá nhân hóa của riêng nó thông qua phương thức định nghĩa các thuộc tính của đối tượng. Việc bắt gặp kỹ thuật này thực sự không mấy phổ biến, nguyên nhân sâu xa là bởi vì trong thực tiễn thiết kế phần mềm, các kiến trúc sư rất hiếm khi bị dồn vào một tình huống bắt buộc phải giành quyền kiểm soát một cách quá đặc thù đối với việc định nghĩa đồng loạt nhiều thuộc tính như vậy. Dẫu vậy, nó vẫn chứng tỏ được giá trị chiến lược to lớn của mình và trở nên vô cùng hữu dụng trong một vài kịch bản kiến trúc giới hạn, điển hình như khi cần khởi tạo hàng loạt các hằng số cấu hình hệ thống, hoặc khi xây dựng các thư viện cốt lõi đòi hỏi tính đóng gói dữ liệu cực kỳ cao và không cho phép sự can thiệp sửa đổi trái phép từ các phân vùng mã nguồn bên ngoài chương trình. Việc am hiểu các tầng sâu này giúp kỹ sư tối ưu hóa tính an toàn của luồng dữ liệu.

Thuộc tính truy cập và cơ chế hàm chặn

Trong đại đa số các trường hợp phổ thông, một bộ mô tả thuộc tính thường sẽ làm nhiệm vụ định nghĩa một thuộc tính giá trị tĩnh, như những gì đã được phơi bày ở phần trước. Thế nhưng, hệ sinh thái ngôn ngữ còn cho phép định nghĩa một chủng loại thuộc tính đặc chủng mang nhiều sắc thái tinh vi hơn, được giới chuyên môn gọi mặt đặt tên là thuộc tính truy cập, hay nói một cách bình dân hơn là các hàm lấy giá trị và hàm thiết lập giá trị. Đối với những thuộc tính mang hình hài dị biệt như thế này, bộ mô tả của nó sẽ thẳng tay cự tuyệt việc định nghĩa một thuộc tính giá trị cố định nào đó, mà thay vào đó, kết cấu nội tạng của nó sẽ trông giống như một tập hợp chứa các hàm thực thi: một hàm sẽ tự động được hệ thống bóp cò kích nổ mỗi khi có ai đó cố tình truy xuất giá trị, và một hàm khác sẽ được triệu hồi mỗi khi có thao tác gán một giá trị mới vào thuộc tính đó. Cơ chế chặn bắt này phá vỡ tư duy lập trình cấu trúc truyền thống, biến một biến số tưởng chừng như tĩnh lặng thành một thực thể phản ứng động có khả năng tự đánh giá và kiểm duyệt luồng thông tin đi vào và đi ra khỏi nó.

Một hàm lấy giá trị thoạt nhìn thì có vẻ như đang thực thi một thao tác truy cập thuộc tính mộc mạc bằng toán tử dấu chấm, thế nhưng nằm sâu dưới lớp vỏ bọc cú pháp đó, cỗ máy ngôn ngữ lại đang âm thầm triệu hồi phương thức lấy giá trị đã được định nghĩa từ trước; hành vi này mang lại một cảm giác y hệt như thể lập trình viên vừa mới trực tiếp gọi một phương thức hàm vậy. Hoàn toàn tương đồng về mặt triết lý, một hàm thiết lập giá trị khoác lên mình hình hài của một phép gán thuộc tính bằng toán tử bằng, thế nhưng thực chất nó lại đang kích hoạt phương thức thiết lập giá trị cùng với dữ liệu được truyền vào; điều này mang lại ảo giác như thể hệ thống vừa mới thực thi một lời gọi hàm truyền tham số. Sự trừu tượng hóa này mang lại sức mạnh to lớn trong việc bảo vệ tính toàn vẹn của dữ liệu nội bộ; thay vì phơi bày trực tiếp biến số ra môi trường bên ngoài để mặc cho các đoạn mã khác cấu xé, đối tượng giờ đây tự dựng lên một hàng rào kiểm duyệt vững chắc, cho phép ghi nhật ký các thao tác, định dạng lại dữ liệu đầu vào, hoặc thậm chí là thẳng tay từ chối việc gán giá trị nếu như dữ liệu đó không thỏa mãn các điều kiện quy chuẩn kinh doanh đã được thiết lập chặt chẽ.

Lợi ích kiến trúc của việc tận dụng các thuộc tính truy cập trong thiết kế hệ thống phần mềm quy mô lớn là không thể đong đếm được, đặc biệt là trong kỷ nguyên của lập trình phản ứng và các kiến trúc dựa trên sự kiện. Lấy một ví dụ học thuật để minh họa: nếu chúng ta khởi tạo một hàm lấy giá trị in ra dòng thông báo mỗi khi nó được truy cập và trả về một con số ảo, đồng thời thiết lập một hàm thiết lập giá trị có nhiệm vụ in ra thông báo phớt lờ mọi phép gán, thì khi một đoạn mã bên ngoài cố tình gán một con số mới vào thuộc tính đó, hệ thống sẽ thực thi hàm chặn bắt, ghi nhận hành vi gán, nhưng giá trị cốt lõi khi được truy xuất lại vẫn không hề thay đổi. Kỹ thuật này chính là xương sống cho các hệ thống ràng buộc dữ liệu hai chiều trong các thư viện giao diện người dùng hiện đại, nơi mà việc thay đổi một thuộc tính trạng thái sẽ tự động kích hoạt các hàm thiết lập để vẽ lại toàn bộ giao diện màn hình mà không cần đến bất kỳ sự can thiệp thủ công nào từ phía lập trình viên, minh chứng cho sự ưu việt của giao thức siêu đối tượng trong việc giải quyết các bài toán công nghệ phức tạp.

Các cờ trạng thái kiểm soát hành vi thuộc tính

Bên cạnh thuộc tính giá trị tĩnh hoặc các hàm truy cập động, cấu trúc của một bộ mô tả thuộc tính còn ẩn chứa ba thuộc tính mô tả bổ sung mang tính chất sinh tử khác, bao gồm: cờ khả năng liệt kê, cờ khả năng ghi đè, và cờ khả năng cấu hình. Thuộc tính cờ khả năng liệt kê nắm giữ quyền sinh sát trong việc kiểm soát xem liệu một thuộc tính cụ thể có được phép xuất hiện mặt mũi trong hàng tá các thao tác liệt kê thuộc tính của đối tượng hay không, điển hình như các phép duyệt thông qua phương thức lấy khóa của đối tượng, phương thức lấy các mục của đối tượng, các vòng lặp duyệt qua từng thuộc tính, và đặc biệt là quá trình sao chép dữ liệu diễn ra khi sử dụng cú pháp trải rộng đối tượng hoặc phương thức hợp nhất đối tượng. Trong bối cảnh thông thường, đại đa số các thuộc tính nên được giữ nguyên trạng thái cho phép liệt kê, thế nhưng các kiến trúc sư hoàn toàn có đặc quyền dán nhãn không thể liệt kê cho một vài thuộc tính đặc chủng trên một đối tượng nếu như chúng mang tính chất là dữ liệu nội bộ nhạy cảm hoặc các phương thức tiện ích không nên bị kéo theo vào các tiến trình lặp hay sao chép dữ liệu bề mặt.

Thuộc tính cờ khả năng ghi đè lại đóng một vai trò quản lý hoàn toàn khác, nó chịu trách nhiệm kiểm duyệt xem liệu một thao tác gán giá trị mới thông qua toán tử bằng có được hệ thống dung túng và cấp phép hay không. Để có thể rèn đúc một thuộc tính trở thành dạng chỉ đọc một cách kiên cố, lập trình viên cần phải định nghĩa nó đi kèm với cờ khả năng ghi đè được thiết lập thành giá trị sai. Tuy nhiên, cần phải soi xét kỹ một lỗ hổng logic tinh vi: chừng nào mà thuộc tính đó vẫn còn giữ được cờ khả năng cấu hình, thì phương thức định nghĩa thuộc tính của đối tượng vẫn hoàn toàn có đủ quyền lực pháp lý để can thiệp và thay đổi giá trị của nó bằng cách thiết lập lại một thuộc tính giá trị mới toanh vào bên trong bộ mô tả, qua mặt hoàn toàn rào cản của cờ khả năng ghi đè. Điều này phản ánh một triết lý thiết kế ngôn ngữ cực kỳ linh hoạt nhưng cũng đầy cạm bẫy, đòi hỏi sự am hiểu tường tận để không tạo ra những trạng thái đối tượng mâu thuẫn gây nên các lỗi tiềm ẩn khó dò tìm trong hệ thống.

Cuối cùng, thuộc tính cờ khả năng cấu hình chính là chốt chặn bảo mật cuối cùng, nó nắm quyền kiểm soát xem liệu bản thân bộ mô tả của một thuộc tính có thể bị đem ra định nghĩa lại hoặc ghi đè một lần nào nữa hay không. Một thuộc tính một khi đã bị giáng xuống trạng thái không thể cấu hình thì nó sẽ bị khóa chặt vĩnh viễn vào định nghĩa hiện tại của nó, và mọi nỗ lực tuyệt vọng nhằm mục đích thay đổi nó thông qua phương thức định nghĩa thuộc tính của đối tượng đều sẽ chuốc lấy sự thất bại thảm hại, bất chấp việc hệ thống có ném ra ngoại lệ hay chết trong im lặng. Mặc dù vậy, một thuộc tính không thể cấu hình vẫn giữ được một đặc quyền nhỏ nhoi là nó vẫn có thể được gán cho những giá trị mới thông qua toán tử bằng, miễn là cờ khả năng ghi đè của nó vẫn còn đang được thiết lập ở trạng thái đúng bên trong bộ mô tả thuộc tính. Sự tương tác phức tạp giữa ba cờ trạng thái này tạo nên một ma trận kiểm soát truy cập dữ liệu cực kỳ mạnh mẽ, là nền móng cho việc xây dựng các cấu trúc dữ liệu bất biến và các hàm tạo an toàn trong các ứng dụng máy tính cấp doanh nghiệp.

Các kiểu phụ chuyên biệt của đối tượng

Sự phong phú của ngôn ngữ JavaScript không chỉ dừng lại ở các đối tượng tiêu chuẩn, mà nó còn được mở rộng thông qua các biến thể đối tượng được chuyên biệt hóa cho các cấu trúc dữ liệu và logic xử lý phức tạp.

Đặc tính cấu trúc của mảng dữ liệu

Tồn tại vô vàn những biến thể kiểu phụ mang tính chuyên biệt hóa cao của đối tượng trong hệ sinh thái ngôn ngữ JavaScript, thế nhưng nếu phải chỉ mặt điểm tên, thì hai kiểu phụ mang tính chất phổ quát nhất mà mọi nhà phát triển đều sẽ phải chạm trán và tương tác liên tục hàng ngày chính là mảng dữ liệu và các hàm điện toán. Bằng thuật ngữ kiểu phụ, chúng tôi muốn đề cập đến một hệ tư tưởng về một kiểu dữ liệu dẫn xuất, một thực thể đã vinh dự được kế thừa toàn bộ các hành vi cốt lõi từ một kiểu dữ liệu cha mẹ, nhưng sau đó lại tự mình chuyên biệt hóa hoặc dang tay mở rộng thêm những hành vi đó để phục vụ cho các mục đích hẹp hơn. Nói một cách hàn lâm, những giá trị thuộc về các kiểu phụ này mang trong mình DNA hoàn chỉnh của một đối tượng thực thụ, thế nhưng bản ngã của chúng lại vượt xa hơn giới hạn của những đối tượng đơn thuần.

Mảng dữ liệu, xét về mặt kiến trúc bộ nhớ, là những đối tượng được sinh ra với một sứ mệnh cực kỳ chuyên biệt: đó là trở thành những cấu trúc được lập chỉ mục bằng con số một cách có trật tự, thay vì phụ thuộc vào việc sử dụng các tọa độ thuộc tính được đặt tên bằng chuỗi ký tự tự do. Mặc dù đã khoác lên mình hình hài của mảng, chúng vẫn không thể rũ bỏ bản chất là đối tượng, vì vậy việc định nghĩa một thuộc tính có tên gọi bằng chuỗi ký tự thông thường vẫn hoàn toàn hợp lệ về mặt ngữ pháp. Tuy nhiên, giới tinh hoa lập trình cực kỳ cau mày và phản đối gay gắt hành vi trộn lẫn các thuộc tính có tên vào bên trong những cấu trúc mảng vốn dĩ sinh ra để được lập chỉ mục bằng số, bởi hành động đó phá vỡ tính nhất quán của dữ liệu và làm vô hiệu hóa các thuật toán tối ưu hóa của cỗ máy thực thi. Mảng dữ liệu tốt nhất là nên được định nghĩa bằng cú pháp trực tiếp có nét tương đồng với đối tượng, thế nhưng phải sử dụng cặp ngoặc vuông góc cạnh thay vì cặp ngoặc nhọn uốn lượn. Ngôn ngữ JavaScript thể hiện sự bao dung tuyệt đối khi cho phép bất kỳ sự pha trộn hỗn tạp nào giữa các kiểu giá trị bên trong một mảng, bao hàm cả việc nhét đối tượng, các mảng khác, hay thậm chí là các hàm vào làm phần tử.

Như mọi nhà nghiên cứu có lẽ đều đã thấu tỏ, các mảng dữ liệu vận hành theo nguyên lý chỉ mục bắt đầu từ số không, một khái niệm toán học mang hàm ý rằng phần tử đứng ở vị trí tiên phong trong mảng sẽ ngự trị tại tọa độ chỉ mục là số không, chứ tuyệt đối không phải là số một. Hãy gọi lại một chân lý kỹ thuật đã được thiết lập: bất kỳ một tên thuộc tính kiểu chuỗi nào nằm trên một đối tượng mà có vẻ bề ngoài trông giống như một con số nguyên – tức là nó có khả năng bị hệ thống ép buộc chuyển đổi một cách hợp lệ thành một con số nguyên toán học – thì trên thực tế nó sẽ bị hệ thống đối xử y hệt như một thuộc tính kiểu số nguyên, hay nói cách khác là một chỉ mục số nguyên. Quy luật thép này cũng hoàn toàn ứng nghiệm đối với các mảng dữ liệu; người lập trình nên luôn luôn sử dụng một con số thuần túy như một chỉ mục số nguyên hay tên thuộc tính, thế nhưng nếu lỡ dại sử dụng một chuỗi ký tự chứa con số, ngôn ngữ JavaScript sẽ tự động ngầm định rằng đó là một con số nguyên và sẽ nai lưng ra thực hiện việc chuyển đổi đó ở tầng vi mạch. Có một ngoại lệ phá vỡ quy tắc vàng cấm sử dụng thuộc tính có tên trên mảng, đó là việc mọi mảng dữ liệu đều tự động phơi bày một thuộc tính độ dài, thứ liên tục được hệ thống tự động theo dõi và cập nhật để phản ánh chiều dài hay số lượng phần tử thực tế của mảng. Rất nhiều nhà phát triển mắc phải một ảo tưởng sai lầm khi tin rằng thuộc tính độ dài của mảng về cơ bản là một hàm lấy giá trị, nhưng sự thật phũ phàng là không phải như vậy. Hậu quả của sự lú lẫn này là họ lầm tưởng rằng việc truy cập vào thuộc tính này sẽ tiêu tốn rất nhiều tài nguyên hệ thống – cứ như thể ngôn ngữ phải cày cuốc đếm lại từ đầu chiều dài mảng mỗi lần gọi – và từ đó sinh ra thói quen tối ưu hóa thừa thãi bằng cách lưu trữ tạm độ dài trước khi chạy các vòng lặp. Mặc dù thủ thuật này từng được coi là tiêu chuẩn vàng về mặt hiệu suất trong quá khứ, nhưng trong suốt một thập kỷ qua, nó đã biến chất thành một khuôn mẫu chống lại tự nhiên, bởi vì cỗ máy thực thi hiện đại cực kỳ thông minh trong việc quản lý thuộc tính độ dài; việc con người cố gắng khôn lỏi hơn cỗ máy chỉ làm chậm quá trình xử lý, do đó chiến lược tối ưu nhất là cứ để cỗ máy thực thi làm tốt công việc của nó và tự do truy cập thuộc tính này bất cứ lúc nào cần thiết.

Rủi ro từ các khoảng trống bộ nhớ trong mảng

Cấu trúc mảng dữ liệu trong ngôn ngữ JavaScript cũng ẩn chứa một lỗ hổng thiết kế cực kỳ tai hại và đáng tiếc, một thứ thường được giới học thuật mổ xẻ dưới cái tên các khoảng trống bộ nhớ… Cơ chế sinh ra thảm họa này diễn ra như sau: nếu một lập trình viên thực hiện thao tác gán một giá trị mới vào một chỉ mục nằm cách xa điểm kết thúc hiện tại của mảng lớn hơn một vị trí, cỗ máy ngôn ngữ JavaScript sẽ lựa chọn một hướng xử lý kỳ quặc là bỏ mặc những khoảng tọa độ nằm kẹp ở giữa trong trạng thái trống rỗng thay vì chủ động tự động gán cho chúng giá trị không xác định như những gì logic thông thường vẫn hay kỳ vọng. Hành vi vô trách nhiệm này tạo ra một mảng dữ liệu có hình hài cấu trúc bị phân mảnh, nơi mà độ dài vật lý được báo cáo không hề phản ánh chính xác số lượng dữ liệu thực tế đang tồn tại bên trong nó.

Để minh họa một cách trực quan: nếu một mảng đang có độ dài là ba, và một thao tác gán bất ngờ đưa một chuỗi ký tự vào tọa độ chỉ mục thứ mười bốn, hệ thống sẽ ngay lập tức kéo giãn thuộc tính độ dài lên thành mười lăm. Khi tiến hành kiểm tra cấu trúc của mảng lúc này, nó sẽ phơi bày một hình hài dị hợm bao gồm ba phần tử hợp lệ ban đầu, theo sau là mười một khoảng trống vô hình, và chốt chặn bằng phần tử vừa được gán. Nguy hiểm hơn, nếu thực hiện phép thử truy cập vào một trong những tọa độ khoảng trống đó, kết quả nôn ra sẽ là giá trị không xác định; điều này tạo ra một ảo giác quang học chết người khiến cho nó trông giống hệt như một tọa độ bình thường đang chứa chấp một giá trị không xác định hàng thật giá thật, nhưng hãy cảnh giác cao độ, vì đó chỉ là một cú lừa ngoạn mục của hệ thống. Các khoảng trống này không thực sự tồn tại trong bản đồ phân bổ bộ nhớ của đối tượng mảng, mà nó chỉ là một sự tính toán ảo của cỗ máy thực thi khi được truy vấn.

Có thể một số nhà phát triển sẽ đặt ra câu hỏi phản biện rằng tại sao sự tồn tại của các khoảng trống bộ nhớ này lại bị coi là một thảm họa tồi tệ đến như vậy? Một trong những nguyên nhân chí mạng nhất là: hệ sinh thái ngôn ngữ cung cấp hàng loạt các giao diện lập trình ứng dụng tích hợp sẵn thao tác trên mảng, điển hình như phương thức ánh xạ dữ liệu, thế nhưng những phương thức này lại sở hữu một hành vi cực kỳ gây sốc là chúng sẽ thẳng tay bước qua và bỏ lơ hoàn toàn các khoảng trống bộ nhớ mà không hề có bất kỳ một cảnh báo nào! Hậu quả là các thuật toán tính toán và biến đổi dữ liệu sẽ sinh ra những kết quả thiếu đồng nhất, gây ra sự sụp đổ dây chuyền trong các luồng xử lý dữ liệu phức tạp. Vì vậy, một nguyên tắc thiết kế bất di bất dịch là vĩnh viễn, tuyệt đối không bao giờ được phép cố tình tạo ra những khoảng trống bộ nhớ bên trong các mảng dữ liệu; đây là một vấn đề đã được đồng thuận rộng rãi trong giới học thuật và không cần phải tranh cãi thêm, nó đích thị là một trong những khía cạnh tồi tệ nhất trong thiết kế của ngôn ngữ JavaScript.

Bản chất đối tượng của hàm điện toán

Khía cạnh thứ hai cần phải đào sâu phân tích trong bức tranh về các kiểu phụ chính là bản chất thực sự của các hàm điện toán. Sẽ không có quá nhiều lý thuyết hàn lâm phức tạp cần phải bàn luận sâu về các hàm tại chuyên mục này, ngoại trừ việc phải nhấn mạnh và vạch trần một sự thật cốt lõi rằng: xét cho cùng, chúng cũng chỉ là một loại kiểu phụ thuộc họ hàng nhà đối tượng. Bản chất sâu xa này mang một hàm ý kỹ thuật vô cùng to lớn: bên cạnh đặc quyền tối thượng là khả năng chứa đựng các khối mã có thể bị cỗ máy kích hoạt và thực thi, bản thân các hàm cũng hoàn toàn sở hữu năng lực tiếp nhận các thuộc tính được đặt tên đắp thêm vào cơ thể chúng, hoặc cho phép các đoạn mã khác thọc tay vào truy xuất thông tin từ những thuộc tính đó y hệt như một cấu trúc dữ liệu tĩnh. Tính chất lưỡng cực này biến hàm trở thành những công cụ linh hoạt bậc nhất trong lập trình chức năng.

Theo cấu trúc mặc định, các hàm luôn được hệ thống ưu ái trang bị sẵn hai thuộc tính đã được định nghĩa từ trước, những thứ mà các kiến trúc sư phần mềm sẽ rất thường xuyên phải tương tác, đặc biệt là khi phải dấn thân vào những kỹ thuật lập trình siêu hình cấp cao: thuộc tính tên và thuộc tính độ dài. Thuộc tính độ dài của một hàm mang một ý nghĩa học thuật khá chuyên biệt: nó không phản ánh số dòng mã bên trong, mà nó đại diện cho tổng số lượng đếm được của các tham số đã được định nghĩa một cách tường minh trên chữ ký hàm, nhưng quá trình đếm này sẽ ngay lập tức bị dừng lại và không bao gồm bất kỳ tham số nào có chứa một giá trị mặc định được định nghĩa sẵn, cũng như từ chối đếm tham số phần còn lại mang cú pháp ba dấu chấm. Sự phức tạp trong việc tính toán độ dài này thường được ứng dụng để xây dựng các kỹ thuật như áp dụng hàm một phần hoặc xử lý đa hình trong các thư viện tiện ích lõi.

Mặc dù có khả năng lưu trữ, nhưng một lời khuyên mang tính chất định hướng kiến trúc là người lập trình nên chủ động né tránh hành vi gán các thuộc tính tùy chỉnh trực tiếp lên trên các đối tượng hàm. Nếu như khát khao thiết kế là muốn lưu trữ những thông tin siêu dữ liệu phụ trợ có tính liên kết chặt chẽ với một hàm cụ thể nào đó, thì giải pháp tối ưu và thanh lịch nhất là hãy viện đến sự phục vụ của một cấu trúc dữ liệu Bản đồ tách biệt – hoặc cấu trúc Bản đồ yếu để tối ưu hóa việc thu gom rác thải bộ nhớ – trong đó lấy chính đối tượng hàm đó làm khóa định danh, và phần thông tin phụ trợ kia sẽ đóng vai trò là giá trị được lưu trữ. Cách tiếp cận phân tách dữ liệu này không chỉ giữ cho đối tượng hàm được tinh gọn, ngăn chặn nguy cơ rò rỉ bộ nhớ từ việc giữ lại các tham chiếu không cần thiết, mà còn giảm thiểu khả năng xảy ra va chạm tên thuộc tính giữa logic nghiệp vụ của con người và các tính năng nội tại có thể được mở rộng trong tương lai của cỗ máy ngôn ngữ JavaScript.

Quản lý đặc tính mở rộng và đóng băng đối tượng

Vượt ra ngoài ranh giới của việc kiểm soát từng thuộc tính đơn lẻ, ngôn ngữ cung cấp các cơ chế quyền năng để áp đặt các quy tắc bất biến lên toàn bộ vòng đời của một đối tượng.

Khả năng mở rộng của đối tượng

Bên cạnh việc định nghĩa các hành vi chuyên biệt cho từng thuộc tính riêng lẻ, hệ thống còn cung cấp những cơ chế cho phép cấu hình và áp đặt một số hành vi nhất định lên trên toàn bộ diện tích bề mặt của cấu trúc đối tượng, bao gồm: tính có thể mở rộng, trạng thái bị niêm phong, và trạng thái bị đóng băng. Khái niệm khả năng mở rộng ở đây mang một ý nghĩa học thuật ám chỉ việc xem liệu một đối tượng cụ thể có được hệ thống bật đèn xanh cho phép định nghĩa hoặc đắp thêm những thuộc tính hoàn toàn mới vào cấu trúc dữ liệu của nó hay không. Theo cấu hình mặc định từ thuở hồng hoang, toàn bộ mọi đối tượng được sinh ra đều mang trong mình đặc tính có thể mở rộng tự do, thế nhưng các kiến trúc sư hoàn toàn có đủ thẩm quyền để can thiệp và bóp nghẹt khả năng mở rộng đối với một đối tượng bất kỳ thông qua phương thức ngăn chặn mở rộng đối tượng. Quyết định kiến trúc này thường được đưa ra khi cần bảo vệ cấu trúc cốt lõi của một đối tượng trạng thái khỏi sự tiêm nhiễm ngẫu nhiên từ các hàm xử lý bên thứ ba.

Hệ quả của việc áp dụng lệnh cấm mở rộng này sẽ khác biệt tùy thuộc vào chế độ thực thi của chương trình. Nếu đoạn mã đang được vận hành trong một môi trường chế độ lỏng lẻo thông thường, một thao tác gán mang mưu đồ kiến tạo nên một thuộc tính mới toanh sẽ bị hệ thống âm thầm bác bỏ và chết trong im lặng mà không để lại dấu vết gì; ngược lại, nếu chương trình đang được cùm kẹp trong chế độ nghiêm ngặt, một ngoại lệ chết người sẽ ngay lập tức được hệ thống ném ra để cảnh báo về hành vi vi phạm ranh giới bộ nhớ. Tuy nhiên, cần phải làm rõ một điểm mù về mặt khái niệm: việc khóa chặt khả năng mở rộng không hề đụng chạm hay tước đoạt đi quyền lực của các thao tác cấu hình lại hoặc thay đổi giá trị của những thuộc tính vốn dĩ đã tồn tại từ trước trên đối tượng.

Việc ứng dụng phương thức ngăn chặn mở rộng thường được xem là bước đi đầu tiên trong quá trình thiết lập tính bất biến cho cấu trúc dữ liệu. Khi so sánh với các giới hạn nghiêm ngặt hơn, thao tác này mang lại một mức độ linh hoạt tương đối, cho phép đối tượng tiếp tục tiến hóa về mặt dữ liệu bề mặt mà không làm thay đổi cấu trúc định danh tĩnh của nó. Trong các kiến trúc ứng dụng dựa trên luồng dữ liệu đơn hướng, việc ngăn chặn mở rộng đối tượng kho dữ liệu cục bộ giúp các kỹ sư yên tâm rằng không có bất kỳ một luồng sự kiện bất thường nào có thể tự ý khai sinh thêm các điểm dữ liệu mới không nằm trong khuôn mẫu đã được thiết kế sẵn.

Cơ chế niêm phong cấu trúc đối tượng

Nâng cao hơn một bậc so với việc chỉ đơn thuần ngăn chặn sự phình to của cấu trúc, cơ chế niêm phong đối tượng mang lại một lớp áo giáp phòng ngự kiên cố hơn rất nhiều cho vòng đời của dữ liệu. Khái niệm niêm phong, được thực thi thông qua phương thức niêm phong đối tượng, về bản chất là một lệnh thao tác kép: nó không chỉ thực thi lệnh cấm tuyệt đối việc đắp thêm bất kỳ thuộc tính mới nào vào đối tượng – y hệt như những gì phương thức ngăn chặn mở rộng đã làm – mà nó còn tiến hành quét qua toàn bộ các thuộc tính hiện đang tồn tại trên đối tượng đó và thẳng tay cưỡng ép thuộc tính cờ khả năng cấu hình của toàn bộ chúng về trạng thái sai. Sự can thiệp tinh vi này tạo ra một hệ quả kép mang tính chất ràng buộc cấu trúc cực kỳ mạnh mẽ.

Một khi đối tượng đã bị niêm phong, bức tường phòng thủ của nó sẽ chặn đứng mọi nỗ lực tuyệt vọng nhằm mục đích xóa bỏ một thuộc tính đang tồn tại thông qua toán tử xóa sổ, đồng thời nó cũng từ chối mọi thủ đoạn nhằm mục đích chuyển đổi đặc tính của các bộ mô tả thuộc tính – chẳng hạn như âm mưu biến một thuộc tính giá trị tĩnh thành một thuộc tính truy cập có chứa hàm lấy giá trị và thiết lập giá trị, hoặc thay đổi khả năng liệt kê của chúng. Tuy nhiên, một kẽ hở kiến trúc vẫn được cố tình để ngỏ: nếu như bản thân một thuộc tính vẫn còn đang mang trên mình cờ khả năng ghi đè ở trạng thái đúng, thì dữ liệu vật lý nằm bên trong thuộc tính đó vẫn hoàn toàn có thể bị thay đổi liên tục thông qua các phép gán thông thường. Lỗ hổng này không phải là lỗi thiết kế, mà là một sự thỏa hiệp có chủ đích.

Trong thực tiễn xây dựng các hệ thống công nghệ thông tin quy mô lớn, thao tác niêm phong thường được ứng dụng để khóa chặt lớp giao diện trao đổi dữ liệu hoặc các khối dữ liệu cấu hình hệ thống sau khi quá trình khởi tạo đã hoàn tất. Bằng cách khóa chặt hình hài cấu trúc nhưng vẫn cho phép nội dung dữ liệu được cập nhật, đối tượng niêm phong trở thành một vật chứa lý tưởng để theo dõi trạng thái ứng dụng: số lượng và tên gọi của các biến trạng thái được bảo toàn nguyên vẹn, ngăn chặn rủi ro lỗi logic do mất mát thuộc tính, nhưng luồng thông tin phản ứng vẫn có thể chảy qua chúng một cách mượt mà và an toàn.

Cơ chế đóng băng và tính bất biến sâu

Nằm ở đỉnh cao của tháp phòng ngự tính toàn vẹn dữ liệu chính là cơ chế đóng băng đối tượng, một lệnh thực thi mang tính chất hủy diệt mọi khả năng biến đổi cấu trúc, được kích hoạt thông qua phương thức đóng băng đối tượng. Thao tác này là sự kết hợp cực đoan nhất của các giới hạn: nó kích hoạt cơ chế niêm phong lên đối tượng – tức là cấm thêm mới, cấm xóa bỏ, cấm thay đổi cấu hình bộ mô tả – và đồng thời giáng một đòn chí mạng bằng cách quét qua toàn bộ các thuộc tính dữ liệu và tước đoạt đi cờ khả năng ghi đè của chúng, ép toàn bộ trở về trạng thái sai. Khoảnh khắc lệnh đóng băng được thi hành, đối tượng đó chính thức trở thành một tảng băng trôi vĩnh cửu trong không gian bộ nhớ: không thể mở rộng, không thể cấu trúc lại, và bất khả xâm phạm về mặt thay đổi giá trị.

Tuy nhiên, giới học thuật cần phải đặc biệt tỉnh táo trước một ảo giác về mặt kỹ thuật: phương thức đóng băng của ngôn ngữ JavaScript mang bản chất là một lệnh đóng băng nông. Điều này mang ý nghĩa vô cùng nguy hiểm là thao tác đóng băng chỉ có hiệu lực ở tầng bề mặt của đối tượng mục tiêu; nếu như bản thân các thuộc tính của đối tượng đó lại đang ôm giữ những sợi dây tham chiếu trỏ đến các đối tượng con nằm sâu bên trong, thì những đối tượng con đó vẫn hoàn toàn được tự do tung hoành và không hề bị ảnh hưởng bởi lệnh đóng băng của đối tượng cha. Để giải quyết triệt để bài toán này và đạt được tính bất biến thực sự, các kiến trúc sư buộc phải tự tay thiết kế và triển khai các thuật toán đóng băng đệ quy sâu, cày xới qua mọi nhánh của cây đối tượng để áp đặt lệnh đóng băng lên từng nút cấu trúc một.

Đúng như những gì đã được hé lộ ở ngay đầu chương này, các đối tượng trong hệ sinh thái ngôn ngữ JavaScript vận hành ngoan ngoãn dựa trên một hệ thống các quy luật tối cao được vinh danh là Giao thức siêu đối tượng. Khi đã thấu tỏ tận gốc rễ cách thức mà các đối tượng cày cuốc theo cơ chế mặc định, tham vọng tiếp theo của các nhà thiết kế là chuyển hướng tập trung sang việc nghiên cứu cách thức làm thế nào để có thể thọc tay vào can thiệp vào một số hành vi mặc định này, qua đó ghi đè và cá nhân hóa chúng để phục vụ cho các thuật toán phi truyền thống. Việc ứng dụng các cấu trúc Biểu tượng đặc chủng đã được tích hợp sẵn trong giao thức chính là chìa khóa vạn năng để bẻ cong thực tại của đối tượng, cho phép chúng ta thay đổi cách thức đối tượng phản ứng khi bị ép kiểu thành chuỗi, cách nó xử lý các vòng lặp ẩn, hoặc cách nó giao tiếp với các biểu thức toán học, mở ra một chân trời mới cho việc tối ưu hóa hiệu suất lập trình.

Chuỗi nguyên mẫu và cơ chế ủy quyền

Sức mạnh thực sự của ngôn ngữ không nằm ở bản thân đối tượng đơn lẻ, mà nằm ở hệ thống mạng lưới liên kết ngầm định cho phép chúng chia sẻ dữ liệu và hành vi một cách cực kỳ tinh vi.

Bản chất của liên kết nguyên mẫu nội tại

Một trong những đặc điểm cấu trúc mang tính chất quyết định nhất, nhưng cũng thường xuyên tàng hình và ít lộ liễu nhất, của một đối tượng – và đồng thời cũng là một phần không thể tách rời của Giao thức siêu đối tượng – được giới học thuật tôn sùng với cái tên là chuỗi nguyên mẫu của nó; ký hiệu cú pháp chính thức được ghi chép trong tài liệu đặc tả của ngôn ngữ JavaScript là thuộc tính nguyên mẫu ẩn nằm trong ngoặc vuông. Các nghiên cứu sinh phải cực kỳ tỉnh táo để không bị rơi vào cái bẫy nhầm lẫn tai hại giữa thuộc tính nguyên mẫu ẩn nội bộ này với một thuộc tính hoàn toàn công khai mang cái tên là nguyên mẫu. Bất chấp sự trùng lặp đáng nguyền rủa về mặt tên gọi, đây là hai khái niệm kiến trúc mang bản chất hoàn toàn tách biệt nhau. Thuộc tính nguyên mẫu ẩn thực chất là một sợi dây liên kết nội tại vô hình mà một đối tượng tự động được cỗ máy ban phát ngay tại khoảnh khắc nó vừa được thai nghén và nặn ra, sợi dây này mang nhiệm vụ trỏ thẳng đến một đối tượng vật lý khác trong bộ nhớ. Mối liên kết ngầm định này là một đặc tính cấu trúc bị che giấu, thường hoạt động một cách cực kỳ tinh tế đằng sau hậu trường của một đối tượng, thế nhưng nó lại mang trong mình sức mạnh tạo ra những cú tác động kinh thiên động địa lên cái cách mà mọi tương tác đối với đối tượng đó sẽ được hệ thống phân giải và diễn ra như thế nào.

Nó được xưng tụng là một chuỗi bởi vì một đối tượng này sẽ tiến hành liên kết với một đối tượng khác, và cái đối tượng trung gian đó lại tiếp tục phóng một sợi dây liên kết trỏ đến một đối tượng khác nữa,… và cứ thế cấu trúc móc xích này kéo dài ra. Chắc chắn sẽ phải tồn tại một điểm kết thúc hoặc một đỉnh cho cái chuỗi liên kết này, nơi mà sự móc nối bị buộc phải dừng lại hoàn toàn và không còn bất kỳ con đường nào khác để có thể tiếp tục hành trình tra cứu nữa. Chúng ta đã từng được chiêm ngưỡng hàng tá những hệ lụy sinh ra từ sợi dây liên kết nguyên mẫu ẩn này ở phần kiến thức nền tảng trong Chương 1, lấy ví dụ, theo quy luật phân bổ mặc định, toàn bộ mọi đối tượng đều sẽ được liên kết nguyên mẫu ẩn trỏ thẳng đến cái đối tượng gốc được tích hợp sẵn trong hệ thống mang tên gọi là nguyên mẫu của đối tượng tổng. Cái tên gọi nguyên mẫu của đối tượng tổng đó tự thân nó đã có khả năng dội bom gây lú lẫn cực độ, bởi vì nó đang trơ trẽn sử dụng một thuộc tính mang tên là nguyên mẫu; tạm gác lại những mối bận tâm đau đầu về việc hai khái niệm này có mối quan hệ mờ ám gì với nhau, mà lúc này, hãy cứ nhắm mắt ngầm định chấp nhận sự hiện diện của cái đối tượng tích hợp sẵn mang tầm quan trọng sinh tử nhưng lại sở hữu một cái tên gọi kỳ quặc này.

Khi chúng ta tự tin thi triển những thao tác truy cập thuộc tính mà bản thân đối tượng mục tiêu hoàn toàn không hề sở hữu, hệ thống không hề ném ra lỗi, mà thực chất chúng ta đang vô tình hưởng lợi và bòn rút sức mạnh từ sợi dây liên kết nguyên mẫu ẩn nội tại này mà không hề hay biết. Bởi vì đối tượng hiện tại không hề chứa chấp những thuộc tính phương thức như chuyển đổi thành chuỗi hay kiểm tra sở hữu thuộc tính được định nghĩa trực tiếp trên cơ thể nó, những thao tác truy cập thuộc tính đó thực chất sẽ dẫn đến một kết cục là chúng tiến hành ủy quyền toàn bộ quá trình truy cập đó để tiếp tục cuộc hành trình lùng sục dọc theo cái chuỗi nguyên mẫu ẩn. Sự kỳ diệu nằm ở chỗ, việc một đối tượng có năng lực thọc tay vào truy cập một thuộc tính phương thức cho dù nó không thực sự ôm giữ phương thức đó trên người, thường được dân tình và giới lập trình phổ thông xưng tụng bằng một khái niệm hoa mỹ là tính kế thừa, hay nếu muốn diễn đạt một cách hàn lâm và chính xác hơn thì gọi là kế thừa nguyên mẫu… Mặc dù cá nhân tôi mang trong mình một sự bức xúc và bất mãn tột độ đối với việc lạm dụng cái thuật ngữ kế thừa ở đây – đáng lý ra nó bắt buộc phải được gọi thẳng mặt là cơ chế ủy quyền mới phản ánh đúng bản chất! – thế nhưng bởi vì đó là cái cách mà đại đa số quần chúng ngoài kia vẫn thường dùng để gọi tên nó, nên chúng ta đành phải cắn răng miễn cưỡng tuân phục và sử dụng chung một hệ thống thuật ngữ đó cho thời điểm hiện tại. Nguyên mẫu của đối tượng tổng ôm giữ hàng tá những thuộc tính và phương thức được tích hợp sẵn, toàn bộ đống tài sản này đều sẽ được kế thừa bởi bất kỳ một đối tượng nào sở hữu sợi dây liên kết nguyên mẫu ẩn, bất kể là liên kết một cách trực tiếp hay lắt léo gián tiếp thông qua sợi dây liên kết của một đối tượng trung gian khác, trỏ về nguyên mẫu của đối tượng tổng. Như đã được bổ sung từ thời Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2022, ngôn ngữ JavaScript rốt cuộc cũng đã chịu tích hợp thêm một phiên bản tĩnh của tiện ích kiểm tra sở hữu mang tên phương thức kiểm tra sở hữu tĩnh của đối tượng, một hình thái giờ đây được giới kiến trúc sư đánh giá là một lựa chọn thiết kế tối ưu và mạnh mẽ hơn rất nhiều, và cái hình thái phương thức phiên bản thực thể kế thừa cũ kỹ kia giờ đây nói chung nên bị tẩy chay và đưa vào dĩ vãng.

Khởi tạo đối tượng với nguyên mẫu tùy chỉnh

Theo những quy tắc thiết kế cấu trúc mặc định, bất kỳ một đối tượng nào do chính tay lập trình viên nhào nặn ra trong các chương trình phần mềm đều sẽ tự động bị ép buộc liên kết nguyên mẫu ẩn trỏ đến cái đối tượng nguyên mẫu của đối tượng tổng đó. Thế nhưng, sức mạnh kiến trúc của ngôn ngữ cho phép các chuyên gia hoàn toàn có đặc quyền khởi tạo ra một đối tượng với một định tuyến liên kết hoàn toàn khác biệt bằng cách sử dụng phương thức khởi tạo đối tượng. Phương thức quyền năng này yêu cầu truyền vào một đối số đầu tiên, thứ sẽ đóng vai trò là giá trị được dùng để thiết lập cho liên kết nguyên mẫu ẩn của cái đối tượng vừa mới được nặn ra từ hư không đó.

Một điểm yếu chết người cần phải thừa nhận đối với hướng tiếp cận kiến trúc này là người lập trình sẽ bị tước đoạt đi cơ hội sử dụng cú pháp khai báo trực tiếp bằng ngoặc nhọn quen thuộc, thế nên tại khoảnh khắc khởi tạo, họ hoàn toàn không thể định nghĩa nhồi nhét thêm bất kỳ nội dung thuộc tính nào cho đối tượng mới đó, mà sau đó đành phải nai lưng ra định nghĩa từng thuộc tính một cách thủ công và chậm chạp bằng cách sử dụng toán tử bằng. Vẫn còn một con đường thiết kế khác, dù ít được giới tinh hoa khuyến khích hơn, đó là có thể kết hợp việc sử dụng cú pháp khai báo trực tiếp bằng ngoặc nhọn song hành cùng với một thuộc tính ma thuật mang tên nguyên mẫu hai dấu gạch dưới mang hình hài vô cùng dị hợm. Phải gióng lên một hồi chuông cảnh báo lịch sử rằng: cái thuộc tính nguyên mẫu hai dấu gạch dưới mang vẻ ngoài dị hợm đó vốn dĩ đã tồn tại dai dẳng bên trong nội tạng của một số cỗ máy thực thi ngôn ngữ JavaScript xuyên suốt hơn hai thập kỷ qua, thế nhưng nó chỉ thực sự được ủy ban nâng lên thành chuẩn mực chính thức trong ngôn ngữ kể từ thời đại của Tiêu chuẩn ngôn ngữ kịch bản châu Âu 6 vào năm 2015. Đáng buồn thay, nó lại bị hắt hủi và chỉ được nhét vào trong Phụ lục B của tài liệu đặc tả kỹ thuật, một nơi chuyên dùng để liệt kê danh sách những tính năng mà Ủy ban kỹ thuật 39 đành phải cắn răng miễn cưỡng chấp nhận nhúng vào chỉ bởi vì chúng đã trót tồn tại quá phổ biến bên trong hàng tá các cỗ máy thực thi dựa trên trình duyệt web, và do đó chúng trở thành một thực tại phũ phàng không thể chối cãi bất chấp một sự thật là chúng hoàn toàn không hề có xuất thân chính thống từ Ủy ban kỹ thuật 39. Tính năng tàn dư này do đó được tài liệu đặc tả bảo chứng là sẽ tồn tại trong mọi cỗ máy thực thi dựa trên trình duyệt web tuân thủ chuẩn, thế nhưng nó lại hoàn toàn không được đảm bảo pháp lý là sẽ sống sót và vận hành trơn tru trong những cỗ máy độc lập phi trình duyệt khác. Môi trường thực thi Node.js tận dụng lại cỗ máy vi tám từ trình duyệt Chrome, thế nên nó nghiễm nhiên được thừa hưởng thuộc tính nguyên mẫu hai dấu gạch dưới này theo mặc định như một tai nạn lịch sử, đòi hỏi lập trình viên phải hết sức cảnh giác khi ứng dụng tính năng này vào các kiến trúc đa nền tảng.

Đối số thứ hai mang tính tùy chọn khi truyền vào phương thức khởi tạo đối tượng – giống hệt như đối số thứ hai của phương thức định nghĩa các thuộc tính của đối tượng đã được mổ xẻ ở phần trước – là một đối tượng chứa đựng các thuộc tính đóng vai trò là các bộ mô tả dùng để định nghĩa giá trị ban đầu cho đối tượng mới. Trong thực tiễn chinh chiến ngoài chiến trường phần mềm, hình thái sử dụng đối số thứ hai này cực kỳ hiếm khi xuất hiện, nguyên nhân sâu xa có lẽ là do việc phải nai lưng ra khai báo chi tiết cấu trúc của các bộ mô tả hoàn chỉnh mang lại một cảm giác quá đỗi vụng về và rườm rà hơn rất nhiều so với việc chỉ đơn thuần vứt vào những cặp tên và giá trị mộc mạc. Nhưng sự phức tạp đó lại mang đến một sức mạnh kiểm soát tuyệt đối, cho phép thiết lập ngay từ ban đầu các cờ trạng thái niêm phong cho từng thuộc tính ngay tại khoảnh khắc khởi tạo đối tượng, đảm bảo tính toàn vẹn bộ nhớ tối đa.

Đối tượng từ điển và khoảng trống nguyên mẫu

Như chúng ta đã từng đề cập một cách học thuật ở phần trước, chuỗi nguyên mẫu ẩn bắt buộc phải có một điểm dừng lại ở một tọa độ nào đó trong bộ nhớ, nhằm mục đích đảm bảo cho các thuật toán tra cứu thuộc tính không rơi vào thảm họa lặp vô tận trọn đời. Nguyên mẫu của đối tượng tổng trong đại đa số các thiết kế chuẩn mực chính là điểm hội tụ trên cùng hoặc điểm kết thúc của mọi chuỗi nguyên mẫu ẩn, bởi vì bản thân liên kết nguyên mẫu ẩn của chính nó mang giá trị rỗng, và do đó thuật toán tra cứu hoàn toàn không còn bất kỳ nấc thang nào khác để có thể tiếp tục hành trình bòn rút dữ liệu nữa. Thế nhưng, sức mạnh tối thượng của ngôn ngữ cũng cho phép các kỹ sư tự tay định nghĩa ra những đối tượng mang theo giá trị rỗng của riêng chúng dành cho liên kết nguyên mẫu ẩn, bằng cách truyền giá trị rỗng vào phương thức khởi tạo đối tượng.

Việc khởi tạo ra một đối tượng hoàn toàn trống trơn không hề dính líu đến bất kỳ sợi dây liên kết nguyên mẫu ẩn nào trỏ về nguyên mẫu của đối tượng tổng thực sự có thể mang lại những giá trị kiến trúc vô cùng to lớn. Lấy một ví dụ phản biện, như những gì đã được khắc cốt ghi tâm trong Chương 1, cấu trúc toán tử in và vòng lặp for…in sẽ không ngừng xách vali đi cày xới dọc theo chuỗi nguyên mẫu ẩn để truy lùng các thuộc tính được kế thừa. Thế nhưng hành vi tọc mạch này đôi khi lại là một hệ lụy hoàn toàn không mong muốn, bởi vì rất có thể người thiết kế kiến trúc hoàn toàn không hề khát khao một thứ gì đó kiểu như phép thử tra cứu phương thức chuyển đổi thành chuỗi thông qua toán tử in trên đối tượng lại có thể vượt qua bài kiểm tra và phân giải thành công. Thêm vào đó, một đối tượng mang trong mình một liên kết nguyên mẫu ẩn rỗng tuếch sẽ được bảo vệ an toàn tuyệt đối, miễn nhiễm khỏi bất kỳ một vụ va chạm kế thừa tai nạn nào xảy ra giữa các tên thuộc tính do chính nó sở hữu với những cái tên mà nó vô tình nhận thừa kế từ những thực thể ngoại lai khác nằm đâu đó trên chuỗi. Chủng loại đối tượng dị biệt nhưng cực kỳ hữu dụng này thỉnh thoảng vẫn được dân tình trong giới lập trình rỉ tai nhau và gọi bằng một thuật ngữ phổ biến là các đối tượng từ điển…

Cần phải soi chiếu thật kỹ vào cái tên gọi thuộc tính công khai nguyên mẫu đang nằm chễm chệ trong định danh hoặc tọa độ của đối tượng đặc biệt này, nguyên mẫu của đối tượng tổng; rốt cuộc thì sự hiện diện của nó mang ý nghĩa sâu xa gì? Cấu trúc hàm Đối tượng tổng về cơ bản chính là một lời gọi hàm tạo; theo luật định mặc định, toàn bộ mọi hàm điện toán – mà bản thân chúng thực chất cũng chính là các đối tượng ngầm định – đều mang trên mình một thuộc tính nguyên mẫu công khai như vậy, và sợi dây tham chiếu của thuộc tính đó trỏ thẳng vào một đối tượng vật lý. Và ngay tại cái tọa độ này, mâu thuẫn về mặt đặt tên giữa liên kết nguyên mẫu ẩn nội bộ và thuộc tính nguyên mẫu công khai mới thực sự giáng một đòn đau điếng cắn trả lại sự thấu hiểu của chúng ta. Thuộc tính nguyên mẫu công khai nằm trên một hàm hoàn toàn không hề có chức năng định nghĩa bất kỳ một sợi dây liên kết nội tại nào mà bản thân cái hàm đó đang trực tiếp trải nghiệm; trên thực tế, các hàm điện toán dưới tư cách là đối tượng sở hữu riêng cho mình một liên kết nguyên mẫu ẩn nội bộ trỏ đến một tọa độ hoàn toàn khác biệt trong bộ nhớ. Đúng hơn, thuộc tính nguyên mẫu công khai trên một hàm chỉ đơn thuần đóng vai trò là một sợi dây tham chiếu trỏ đến một đối tượng mà cái đối tượng đó đáng lý ra phải được trỏ TỚI bởi bất kỳ một đối tượng phái sinh nào khác được sinh ra từ hư không khi các đoạn mã thi hành việc triệu hồi cái hàm đó song hành cùng từ khóa new. Các hàm bản thân chúng lại được liên kết nguyên mẫu ẩn trỏ thẳng đến đối tượng nguyên mẫu của hàm tổng, nơi chứa đựng các phương thức cốt lõi mà mọi hàm đều kế thừa như phương thức bối cảnh gọi và phương thức áp dụng mảng tham số. Lại một lần nữa, đây quả là một chủ đề học thuật bị hệ thống biến tấu trở nên lú lẫn và hoang mang hơn gấp bội phần chỉ bởi vì sự giẫm đạp và chồng chéo về mặt tên gọi giữa liên kết nguyên mẫu ẩn nội bộ và thuộc tính nguyên mẫu công khai!

Kết luận

Các thuộc tính tồn tại trên các đối tượng bị kiểm soát vận mệnh và được định nghĩa ở tầng nội bộ bởi một siêu đối tượng bộ mô tả, một cấu trúc chứa đựng các cờ thuộc tính siêu dữ liệu đóng vai trò quyết định, điển hình như cờ giá trị thể hiện cho giá trị hiện thời đang lưu trữ của thuộc tính và cờ khả năng liệt kê mang bản chất boolean nắm quyền kiểm duyệt xem liệu thuộc tính đó có được phép lộ diện trong các danh sách liệt kê chuyên biệt chỉ dành cho các thuộc tính và tên thuộc tính có thể liệt kê được hay không. Toàn bộ cái cơ chế ma quỷ điều khiển cách thức mà các đối tượng cùng với các thuộc tính của chúng vận hành bên trong không gian bộ nhớ của ngôn ngữ JavaScript được giới hàn lâm gọi chung là giao thức siêu đối tượng… Các kiến trúc sư phần mềm hoàn toàn có thể thiết lập sự kiểm soát tinh vi đến từng độ phân giải chính xác nhất đối với hành vi của các thuộc tính thông qua phương thức định nghĩa thuộc tính của đối tượng, cũng như áp đặt các quy tắc hành vi trên quy mô toàn đối tượng bằng sức mạnh của phương thức đóng băng đối tượng. Thế nhưng, mang trong mình một quyền năng còn khủng khiếp hơn thế, chúng ta có thể thọc tay vào can thiệp và ghi đè lên những hành vi hệ thống mặc định nhất định trên các đối tượng bằng cách lợi dụng các cấu trúc Biểu tượng đặc chủng đã được định nghĩa sẵn. Các liên kết nguyên mẫu ẩn nội tại giữa các đối tượng chính là xương sống cho phép những thao tác truy cập thuộc tính hoặc phương thức nhắm vào một đối tượng – trong trường hợp thuộc tính hoặc phương thức bị truy vấn hoàn toàn vắng mặt – được hệ thống xử lý khéo léo bằng cách ủy quyền toàn bộ quá trình tra cứu phân giải đó sang cho một đối tượng mục tiêu khác trên chuỗi. Khi cái trò ủy quyền kiến trúc này có sự nhúng tay của một phương thức xử lý, bối cảnh không gian bộ nhớ để cho cái phương thức đó chạy sẽ được chia sẻ từ đối tượng khởi nguồn ban đầu sang đối tượng mục tiêu thông qua cơ chế ràng buộc của từ khóa this. Đạt được sự thấu hiểu tuyệt đối về vòng đời thuộc tính và chuỗi nguyên mẫu là chìa khóa tối thượng để khai phá toàn bộ sức mạnh ẩn tàng của ngôn ngữ lập trình này.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 1.1 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 1.2 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 1.3 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 1.4 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.1 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.2 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.3 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.4 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.5 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.6 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.7 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 2.8 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 3.1 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 3.2 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 3.3 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 3.4 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 3.5 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 4.1 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 4.2 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 4.3 tại đây.

Đọc Giải mã bản chất cốt lõi của JavaScript chương 4.4 tại đây.

Giải mã bản chất cốt lõi của JavaScript | Chương 3.2 300 – thu vien, viet lach, javascript, lap trinh, lap trinh web, web development, ydkjs, get started, you dont know js yet, chua biet javascript, chua biet ro javascript, kyle simpson, coercion, type awareness, triet ly lap trinh, giai ma javascript, giai ma ban chat coi loi javascript.
Giải mã bản chất cốt lõi của JavaScript | Chương 3.2.

Chuyên mục triet-ly-lap-trinh

Chuyên mục web-development

Theo dõi hành trình

Hãy để lại thông tin, khi có gì mới thì Nhà văn sẽ gửi thư đến bạn để cập nhật. Cam kết không gửi email rác.

Họ và tên

Email liên lạc

Đôi dòng chia sẻ