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.3

Giải mã bản chất cốt lõi của JavaScript (Phần 3, chương 3) 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.

76 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 3) 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

Khuôn mẫu thiết kế hướng lớp nhìn chung đòi hỏi việc định nghĩa một loại thực thể cụ thể, bao gồm các dữ liệu thành viên và các hành vi phương thức, sau đó tiến hành khởi tạo một hoặc nhiều bản sao cụ thể của định nghĩa lớp này dưới dạng các đối tượng thực tế có khả năng tương tác và thực thi các tác vụ trong không gian bộ nhớ. Hơn thế nữa, hệ tư tưởng hướng lớp cho phép khai báo một mối quan hệ logic giữa hai hoặc nhiều lớp thông qua một cơ chế được gọi là sự kế thừa, nhằm mục đích dẫn xuất ra những lớp con mới và được tăng cường sức mạnh, có khả năng kết hợp và thậm chí là định nghĩa lại các hành vi sẵn có từ lớp cha. Trước thời điểm Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2015 ra đời, các nhà phát triển ngôn ngữ JavaScript đã phải sao chép và bắt chước các khía cạnh của thiết kế hướng lớp – hay còn được gọi là thiết kế hướng đối tượng – bằng cách sử dụng các hàm và đối tượng thông thường, kết hợp chặt chẽ với cơ chế nguyên mẫu ẩn, qua đó tạo ra một khái niệm giả lập được xưng tụng là các lớp nguyên mẫu. Thế nhưng, mang lại niềm vui và sự nhẹ nhõm to lớn cho vô số lập trình viên, tiêu chuẩn mới đã chính thức giới thiệu một bộ cú pháp chuyên biệt, bao gồm các từ khóa lớp và từ khóa mở rộng, để có thể diễn đạt các thiết kế hướng lớp một cách mang tính khai báo, thanh lịch và rõ ràng hơn rất nhiều so với những nỗ lực chắp vá trước đây. Vào cái thời điểm mà từ khóa lớp vừa mới được trình làng, thứ cú pháp chuyên dụng mới mẻ này gần như hoàn toàn chỉ là một lớp đường cú pháp nhằm mục đích làm cho các định nghĩa lớp trở nên tiện lợi, dễ viết và dễ đọc hiểu hơn đối với con người. Tuy nhiên, trong suốt nhiều năm liên tục phát triển kể từ bản cập nhật lịch sử đó, từ khóa lớp đã dần trưởng thành và tiến hóa thành một cơ chế tính năng hạng nhất của riêng nó, tích lũy được một khối lượng khổng lồ các cú pháp chuyên dụng và những hành vi phức tạp vượt xa hoàn toàn những khả năng chật hẹp của các lớp nguyên mẫu thời tiền sử. Bất chấp một sự thật rành rành là cấu trúc lớp hiện đại ngày nay gần như không còn giữ lại bất kỳ một nét tương đồng nào về mặt hình thức so với phong cách mã nguồn lớp nguyên mẫu cổ điển, cỗ máy thực thi vi mạch của ngôn ngữ JavaScript về mặt bản chất vẫn chỉ đang âm thầm thực hiện công việc móc nối các đối tượng lại với nhau thông qua cơ chế nguyên mẫu ẩn đã tồn tại bền vững từ thuở hồng hoang. Nói một cách hình tượng và mang tính chất kiến trúc, hệ thống lớp không phải là một trụ cột độc lập hoàn toàn mới của ngôn ngữ để có thể đứng ngang hàng với trụ cột nguyên mẫu, mà nó mang dáng dấp giống như một phần đỉnh cột được điêu khắc vô cùng hoa mỹ và lộng lẫy ngự trị trên cùng của cái trụ cột nguyên mẫu vững chắc đó. Bởi vì mã nguồn viết theo phong cách lớp hiện nay đã gần như thay thế và loại bỏ hoàn toàn mọi lối viết mã lớp nguyên mẫu trước đây trong các dự án thực tế, nên toàn bộ phần văn bản chính trong chương này sẽ chỉ tập trung mổ xẻ một cách chuyên sâu vào cấu trúc lớp cùng với hàng tá những đặc điểm chi tiết muôn hình vạn trạng của nó, đồng thời chúng tôi sẽ chỉ trình bày sơ lược về phong cách cũ trong một phần phụ lục dành cho những người hoài cổ muốn tìm hiểu về mặt lịch sử.

Định hướng thiết kế hướng lớp và tiêu chí áp dụng

Hệ tư tưởng định hướng lớp về bản chất là một khuôn mẫu thiết kế kiến trúc phần mềm, đồng nghĩa với việc nó là một sự lựa chọn chiến lược về cái cách mà các kỹ sư quyết định tổ chức thông tin và hành vi bên trong chương trình máy tính của mình. Khuôn mẫu thiết kế này luôn mang trong mình những ưu điểm và nhược điểm cố hữu, và tuyệt đối không phải là một giải pháp vạn năng có khả năng giải quyết trọn vẹn mọi bài toán lập trình. Vậy làm thế nào để bạn nhận biết chính xác thời điểm vàng nên sử dụng các lớp? Dưới góc độ lý thuyết hàn lâm, định hướng lớp là phương pháp phân rã một miền nghiệp vụ thành các mảnh ghép có thể được phân loại qua mối quan hệ là một, nhóm các thực thể dựa trên tập hợp đặc tính cốt lõi mà chúng chia sẻ chung với những thực thể tương đồng khác.

Bản chất phân loại và tư duy thiết kế hướng lớp

Hệ tư tưởng định hướng lớp về bản chất là một khuôn mẫu thiết kế kiến trúc phần mềm, điều này mang một ý nghĩa sâu xa rằng nó là một sự lựa chọn chiến lược về cái cách mà các kỹ sư quyết định tổ chức khối lượng thông tin khổng lồ và các hành vi phức tạp bên trong chương trình máy tính của họ. Khuôn mẫu thiết kế này luôn luôn mang trong mình những ưu điểm vượt trội đi kèm với những nhược điểm cố hữu không thể tránh khỏi, và do đó, nó tuyệt đối không bao giờ là một giải pháp vạn năng có khả năng giải quyết trọn vẹn mọi bài toán lập trình xuất hiện trên đời. Vậy làm thế nào để các kiến trúc sư phần mềm có thể nhận biết được chính xác cái thời điểm vàng mà họ thực sự nên viện đến sự phục vụ của các cấu trúc lớp? Xét trên một phương diện lý thuyết hàn lâm thuần túy, định hướng lớp là một phương pháp luận tư duy nhằm chia cắt và phân rã một miền nghiệp vụ kinh doanh của một chương trình thành một hoặc nhiều mảnh ghép cấu trúc độc lập, mà mỗi mảnh ghép trong số đó đều có thể được định nghĩa một cách rành mạch thông qua một phép phân loại mang tính chất là một – tức là hành động gom nhóm một thực thể cụ thể vào trong một hoặc nhiều tập hợp chứa đựng những đặc tính mà thực thể đó chia sẻ chung với những thực thể có nét tương đồng khác. Người ta thường sử dụng mệnh đề logic thực thể X là một thực thể Y, mang hàm ý khẳng định rằng thực thể X sở hữu ít nhất là toàn bộ những đặc tính cốt lõi tạo nên bản chất của một thực thể thuộc chủng loại Y.

Để có thể mường tượng một cách trực quan và sâu sắc hơn về nguyên lý phân loại này, chúng ta hãy cùng nhau xem xét ví dụ điển hình về những chiếc máy vi tính trong thế giới thực. Theo góc nhìn vật lý, chúng ta hoàn toàn có thể khẳng định rằng một chiếc máy vi tính là một thiết bị sử dụng điện, bởi vì nó bắt buộc phải tiêu thụ dòng điện năng bao gồm các yếu tố như điện áp hay cường độ dòng điện để có thể duy trì nguồn năng lượng hoạt động. Đẩy mức độ phân loại lên một tầm cao phức tạp hơn, nó còn là một thiết bị điện tử thực thụ, bởi vì nó sở hữu năng lực thao túng và điều khiển dòng điện năng đó vượt xa khỏi việc chỉ đơn thuần điều hướng các hạt electron chạy vòng quanh trong các trường điện từ, từ đó kiến tạo nên một hệ thống mạch điện logic mang ý nghĩa sâu sắc để ép buộc dòng điện phải nai lưng ra thực thi những tác vụ tính toán phức tạp hơn rất nhiều. Đặt lên bàn cân so sánh để thấy rõ sự khác biệt bản chất, một chiếc đèn bàn cơ bản nhất chắc chắn là một thiết bị sử dụng điện, thế nhưng nó lại hoàn toàn không đủ tư cách để được xếp vào hàng ngũ của những thiết bị điện tử tinh vi. Dựa trên nền tảng tư duy phân loại đó, chúng ta hoàn toàn có đủ cơ sở học thuật để định nghĩa ra một lớp mang tên thiết bị điện nhằm mục đích miêu tả một cách trừu tượng những gì mà các thiết bị sử dụng điện cần có và có khả năng thực hiện.

Ngay sau khi thiết lập được lớp nền tảng đó, chúng ta có thể tiếp tục tiến thêm một bước nữa để định nghĩa ra một lớp chuyên biệt hơn mang tên thiết bị điện tử, và tiến hành định nghĩa rằng bên cạnh việc kế thừa trọn vẹn bản chất của một thiết bị điện, những thực thể thuộc lớp thiết bị điện tử còn được bổ sung thêm năng lực thao túng dòng điện để kiến tạo ra những kết quả đầu ra mang tính chuyên biệt hóa cao độ. Chính tại cái giao điểm kiến trúc này, ánh hào quang của hệ tư tưởng định hướng lớp bắt đầu tỏa sáng một cách rực rỡ và phô diễn toàn bộ sức mạnh hủy diệt của nó trong việc tái sử dụng mã nguồn. Thay vì phải hì hục cắn răng định nghĩa lại từ đầu toàn bộ những đặc tính cốt lõi của lớp thiết bị điện vào bên trong nội tạng của lớp thiết bị điện tử một cách dư thừa và lãng phí, chúng ta hoàn toàn có thể định nghĩa lớp thiết bị điện tử theo một cách thức thông minh hơn để nó có thể chia sẻ hoặc nhận thừa kế một cách tự động toàn bộ những đặc tính đó từ lớp thiết bị điện, và sau đó chỉ việc tập trung vào việc bổ sung hoặc định nghĩa lại những hành vi độc đáo duy nhất biến một thiết bị trở thành thiết bị điện tử thực thụ. Mối quan hệ cấu trúc mang tính chất cha con giữa hai lớp này – thứ thường được giới học thuật gọi bằng thuật ngữ sự kế thừa – chính là một trong những khía cạnh kiến trúc mang tính chìa khóa quan trọng bậc nhất của hệ tư tưởng định hướng lớp.

Nguyên tắc đóng gói và mối quan hệ thành phần

Thông qua những phân tích lý thuyết đã được mổ xẻ tường tận, hệ tư tưởng định hướng lớp lộ diện như một phương pháp luận tư duy cấp cao để các kỹ sư có thể chiêm nghiệm về những thực thể mà chương trình máy tính của họ đang khát khao cần đến, đồng thời tiến hành phân loại chúng thành những nhóm cấu trúc dựa trên những đặc tính cốt lõi của chúng, bao gồm việc xác định xem chúng đang ôm giữ những thông tin dữ liệu gì và những thao tác tính toán nào có thể được phép thực thi trên mớ dữ liệu đó. Quá trình này đòi hỏi việc vạch ra những mối quan hệ logic tinh vi giữa các nhóm đặc tính khác nhau, tạo nên một mạng lưới cấu trúc thông tin phản ánh trung thực mô hình nghiệp vụ của thế giới thực. Thế nhưng, nếu chúng ta tạm gác lại những lý thuyết hàn lâm trừu tượng và bước những bước chân thực tế hơn vào một góc nhìn mang đậm tính thực dụng của ngành công nghiệp phần mềm: nếu như chương trình máy tính của bạn đang gánh vác một trọng trách là phải lưu giữ và thao tác cùng một lúc với hàng tá những tập hợp dữ liệu và hành vi có cấu trúc giống hệt nhau dưới dạng các bản sao, thì dự án của bạn rất có thể sẽ gặt hái được những lợi ích kiến trúc vô cùng to lớn từ việc áp dụng hệ tư tưởng định hướng lớp.

Để làm sáng tỏ lập luận thực dụng này, tôi xin phép được phác họa một câu chuyện minh họa ngắn gọn dựa trên trải nghiệm cá nhân. Cách đây vài thập kỷ, ngay sau cái khoảnh khắc tôi vừa mới cày ải xong gần như toàn bộ chương trình học của một tấm bằng cử nhân khoa học máy tính trên giảng đường đại học, tôi đã vinh dự được ngồi vào vị trí công việc đầu tiên dưới danh xưng là một nhà phát triển phần mềm chuyên nghiệp. Nhiệm vụ đầu tiên mà tôi bị giao phó là phải tự thân vận động xây dựng từ con số không một hệ thống phần mềm chuyên dùng để theo dõi bảng chấm công và tính toán lương bổng cho nhân sự. Tôi đã quyết định xây dựng toàn bộ hệ thống máy chủ vận hành ở phía sau bằng ngôn ngữ PHP, kết hợp với hệ quản trị cơ sở dữ liệu MySQL, và mạnh dạn sử dụng ngôn ngữ JavaScript để kiến tạo nên phần giao diện tương tác ở phía trước, bất chấp một thực tế là ngôn ngữ này vẫn còn đang ở trong giai đoạn sơ khai và chưa đạt đến độ chín muồi như hiện nay vào cái thời điểm giao thời của đầu thế kỷ. Bởi vì chương trình đào tạo khoa học máy tính của tôi đã nhồi nhét cực kỳ nặng nề hệ tư tưởng định hướng lớp xuyên suốt các môn học, nên tôi đã mang trong mình một sự háo hức tột độ để đem toàn bộ mớ lý thuyết hàn lâm đó ra ứng dụng vào trong dự án thực tế đầu tay này.

Bám sát theo thiết kế kiến trúc của chương trình, tôi đã tiến hành định nghĩa khái niệm về một thực thể bảng chấm công như là một tập hợp chứa đựng từ hai đến ba thực thể tuần, và mỗi một thực thể tuần lại được định nghĩa là một tập hợp bao bọc từ năm đến bảy thực thể ngày, rồi mỗi một thực thể ngày lại tiếp tục là một tập hợp chứa đựng hàng tá các thực thể nhiệm vụ nhỏ lẻ. Nếu như tôi phát sinh nhu cầu muốn điều tra xem rốt cuộc có tổng cộng bao nhiêu giờ đồng hồ đã được ghi nhận vào bên trong một bản sao bảng chấm công cụ thể, tôi chỉ việc đơn giản bóp cò gọi một thao tác tính tổng thời gian nhắm thẳng vào bản sao đó. Thực thể bảng chấm công đã tự mình định nghĩa cái thao tác này bằng cách chạy một vòng lặp quét qua toàn bộ tập hợp các tuần của nó, liên tục gọi thao tác tính tổng thời gian trên từng tuần một và tiến hành cộng dồn các giá trị thu được lại với nhau. Theo một quy luật đệ quy tương tự, mỗi tuần lại thực hiện hành động y hệt đối với toàn bộ các ngày của nó, và mỗi ngày lại tiếp tục làm như vậy đối với toàn bộ các nhiệm vụ của nó. Cái ý niệm triết học đang được phác họa ở đây, vốn là một trong những nền tảng tối thượng của các khuôn mẫu thiết kế như định hướng lớp, được giới chuyên môn gọi bằng cái tên là tính đóng gói… Mỗi một cấp độ thực thể đều tự mình đóng gói một cách kín kẽ – hay nói cách khác là kiểm soát, che giấu, và trừu tượng hóa – những chi tiết triển khai nội tạng của nó bao gồm cả dữ liệu lẫn hành vi, trong khi vẫn phô bày ra bên ngoài một giao diện tương tác vô cùng hữu dụng và thanh lịch. Tuy nhiên, nếu chỉ dựa dẫm vào mỗi tính đóng gói thôi thì chưa đủ sức nặng để trở thành một lý lẽ biện minh hoàn hảo cho việc áp dụng định hướng lớp, bởi vì thế giới lập trình không hề thiếu những khuôn mẫu thiết kế khác cũng dư sức cung cấp một mức độ đóng gói hoàn toàn tương đương. Điểm khác biệt mấu chốt nằm ở chỗ thiết kế lớp của tôi đã bòn rút sức mạnh từ cơ chế kế thừa như thế nào: tôi đã dựng lên một lớp cơ sở làm nền tảng định nghĩa một tập hợp các thao tác chuẩn mực như tính tổng thời gian, và sau đó mỗi một kiểu lớp thực thể của tôi đều tiến hành mở rộng hoặc dẫn xuất từ cái lớp cơ sở này. Điều đó mang một ý nghĩa vĩ đại rằng mỗi một lớp con đều tự động nhận thừa kế cái năng lực cộng dồn tổng thời gian này, thế nhưng điểm mấu chốt là mỗi lớp con lại tự mình áp dụng những phần mở rộng và những định nghĩa mang đậm tính cá nhân hóa để quyết định xem những chi tiết nội tạng về việc làm thế nào để thi hành cái công việc đó sẽ diễn ra ra sao. Vẫn còn lẩn khuất một khía cạnh kiến trúc vô cùng tinh vi khác của khuôn mẫu thiết kế đang ngấm ngầm phát huy tác dụng ở đây, đó chính là mối quan hệ thành phần: mỗi một thực thể đều được hệ thống định nghĩa như một tập hợp cấu thành từ những thực thể nhỏ hơn khác, tạo nên một cây cấu trúc phân cấp đồ sộ.

Bài toán đa bản sao và giới hạn của khuôn mẫu lớp

Như tôi đã từng hé lộ một cách chiến lược ở phần trước, một phương pháp tiếp cận cực kỳ thực dụng để có thể đưa ra phán quyết xem liệu kiến trúc phần mềm của bạn có thực sự khát khao hệ tư tưởng định hướng lớp hay không là hãy xem xét liệu chương trình của bạn có dự định khởi tạo ra hàng tá những bản sao của cùng một loại hành vi hay một loại lớp duy nhất hay không. Lật lại cái ví dụ kinh điển về hệ thống bảng chấm công, chúng ta đã sở hữu tổng cộng bốn lớp riêng biệt: Bảng chấm công, Tuần, Ngày, và Nhiệm vụ. Thế nhưng điểm tạo nên sự khác biệt cốt lõi là đối với mỗi một lớp, chúng ta lại liên tục khởi tạo và thao tác với hàng tá những bản sao của chúng cùng một lúc trong bộ nhớ hệ thống. Sự tồn tại của nhiều bản sao đồng thời chính là mảnh đất màu mỡ nhất để cấu trúc lớp phát huy tối đa tiềm năng quản lý trạng thái độc lập nhưng chia sẻ chung khuôn mẫu hành vi, giúp tiết kiệm bộ nhớ và dễ dàng bảo trì.

Trái ngược hoàn toàn với kịch bản đó, giả sử như chúng ta bị rơi vào một tình huống mà nhu cầu thực tế chỉ đòi hỏi sự hiện diện của đúng một bản sao duy nhất cho một lớp, lấy ví dụ như chỉ cần tạo ra đúng một thực thể Máy vi tính vốn là một bản sao của lớp Điện tử, mà lớp này lại là một lớp con dẫn xuất từ lớp Điện, thì hệ tư tưởng định hướng lớp có lẽ sẽ không thể phô diễn được quá nhiều những lợi ích kiến trúc mang tính đột phá của nó. Phân tích một cách đặc biệt và sâu sắc hơn, nếu như logic của chương trình hoàn toàn không có nhu cầu phải nặn ra một bản sao nào của lớp Điện, thì việc chúng ta cất công tách bạch và phân rã lớp Điện ra khỏi lớp Điện tử thực chất chẳng mang lại một lợi ích rõ rệt nào cả, và hệ lụy tất yếu là chúng ta đang không hề nhận được bất kỳ một sự trợ giúp đắc lực nào từ khía cạnh kế thừa của hệ tư tưởng định hướng lớp. Việc thiết kế quá mức cần thiết trong những trường hợp đơn lẻ này không chỉ làm tăng độ phức tạp không đáng có của mã nguồn mà còn tiêu tốn tài nguyên phân tích cú pháp của cỗ máy thực thi mà không mang lại giá trị hoàn vốn tương xứng.

Rút ra một chân lý kỹ thuật từ những phân tích trên: nếu như bạn nhận thấy bản thân đang say sưa thiết kế một chương trình bằng cách băm vằm một miền vấn đề nghiệp vụ ra thành hàng tá những lớp thực thể khác nhau, thế nhưng khi đi sâu vào mã nguồn thực tế của chương trình, bạn lại chỉ vĩnh viễn cần đến sự phục vụ của đúng một thực thể cụ thể duy nhất cho một loại hành vi hoặc một định nghĩa lớp, thì rất có khả năng bạn thực sự hoàn toàn không hề cần đến hệ tư tưởng định hướng lớp một chút nào. Thế giới khoa học máy tính luôn tồn tại sẵn những khuôn mẫu thiết kế khác có tiềm năng mang lại một sự tương thích và hiệu quả vượt trội hơn rất nhiều đối với nỗ lực công việc của bạn trong những bài toán đơn bản sao này. Tuy nhiên, nếu như bạn nhận thấy bản thân đang ôm ấp một khát khao mãnh liệt muốn định nghĩa ra các lớp nền tảng, cùng với những lớp con nhận thừa kế trực tiếp từ chúng, và nếu như bạn chắc chắn rằng mình sẽ tiến hành khởi tạo một hoặc nhiều lớp trong số đó lặp đi lặp lại hàng tá lần trong suốt vòng đời ứng dụng, thì hệ tư tưởng định hướng lớp nghiễm nhiên trở thành một ứng cử viên sáng giá bậc nhất. Và để có thể thực thi hệ tư tưởng định hướng lớp một cách chuẩn mực bên trong hệ sinh thái ngôn ngữ JavaScript, bạn gần như chắc chắn sẽ phải viện đến sức mạnh tối thượng của từ khóa lớp.

Cú pháp nền tảng và cơ chế vận hành của lớp

Từ khóa lớp mang trong mình quyền năng tối thượng cho phép định nghĩa một lớp dưới hình thái của một câu lệnh khai báo tường minh hoặc một biểu thức đánh giá giá trị. Khi là một câu lệnh khai báo, khối định nghĩa lớp sẽ xuất hiện chễm chệ ở vị trí của một câu lệnh tiêu chuẩn; ngược lại, khi là một biểu thức, nó nằm ở vị trí của một giá trị được gán và hoàn toàn có thể ẩn danh hoặc sở hữu tên định danh riêng. Nằm lọt thỏm bên trong khối cơ thể của lớp, các phương thức được định nghĩa một cách thanh lịch mà không cần đến từ khóa hàm, đồng thời hệ thống cấm tiệt việc sử dụng dấu phẩy hay dấu chấm phẩy để phân cách giữa chúng. Một điểm đáng lưu tâm: khi bước chân vào giới hạn của khối lớp, toàn bộ mã đều bị ép buộc chạy trong chế độ nghiêm ngặt một cách ngầm định (ngay cả khi không có chỉ thị use strict), điều này tác động trực tiếp đến hành vi của từ khóa this trong các lời gọi hàm.

Phương thức khởi tạo và bản chất hàm của lớp

Từ khóa lớp mang trong mình một quyền năng tối thượng cho phép các lập trình viên định nghĩa một lớp dưới hình thái của một câu lệnh khai báo tường minh hoặc một biểu thức đánh giá giá trị. Khi khoác lên mình hình hài của một câu lệnh khai báo, một khối định nghĩa lớp sẽ ngang nhiên xuất hiện chễm chệ ở vị trí của một câu lệnh tiêu chuẩn và mang một dáng vẻ cú pháp giống như thế này: bắt đầu bằng từ khóa lớp, theo sau là tên định danh viết hoa chữ cái đầu, và kết thúc bằng một khối ngoặc nhọn chứa đựng các phương thức nội tại. Ở một thái cực hoàn toàn đối lập, khi được sử dụng dưới hình hài của một biểu thức, một khối định nghĩa lớp sẽ khiêm nhường xuất hiện ở vị trí của một giá trị được gán, và nó hoàn toàn có đặc quyền sở hữu một tên định danh riêng biệt hoặc chọn cách ẩn danh hoàn toàn để phục vụ cho các kỹ thuật khởi tạo đối tượng động ngay tại thời điểm chạy chương trình. Bất kể sử dụng hình thái nào, phần nội tạng nằm bên trong một khối cơ thể của một lớp thường xuyên chứa đựng một hoặc hàng tá những định nghĩa phương thức dùng để trang bị hành vi cho các bản sao đối tượng sau này.

Nằm lọt thỏm bên trong khối cơ thể của một lớp, các phương thức được định nghĩa một cách vô cùng thanh lịch mà hoàn toàn không cần đến sự hiện diện thừa thãi của từ khóa hàm, và hệ thống cũng cấm tiệt sự xuất hiện của các dấu phẩy hoặc dấu chấm phẩy đóng vai trò làm hàng rào phân cách giữa các định nghĩa phương thức với nhau. Cần phải gióng lên một hồi chuông cảnh báo về mặt ngữ cảnh thực thi: khi bước chân vào bên trong giới hạn của một khối lớp, toàn bộ mọi đoạn mã đều bị hệ thống ép buộc phải chạy trong môi trường chế độ nghiêm ngặt một cách ngầm định, bất chấp việc có sự xuất hiện của chỉ thị use strict trong tệp tin hay trong các hàm của nó hay không. Quy định khắt khe này mang lại một tác động mang tính chất chí mạng đối với cách thức vận hành của từ khóa this trong các lời gọi hàm, một chủ đề phức tạp sẽ được chúng tôi mổ xẻ tường tận ở Chương 4.

Một trong những phương thức đặc chủng mang tính chất sinh tử mà toàn bộ mọi lớp đều phải sở hữu được gọi là phương thức khởi tạo… Nếu như người lập trình lười biếng và bỏ sót việc khai báo nó, hệ thống sẽ tự động ngầm định chèn vào một phương thức khởi tạo rỗng tuếch mặc định ngay bên trong định nghĩa lớp để đảm bảo cấu trúc không bị sụp đổ. Phương thức khởi tạo này mang một sứ mệnh vô cùng quan trọng là nó sẽ bị cỗ máy thực thi gọi ra để kích nổ bất cứ khi nào có một bản sao mới của lớp được nặn ra từ hư không thông qua sức mạnh của từ khóa tạo mới. Bất chấp một sự thật là cú pháp của ngôn ngữ mang lại một ảo giác ngầm định rằng thực sự có tồn tại một hàm vật lý mang tên là hàm khởi tạo, hệ thống JavaScript thực chất lại tiến hành định nghĩa một hàm điện toán theo đúng những gì được chỉ định, thế nhưng lại gán cho nó cái tên trùng khớp hoàn toàn với tên của lớp vừa được khai báo. Tuy nhiên, đây tuyệt đối không phải là một hàm điện toán thông thường; chủng loại hàm đặc chủng này bị áp đặt những quy tắc hành xử có phần dị biệt hơn rất nhiều: mọi nỗ lực tuyệt vọng nhằm gọi cái hàm lớp này như một hàm thông thường mà không có sự tháp tùng của từ khóa tạo mới đều sẽ vấp phải sự trừng phạt thích đáng của hệ thống thông qua một ngoại lệ lỗi kiểu dữ liệu chết người. Các nhà kiến trúc hoàn toàn có thể tự do khởi tạo ra bao nhiêu bản sao khác biệt của một lớp tùy thích, và mỗi một bản sao được sinh ra đều là một đối tượng độc lập hoàn toàn, sở hữu một không gian trạng thái riêng biệt không hề liên quan đến các bản sao anh em của nó.

Phương thức của lớp và cơ chế chia sẻ nguyên mẫu

Mỗi một đối tượng bản sao được sinh ra từ quá trình khởi tạo đều tự động được hệ thống ban phát cho một sợi dây liên kết nguyên mẫu ẩn nội bộ trỏ thẳng đến đối tượng nguyên mẫu của lớp, một khái niệm kiến trúc đã được chúng tôi giải phẫu chi tiết ở Chương 2. Trong cơ chế vận hành của mã nguồn ngôn ngữ JavaScript, định danh của lớp đóng vai trò kép: nó vừa là một định nghĩa kiến trúc lớp mang tính khai báo, lại vừa kiêm luôn vai trò là một hàm tạo mang cùng một tên gọi dùng để khởi tạo bộ nhớ cho đối tượng. Nếu như lập trình viên chủ động đắp thêm một thuộc tính dữ liệu mới toanh vào một đối tượng bản sao cụ thể, thì cái thuộc tính vừa được sinh ra đó sẽ chỉ chễm chệ ngự trị duy nhất trên cái bản sao cụ thể đó mà thôi, và nó vĩnh viễn không bao giờ tồn tại hay hiện diện theo bất kỳ một cách thức nào cho phép những đối tượng bản sao độc lập khác có cơ hội thọc tay vào để truy cập hay sửa đổi. Sự cô lập dữ liệu này chính là nền tảng cốt lõi đảm bảo tính toàn vẹn trạng thái cho từng thực thể hoạt động độc lập trong hệ thống.

Như những gì đã được phô diễn trong các cấu trúc cú pháp, một định nghĩa lớp hoàn toàn có khả năng dung chứa một hoặc hàng tá những định nghĩa phương thức nội tại nhằm cung cấp kho vũ khí hành vi cho các bản sao. Khi chúng ta quan sát cú pháp truy cập phương thức từ bên ngoài, một thuộc tính phương thức mang lại một ảo giác quang học hoàn hảo như thể nó đang thực sự tồn tại trên – hoặc thuộc quyền sở hữu độc quyền của – chính cái đối tượng bản sao đang gọi nó. Thế nhưng, đó chỉ là một ảo ảnh sa mạc đánh lừa thị giác của những người mới vào nghề. Sự thật phũ phàng và sâu sắc hơn ở tầng vi mạch là mỗi một phương thức của lớp đều được cỗ máy ngôn ngữ bứt ra và gắn chặt vào đối tượng nguyên mẫu, một thuộc tính đặc chủng trực thuộc hàm tạo của lớp đó. Cơ chế tối ưu hóa bộ nhớ này đảm bảo rằng hàng triệu bản sao được tạo ra cũng sẽ không làm phình to bộ nhớ chứa các khối mã hàm lặp đi lặp lại.

Hệ quả tất yếu của cơ chế này là phương thức đó chỉ thực sự tồn tại vật lý dưới thân phận là một thuộc tính nằm trên đối tượng nguyên mẫu của lớp. Bởi vì đối tượng bản sao đã được liên kết nguyên mẫu ẩn trỏ đến đối tượng nguyên mẫu của lớp thông qua phép màu của quá trình khởi tạo bằng từ khóa tạo mới, nên sợi dây tham chiếu gọi phương thức nhắm vào bản sao sẽ tự động kích hoạt một cuộc hành trình lùng sục men theo chuỗi nguyên mẫu ẩn và cuối cùng sẽ tìm thấy cái phương thức đích thực đang nằm chờ để được đem ra thực thi. Một nguyên tắc thiết kế bất di bất dịch là các phương thức của lớp chỉ nên được triệu hồi thông qua một đối tượng bản sao; mọi mưu đồ nhằm triệu hồi phương thức trực tiếp từ định danh lớp sẽ chuốc lấy thất bại thảm hại bởi vì hoàn toàn không hề tồn tại bất kỳ một thuộc tính nào mang tên như vậy gắn trên bản thân hàm tạo lớp. Mặc dù lập trình viên hoàn toàn có thể sử dụng thủ thuật tà đạo để triệu hồi phương thức trực tiếp từ đối tượng nguyên mẫu của lớp, thế nhưng hành vi này nhìn chung bị cộng đồng học thuật đánh giá là không chuẩn mực và không hề được khuyến khích trong lối viết mã định hướng lớp tiêu chuẩn. Chân lý cuối cùng là hãy luôn luôn tiếp cận và truy cập các phương thức của lớp thông qua các đối tượng bản sao để đảm bảo tính toàn vẹn của ngữ cảnh thực thi.

Trường dữ liệu công khai và bối cảnh của từ khóa này

Chúng tôi cam kết sẽ dành hẳn một chương chuyên sâu ở phần sau để mổ xẻ và lột trần toàn bộ sự thật về từ khóa this với một mức độ chi tiết đến từng chân tơ kẽ tóc. Thế nhưng, khi đặt trong mối tương quan chặt chẽ với mã nguồn định hướng lớp, từ khóa this nhìn chung luôn luôn mang một sứ mệnh trỏ thẳng đến bản sao đối tượng hiện tại đang đóng vai trò là ngữ cảnh không gian cho bất kỳ một lời gọi phương thức nào đang diễn ra. Nằm lọt thỏm bên trong phương thức khởi tạo, cũng như bên trong bất kỳ một phương thức thông thường nào khác, các nhà phát triển hoàn toàn có đặc quyền sử dụng cú pháp tham chiếu this. để tiến hành đắp thêm những thuộc tính mới toanh hoặc thọc tay vào truy cập những thuộc tính đã có sẵn trên bản sao hiện tại. Bất kỳ thuộc tính nào không ôm giữ những giá trị dạng hàm điện toán, mà được đắp thêm vào một bản sao của lớp – thường là thông qua bàn tay nhào nặn của phương thức khởi tạo – đều được giới chuyên môn xưng tụng bằng thuật ngữ thành viên, nhằm mục đích phân định rạch ròi với thuật ngữ phương thức vốn chỉ dành riêng cho những khối hàm có khả năng thực thi lệnh.

Thay vì phải hì hục định nghĩa một thành viên của bản sao lớp theo phong cách mệnh lệnh thủ công thông qua cú pháp this. bên trong phương thức khởi tạo hoặc một phương thức nào đó, cấu trúc lớp hiện đại cho phép khai báo một cách mang tính khai báo các trường dữ liệu trực tiếp ngay bên trong thân của lớp, những trường này sẽ tương ứng một cách trực tiếp với những thành viên sẽ được hệ thống nặn ra trên mỗi một bản sao sau này. Các trường dữ liệu công khai hoàn toàn có quyền sở hữu một giá trị khởi tạo đi kèm, thế nhưng đó không phải là một yêu cầu mang tính ép buộc về mặt ngữ pháp. Một lời khuyên kiến trúc là nếu như bạn quyết định không khởi tạo giá trị cho một trường ngay tại định nghĩa lớp, thì gần như chắc chắn bạn nên đảm nhận việc khởi tạo nó bên trong phương thức khởi tạo để tránh những lỗi trạng thái không xác định về sau. Các trường dữ liệu cũng hoàn toàn sở hữu năng lực tham chiếu chéo lẫn nhau, thông qua cú pháp truy cập this. một cách vô cùng thuận tự nhiên. Một mẹo nhỏ để dễ hình dung là người lập trình có thể tưởng tượng các khai báo trường dữ liệu công khai này giống hệt như việc chúng đang chễm chệ xuất hiện ở ngay dòng đầu tiên của phương thức khởi tạo, với mỗi trường đều được ngầm gắn thêm một tiền tố this. mà cú pháp khai báo trong thân lớp đã cho phép chúng ta lược bỏ đi cho đỡ rườm rà. Y hệt như khái niệm tên thuộc tính được tính toán đã được mổ xẻ ở Chương 1, các tên trường dữ liệu cũng hoàn toàn có khả năng được hệ thống tính toán động dựa trên các biểu thức.

Tuy nhiên, có một khuôn mẫu thiết kế dị hợm đã trỗi dậy và dần trở nên cực kỳ thịnh hành trong giới lập trình, nhưng cá nhân tôi lại mang một niềm tin sắt đá rằng nó đích thị là một khuôn mẫu chống lại tự nhiên đối với cấu trúc lớp: đó là hành vi gán một hàm mũi tên vào một trường dữ liệu công khai. Tôi kịch liệt phản đối trò này, bởi vì nó phản bội lại chính bản chất của trụ cột nguyên mẫu của ngôn ngữ. Tại sao các lập trình viên lại thích làm vậy? Bởi vì họ dường như liên tục bị dồn vào trạng thái hoang mang tột độ bởi những quy tắc ràng buộc động của từ khóa this, thế nên họ quyết định dùng vũ lực để ép buộc một sự ràng buộc tĩnh thông qua sức mạnh của hàm mũi tên, đảm bảo rằng hàm đó luôn luôn bị trói chặt vào bản sao hiện tại bất chấp cách nó được gọi ra sao. Mặc dù khao khát về sự tiện lợi đó là hoàn toàn có thể cảm thông được, nhưng hệ lụy là chúng ta đang đánh mất đi tính chất phương thức nguyên mẫu được chia sẻ của khối hàm đó, và biến nó trở thành một thuộc tính riêng biệt nằm trên từng bản sao. Điều đó đồng nghĩa với việc chúng ta đang tàn nhẫn ra lệnh cho hệ thống phải đẻ ra một thuộc tính hàm mới toanh cho mỗi một bản sao được khởi tạo, thay vì chỉ tạo ra nó đúng một lần duy nhất trên nguyên mẫu của hàm tạo lớp rồi chia sẻ cho muôn loài. Hành vi này gây ra một sự lãng phí vô nghĩa về mặt hiệu suất thực thi và tài nguyên bộ nhớ, và nghiêm trọng hơn, nó phá nát hoàn toàn lý do kiến trúc khiến cho việc sử dụng lớp và các phương thức nhận thức được từ khóa this trở nên hữu dụng và mang lại sức mạnh tột đỉnh. Nếu như thứ duy nhất bạn thực sự khát khao chỉ là những hàm được cố định tĩnh vào một bối cảnh cụ thể mà không cần đến bất kỳ sự linh hoạt động hay cơ chế chia sẻ bộ nhớ nào, thì thứ bạn đang tìm kiếm thực chất chính là… cơ chế bao đóng, chứ tuyệt đối không phải là cơ chế lớp.

Mở rộng lớp và tính đa hình trong thiết kế

Chìa khóa vạn năng để có thể mở khóa và giải phóng toàn bộ sức mạnh hủy diệt của cơ chế kế thừa lớp chính là thông qua từ khóa mở rộng, thứ mang trên mình sứ mệnh định nghĩa một mối quan hệ ràng buộc logic chặt chẽ giữa hai cấu trúc lớp riêng biệt. Thông qua cơ chế này, một lớp cơ sở có thể định nghĩa các trường dữ liệu và phương thức ban đầu, sau đó một lớp dẫn xuất hay lớp con tiến hành kế thừa nó. Lớp con không chỉ tiếp nhận các thuộc tính từ lớp cha mà còn có quyền năng tái khởi tạo giá trị, ghi đè hành vi, hoặc đắp thêm những thành viên và phương thức hoàn toàn mới toanh. Sự tương tác phức tạp này tạo ra tính đa hình, nơi các phương thức ở những tầng cấp bậc khác nhau của cây phả hệ tự động phân giải và thực thi hành vi thông qua chuỗi nguyên mẫu ẩn.

Kế thừa lớp thông qua cơ chế ủy quyền nguyên mẫu

Chìa khóa vạn năng để có thể mở khóa và giải phóng toàn bộ sức mạnh hủy diệt của cơ chế kế thừa lớp chính là thông qua từ khóa mở rộng, thứ mang trên mình sứ mệnh định nghĩa một mối quan hệ ràng buộc chặt chẽ giữa hai cấu trúc lớp riêng biệt. Hãy dành ra một vài khoảnh khắc tĩnh tâm để nghiền ngẫm lại đoạn mã cấu trúc và đảm bảo rằng bộ não của bạn hoàn toàn thấu tỏ được những cơ chế ma quỷ nào đang thực sự diễn ra bên dưới lớp vỏ bọc cú pháp. Lớp cơ sở định nghĩa các trường dữ liệu hay còn gọi là các thành viên, và gán cho chúng những giá trị khởi tạo ban đầu, đồng thời nó cũng định nghĩa một phương thức nội tại chuyên làm nhiệm vụ truy cập vào thành viên bản sao này và nôn giá trị của nó ra ngoài. Thế nhưng, khi một lớp thứ hai sử dụng từ khóa mở rộng để kết nối với lớp cơ sở đó, nó đã tự biến bản thân mình thành một lớp dẫn xuất, một lớp con, hay thuật ngữ được giới giang hồ ưa chuộng nhất là một lớp kế thừa. Nằm lọt thỏm bên trong nội tạng của lớp con này, chính cái thuộc tính vừa được nhận thừa kế từ lớp cơ sở lại bị ghi đè và khởi tạo lại với một giá trị hoàn toàn khác biệt, minh chứng cho khả năng tùy biến trạng thái ở tầng dẫn xuất.

Nó đồng thời cũng đắp thêm một phương thức thành viên mới toanh vào cơ thể mình, phương thức này lại tự mình bóp cò gọi một phương thức khác thông qua từ khóa this. Khi phương thức đó của bản sao lớp con được triệu hồi để thực thi, phương thức được thừa kế cũng sẽ theo đó mà bị kích nổ, và cái phương thức thừa kế đó lại tiến hành tạo ra một tham chiếu nhắm thẳng vào thuộc tính của bản sao thông qua từ khóa this. Bởi vì từ khóa this ngay tại khoảnh khắc đó đang chĩa mũi nhọn trỏ thẳng vào bản sao của lớp con, nên cái giá trị mà nó đào bới tìm thấy giờ đây sẽ là cái giá trị đã bị ghi đè, chứ tuyệt đối không phải là cái giá trị nguyên thủy từ đối tượng của lớp cơ sở. Cơ chế phân giải tham chiếu động này chính là nền tảng tạo nên sự linh hoạt tuyệt đối của các hệ thống hướng đối tượng.

Tuy nhiên, người lập trình cần phải tẩy não ngay lập tức cái ảo giác sai lầm rằng việc một lớp con sử dụng từ khóa mở rộng đối với một lớp cơ sở đồng nghĩa với việc nó đang nhận được một bản sao chép vật lý chứa toàn bộ mọi hành vi đã được định nghĩa trong lớp cơ sở đó. Hơn thế nữa, sẽ là một sự lú lẫn cực độ nếu như tin rằng đối tượng bản sao cụ thể lại được hệ thống sao chép trút xuống toàn bộ các phương thức từ lớp con và cả lớp cơ sở. Đó hoàn toàn không phải là một mô hình tư duy chuẩn xác để có thể thấu hiểu cách thức mà ngôn ngữ JavaScript triển khai hệ tư tưởng định hướng lớp. Nếu như bạn mang đối tượng bản sao của lớp con ra soi dưới kính hiển vi bộ nhớ, bạn sẽ kinh ngạc nhận ra rằng nó chỉ mang trên mình đúng những thuộc tính đóng vai trò là thành viên bản sao, chứ hoàn toàn không hề có sự hiện diện của bất kỳ phương thức nào. Vậy thì cái phương thức đó đang lẩn trốn ở cái xó xỉnh nào? Câu trả lời là nó đang nằm an tọa trên đối tượng nguyên mẫu của hàm tạo lớp. Và bản sao đối tượng chỉ đơn thuần được cấp quyền truy cập vào cái phương thức đó thông qua sợi dây liên kết nguyên mẫu ẩn nội bộ của nó. Nói một cách ngôn ngữ kiến trúc, các đối tượng nguyên mẫu đang tiến hành chia sẻ quyền truy cập đối với các phương thức của chúng cho các lớp con và các bản sao ở tuyến dưới. Các phương thức vĩnh viễn nằm yên tại vị trí ban đầu của chúng, và tuyệt đối không bao giờ bị hệ thống sao chép nhân bản dọc theo chuỗi kế thừa. Mặc dù cú pháp lớp mang lại một vẻ đẹp vô cùng hoa mỹ và thanh lịch, xin đừng bao giờ quên đi cái bản chất thực sự đang âm thầm diễn ra bên dưới lớp vỏ bọc đó: cỗ máy JavaScript chỉ đang nai lưng ra móc nối các đối tượng lại với nhau dọc theo một chuỗi liên kết nguyên mẫu ẩn mà thôi.

