Sau nhiều lần chỉnh sửa, câu trả lời này đã trở thành một con quái vật. Tôi xin lỗi trước.
Trước hết, eval()
không phải lúc nào cũng xấu và có thể mang lại lợi ích về hiệu suất khi được sử dụng trong đánh giá lười biếng, chẳng hạn. Đánh giá lười biếng tương tự như tải lười biếng, nhưng về cơ bản, bạn lưu trữ mã của mình trong các chuỗi, sau đó sử dụng eval
hoặc new Function
để đánh giá mã. Nếu bạn sử dụng một số mánh khóe, thì nó sẽ trở nên hữu ích hơn nhiều so với cái ác, nhưng nếu bạn không, nó có thể dẫn đến những điều tồi tệ. Bạn có thể xem hệ thống mô-đun của tôi sử dụng mẫu này: https://github.com/TheHydroImpulse/resolve.js . Resolve.js sử dụng eval thay vì new Function
chủ yếu để mô hình hóa CommonJS exports
và module
các biến có sẵn trong mỗi mô-đun và new Function
bọc mã của bạn trong một hàm ẩn danh, tuy nhiên, tôi kết thúc việc bọc từng mô-đun trong một hàm tôi thực hiện thủ công kết hợp với eval.
Bạn đọc thêm về nó trong hai bài viết sau, phần sau cũng đề cập đến phần đầu tiên.
Máy phát điện hài hòa
Bây giờ, các máy phát điện cuối cùng đã hạ cánh trong V8 và do đó trong Node.js, dưới một cờ ( --harmony
hoặc --harmony-generators
). Những thứ này làm giảm đáng kể số lượng địa ngục gọi lại mà bạn có. Nó làm cho việc viết mã không đồng bộ thực sự tuyệt vời.
Cách tốt nhất để sử dụng máy phát điện là sử dụng một số loại thư viện điều khiển. Điều này sẽ cho phép chảy để tiếp tục đi khi bạn mang lại trong máy phát điện.
Tóm tắt / Tổng quan:
Nếu bạn không quen thuộc với máy phát điện, thì đó là cách thực hành tạm dừng thực thi các chức năng đặc biệt (được gọi là máy phát điện). Thực hành này được gọi là năng suất bằng cách sử dụng yield
từ khóa.
Thí dụ:
function* someGenerator() {
yield []; // Pause the function and pass an empty array.
}
Do đó, bất cứ khi nào bạn gọi hàm này lần đầu tiên, nó sẽ trả về một thể hiện của trình tạo mới. Điều này cho phép bạn gọi next()
đối tượng đó để khởi động hoặc tiếp tục trình tạo.
var gen = someGenerator();
gen.next(); // { value: Array[0], done: false }
Bạn sẽ tiếp tục gọi next
cho đến khi done
trở về true
. Điều này có nghĩa là trình tạo đã hoàn thành xong việc thực thi và không còn yield
câu lệnh nào nữa .
Kiểm soát dòng chảy:
Như bạn có thể thấy, điều khiển máy phát điện không tự động. Bạn cần tiếp tục thủ công từng cái một. Đó là lý do tại sao các thư viện dòng điều khiển như đồng được sử dụng.
Thí dụ:
var co = require('co');
co(function*() {
yield query();
yield query2();
yield query3();
render();
});
Điều này cho phép khả năng viết mọi thứ trong Node (và trình duyệt với Regenerator của Facebook , lấy đầu vào, mã nguồn sử dụng các trình tạo hài hòa và tách ra mã ES5 tương thích hoàn toàn) với kiểu đồng bộ.
Các trình tạo vẫn còn khá mới và do đó yêu cầu Node.js> = v11.2. Khi tôi viết bài này, v0.11.x vẫn không ổn định và do đó nhiều mô đun gốc bị hỏng và sẽ đến v0.12, nơi API gốc sẽ bình tĩnh lại.
Để thêm vào câu trả lời ban đầu của tôi:
Gần đây tôi đã thích một API chức năng hơn trong JavaScript. Công ước không sử dụng OOP đằng sau hậu trường khi cần nhưng nó đơn giản hóa mọi thứ.
Lấy ví dụ một hệ thống xem (máy khách hoặc máy chủ).
view('home.welcome');
Dễ đọc hơn hoặc làm theo hơn:
var views = {};
views['home.welcome'] = new View('home.welcome');
Các view
chức năng đơn giản để kiểm tra nếu quan điểm tương tự đã tồn tại trong một bản đồ địa phương. Nếu chế độ xem không tồn tại, nó sẽ tạo chế độ xem mới và thêm mục nhập mới vào bản đồ.
function view(name) {
if (!name) // Throw an error
if (view.views[name]) return view.views[name];
return view.views[name] = new View({
name: name
});
}
// Local Map
view.views = {};
Vô cùng cơ bản, phải không? Tôi thấy nó đơn giản hóa đáng kể giao diện công cộng và làm cho nó dễ sử dụng hơn. Tôi cũng sử dụng khả năng chuỗi ...
view('home.welcome')
.child('menus')
.child('auth')
Tower, một khung mà tôi đang phát triển (với người khác) hoặc phát triển phiên bản tiếp theo (0.5.0) sẽ sử dụng phương pháp tiếp cận chức năng này trong hầu hết các giao diện phơi bày.
Một số người tận dụng các sợi như một cách để tránh "địa ngục gọi lại". Đó là một cách tiếp cận hoàn toàn khác với JavaScript và tôi không phải là một fan hâm mộ lớn của nó, nhưng nhiều khung / nền tảng sử dụng nó; bao gồm cả Meteor, vì chúng coi Node.js là một luồng / mỗi nền tảng kết nối.
Tôi muốn sử dụng một phương pháp trừu tượng để tránh địa ngục gọi lại. Nó có thể trở nên cồng kềnh, nhưng nó đơn giản hóa rất nhiều mã ứng dụng thực tế. Khi giúp xây dựng khung TowerJS , nó đã giải quyết được rất nhiều vấn đề của chúng tôi, tuy nhiên, rõ ràng bạn vẫn sẽ có một số mức gọi lại, nhưng việc lồng không sâu.
// app/config/server/routes.js
App.Router = Tower.Router.extend({
root: Tower.Route.extend({
route: '/',
enter: function(context, next) {
context.postsController.page(1).all(function(error, posts) {
context.bootstrapData = {posts: posts};
next();
});
},
action: function(context, next) {
context.response.render('index', context);
next();
},
postRoutes: App.PostRoutes
})
});
Một ví dụ về hệ thống định tuyến và "bộ điều khiển" của chúng tôi, hiện đang được phát triển, mặc dù khá khác biệt so với "giống như đường ray" truyền thống. Nhưng ví dụ này cực kỳ mạnh mẽ và giảm thiểu số lượng cuộc gọi lại và làm cho mọi thứ khá rõ ràng.
Vấn đề với cách tiếp cận này là mọi thứ đều được trừu tượng hóa. Không có gì chạy như hiện trạng và đòi hỏi một "khung" đằng sau nó. Nhưng nếu các loại tính năng và phong cách mã hóa này được triển khai trong một khung, thì đó là một chiến thắng rất lớn.
Đối với các mẫu trong JavaScript, nó trung thực phụ thuộc. Kế thừa chỉ thực sự hữu ích khi sử dụng CoffeeScript, Ember hoặc bất kỳ khung / cơ sở hạ tầng "lớp" nào. Khi bạn ở trong môi trường JavaScript "thuần túy", sử dụng giao diện nguyên mẫu truyền thống sẽ hoạt động như một nét quyến rũ:
function Controller() {
this.resource = get('resource');
}
Controller.prototype.index = function(req, res, next) {
next();
};
Ember.js bắt đầu, đối với tôi ít nhất, bằng cách sử dụng một cách tiếp cận khác để xây dựng các đối tượng. Thay vì xây dựng độc lập từng phương thức nguyên mẫu, bạn sẽ sử dụng giao diện giống như mô-đun.
Ember.Controller.extend({
index: function() {
this.hello = 123;
},
constructor: function() {
console.log(123);
}
});
Tất cả đều là các kiểu "mã hóa" khác nhau, nhưng hãy thêm vào cơ sở mã của bạn.
Đa hình
Đa hình không được sử dụng rộng rãi trong JavaScript thuần túy, trong đó làm việc với tính kế thừa và sao chép mô hình giống như "lớp" đòi hỏi rất nhiều mã soạn sẵn.
Thiết kế dựa trên sự kiện / thành phần
Các mô hình dựa trên sự kiện và dựa trên thành phần là những người chiến thắng IMO hoặc dễ làm việc nhất, đặc biệt là khi làm việc với Node.js, có thành phần EventEuctor tích hợp, tuy nhiên, việc triển khai các trình phát như vậy là không đáng kể, đó chỉ là một bổ sung hay .
event.on("update", function(){
this.component.ship.velocity = 0;
event.emit("change.ship.velocity");
});
Chỉ là một ví dụ, nhưng đó là một mô hình tốt để làm việc. Đặc biệt là trong một dự án định hướng trò chơi / thành phần.
Thiết kế thành phần là một khái niệm riêng biệt, nhưng tôi nghĩ rằng nó hoạt động rất tốt khi kết hợp với các hệ thống sự kiện. Các trò chơi thường được biết đến với thiết kế dựa trên thành phần, trong đó lập trình hướng đối tượng chỉ đưa bạn đến nay.
Thiết kế dựa trên thành phần có nó sử dụng. Nó phụ thuộc vào loại hệ thống tòa nhà của bạn. Tôi chắc chắn rằng nó sẽ hoạt động với các ứng dụng web, nhưng nó hoạt động rất tốt trong môi trường chơi trò chơi, vì số lượng đối tượng và các hệ thống riêng biệt, nhưng các ví dụ khác chắc chắn tồn tại.
Pub / Sub mẫu
Sự kiện ràng buộc và pub / sub là tương tự. Mẫu pub / sub thực sự tỏa sáng trong các ứng dụng Node.js vì ngôn ngữ thống nhất, nhưng nó có thể hoạt động trong bất kỳ ngôn ngữ nào. Hoạt động rất tốt trong các ứng dụng, trò chơi thời gian thực, v.v.
model.subscribe("message", function(event){
console.log(event.params.message);
});
model.publish("message", {message: "Hello, World"});
Người quan sát
Đây có thể là một ý kiến chủ quan, vì một số người chọn nghĩ về mẫu Người quan sát là quán rượu / phụ, nhưng họ có những điểm khác biệt.
"Người quan sát là một mẫu thiết kế trong đó một đối tượng (được gọi là chủ thể) duy trì một danh sách các đối tượng tùy thuộc vào nó (người quan sát), tự động thông báo cho họ về bất kỳ thay đổi nào về trạng thái." - Mẫu quan sát
Mẫu quan sát là một bước vượt ra ngoài các hệ thống pub / sub điển hình. Các đối tượng có mối quan hệ chặt chẽ hoặc phương thức giao tiếp với nhau. Một đối tượng "Chủ thể" sẽ giữ một danh sách những người phụ thuộc "Người quan sát". Chủ đề sẽ giữ cho nó quan sát cập nhật.
Lập trình phản ứng
Lập trình phản ứng là một khái niệm nhỏ hơn, chưa biết nhiều hơn, đặc biệt là trong JavaScript. Có một khung / thư viện (mà tôi biết) cho thấy dễ dàng làm việc với API để sử dụng "lập trình phản ứng" này.
Tài nguyên về lập trình phản ứng:
Về cơ bản, nó có một bộ dữ liệu đồng bộ hóa (có thể là biến, hàm, v.v.).
var a = 1;
var b = 2;
var c = a + b;
a = 2;
console.log(c); // should output 4
Tôi tin rằng lập trình phản ứng được ẩn đáng kể, đặc biệt là trong các ngôn ngữ bắt buộc. Đó là một mô hình lập trình mạnh mẽ đáng kinh ngạc, đặc biệt là trong Node.js. Sao băng đã tạo ra động cơ phản ứng của riêng nó, trong đó khung cơ bản dựa trên. Làm thế nào để phản ứng của sao băng hoạt động đằng sau hậu trường? là một tổng quan tuyệt vời về cách nó hoạt động trong nội bộ.
Meteor.autosubscribe(function() {
console.log("Hello " + Session.get("name"));
});
Điều này sẽ thực thi bình thường, hiển thị giá trị của name
, nhưng nếu chúng ta thay đổi nó
Phiên.set ('tên', 'Bob');
Nó sẽ xuất lại màn hình console.log Hello Bob
. Một ví dụ cơ bản, nhưng bạn có thể áp dụng kỹ thuật này cho các mô hình dữ liệu và giao dịch thời gian thực. Bạn có thể tạo các hệ thống cực kỳ mạnh mẽ đằng sau giao thức này.
Sao băng ...
Mô hình phản ứng và mô hình Observer khá giống nhau. Sự khác biệt chính là mẫu quan sát thường mô tả luồng dữ liệu với toàn bộ các đối tượng / lớp so với lập trình phản ứng mô tả luồng dữ liệu đến các thuộc tính cụ thể thay thế.
Sao băng là một ví dụ tuyệt vời về lập trình phản ứng. Thời gian chạy của nó hơi phức tạp một chút do thiếu các sự kiện thay đổi giá trị gốc của JavaScript (Các proxy của Harmony thay đổi điều đó). Các khung công tác phía máy khách khác, Ember.js và AngularJS cũng sử dụng lập trình phản ứng (đến một số phần mở rộng).
Hai khung sau sử dụng mẫu phản ứng đáng chú ý nhất trên các mẫu của chúng (đó là tự động cập nhật). Angular.js sử dụng một kỹ thuật kiểm tra bẩn đơn giản. Tôi sẽ không gọi đây là chương trình phản ứng chính xác, nhưng nó gần, vì kiểm tra bẩn không phải là thời gian thực. Ember.js sử dụng một cách tiếp cận khác. Sử dụng Ember set()
và get()
các phương thức cho phép chúng cập nhật ngay lập tức tùy thuộc vào các giá trị. Với runloop của họ, nó cực kỳ hiệu quả và cho phép nhiều giá trị phụ thuộc hơn, trong đó góc có giới hạn lý thuyết.
Hứa
Không phải là một sửa chữa cho các cuộc gọi lại, nhưng lấy ra một số thụt lề và giữ các hàm lồng nhau ở mức tối thiểu. Nó cũng thêm một số cú pháp tốt đẹp cho vấn đề.
fs.open("fs-promise.js", process.O_RDONLY).then(function(fd){
return fs.read(fd, 4096);
}).then(function(args){
util.puts(args[0]); // print the contents of the file
});
Bạn cũng có thể truyền bá các hàm gọi lại để chúng không nội tuyến, nhưng đó là một quyết định thiết kế khác.
Một cách tiếp cận khác là kết hợp các sự kiện và hứa hẹn nơi bạn sẽ có chức năng gửi các sự kiện một cách thích hợp, sau đó các chức năng thực sự (những chức năng có logic thực sự bên trong chúng) sẽ liên kết với một sự kiện cụ thể. Sau đó, bạn sẽ truyền phương thức bộ điều phối bên trong mỗi vị trí gọi lại, tuy nhiên, bạn phải tìm ra một số nút sẽ xuất hiện trong đầu, chẳng hạn như tham số, biết chức năng nào sẽ được gửi đến, v.v ...
Chức năng đơn
Thay vì có một mớ hỗn độn khổng lồ của địa ngục gọi lại, hãy giữ một chức năng duy nhất cho một nhiệm vụ duy nhất và thực hiện tốt nhiệm vụ đó. Đôi khi bạn có thể vượt lên chính mình và thêm nhiều chức năng hơn trong mỗi chức năng, nhưng hãy tự hỏi: Điều này có thể trở thành một chức năng độc lập không? Đặt tên cho hàm, và điều này sẽ dọn sạch vết lõm của bạn và kết quả là làm sạch vấn đề gọi lại địa ngục.
Cuối cùng, tôi khuyên bạn nên phát triển hoặc sử dụng một "khung" nhỏ, về cơ bản chỉ là xương sống cho ứng dụng của bạn và dành thời gian để thực hiện trừu tượng hóa, quyết định hệ thống dựa trên sự kiện hoặc "tải các mô-đun nhỏ "hệ thống độc lập". Tôi đã làm việc với một số dự án Node.js trong đó mã đặc biệt lộn xộn với địa ngục gọi lại, nhưng cũng thiếu suy nghĩ trước khi chúng bắt đầu viết mã. Dành thời gian của bạn để suy nghĩ về các khả năng khác nhau về API và cú pháp.
Ben Nadel đã thực hiện một số bài đăng blog thực sự tốt về JavaScript và một số mẫu khá nghiêm ngặt và nâng cao có thể hoạt động trong tình huống của bạn. Một số bài viết hay mà tôi sẽ nhấn mạnh:
Đảo ngược kiểm soát
Mặc dù không liên quan chính xác đến địa ngục gọi lại, nhưng nó có thể giúp bạn kiến trúc tổng thể, đặc biệt là trong các bài kiểm tra đơn vị.
Hai phiên bản phụ chính của điều khiển đảo ngược là Dependency Injection và Service Locator. Tôi thấy Trình định vị dịch vụ là dễ nhất trong JavaScript, trái ngược với Dependency Injection. Tại sao? Chủ yếu là vì JavaScript là một ngôn ngữ động và không tồn tại kiểu gõ tĩnh. Java và C #, trong số những người khác, "được biết đến" về việc tiêm phụ thuộc vì bạn có thể phát hiện các loại và chúng đã được xây dựng trong các giao diện, lớp, v.v ... Điều này làm cho mọi thứ khá dễ dàng. Tuy nhiên, bạn có thể tạo lại chức năng này trong JavaScript, tuy nhiên, nó sẽ không giống hệt nhau và hơi rắc rối, tôi thích sử dụng công cụ định vị dịch vụ trong các hệ thống của mình.
Bất kỳ loại kiểm soát đảo ngược nào cũng sẽ tách mã của bạn thành các mô-đun riêng biệt có thể bị chế giễu hoặc làm giả bất cứ lúc nào. Thiết kế một phiên bản thứ hai của công cụ kết xuất của bạn? Tuyệt vời, chỉ cần thay thế giao diện cũ cho giao diện mới. Các trình định vị dịch vụ đặc biệt thú vị với Harmony Proxies mới, tuy nhiên, chỉ có thể sử dụng hiệu quả trong Node.js, nó cung cấp API đẹp hơn, thay vào đó sử dụng Service.get('render');
và thay vào đó Service.render
. Tôi hiện đang làm việc trên loại hệ thống đó: https://github.com/TheHydroImpulse/Ettore .
Mặc dù thiếu kiểu gõ tĩnh (gõ tĩnh là một lý do có thể cho việc sử dụng hiệu quả trong việc tiêm phụ thuộc trong Java, C #, PHP - Nó không được gõ tĩnh, nhưng nó có gợi ý kiểu.) Có thể được xem là một điểm tiêu cực, bạn có thể xem chắc chắn biến nó thành một điểm mạnh. Bởi vì mọi thứ đều động, bạn có thể thiết kế một hệ thống tĩnh "giả". Kết hợp với một bộ định vị dịch vụ, bạn có thể có mỗi thành phần / mô đun / lớp / thể hiện gắn với một loại.
var Service, componentA;
function Manager() {
this.instances = {};
}
Manager.prototype.get = function(name) {
return this.instances[name];
};
Manager.prototype.set = function(name, value) {
this.instances[name] = value;
};
Service = new Manager();
componentA = {
type: "ship",
value: new Ship()
};
Service.set('componentA', componentA);
// DI
function World(ship) {
if (ship === Service.matchType('ship', ship))
this.ship = new ship();
else
throw Error("Wrong type passed.");
}
// Use Case:
var worldInstance = new World(Service.get('componentA'));
Một ví dụ đơn giản. Đối với một thế giới thực, việc sử dụng hiệu quả, bạn sẽ cần đưa khái niệm này đi xa hơn, nhưng nó có thể giúp tách rời hệ thống của bạn nếu bạn thực sự muốn tiêm phụ thuộc truyền thống. Bạn có thể cần phải mân mê khái niệm này một chút. Tôi đã không nghĩ nhiều vào ví dụ trước.
Bộ điều khiển xem mô hình
Mẫu rõ ràng nhất và được sử dụng nhiều nhất trên web. Một vài năm trước, JQuery là tất cả các cơn thịnh nộ, và do đó, các plugin JQuery đã ra đời. Bạn không cần một khung công tác đầy đủ ở phía máy khách, chỉ cần sử dụng jquery và một vài plugin.
Bây giờ, có một cuộc chiến khung JavaScript phía máy khách rất lớn. Hầu hết trong số đó sử dụng mô hình MVC và tất cả đều sử dụng nó khác nhau. MVC không phải lúc nào cũng được thực hiện như nhau.
Nếu bạn đang sử dụng các giao diện nguyên mẫu truyền thống, bạn có thể gặp khó khăn khi nhận được đường cú pháp hoặc API đẹp khi làm việc với MVC, trừ khi bạn muốn thực hiện một số công việc thủ công. Ember.js giải quyết điều này bằng cách tạo một hệ thống "lớp" / đối tượng ". Một bộ điều khiển có thể trông giống như:
var Controller = Ember.Controller.extend({
index: function() {
// Do something....
}
});
Hầu hết các thư viện phía máy khách cũng mở rộng mẫu MVC bằng cách giới thiệu các trình trợ giúp xem (trở thành các khung nhìn) và các mẫu (trở thành các khung nhìn).
Các tính năng JavaScript mới:
Điều này sẽ chỉ hiệu quả nếu bạn đang sử dụng Node.js, nhưng dù sao, nó là vô giá. Buổi nói chuyện này tại NodeConf của Brendan Eich mang đến một số tính năng mới thú vị. Cú pháp hàm được đề xuất và đặc biệt là thư viện js của Task.js.
Điều này có thể sẽ khắc phục hầu hết các vấn đề với chức năng lồng nhau và sẽ mang lại hiệu suất tốt hơn một chút do thiếu chi phí hoạt động.
Tôi không chắc chắn nếu V8 hỗ trợ điều này một cách tự nhiên, lần cuối tôi đã kiểm tra bạn cần kích hoạt một số cờ, nhưng điều này hoạt động trong một cổng Node.js sử dụng SpiderMonkey .
Tài nguyên bổ sung: