paint-brush
Cách nghiên cứu mã nguồn: Nguyên mẫu đối tượng và Mixin trong Express.jstừ tác giả@luminix
187 lượt đọc

Cách nghiên cứu mã nguồn: Nguyên mẫu đối tượng và Mixin trong Express.js

từ tác giả Lumin-ix11m2024/08/19
Read on Terminal Reader

dài quá đọc không nổi

Nghiên cứu mã nguồn chắc chắn có thể thay đổi quỹ đạo sự nghiệp phát triển của bạn. Ngay cả khi nhìn sâu hơn bề mặt, chỉ một cấp độ cũng có thể giúp bạn khác biệt so với hầu hết các nhà phát triển trung bình. Đây chính là nội dung của loạt bài này: không bằng lòng với API, mà vượt ra ngoài, học cách tái tạo các công cụ này. Thoát khỏi sự tầm thường trong thế giới cường điệu về AI này chính là điều khiến một nhà phát triển trở nên có giá trị hơn mức trung bình!
featured image - Cách nghiên cứu mã nguồn: Nguyên mẫu đối tượng và Mixin trong Express.js
Lumin-ix HackerNoon profile picture
0-item
1-item

Nghiên cứu mã nguồn chắc chắn có thể thay đổi quỹ đạo sự nghiệp phát triển của bạn. Ngay cả khi nhìn sâu hơn bề mặt, chỉ một cấp độ cũng có thể giúp bạn khác biệt so với hầu hết các nhà phát triển trung bình.


Đây là bước đầu tiên để thành thạo!


Đây là một câu chuyện cá nhân: Trong công việc hiện tại của tôi tại một công ty khởi nghiệp AI/ML, nhóm không thể tìm ra cách lấy dữ liệu Neo4j từ máy chủ đến giao diện người dùng để trực quan hóa và họ phải thuyết trình trong 12 giờ. Tôi được đưa vào làm việc tự do và bạn có thể thấy rõ sự hoảng loạn. Vấn đề là dữ liệu do Neo4j trả về không đúng định dạng mà công cụ trực quan hóa neo4jd3 mong đợi.


Hãy tưởng tượng thế này: Neo4jd3 mong đợi một hình tam giác, và Neo4j trả về một hình vuông. Đó là sự không tương thích ngay tại đó!


neo4jd3


Chúng ta có thể sớm thực hiện khoa học dữ liệu đồ thị bằng JavaScript và Neo4j trong Mastered ! Hình ảnh này gợi nhớ đến quá khứ.


Chỉ có hai lựa chọn: làm lại toàn bộ phần phụ trợ Neo4j hoặc nghiên cứu mã nguồn Neo4jd3, tìm ra định dạng mong muốn, sau đó tạo một bộ điều hợp để chuyển đổi hình vuông thành hình tam giác.


 neo4jd3 <- adapter <- Neo4j 

bộ chuyển đổi viz



Bộ não của tôi mặc định đọc mã nguồn và tôi đã tạo một bộ điều hợp: neo4jd3-ts .


 import createNeoChart, { NeoDatatoChartData } from "neo4jd3-ts";


Bộ điều hợp là NeoDatatoChartData và mọi thứ khác đều là lịch sử. Tôi đã ghi nhớ bài học này, và mỗi khi có cơ hội, tôi lại hạ cấp xuống một cấp trong mọi công cụ tôi sử dụng. Nó đã trở nên phổ biến đến mức đôi khi tôi thậm chí không đọc tài liệu.


Cách tiếp cận này đã thay đổi sự nghiệp của tôi rất nhiều. Mọi thứ tôi làm đều giống như phép thuật. Trong vài tháng, tôi đã dẫn đầu các dự án và di chuyển máy chủ quan trọng, tất cả là vì tôi đã tiến một bước tới nguồn.


Đây chính là nội dung của loạt bài này: không dừng lại ở API, mà vượt ra ngoài, học cách tái tạo các công cụ này. Thoát khỏi sự tầm thường trong thế giới cường điệu về AI này chính là điều khiến một nhà phát triển trở nên có giá trị hơn mức trung bình!