Ghi đè phương thức và quyền năng của từ khóa siêu việt

Bên cạnh khả năng càn lướt ghi đè một trường dữ liệu hay một thành viên bên trong nội tạng của một lớp con, kiến trúc sư phần mềm hoàn toàn có đặc quyền ghi đè, hay nói cách khác là định nghĩa lại, một phương thức đã được thừa kế từ lớp cha. Lớp con sẽ tiến hành ghi đè phương thức thừa kế đó nhằm mục đích ban phát cho nó một hành vi hoàn toàn khác biệt và mang đậm tính chuyên biệt hóa. Mặc dù vậy, hệ thống vẫn luôn luôn rộng cửa cho phép bạn tiến hành khởi tạo một bản sao từ chính lớp cơ sở gốc, thứ sẽ đẻ ra một đối tượng trung thành tuyệt đối với việc sử dụng định nghĩa nguyên thủy cho cái phương thức đó. Kỹ thuật ghi đè này không phá hủy cấu trúc của lớp cha, mà chỉ che khuất nó trên nhánh kế thừa của lớp con cụ thể đó.

Nếu như bạn rơi vào một kịch bản phức tạp mà trong đó bạn khao khát muốn thọc tay truy cập vào một phương thức được thừa kế ngay từ bên trong nội tạng của một lớp con bất chấp một sự thật phũ phàng là nó đã bị ghi đè, hệ thống cung cấp cho bạn một lối thoát mang tên từ khóa super để thế vai cho từ khóa this thông thường. Cái năng lực ma quỷ cho phép các phương thức mang tên gọi giống hệt nhau, ngự trị ở những tầng cấp bậc khác nhau của cây phả hệ kế thừa, có khả năng phô diễn những hành vi hoàn toàn khác biệt tùy thuộc vào việc chúng được truy cập một cách trực tiếp hay được tham chiếu một cách tương đối thông qua từ khóa super, được giới học thuật tôn sùng bằng khái niệm tính đa hình của phương thức… Đó thực sự là một vũ khí mang sức mạnh hủy diệt và là một mảnh ghép kiến trúc không thể thiếu của hệ tư tưởng định hướng lớp, miễn là nó được đưa vào sử dụng một cách khôn ngoan và đúng đắn.

Bên cạnh việc một phương thức của lớp con sử dụng sợi dây tham chiếu super. để kết nối với một định nghĩa phương thức được thừa kế, một quy tắc thép của ngôn ngữ là phương thức khởi tạo của một lớp con bị hệ thống ép buộc phải tiến hành triệu hồi phương thức khởi tạo của lớp cơ sở một cách hoàn toàn thủ công thông qua lời gọi hàm super(…). Một phương thức khởi tạo của lớp con khi được định nghĩa một cách tường minh bắt buộc phải bóp cò gọi super(…) để nhường quyền cho quá trình khởi tạo của lớp thừa kế được chạy trước, và cái hành động nhượng quyền đó bắt buộc phải diễn ra trước khi phương thức khởi tạo của lớp con dám cả gan thực hiện bất kỳ một tham chiếu nào nhắm vào từ khóa this hoặc cố gắng hoàn tất việc trả về kết quả. Nếu người lập trình ngoan cố vi phạm đạo luật này, một ngoại lệ chết người ngay tại thời điểm chạy sẽ bị hệ thống ném thẳng vào mặt khi cái phương thức khởi tạo của lớp con đó bị kích nổ thông qua từ khóa tạo mới. Thật may mắn thay, nếu như bạn lười biếng và bỏ sót việc định nghĩa phương thức khởi tạo cho lớp con, thì cái phương thức khởi tạo mặc định do hệ thống chèn vào sẽ tự động gánh vác trách nhiệm triệu hồi super() thay cho bạn. Có một tiểu tiết kiến trúc vô cùng tinh vi cần phải khắc cốt ghi tâm: nếu như bạn định nghĩa một trường dữ liệu bên trong một lớp con, và đồng thời cũng tự tay định nghĩa một phương thức khởi tạo tường minh cho cái lớp con đó, thì các thao tác khởi tạo giá trị cho các trường dữ liệu sẽ không diễn ra ngay tại dòng lệnh đầu tiên của phương thức khởi tạo, mà chúng sẽ chen ngang chạy lọt thỏm vào giữa cái lời gọi super(…) và bất kỳ một đoạn mã lệnh nào nằm lót tót theo sau bên trong phương thức khởi tạo đó.

Kiểm tra kiểu thực thể và bản chất của sự chia sẻ

Trong quá trình bảo trì và phân tích cấu trúc, rất có thể bạn sẽ nảy sinh nhu cầu muốn xác định xem bên trong một phương thức khởi tạo, liệu cái lớp đó đang bị hệ thống khởi tạo một cách trực tiếp làm đối tượng gốc, hay là nó chỉ đang đóng vai trò làm bàn đạp và bị khởi tạo gián tiếp từ một lớp con thông qua một lời gọi super(). Hệ thống cung cấp một lối thoát kiến trúc bằng cách sử dụng một thuộc tính giả đặc biệt mang tên new.target. Thuộc tính giả này sẽ trỏ thẳng đến cấu trúc hàm tạo thực sự đã bị toán tử tạo mới nhắm vào ngay từ ban đầu, cho phép các kỹ sư thiết lập các lớp trừu tượng bằng cách ném ra lỗi nếu như lớp đó bị khởi tạo một cách trực tiếp trái với thiết kế.

Hơn thế nữa, bạn cũng có thể khát khao muốn soi chiếu nội tạng của một bản sao đối tượng cụ thể để xác minh xem liệu nó có thực sự là một bản sao hợp pháp của một lớp nhất định nào đó hay không. Chúng ta thi hành nhiệm vụ kiểm tra này thông qua sức mạnh của toán tử kiểm tra bản sao. Sẽ là một cú sốc nhẹ đối với những người mới học khi chứng kiến một bản sao của lớp con lại trả về kết quả là đúng khi bị đem ra kiểm tra xem nó có phải là bản sao của lớp cơ sở hay không. Để có thể thấu tỏ căn nguyên sâu xa của hiện tượng này, một phương pháp tiếp cận cực kỳ hữu hiệu là hãy thử vẽ ra trong tâm trí cả hai chuỗi nguyên mẫu ẩn của chúng. Toán tử kiểm tra bản sao không mang một tầm nhìn hạn hẹp chỉ soi xét mỗi cái đối tượng hiện tại đang đứng trước mặt nó, mà thực chất nó mang trong mình sứ mệnh cao cả là xách vali đi cày xới và duyệt qua toàn bộ cây phả hệ kế thừa của lớp – hay nói cách khác là cày xới dọc theo chuỗi nguyên mẫu ẩn – cho đến khi nó săn lùng được một điểm khớp nối hoặc đi đến ngõ cụt. Do đó, bản sao của lớp con nghiễm nhiên được hệ thống công nhận là một bản sao hợp pháp của cả lớp con lẫn lớp cơ sở.

Để có thể phơi bày cái chân lý này ra ánh sáng một cách trần trụi và rõ ràng hơn nữa, một con đường tà đạo khác nhưng ít tiện lợi hơn để thi hành một phép thử tra cứu giống y hệt như toán tử kiểm tra bản sao là viện đến sự phục vụ của một tiện ích được thừa kế từ đối tượng nguyên mẫu tổng, mang tên phương thức kiểm tra nguyên mẫu của đối tượng. Phương thức tiện ích này giúp làm sáng tỏ một cách vô cùng rõ ràng lý do tại sao cả hai phương pháp kiểm tra đều nôn ra kết quả là đúng: bởi vì đối tượng nguyên mẫu của lớp cơ sở thực sự đang chễm chệ nằm trên chuỗi nguyên mẫu ẩn của cái đối tượng bản sao lớp con đó. Nếu như cái tham vọng thực sự của bạn lại là muốn kiểm tra xem liệu cái bản sao đối tượng đó có phải là một sản phẩm được nặn ra duy nhất và trực tiếp bởi một lớp cụ thể nào đó hay không, thì hãy thẳng tay soi xét thuộc tính hàm tạo của chính bản sao đó. Tuy nhiên, hãy khắc cốt ghi tâm rằng cái thuộc tính hàm tạo đang được trưng bày ra ở đây hoàn toàn không phải là một tài sản vật lý thực sự hiện diện hay thuộc quyền sở hữu riêng của cái đối tượng bản sao đó; nó được bòn rút thông qua sợi dây liên kết nguyên mẫu ẩn trỏ đến đối tượng nguyên mẫu của lớp.

Hành vi tĩnh và quản lý trạng thái cấp độ lớp

Từ đầu chương đến giờ, chúng ta đã liên tục nhấn mạnh hai tọa độ không gian bộ nhớ khác biệt chuyên dùng để làm nơi cư ngụ cho dữ liệu hoặc các hành vi phương thức: nằm trên đối tượng nguyên mẫu của hàm tạo, hoặc nằm trực tiếp trên đối tượng bản sao. Thế nhưng, hệ sinh thái kiến trúc vẫn còn giấu giếm một sự lựa chọn thứ ba đầy uy lực: lưu trữ trực tiếp trên bản thân cấu trúc hàm tạo (tức đối tượng hàm). Trong một hệ thống định hướng lớp, không phải bất kỳ dữ liệu hay hành vi nào cũng bắt buộc phải nhận thức được sự tồn tại của một bản sao. Khi một lớp cần định nghĩa những khối dữ liệu chung (như hằng số) hoặc các tiện ích độc lập hoàn toàn với việc khởi tạo đối tượng, ngôn ngữ cung cấp một cơ chế mạnh mẽ mang tên các thuộc tính và hàm tĩnh thông qua từ khóa tĩnh.

Vai trò của thành viên tĩnh trong kiến trúc phần mềm

