Có bất kỳ nguyên tắc OO nào thực tế có thể áp dụng cho Javascript không?


79

Javascript là ngôn ngữ hướng đối tượng dựa trên nguyên mẫu nhưng có thể trở thành dựa trên lớp theo nhiều cách khác nhau, bằng cách:

  • Viết các hàm được sử dụng như các lớp một mình
  • Sử dụng hệ thống lớp tiện lợi trong khung (như mootools Class.Class )
  • Tạo nó từ Coffeescript

Lúc đầu, tôi có xu hướng viết mã dựa trên lớp trong Javascript và chủ yếu dựa vào nó. Tuy nhiên, gần đây tôi đã sử dụng các khung Javascript và NodeJS , thoát khỏi khái niệm này về các lớp và dựa nhiều hơn vào bản chất động của mã như:

  • Lập trình không đồng bộ, sử dụng và viết mã viết sử dụng các cuộc gọi lại / sự kiện
  • Đang tải các mô-đun bằng RequireJS (để chúng không bị rò rỉ vào không gian tên toàn cầu)
  • Các khái niệm lập trình chức năng như hiểu danh sách (bản đồ, bộ lọc, v.v.)
  • Trong số những thứ khác

Những gì tôi thu thập được cho đến nay là hầu hết các nguyên tắc và mẫu OO mà tôi đã đọc (chẳng hạn như các mẫu RẮN và GoF) được viết cho các ngôn ngữ OO dựa trên lớp trong tâm trí như Smalltalk và C ++. Nhưng có bất kỳ trong số chúng áp dụng cho một ngôn ngữ dựa trên nguyên mẫu như Javascript không?

Có bất kỳ nguyên tắc hoặc mẫu nào chỉ dành riêng cho Javascript không? Nguyên tắc để tránh địa ngục gọi lại , tệ nạn xấu xa , hoặc bất kỳ chống mẫu nào khác, v.v.

Câu trả lời:


116

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 evalhoặ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 Functionchủ yếu để mô hình hóa CommonJS exportsmodulecác biến có sẵn trong mỗi mô-đun và new Functionbọ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ờ ( --harmonyhoặ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 yieldtừ 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 nextcho đến khi donetrở 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 yieldcâ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 viewchứ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.jsAngularJS 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()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:


2
Đẹp viết lên. Cá nhân tôi không có sử dụng cho MV? thư viện. Chúng tôi có mọi thứ chúng tôi cần để tổ chức mã cho các ứng dụng phức tạp hơn lớn hơn. Tất cả đều nhắc nhở tôi quá nhiều về Java và C # khi cố gắng ném những tấm màn tào lao khác nhau của họ lên những gì thực sự xảy ra trong giao tiếp máy chủ-máy khách. Chúng tôi có một DOM. Chúng tôi có đoàn sự kiện. Chúng tôi có OOP. Tôi có thể liên kết các sự kiện của riêng tôi với dữ liệu thay đổi tyvm.
Erik Reppen

2
"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ụ đó." - Thơ.
CuriousWebDeveloper

1
Javascript khi trải qua một thời kỳ rất đen tối vào đầu những năm 2000 khi ít người hiểu cách viết các ứng dụng lớn bằng cách sử dụng nó. Giống như @ErikReppen nói, nếu bạn thấy ứng dụng JS của bạn trông giống như ứng dụng Java hoặc C # thì bạn đã làm sai.
ba lô

3

Thêm vào câu trả lời của Daniels:

Giá trị / thành phần quan sát được

Ý tưởng này được mượn từ khung MVVM Knockout.JS ( ko.observable ), với ý tưởng rằng các giá trị và đối tượng có thể là đối tượng quan sát được và một khi thay đổi xảy ra trong một giá trị hoặc đối tượng, nó sẽ tự động cập nhật tất cả các quan sát viên. Về cơ bản, nó là mẫu quan sát được triển khai trong Javascript và thay vào đó, hầu hết các khung công tác pub / sub được triển khai như thế nào, "khóa" là chính chủ thể thay vì một đối tượng tùy ý.

Cách sử dụng như sau:

// the subjects
// plain old javascript object with observable values
var shipComponent = {
    velocity : observable(0)
};

// the observer, a player user interface
// implemented with revealing module pattern
var playerUi = (function(ship) {

  var module = {
    setVelocity: function (x) { 
      // ... sets the velocity on the player user interface
    },

    // only called once
    init: function() {

      // subscribe to changes on the velocity value
      // using the module's function as callback
      module.velocity.onChange(playerUi.setVelocity);
    }
  };

  return module;
})(shipComponent).init();

// the player ui will change when the velocity value is changed
shipComponent.velocity.set(10);

Ý tưởng là các nhà quan sát thường biết đối tượng ở đâu và đăng ký theo dõi nó như thế nào. Ưu điểm của việc này thay vì pub / sub là đáng chú ý nếu bạn phải thay đổi mã nhiều vì sẽ dễ dàng loại bỏ các đối tượng hơn như một bước tái cấu trúc. Ý tôi là vì một khi bạn loại bỏ một chủ đề thì mọi người phụ thuộc vào nó sẽ thất bại. Nếu mã thất bại nhanh thì bạn biết nơi để loại bỏ các tham chiếu còn lại. Điều này trái ngược với chủ đề tách rời hoàn toàn (như với khóa chuỗi trong mẫu pub / sub) và có cơ hội cao hơn trong mã đặc biệt là nếu các khóa động được sử dụng và lập trình viên bảo trì không nhận ra điều đó (đã chết mã trong lập trình bảo trì là một vấn đề gây phiền nhiễu).

Trong chương trình trò chơi, điều này làm giảm nhu cầu của Ye Olde cập nhật loop mẫu và nhiều hơn nữa vào một evented / thành ngữ lập trình phản ứng, bởi vì càng sớm càng cái gì đó đang thay đổi đề tài này sẽ tự động cập nhật tất cả các quan sát viên về việc thay đổi, mà không cần phải chờ đợi cho chu kỳ cập nhật để thực thi. Có những cách sử dụng cho vòng lặp cập nhật (đối với những thứ cần được đồng bộ hóa với thời gian trò chơi đã trôi qua), nhưng đôi khi bạn không muốn làm lộn xộn nó khi chính các thành phần có thể tự động cập nhật với mẫu này.

Việc triển khai thực tế của chức năng có thể quan sát thực sự dễ viết và dễ hiểu một cách đáng ngạc nhiên (đặc biệt nếu bạn biết cách xử lý các mảng trong javascript và mẫu quan sát viên ):

var observable = function(v) {
    var val = v, subscribers = [];

    // the observable object,
    // as revealing module
    var output = {

        // subscribes to event
        onChange : function(func) {
            // idiomatic JS to add object to the
            // subscribers array
            subscribers.push(func);

            return output: // enables chaining
        },

        // the method that changes the observable object
        // and emits the event
        set : function(v) {
            var i;
            val = v;
            for (i = 0, i < subscribers.length; i++) {
                // this is hardly fault tolerant but as long
                // as subscribers are functions it'll work
                subscribers[i](v);
            }

            return output;
        }

    };

    return output;
};

Tôi đã thực hiện một đối tượng quan sát được trong JsFiddle tiếp tục điều này với việc quan sát các thành phần và có thể xóa các thuê bao. Hãy thử nghiệm JsFiddle.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.