Kế hoạch của tôi với loạt bài này là nghiên cứu các công cụ và thư viện JavaScript phổ biến, cùng nhau tìm hiểu cách chúng hoạt động và những mô hình nào chúng ta có thể học được từ chúng, theo từng công cụ một.


Vì tôi chủ yếu là một kỹ sư phần mềm (full stack, đúng vậy, nhưng xử lý phần mềm phần mềm 90% thời gian), nên không có công cụ nào tốt hơn để bắt đầu ngoài Express.js.


Giả định của tôi là bạn có kinh nghiệm lập trình và nắm vững các nguyên tắc cơ bản của lập trình! Bạn có thể được xếp vào loại người mới bắt đầu nâng cao.


Sẽ rất khó khăn và tẻ nhạt khi cố gắng học/dạy mã nguồn trong khi dạy các kiến thức cơ bản. Bạn có thể tham gia loạt bài này, nhưng hãy chuẩn bị tinh thần là nó sẽ rất khó. Tôi không thể bao quát hết mọi thứ, nhưng tôi sẽ cố gắng hết sức có thể.


Bài viết này được viết trước Express vì một lý do: Tôi quyết định đề cập đến một thư viện rất nhỏ mà Express phụ thuộc vào, đó là merge-descriptors , khi tôi viết bài này, thư viện này đã có 27.181.495 lượt tải xuống và chỉ có 26 dòng mã.


Điều này sẽ giúp chúng ta có cơ hội thiết lập cấu trúc và cho phép tôi giới thiệu các nguyên tắc cơ bản về đối tượng, vốn rất quan trọng trong việc xây dựng các mô-đun JavaScript.

Cài đặt

Trước khi chúng ta tiến hành, hãy đảm bảo bạn có mã nguồn Express và merge-descriptors trong hệ thống của mình. Bằng cách này, bạn có thể mở nó trong IDE và tôi có thể hướng dẫn bạn bằng số dòng về nơi chúng ta đang tìm kiếm.


Express là một thư viện mạnh mẽ. Chúng tôi sẽ đề cập đến nhiều nhất có thể trong một vài bài viết trước khi chuyển sang công cụ khác.


Mở mã nguồn Express trong IDE của bạn, tốt nhất là theo số dòng, điều hướng đến thư mục lib và mở tệp express.js , tệp nhập.


Ở dòng 17, đây là thư viện đầu tiên của chúng tôi:


 var mixin = require('merge-descriptors');


Cách sử dụng ở dòng 42 và 43:


 mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);


Trước khi chúng ta khám phá những gì đang diễn ra ở đây, chúng ta cần lùi lại một bước và nói về các đối tượng trong JavaScript, ngoài cấu trúc dữ liệu. Chúng ta sẽ thảo luận về thành phần, kế thừa, nguyên mẫu và mixin—tiêu đề của bài viết này.

Đối tượng JavaScript

Đóng mã nguồn Express và tạo một thư mục mới ở đâu đó để theo dõi khi chúng ta tìm hiểu những kiến thức cơ bản quan trọng về đối tượng này.