Từ đầu chương đến giờ, chúng ta đã liên tục đặt trọng tâm phân tích vào hai tọa độ không gian bộ nhớ khác biệt chuyên dùng để làm nơi cư ngụ cho dữ liệu hoặc các hành vi phương thức: một là trên đối tượng nguyên mẫu của hàm tạo, hai là trực tiếp trên đối tượng bản sao. Thế nhưng, hệ sinh thái kiến trúc vẫn còn giấu giếm một sự lựa chọn thứ ba đầy uy lực: đó là lưu trữ trực tiếp trên bản thân cấu trúc hàm tạo hay chính là đối tượng hàm đó. Nằm lọt thỏm bên trong một hệ thống định hướng lớp truyền thống như các ngôn ngữ biên dịch cổ điển, những phương thức được định nghĩa trên một lớp hoàn toàn không phải là những thực thể vật lý cụ thể mà bạn có thể cả gan triệu hồi hay tương tác trực tiếp. Bạn bị ép buộc phải tiến hành khởi tạo một lớp để nặn ra một đối tượng vật lý cụ thể thì mới có đủ tư cách để bóp cò gọi những phương thức đó. Tuy nhiên, những ngôn ngữ dựa trên cơ chế nguyên mẫu như ngôn ngữ JavaScript lại có xu hướng bôi mờ đi cái lằn ranh giới hạn này: toàn bộ mọi phương thức được định nghĩa bên trong lớp đều biến thành những hàm hàng thật giá thật nằm chễm chệ trên đối tượng nguyên mẫu của hàm tạo, và hệ lụy là bạn hoàn toàn có thừa khả năng để triệu hồi chúng nếu muốn. Thế nhưng, như tôi đã từng hùng hồn tuyên bố ở những phần trước, bạn thực sự tuyệt đối không nên làm cái trò tà đạo đó, bởi vì đó không phải là cái kịch bản mà cỗ máy thiết kế ngôn ngữ JavaScript kỳ vọng bạn sẽ áp dụng khi viết các lớp của mình, và việc đi ngược lại tự nhiên sẽ đẩy bạn vào vô số những hành vi lỗi góc cạnh đầy ma quái. Giải pháp khôn ngoan nhất là hãy an phận bước đi trên cái con đường chính đạo hẹp hòi mà cấu trúc lớp đã dọn sẵn cho bạn.

Không phải bất kỳ một hành vi nào mà chúng ta cất công định nghĩa và khát khao muốn gắn kết hoặc tổ chức chung với một lớp đều bắt buộc phải nhận thức được sự tồn tại của một đối tượng bản sao. Hơn thế nữa, trong nhiều kịch bản thiết kế, đôi khi một lớp bị hệ thống đòi hỏi phải công khai định nghĩa ra những khối dữ liệu dùng chung, điển hình như các hằng số hệ thống, mà các lập trình viên sử dụng cái lớp đó bắt buộc phải thọc tay vào để truy cập, hoàn toàn độc lập và không hề mảy may quan tâm đến việc họ đã từng khởi tạo ra bất kỳ một bản sao nào hay chưa. Vậy thì, làm thế quái nào mà một hệ thống lớp lại có thể bật đèn xanh cho việc định nghĩa ra những khối dữ liệu và hành vi đáng lý ra phải được gắn liền với một lớp nhưng lại hoàn toàn độc lập và mù tịt về sự tồn tại của các đối tượng đã được khởi tạo? Câu trả lời cứu rỗi nằm ở các thuộc tính và hàm tĩnh…

Tôi sẽ ưu tiên sử dụng thuật ngữ thuộc tính tĩnh và hàm tĩnh, thay vì xài các từ ngữ như thành viên hay phương thức, hoàn toàn chỉ với một mục đích duy nhất là làm cho bức tranh kiến trúc trở nên rõ ràng hơn rằng luôn luôn tồn tại một hố sâu ngăn cách sự khác biệt giữa các thành viên bị trói buộc vào bản sao cùng với các phương thức nhận thức được bản sao, so với những thuộc tính phi bản sao và những hàm hoàn toàn mù tịt về bản sao. Chúng ta viện đến sức mạnh của từ khóa tĩnh bên trong nội tạng của các lớp để thiết lập một ranh giới phân định rạch ròi cho những định nghĩa đặc chủng này. Những thuộc tính hay hàm tĩnh đó hoàn toàn có thể được vứt vào một cái xó xỉnh nào đó khác bên ngoài định nghĩa lớp, thế nhưng bởi vì chúng mang một mối quan hệ ruột thịt trực tiếp với chính cấu trúc lớp đó, nên việc gom chúng lại và tổ chức trực tiếp bên trong lớp là một quyết định mang ý nghĩa logic hợp lý nhất về mặt kiến trúc. Đừng bao giờ lãng quên một chân lý rằng khi bạn đắm chìm trong việc sử dụng cú pháp lớp, cái tên định danh của lớp thực chất lại chính là cái tên của một hàm tạo cấu trúc mà ngôn ngữ JavaScript đã ngầm định nghĩa ra ở tầng dưới. Do đó, việc truy cập vào một thuộc tính tĩnh chỉ đơn thuần là một thao tác truy cập thuộc tính mộc mạc nhắm thẳng vào chính đối tượng hàm đó mà thôi. Phải luôn luôn duy trì sự tỉnh táo để không bị rơi vào trạng thái lú lẫn mà đánh đồng những thuộc tính đó với những thuộc tính phương thức được lưu trữ trên nguyên mẫu của hàm tạo hay những thuộc tính thành viên được nhét vào bên trong đối tượng bản sao.

Khởi tạo giá trị tĩnh và khối khởi tạo tĩnh

Khối giá trị nằm chễm chệ bên trong một phép gán khởi tạo tĩnh hoàn toàn có đặc quyền chứa chấp những sợi dây tham chiếu từ khóa this, và trong cái ngữ cảnh đặc chủng này, từ khóa đó sẽ mang sứ mệnh trỏ thẳng về chính bản thân cấu trúc lớp – hay nói một cách chính xác đến mức tàn nhẫn là trỏ về hàm tạo – chứ tuyệt đối không phải là trỏ về một bản sao đang sống nào đó. Lời khuyên xương máu là tôi kịch liệt phản đối việc mang cái thủ thuật gọi từ khóa tạo mới đi kèm với từ khóa this ra ứng dụng trong môi trường sản xuất thực tế; đoạn mã sẽ khoác lên mình một vẻ đẹp thanh lịch và dễ đọc hiểu hơn rất nhiều nếu bạn gọi tên định danh của lớp một cách tường minh, vì vậy hãy ưu tiên cho phương pháp tiếp cận minh bạch đó.

Có một tiểu tiết kỹ thuật mang tầm quan trọng sinh tử mà tuyệt đối không được phép nhắm mắt làm ngơ: đi ngược lại hoàn toàn với hành vi của các phép khởi tạo trường dữ liệu công khai vốn chỉ chịu nai lưng ra chạy khi và chỉ khi một quá trình khởi tạo bản sao thông qua từ khóa tạo mới được kích nổ, các phép khởi tạo tĩnh của lớp lại luôn luôn bị cỗ máy ép buộc phải chạy ngay tắp lự ngay sau khoảnh khắc mà cấu trúc lớp vừa mới hoàn tất việc được định nghĩa vào trong bộ nhớ. Thêm một rào cản nữa, thứ tự phân bổ của các phép khởi tạo tĩnh mang tính chất quyết định sự sống còn của dữ liệu; bạn hoàn toàn có thể tưởng tượng các câu lệnh này đang bị cỗ máy đem ra xử trảm và đánh giá một cách tuần tự từng câu lệnh một theo dòng chảy từ trên xuống dưới. Hoàn toàn tương đồng với các thành viên của lớp, các thuộc tính tĩnh không bị pháp luật ngôn ngữ ép buộc phải được khởi tạo giá trị ban đầu, và nếu bị bỏ trống chúng sẽ ngầm định mang giá trị không xác định, thế nhưng trong thực tiễn kiến trúc, việc nhét cho chúng một giá trị ban đầu là một hành vi phổ biến hơn rất nhiều để ngăn chặn các lỗi logic không lường trước được.

Mới đây thôi, vào thời đại của Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2022, sức mạnh của từ khóa tĩnh đã được nâng cấp và mở rộng đến mức giờ đây nó hoàn toàn có khả năng định nghĩa ra hẳn một khối mã lệnh nằm lọt thỏm bên trong nội tạng của lớp nhằm phục vụ cho những mưu đồ khởi tạo tinh vi và phức tạp hơn rất nhiều dành cho các thành phần tĩnh. Việc sử dụng câu lệnh khai báo biến cục bộ bên trong khối này hoàn toàn không phải là một tính năng ma thuật đặc chủng nào của lớp; nó hoạt động nguyên thủy và chính xác giống hệt như một câu lệnh khai báo biến cục bộ mộc mạc nằm trong bất kỳ một khối phạm vi thông thường nào khác. Khối khởi tạo tĩnh này cũng chứng tỏ được sức mạnh vô song của mình trong những kịch bản xử lý lỗi phức tạp, điển hình như việc bao bọc các câu lệnh bắt lỗi ngoại lệ xung quanh những phép tính toán biểu thức tiềm ẩn rủi ro sụp đổ hệ thống.

Kế thừa tĩnh và sự chuyển dịch nguyên mẫu của hàm tạo

Các thành phần tĩnh của lớp hoàn toàn có đặc quyền được thừa kế bởi các lớp con ở tuyến dưới, có khả năng bị ghi đè không thương tiếc, và từ khóa super hoàn toàn có thể được lôi ra xài để thiết lập các tham chiếu nhắm vào lớp cơ sở cũng như thi triển các kỹ thuật đa hình cho hàm tĩnh, toàn bộ những cơ chế ma quỷ này đều vận hành theo một phương thức gần như giống hệt với cách mà cơ chế kế thừa thao túng các thành viên và phương thức của bản sao. Đừng bao giờ nhắm mắt lướt qua để rồi lãng quên cái cơ chế vận hành thực sự của ngôn ngữ JavaScript đang âm thầm cày cuốc bên dưới đáy hệ thống ở khía cạnh này. Giống y hệt như cơ chế kế thừa phương thức đã được đem ra mổ xẻ ở những phần trước, cái gọi là sự kế thừa tĩnh này tuyệt đối không phải là một hành vi sao chép nhân bản các thuộc tính hay hàm tĩnh từ lớp cơ sở chuyển giao sang cho lớp con; bản chất thực sự của nó là sự chia sẻ quyền truy cập thông qua sức mạnh của chuỗi nguyên mẫu ẩn.