Đối tượng là sự đóng gói dữ liệu và hành vi, là cốt lõi của Lập trình hướng đối tượng (OOP) . Sự thật thú vị: hầu như mọi thứ trong JavaScript đều là đối tượng.


 const person = { // data name: "Jane", age: 0, // behavior grow(){ this.age += 1; } };

Mọi thứ giữa dấu ngoặc mở và đóng trong đối tượng person được gọi là thuộc tính riêng của đối tượng . Điều này rất quan trọng.


Thuộc tính riêng là những thuộc tính trực tiếp trên đối tượng. name , agegrow là thuộc tính riêng của person .


Điều này quan trọng vì mọi đối tượng JavaScript đều có thuộc tính prototype . Hãy mã hóa đối tượng trên thành một bản thiết kế hàm để cho phép chúng ta tạo các đối tượng person một cách động.


 function createNewPerson(name, age){ this.name = name; this.age = age; } createNewPerson.prototype.print = function(){ console.log(`${this.name} is ${this.age}`); }; const john = new createNewPerson("John", 32);

Nguyên mẫu là cách các đối tượng JavaScript kế thừa các thuộc tính và phương thức từ các đối tượng khác. Sự khác biệt giữa Own PropertiesPrototype là khi truy cập một thuộc tính trên một đối tượng:


 john.name; // access


JavaScript sẽ tìm trong Own Properties trước, vì chúng có mức độ ưu tiên cao. Nếu không tìm thấy thuộc tính, nó sẽ tìm trong đối tượng prototype riêng của đối tượng theo cách đệ quy cho đến khi tìm thấy null và đưa ra lỗi.


Một đối tượng nguyên mẫu có thể kế thừa từ một đối tượng khác thông qua nguyên mẫu của chính nó. Đây được gọi là chuỗi nguyên mẫu.


 console.log(john.hasOwnProperty('name')); // true console.log(john.hasOwnProperty('print')); // false, it's in the prototype


Tuy nhiên, print có tác dụng với john :


 john.print(); // "John is 32"


Đây là lý do tại sao JavaScript được định nghĩa là ngôn ngữ dựa trên nguyên mẫu. Chúng ta có thể làm nhiều hơn với nguyên mẫu ngoài việc chỉ thêm thuộc tính và phương thức, chẳng hạn như kế thừa.


"Hello world" của phép kế thừa là đối tượng mammal. Hãy tạo lại nó bằng JavaScript.


 // our Mammal blueprint function Mammal(name) { this.name = name; } Mammal.prototype.breathe = function() { console.log(`${this.name} is breathing.`); };


Trong JavaScript, có một hàm tĩnh bên trong đối tượng Object :


 Object.create();


Nó tạo ra một đối tượng tương tự như {}new functionBlueprint , nhưng điểm khác biệt là create có thể lấy một nguyên mẫu làm tham số để kế thừa.


 // we use a cat blueprint function here (implemented below) Cat.prototype = Object.create(Mammal.prototype); // correction after we inherited all the properties Cat.prototype.constructor = Cat;


Bây giờ Cat sẽ có phương thức breathe được tìm thấy trong Mammal , nhưng điều quan trọng cần biết là Cat đang trỏ đến Mammal làm nguyên mẫu của nó.

Giải thích:

  1. Bản thiết kế động vật có vú : Đầu tiên, chúng ta định nghĩa hàm Động Mammal và thêm phương thức breathe vào nguyên mẫu của nó.


  2. Kế thừa Cat : Chúng ta tạo hàm Cat và đặt Cat.prototype thành Object.create(Mammal.prototype) . Điều này khiến nguyên mẫu Cat kế thừa từ Mammal , nhưng nó thay đổi con trỏ constructor thành Mammal .


  3. Sửa lỗi Constructor : Chúng tôi sửa lỗi Cat.prototype.constructor để trỏ trở lại Cat , đảm bảo rằng đối tượng Cat vẫn giữ nguyên danh tính của nó trong khi kế thừa các phương thức từ Mammal . Cuối cùng, chúng tôi thêm phương thức meow vào Cat .


Cách tiếp cận này cho phép đối tượng Cat truy cập các phương thức từ cả Mammal (như breathe ) và nguyên mẫu của chính nó (như meow ).


Chúng ta cần phải sửa lỗi đó. Hãy tạo ví dụ đầy đủ:


 function Cat(name, breed) { this.name = name; this.breed = breed; } Cat.prototype = Object.create(Mammal.prototype); // cat prototype pointing to mammal // correction after we inherited all the properties Cat.prototype.constructor = Cat; // we are re-pointing a pointer, the inherited properties are still there Cat.prototype.meow = function() { console.log(`${this.name} is meowing.`); };


Để hiểu Cat.prototype.constructor = Cat , bạn cần biết về con trỏ. Khi chúng ta kế thừa từ Mammal với Object.create , nó sẽ thay đổi con trỏ của nguyên mẫu Cat của chúng ta thành Mammal , điều này là sai. Chúng ta vẫn muốn Cat của mình là cá thể riêng biệt, mặc dù có cha là Mammal .


Đó là lý do tại sao chúng ta phải sửa nó.


Trong ví dụ này, Cat kế thừa từ Mammal bằng cách sử dụng chuỗi nguyên mẫu. Đối tượng Cat có thể truy cập cả phương thức breathemeow .


 const myCat = new Cat("Misty", "Ragdoll"); myCat.breathe(); // Misty is breathing. myCat.meow(); // Misty is meowing.


Chúng ta có thể tạo ra một con chó cũng thừa hưởng từ động vật có vú:


 function Dog(name, breed) { this.name = name; this.breed = breed; } Dog.prototype = Object.create(Mammal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.bark = function() { console.log(`${this.name} is barking.`); }; const myDog = new Dog('Buddy', 'Golden Retriever'); myDog.breathe(); // Buddy is breathing. myDog.bark(); // Buddy is barking.


Chúng ta đã tạo ra kế thừa cổ điển cơ bản, nhưng tại sao điều này lại quan trọng? Tôi nghĩ chúng ta đang đề cập đến mã nguồn!


Đúng vậy, nhưng nguyên mẫu là cốt lõi của việc xây dựng các mô-đun hiệu quả và linh hoạt, ngoài việc kế thừa. Ngay cả các mô-đun đơn giản, được viết tốt cũng được trang bị các đối tượng nguyên mẫu. Chúng tôi chỉ đang đặt những điều cơ bản.


Giải pháp thay thế cho tính kế thừa là hợp thành đối tượng, về cơ bản là lấy hai hoặc nhiều đối tượng và hợp nhất chúng lại với nhau để tạo thành một "siêu" đối tượng.


Mixin cho phép các đối tượng mượn phương thức từ các đối tượng khác mà không cần sử dụng tính kế thừa. Chúng tiện dụng để chia sẻ hành vi giữa các đối tượng không liên quan.


Đó chính là nội dung mà quá trình khám phá đầu tiên của chúng ta thực hiện: thư viện merge-descriptors mà chúng ta đã hứa sẽ đề cập trước.

Mô-đun mô tả hợp nhất

Chúng ta đã thấy nó được sử dụng ở đâu và như thế nào trong Express. Bây giờ chúng ta biết nó được sử dụng cho thành phần đối tượng.


Ở dòng 17, đây là thư viện đầu tiên của chúng ta:


 var mixin = require('merge-descriptors');


Cách sử dụng ở dòng 42 và 43:


 mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);


Với những gì chúng ta biết, chúng ta có thể suy ra rằng mixin đang kết hợp EventEmitter.prototypeproto thành một đối tượng có tên là app .


Chúng ta sẽ tìm hiểu về app khi bắt đầu nói về Express.


Đây là toàn bộ mã nguồn cho merge-descriptors :


 'use strict'; function mergeDescriptors(destination, source, overwrite = true) { if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); } for (const name of Object.getOwnPropertyNames(source)) { if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; } // Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor); } return destination; } module.exports = mergeDescriptors;