Phân tích một cách chính xác đến từng bit bộ nhớ, hàm tạo của lớp con đã bị cỗ máy thực thi của ngôn ngữ JavaScript cưỡng ép thay đổi cấu trúc của sợi dây liên kết nguyên mẫu ẩn nội bộ của nó – bứt nó ra khỏi vị trí mặc định vốn trỏ vào nguyên mẫu của hàm tổng – để chuyển hướng trỏ thẳng vào hàm tạo của lớp cơ sở, và chính cái thủ thuật tráo đổi liên kết nguyên mẫu này mới là thứ phép màu đã bật đèn xanh cho phép thuộc tính tĩnh của lớp con có khả năng ủy quyền tra cứu ngược lên cho thuộc tính tĩnh của lớp cơ sở. Đây là một sự khác biệt kiến trúc khổng lồ so với các ngôn ngữ biên dịch tĩnh.

Sẽ là một điều vô cùng thú vị, mặc dù ở thời điểm hiện tại nó có lẽ chỉ còn mang giá trị về mặt khảo cổ học lịch sử, khi chúng ta nhận ra một sự thật rằng cơ chế kế thừa tĩnh – thứ vốn dĩ tự hào là một mảnh ghép kiến trúc xuất thân từ tận thời kỳ nguyên thủy của bộ tính năng cơ chế lớp trong Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2015! – lại chính là một tính năng đặc thù mang tính chất tiên phong vượt xa khỏi cái ranh giới tầm thường của chỉ là lớp đường cú pháp… Cơ chế kế thừa tĩnh, đúng như hình hài mà chúng ta đang chiêm ngưỡng nó phô diễn sức mạnh ở đây, là một thứ bất khả thi và tuyệt đối không thể nào đạt được hay thậm chí là giả lập được bên trong hệ sinh thái ngôn ngữ JavaScript ở cái kỷ nguyên bóng tối trước năm 2015, dưới thời đại cai trị của phong cách viết mã lớp nguyên mẫu cổ lỗ sĩ. Nó đích thị là một hành vi hệ thống hoàn toàn mới mẻ và độc quyền, chỉ vừa mới được khai sinh và đưa vào phục vụ kể từ thời điểm Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2015 chính thức giáng lâm.

Kiểm soát quyền truy cập và tính riêng tư của lớp

Toàn bộ mọi ngóc ngách kiến trúc mà chúng ta đã thảo luận từ đầu chương đến giờ dưới tư cách là những mảnh ghép cấu thành nên một định nghĩa lớp đều mang tính chất phơi bày và mở toang cửa cho phép truy cập công khai từ bên ngoài, bất luận là thành phần tĩnh, phương thức nguyên mẫu, hay thuộc tính bản sao. Thế nhưng, làm thế nào để bạn cất giấu những thông tin nội bộ đòi hỏi sự bảo mật tuyệt đối và không thể bị dòm ngó từ thế giới bên ngoài của lớp? Đây từng là một trong những tính năng được cộng đồng gào thét đòi hỏi nhiều nhất, và cũng là khiếm khuyết lớn nhất của cấu trúc lớp trong ngôn ngữ JavaScript, cho đến khi nó chính thức được giải quyết triệt để trong Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2022. Cấu trúc lớp giờ đây đã dang tay hỗ trợ một bộ cú pháp mới toanh chuyên dùng để khai báo các trường dữ liệu riêng tư, phương thức riêng tư nội bộ, cũng như các thuộc tính và hàm tĩnh riêng tư.

Động lực thiết kế và nguyên tắc đặc quyền tối thiểu

Toàn bộ mọi ngóc ngách kiến trúc mà chúng ta đã cùng nhau đào bới và xẻ thịt từ đầu chương đến giờ dưới tư cách là những mảnh ghép cấu thành nên một định nghĩa lớp đều mang tính chất phơi bày và mở toang cửa cho phép truy cập công khai từ bên ngoài, bất luận chúng mang thân phận là những thuộc tính hay hàm tĩnh gắn trên lớp, những phương thức ngự trị trên đối tượng nguyên mẫu của hàm tạo, hay là những thuộc tính thành viên nằm chễm chệ trên đối tượng bản sao. Thế nhưng, làm thế quái nào mà các kiến trúc sư có thể cất giấu và lưu trữ những khối thông tin dữ liệu nhạy cảm đòi hỏi sự bảo mật tuyệt đối không thể bị dòm ngó từ thế giới bên ngoài của lớp? Đây từng là một trong những tính năng được cộng đồng gào thét đòi hỏi nhiều nhất, đồng thời cũng là nguồn cơn gây ra vô số những lời than phiền và oán trách cay đắng nhất đối với cơ chế lớp của ngôn ngữ JavaScript, cho mãi đến tận khi nó rốt cuộc cũng chịu được ủy ban ngó ngàng tới và giải quyết triệt để trong bản cập nhật Tiêu chuẩn ngôn ngữ kịch bản châu Âu 2022. Hệ thống lớp giờ đây đã chính thức dang tay ôm trọn lấy một bộ cú pháp mới toanh chuyên dùng để khai báo các trường dữ liệu riêng tư và các phương thức riêng tư nội bộ. Thêm vào đó, việc nặn ra các thuộc tính và hàm tĩnh mang tính chất riêng tư cũng đã trở thành hiện thực.

Trước khi chúng ta tiến hành phô diễn các thủ thuật về cách thức để thi triển các yếu tố riêng tư bên trong cấu trúc lớp, một điều cực kỳ đáng để chúng ta phải dành thời gian ra để chiêm nghiệm là tại sao sự tồn tại của tính năng này lại mang ý nghĩa cứu rỗi đến như vậy? Khi đắm chìm trong các khuôn mẫu thiết kế định hướng bao đóng, chúng ta tự động được hệ thống ban phát cho một cơ chế riêng tư được tích hợp sẵn tận xương tủy. Khoảnh khắc bạn khai báo một biến số nằm lọt thỏm bên trong một không gian phạm vi, nó lập tức trở nên tàng hình và bất khả xâm phạm đối với thế giới bên ngoài phạm vi đó. Việc thu hẹp mức độ phơi bày của một khai báo mang lại lợi ích khổng lồ trong việc phòng chống các thảm họa đụng độ không gian tên. Thế nhưng, nó còn mang một sứ mệnh quan trọng hơn gấp ngàn lần, đó là đảm bảo một tư duy thiết kế phần mềm mang tính chất phòng ngự chuẩn mực, thứ được giới hàn lâm xưng tụng là Nguyên tắc đặc quyền tối thiểu. Nguyên tắc này ban hành một đạo luật thép rằng chúng ta chỉ nên phơi bày một mẩu thông tin hoặc một năng lực tính toán bên trong phần mềm của mình ra một diện tích bề mặt tiếp xúc nhỏ nhất có thể ở mức độ vừa đủ xài. Sự phơi bày quá đà sẽ mở toang cánh cửa rước vào vô vàn những hiểm họa làm cho công tác bảo mật và bảo trì phần mềm trở nên rối rắm tột độ, bao gồm cả viễn cảnh một đoạn mã độc hại ngoại lai lợi dụng kẽ hở để thi hành những tác vụ mà mã nguồn của chúng ta hoàn toàn không hề lường trước hay cho phép. Hơn thế nữa, còn lẩn khuất một mối lo ngại tuy ít mang tính chất chí mạng hơn nhưng lại gây ra nhiều sự nhức nhối không kém: đó là việc các phân vùng khác trong phần mềm của chúng ta lại đi dựa dẫm và xài chùa vào những mảnh ghép mã nguồn mà đáng lý ra chúng ta phải chôn giấu thật kỹ dưới tư cách là những chi tiết triển khai nội bộ. Một khi các đoạn mã ngoại lai đã hình thành thói quen dựa dẫm vào những chi tiết triển khai nội bộ của chúng ta, chúng ta sẽ vĩnh viễn đánh mất đi cái đặc quyền được tự do tái cấu trúc lại mã nguồn của mình mà không nơm nớp lo sợ sẽ làm sụp đổ các phân vùng khác của toàn bộ chương trình.

Nói tóm lại một cách súc tích, chúng ta bắt buộc phải chôn giấu các chi tiết triển khai nếu như sự phơi bày của chúng không phải là một yêu cầu mang tính chất sinh tử. Đứng dưới góc độ triết học này, hệ thống lớp của ngôn ngữ JavaScript mang lại một cảm giác có phần quá đỗi dễ dãi và bao dung khi mà mọi thứ theo mặc định đều bị ép buộc phải phơi mình ra công khai. Sự bổ sung các tính năng riêng tư cho cấp độ lớp thực sự là một cơn mưa rào đúng lúc được chào đón nồng nhiệt để hướng tới một tư duy thiết kế phần mềm chuẩn mực và an toàn hơn. Tuy nhiên, tôi đành phải tạt một gáo nước lạnh làm tụt cảm xúc của bữa tiệc riêng tư này: như đã từng cảnh báo, một trong những khía cạnh mang tầm quan trọng sinh tử nhất của hệ tư tưởng định hướng lớp chính là cơ chế kế thừa thông qua lớp con, thế nhưng thử đoán xem cái kết cục bi thảm nào sẽ giáng xuống đầu một thành viên hay phương thức riêng tư nằm trong một lớp cơ sở, khi cái lớp cơ sở đó bị một lớp con bành trướng và mở rộng? Các thành viên hay phương thức riêng tư mang tính chất riêng tư chỉ và duy nhất đối với cái lớp mà chúng được khai sinh ra, và chúng tuyệt đối không hề được di chúc hay nhận thừa kế theo bất kỳ một cách thức nào bởi một lớp con. Điều này tạo ra một rào cản cực lớn, buộc các nhà phát triển phải đối mặt với một quyết định thiết kế đầy bế tắc: chấp nhận phơi bày dữ liệu ra công khai để lớp con có thể tái sử dụng, hay là bóp méo thiết kế hàm để vượt rào, bởi vì ngôn ngữ JavaScript hoàn toàn không sở hữu mức độ hiển thị được bảo vệ – thứ cho phép riêng tư với bên ngoài nhưng lại mở cửa với lớp con – như trong các ngôn ngữ lập trình truyền thống khác.

Cú pháp riêng tư và những rào cản trong việc kế thừa

Có lẽ các bạn đang mang trong mình một sự háo hức tột độ để rốt cuộc cũng được chiêm ngưỡng tận mắt cái thứ cú pháp dùng để kích hoạt mức độ hiển thị riêng tư đầy ma thuật, phải không? Xin đừng ném đá người đưa tin nếu như bạn cảm thấy phẫn nộ hay tuyệt vọng trước cái hình hài cú pháp mà bạn sắp sửa phải chứng kiến. Sự thật phũ phàng là, ngôn ngữ JavaScript đã không chọn cách làm một việc hợp tình hợp lý nhất là đẻ ra một từ khóa private giống y hệt như cái cách mà họ đã làm với từ khóa static. Thay vào đó, họ lại quyết định ném vào mặt chúng ta một ký tự dấu thăng nghèo nàn. Cú pháp chứa dấu thăng này, bao gồm cả hình thái đi kèm với từ khóa this, bị giáng một lời nguyền là chỉ được hệ thống dung túng và coi là hợp lệ khi nằm lọt thỏm bên trong giới hạn của các khối cơ thể lớp. Nó sẽ ngay lập tức gào thét ném ra những ngoại lệ lỗi cú pháp chết người nếu như bị vác ra sử dụng bừa bãi bên ngoài một cấu trúc lớp.

Trái ngược hoàn toàn với những người anh em trường dữ liệu công khai hay thành viên bản sao công khai của chúng, các trường dữ liệu riêng tư và thành viên bản sao riêng tư bắt buộc phải được khai báo một cách tường minh ngay tại phần thân của lớp. Bạn hoàn toàn bị tước đoạt đi cái quyền năng được phép đắp thêm một thành viên riêng tư vào một khai báo lớp một cách chắp vá và linh động trong khi đang bị nhốt bên trong phương thức khởi tạo; những kiểu phép gán mang hình hài this.#tenThuocTinh = … sẽ chỉ được hệ thống bật đèn xanh cho phép hoạt động nếu và chỉ nếu cái trường riêng tư #tenThuocTinh đó đã được khai báo đàng hoàng từ trước trong thân lớp. Hơn thế nữa, mặc dù các trường riêng tư vẫn bảo toàn được cái quyền lợi được phép bị gán lại bằng những giá trị mới, thế nhưng chúng lại miễn nhiễm tuyệt đối và không thể bị xóa sổ bứt ra khỏi một đối tượng bản sao bằng toán tử xóa, một cái đặc quyền mà các trường công khai hay thành viên lớp vẫn thường xuyên được tận hưởng.

Tôi đã từng gióng lên một hồi chuông cảnh báo ở những phần trước rằng việc thi triển kỹ thuật phân lớp con kết hợp với các lớp chứa chấp những thành viên hay phương thức riêng tư có khả năng biến thành một cái bẫy sập đầy những sự giới hạn trói buộc. Thế nhưng, điều đó không đồng nghĩa với một bản án tử hình rằng chúng vĩnh viễn không thể đội trời chung và được sử dụng song hành cùng nhau. Bởi vì cơ chế kế thừa trong hệ sinh thái ngôn ngữ JavaScript về bản chất là một sự chia sẻ quyền truy cập thông qua sức mạnh của chuỗi nguyên mẫu ẩn, nên nếu như bạn cả gan triệu hồi một phương thức được thừa kế ngay từ bên trong nội tạng của một lớp con, và bản thân cái phương thức thừa kế đó lại quay sang thọc tay vào truy cập hoặc kích nổ các phần tử riêng tư đang lẩn khuất trong chính cái lớp chủ gốc hay lớp cơ sở của nó, thì chuỗi sự kiện này vẫn diễn ra trơn tru và hoạt động một cách hoàn hảo mà không hề gặp phải bất kỳ một rào cản nào. Phương thức khởi tạo của lớp con khi được kích nổ sẽ gọi ngược lên phương thức khởi tạo của lớp cơ sở được thừa kế, và chính phương thức cơ sở này lại hoàn toàn có dư thẩm quyền để truy cập vào phương thức riêng tư nội bộ của lớp cơ sở. Hoàn toàn không có bất kỳ một ngoại lệ lỗi nào bị ném ra, mặc cho một sự thật phũ phàng là lớp con hoàn toàn mù tịt và không có cách nào để có thể nhìn thấy hay thọc tay trực tiếp vào các phần tử riêng tư vốn dĩ đang được cất giấu rất kỹ trên đối tượng bản sao. Bất chấp một sự thật là hàm chức năng đang bị chất vấn hoàn toàn không chứa chấp bất kỳ một sợi dây tham chiếu từ khóa this nào trong nội tạng của nó, thế nên bất luận là nó bị lôi ra triệu hồi từ cái xó xỉnh nào đi chăng nữa, nó vẫn phải đảm bảo đẻ ra một kết quả đầu ra giống hệt nhau, duy trì tính toàn vẹn của logic tĩnh.

Kỹ thuật kiểm tra thuộc tính ẩn và rủi ro rò rỉ dữ liệu

Hãy luôn luôn khắc cốt ghi tâm một chân lý rằng chỉ duy nhất bản thân cái lớp đó mới được quyền nhận thức sự tồn tại của, và do đó mới có đủ thẩm quyền để thi hành phép thử kiểm tra sự tồn tại cho, một cái trường hay phương thức riêng tư mang tính chất bí mật như vậy. Trong quá trình điều hướng logic phần mềm, rất có thể bạn sẽ phát sinh một nỗi khát khao muốn chạy một phép thử để soi xem liệu một trường hay phương thức riêng tư có đang thực sự hiện diện trên một đối tượng bản sao cụ thể nào đó hay không. Để minh họa, bạn rất có thể đang sở hữu một hàm hay phương thức tĩnh nằm chễm chệ trong một lớp, và cái hàm này lại mở toang cửa để tiếp nhận một sợi dây tham chiếu trỏ đến một đối tượng ngoại lai truyền vào. Để có thể thực thi phép thử soi xem liệu cái sợi dây tham chiếu đối tượng được truyền vào kia có thực sự là một sản phẩm đồng hương được nặn ra từ cùng một lớp hay không, hay nói cách khác là có chứa chấp chung những thành viên hay phương thức riêng tư y hệt như vậy hay không, bạn về cơ bản bắt buộc phải thi hành một thứ được gọi là phép kiểm tra nhãn hiệu nhắm thẳng vào cái đối tượng đó.

Một phép thử kiểm tra mang tính chất xâm nhập như vậy hoàn toàn có nguy cơ biến thành một đống mã nguồn lộn xộn và phức tạp đến mức cực hình, bởi vì nếu như bạn lỡ dại thọc tay vào truy cập một trường riêng tư mà xui xẻo thay nó lại không hề tồn tại trên cái đối tượng đó từ trước, hệ thống sẽ ngay lập tức gào thét và ném thẳng vào mặt bạn một ngoại lệ lỗi, ép buộc bạn phải nai lưng ra xây dựng những khối logic bắt lỗi vô cùng xấu xí và cồng kềnh. May mắn thay, các nhà kiến trúc ngôn ngữ đã chừa lại một lối thoát thanh lịch và sạch sẽ hơn rất nhiều, một thứ thủ thuật được giới giang hồ xưng tụng bằng cái tên là phép kiểm tra nhãn hiệu tiện dụng, thông qua việc khai thác sức mạnh của từ khóa in. Phép thử sử dụng cấu trúc truongRiengTu in doiTuong sẽ hoàn toàn câm nín và từ chối việc ném ra một ngoại lệ chết người ngay cả khi nó xách giỏ đi tìm mà không thấy cái trường đó ở đâu, vì vậy nó cung cấp một sự bảo kê an toàn tuyệt đối để các lập trình viên có thể vô tư sử dụng mà không cần phải lồng ghép vào các cấu trúc bắt lỗi, đồng thời cung cấp một kết quả boolean nguyên thủy dễ thao tác.

Mặc dù một thành viên hay phương thức có thể đã được khóa chặt và khai báo một cách cẩn thận bằng mức độ hiển thị riêng tư, thế nhưng nó vẫn hoàn toàn có nguy cơ bị bòn rút hay trích xuất ra khỏi nội tạng của một bản sao lớp thông qua các thủ thuật tham chiếu chéo. Mối lo ngại mang tính chất sống còn lớn nhất ở đây là các nhà phát triển phải duy trì một sự cảnh giác tột độ khi tiến hành chuyển giao các phương thức riêng tư dưới hình hài của các hàm gọi lại, hoặc thực hiện bất kỳ hành vi vô ý nào có khả năng lột trần và phơi bày các yếu tố riêng tư ra cho các phân vùng ngoại lai khác của chương trình dòm ngó. Hoàn toàn không có một rào cản kỹ thuật hay lực lượng cảnh sát nào trong cỗ máy có khả năng ngăn cấm bạn thi hành cái trò mạo hiểm đó, và hệ lụy tất yếu là nó hoàn toàn có nguy cơ tạo ra một lỗ hổng rò rỉ bảo mật riêng tư vô cùng nghiêm trọng nằm ngoài dự tính ban đầu của kiến trúc. Các thuộc tính và hàm tĩnh cũng hoàn toàn được cấp đặc quyền sử dụng ký hiệu dấu thăng để được đóng dấu bảo mật dưới thân phận là các thực thể riêng tư. Tuy nhiên, lại xuất hiện một cái bẫy sập chết người ở đây: trái ngược hoàn toàn với việc các phương thức thừa kế có thể thọc tay vào các thành phần riêng tư của lớp chủ một cách trơn tru, thì các hàm tĩnh công khai được thừa kế khi cố gắng truy cập vào các thành phần tĩnh riêng tư thông qua từ khóa this lại nôn ra lỗi ngoại lệ; giải pháp duy nhất để vá lỗ hổng này là bắt buộc phải sử dụng tên định danh của chính lớp đó thay vì từ khóa this khi truy cập, một sự bất nhất kiến trúc khiến cho không ít lập trình viên phải sôi máu.

Kết luận

Chúng ta đã trải dài qua hàng tá những mảnh ghép tính năng lớp muôn hình vạn trạng và rời rạc. Để khép lại chương này, tôi muốn gom toàn bộ sự chú ý lại để minh họa một vài lát cắt kết hợp các khả năng kiến trúc đó trong một ví dụ tổng thể duy nhất, mang tính thực tiễn và ít gượng ép hơn.

Xuyên suốt toàn bộ chương trình khảo cứu này, chúng ta đã tiến hành tháo dỡ và bày la liệt ra trước mắt hàng tá những mảnh ghép tính năng lớp muôn hình vạn trạng và rời rạc. Để có thể thu gọn lại cái mạng lưới kiến thức đồ sộ này, chúng ta có thể chiêm ngưỡng một hệ thống thiết kế tổng thể, nơi các trường dữ liệu được kết hợp với các giới hạn riêng tư, các cơ chế tĩnh cung cấp các tính năng quản lý ở cấp độ toàn cục, và sự kế thừa đa hình biến hóa các hành vi thành những phản ứng linh hoạt thông qua chuỗi nguyên mẫu. Mặc dù trong thực tế triển khai luôn tồn tại hàng triệu con đường kiến trúc hoàn toàn khác biệt để có thể dựng lên một cấu trúc mã nguồn logic đồ sộ, và hệ tư tưởng định hướng lớp chỉ là một trong những sự lựa chọn đó, thế nhưng việc am hiểu sâu sắc về cách thức mà ngôn ngữ JavaScript thao túng các cấu trúc vật lý ẩn giấu đằng sau lớp đường cú pháp hoa mỹ là một bước tiến mang tính chất sống còn để có thể trở thành một kỹ sư phần mềm thực thụ. Lớp không sinh ra để tiêu diệt nguyên mẫu, mà lớp sinh ra để khoác lên nguyên mẫu một bộ giáp khai báo mạnh mẽ nhất, cho phép chúng ta xây dựng nên những công trình phần mềm trường tồn với thời gian.

Đọ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.3 918 – 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.3.

Chuyên mục kyle-simpson

Chuyên mục chua-biet-javascript

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ẻ