Ngay từ đầu, hãy luôn xem xét cách sử dụng hàm và các tham số mà hàm sử dụng:


 // definition mergeDescriptors(destination, source, overwrite = true) // usage var mixin = require('merge-descriptors'); mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);


App là đích đến của chúng ta. Chúng ta biết mixin có nghĩa là thành phần đối tượng. Về cơ bản, gói này đang thực hiện việc kết hợp đối tượng nguồn thành đối tượng đích, với tùy chọn ghi đè.


Ghi đè, theo giả định, là nếu app (đích) có thuộc tính chính xác mà nguồn có, nếu ghi đè true , nếu không thì giữ nguyên thuộc tính đó và bỏ qua.


Chúng ta biết rằng các đối tượng không thể có cùng một thuộc tính hai lần. Trong các cặp khóa-giá trị (đối tượng), khóa phải là duy nhất.

Với Express, ghi đè là false .


Sau đây là những quy trình quản lý cơ bản, luôn xử lý những lỗi có thể xảy ra:


 if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); }


Đây là phần thú vị nhất: dòng 12.


 for (const name of Object.getOwnPropertyNames(source)) {


Từ trên, chúng ta biết OwnProperty có nghĩa là gì, do đó getOwnPropertyNames rõ ràng có nghĩa là lấy khóa của thuộc tính riêng.


 const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyNames(person); // [ 'name', 'age', 'grow' ]


Nó trả về các khóa dưới dạng một mảng và chúng ta sẽ lặp qua các khóa đó trong trường hợp sau:


 for (const name of Object.getOwnPropertyNames(source)) {


Sau đây là kiểm tra xem đích và nguồn có cùng khóa mà chúng ta đang lặp qua hay không:


 if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; }


Nếu overwrite là false, hãy bỏ qua thuộc tính đó; đừng ghi đè. Đó là những gì continue thực hiện—nó đẩy vòng lặp sang lần lặp tiếp theo và không chạy mã bên dưới, đó là mã sau:


 // Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor);


Chúng ta đã biết getOwnProperty có nghĩa là gì. Từ mới là descriptor . Hãy thử nghiệm hàm này trên đối tượng person của chúng ta:


 const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyDescriptor(person, "grow"); // { // value: [Function: grow], // writable: true, // enumerable: true, // configurable: true // }


Nó trả về hàm grow của chúng ta dưới dạng giá trị và dòng tiếp theo tự giải thích:


 Object.defineProperty(destination, name, descriptor);


Nó lấy mô tả của chúng ta từ nguồn và ghi nó vào đích. Nó sao chép các thuộc tính riêng của nguồn vào đối tượng đích của chúng ta như là các thuộc tính riêng của nó.


Chúng ta hãy làm một ví dụ trong đối tượng person của chúng ta:


 const val = { value: function isAlien() { return false; }, enumerable: true, writable: true, configurable: true, }; Object.defineProperty(person, "isAlien", val);


Bây giờ, person phải xác định thuộc tính isAlien .


Tóm lại, mô-đun được tải xuống nhiều này sao chép các thuộc tính riêng từ một đối tượng nguồn vào một đích với tùy chọn ghi đè.


Chúng tôi đã hoàn thành thành công mô-đun đầu tiên trong lớp mã nguồn này, với nhiều nội dung thú vị hơn sắp ra mắt.


Đây là phần giới thiệu. Chúng tôi bắt đầu bằng cách trình bày các nguyên tắc cơ bản cần thiết để hiểu mô-đun và, như một sản phẩm phụ, hiểu các mẫu trong hầu hết các mô-đun, đó là thành phần đối tượng và kế thừa. Cuối cùng, chúng tôi điều hướng mô- merge-descriptors .


Mẫu này sẽ phổ biến trong hầu hết các bài viết. Nếu tôi cảm thấy có những nguyên tắc cơ bản cần thiết để đề cập, chúng ta sẽ xem xét chúng trong phần đầu tiên và sau đó đề cập đến mã nguồn.


May mắn thay, merge-descriptors được sử dụng trong Express, đây là trọng tâm của chúng tôi khi bắt đầu nghiên cứu mã nguồn. Vì vậy, hãy mong đợi nhiều bài viết về mã nguồn Express.js hơn cho đến khi chúng tôi cảm thấy mình đã chạy đủ tốt Express, sau đó chuyển sang một mô-đun hoặc công cụ khác như Node.js.


Những gì bạn có thể làm trong thời gian chờ đợi như một thử thách là điều hướng đến tệp thử nghiệm trong các mô tả hợp nhất, đọc toàn bộ tệp. Đọc nguồn của riêng bạn là quan trọng, hãy thử và tìm ra nó làm gì và đang thử nghiệm gì sau đó phá vỡ nó, vâng và sửa lại hoặc thêm nhiều thử nghiệm hơn!


Nếu bạn quan tâm đến nội dung thực tế và dài hơn để nâng cao kỹ năng lập trình của mình, bạn có thể tìm hiểu thêm trên Ko-fi .