Learning JavaScript Design Patterns P.3 (ok)

https://addyosmani.com/resources/essentialjsdesignpatterns/book/#introduction

Flyweight

Mẫu Flyweight là một giải pháp cấu trúc cổ điển để tối ưu hóa mã lặp lại, chia sẻ dữ liệu chậm và không hiệu quả. Nó nhằm mục đích giảm thiểu việc sử dụng bộ nhớ trong một ứng dụng bằng cách chia sẻ càng nhiều dữ liệu càng tốt với các đối tượng liên quan (ví dụ: cấu hình ứng dụng, trạng thái, v.v.).

Mô hình này được Paul Calder và Mark Linton hình thành lần đầu tiên vào năm 1990 và được đặt tên theo hạng cân quyền anh bao gồm các võ sĩ có trọng lượng dưới 112lb. Bản thân cái tên Flyweight có nguồn gốc từ phân loại trọng lượng này vì nó đề cập đến trọng lượng nhỏ (dấu chân bộ nhớ) mà mẫu nhằm giúp chúng ta đạt được.

Trong thực tế, chia sẻ dữ liệu Flyweight có thể liên quan đến việc lấy một số đối tượng hoặc cấu trúc dữ liệu tương tự được một số đối tượng sử dụng và đặt dữ liệu này vào một đối tượng bên ngoài duy nhất. Chúng tôi có thể chuyển đối tượng này cho những đối tượng phụ thuộc vào dữ liệu này, thay vì lưu trữ dữ liệu giống hệt nhau trên từng đối tượng.

Using Flyweights

Có hai cách để áp dụng mẫu Flyweight. Đầu tiên là ở lớp dữ liệu, nơi chúng ta giải quyết khái niệm chia sẻ dữ liệu giữa một lượng lớn các đối tượng tương tự được lưu trữ trong bộ nhớ.

Thứ hai là ở lớp DOM, nơi Flyweight có thể được sử dụng như một trình quản lý sự kiện trung tâm để tránh gắn các trình xử lý sự kiện vào mọi phần tử con trong vùng chứa mẹ mà chúng tôi muốn có một số hành vi tương tự.

Vì lớp dữ liệu là nơi mô hình trọng lượng bay được sử dụng nhiều nhất theo truyền thống, chúng ta sẽ xem xét điều này trước tiên.

Flyweights and sharing data

Đối với ứng dụng này, có thêm một số khái niệm xung quanh mẫu Flyweight cổ điển mà chúng ta cần lưu ý. Trong mô hình Flyweight có một khái niệm về hai trạng thái - nội tại và ngoại tại. Thông tin nội tại có thể được yêu cầu bởi các phương thức nội bộ trong các đối tượng của chúng tôi mà chúng hoàn toàn không thể hoạt động nếu không có. Tuy nhiên, thông tin bên ngoài có thể bị xóa và lưu trữ bên ngoài.

Các đối tượng có cùng dữ liệu nội tại có thể được thay thế bằng một đối tượng được chia sẻ duy nhất, được tạo bằng phương pháp gốc. Điều này cho phép chúng tôi giảm số lượng tổng thể của dữ liệu ngầm được lưu trữ khá đáng kể.

Lợi ích của việc này là chúng ta có thể theo dõi các đối tượng đã được khởi tạo để các bản sao mới chỉ được tạo ra nếu trạng thái nội tại khác với đối tượng mà chúng ta đã có.

Chúng tôi sử dụng một trình quản lý để xử lý các trạng thái bên ngoài. Cách thực hiện điều này có thể khác nhau, nhưng một cách tiếp cận để đối tượng manager chứa cơ sở dữ liệu trung tâm về các trạng thái bên ngoài và các đối tượng flyweight mà chúng thuộc về.

Implementing Classical Flyweights

Vì mẫu Flyweight không được sử dụng nhiều trong JavaScript trong những năm gần đây, nên nhiều cách triển khai mà chúng tôi có thể sử dụng để lấy cảm hứng đến từ thế giới Java và C ++.

Cái nhìn đầu tiên của chúng tôi về Flyweights trong mã là việc triển khai JavaScript của tôi về mẫu Java của mẫu Flyweight từ Wikipedia (http://en.wikipedia.org/wiki/Flyweight_pattern).

Chúng tôi sẽ sử dụng ba loại thành phần Flyweight trong triển khai này, được liệt kê bên dưới:

Flyweight tương ứng với một giao diện mà qua đó flyweight có thể nhận và hoạt động trên các trạng thái bên ngoài

Concrete Flyweight thực sự triển khai giao diện Flyweight và lưu trữ trạng thái nội tại. Bê tông Flyweights cần phải có thể thay đổi được và có khả năng điều khiển trạng thái bên ngoài

Flyweight Factory quản lý các đối tượng flyweight và cũng tạo ra chúng. Nó đảm bảo rằng các hạng ruồi của chúng tôi được chia sẻ và quản lý chúng như một nhóm các đối tượng có thể được truy vấn nếu chúng tôi yêu cầu các phiên bản riêng lẻ. Nếu một đối tượng đã được tạo trong nhóm, nó sẽ trả về nó, nếu không nó sẽ thêm một đối tượng mới vào nhóm và trả về nó.

Chúng tương ứng với các định nghĩa sau trong cách triển khai của chúng tôi:

  • CoffeeOrder: Flyweight

  • CoffeeFlavor: Concrete Flyweight

  • CoffeeOrderContext: Helper

  • CoffeeFlavorFactory: Flyweight Factory

  • testFlyweight: Utilization of our Flyweights

Duck punching "implements"

Cú đấm vịt cho phép chúng tôi mở rộng khả năng của một ngôn ngữ hoặc giải pháp mà không nhất thiết phải sửa đổi nguồn thời gian chạy. Vì giải pháp tiếp theo này yêu cầu sử dụng một từ khóa Java (triển khai) để triển khai các giao diện và không được tìm thấy trong JavaScript nguyên bản, nên trước tiên hãy giải quyết vấn đề đó.

Function.prototype.implementsFor hoạt động trên một phương thức khởi tạo đối tượng và sẽ chấp nhận một lớp cha (hàm) hoặc đối tượng và kế thừa từ lớp này bằng cách sử dụng kế thừa thông thường (cho các hàm) hoặc kế thừa ảo (cho các đối tượng).

// Simulate pure virtual inheritance/"implement" keyword for JS
Function.prototype.implementsFor = function(parentClassOrObject) {
  if (parentClassOrObject.constructor === Function) {
    // Normal Inheritance
    this.prototype = new parentClassOrObject();
    this.prototype.constructor = this;
    this.prototype.parent = parentClassOrObject.prototype;
  } else {
    // Pure Virtual Inheritance
    this.prototype = parentClassOrObject;
    this.prototype.constructor = this;
    this.prototype.parent = parentClassOrObject;
  }
  return this;
};

Chúng ta có thể sử dụng điều này để vá việc thiếu từ khóa triển khai bằng cách để một hàm kế thừa giao diện một cách rõ ràng. Bên dưới, CoffeeFlavor triển khai giao diện CoffeeOrder và phải chứa các phương thức giao diện của nó để chúng tôi chỉ định chức năng cấp nguồn cho các triển khai này cho một đối tượng.

// Flyweight object
var CoffeeOrder = {
  // Interfaces
  serveCoffee: function(context) {},
  getFlavor: function() {}
};
// ConcreteFlyweight object that creates ConcreteFlyweight
// Implements CoffeeOrder
function CoffeeFlavor(newFlavor) {
  var flavor = newFlavor;
  // If an interface has been defined for a feature
  // implement the feature
  if (typeof this.getFlavor === "function") {
    this.getFlavor = function() {
      return flavor;
    };
  }
  if (typeof this.serveCoffee === "function") {
    this.serveCoffee = function(context) {
      console.log("Serving Coffee flavor " +
        flavor +
        " to table number " +
        context.getTable());
    };
  }
}
// Implement interface for CoffeeOrder
CoffeeFlavor.implementsFor(CoffeeOrder);
// Handle table numbers for a coffee order
function CoffeeOrderContext(tableNumber) {
  return {
    getTable: function() {
      return tableNumber;
    }
  };
}
function CoffeeFlavorFactory() {
  var flavors = {},
    length = 0;
  return {
    getCoffeeFlavor: function(flavorName) {
      var flavor = flavors[flavorName];
      if (typeof flavor === "undefined") {
        flavor = new CoffeeFlavor(flavorName);
        flavors[flavorName] = flavor;
        length++;
      }
      return flavor;
    },
    getTotalCoffeeFlavorsMade: function() {
      return length;
    }
  };
}
// Sample usage:
// testFlyweight()
function testFlyweight() {
  // The flavors ordered.
  var flavors = [],
    // The tables for the orders.
    tables = [],
    // Number of orders made
    ordersMade = 0,
    // The CoffeeFlavorFactory instance
    flavorFactory = new CoffeeFlavorFactory();
  function takeOrders(flavorIn, table) {
    flavors.push(flavorFactory.getCoffeeFlavor(flavorIn));
    tables.push(new CoffeeOrderContext(table));
    ordersMade++;
  }
  takeOrders("Cappuccino", 2);
  takeOrders("Cappuccino", 2);
  takeOrders("Frappe", 1);
  takeOrders("Frappe", 1);
  takeOrders("Xpresso", 1);
  takeOrders("Frappe", 897);
  takeOrders("Cappuccino", 97);
  takeOrders("Cappuccino", 97);
  takeOrders("Frappe", 3);
  takeOrders("Xpresso", 3);
  takeOrders("Cappuccino", 3);
  takeOrders("Xpresso", 96);
  takeOrders("Frappe", 552);
  takeOrders("Cappuccino", 121);
  takeOrders("Xpresso", 121);
  for (var i = 0; i < ordersMade; ++i) {
    flavors[i].serveCoffee(tables[i]);
  }
  console.log(" ");
  console.log("total CoffeeFlavor objects made: " + flavorFactory.getTotalCoffeeFlavorsMade());
}

Converting code to use the Flyweight pattern

Tiếp theo, chúng ta hãy tiếp tục xem xét Flyweights bằng cách triển khai một hệ thống quản lý tất cả sách trong thư viện. Dữ liệu siêu quan trọng cho mỗi cuốn sách có thể được chia nhỏ như sau:

  • ID

  • Title

  • Author

  • Genre

  • Page count

  • Publisher ID

  • ISBN

Chúng tôi cũng sẽ yêu cầu các thuộc tính sau để theo dõi thành viên nào đã xem một cuốn sách cụ thể, ngày họ đã kiểm tra cũng như ngày trả lại dự kiến.

  • checkoutDate

  • checkoutMember

  • dueReturnDate

  • availability

Do đó, mỗi cuốn sách sẽ được trình bày như sau, trước khi tối ưu hóa bằng cách sử dụng mẫu Flyweight:

var Book = function(id, title, author, genre, pageCount, publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability) {
  this.id = id;
  this.title = title;
  this.author = author;
  this.genre = genre;
  this.pageCount = pageCount;
  this.publisherID = publisherID;
  this.ISBN = ISBN;
  this.checkoutDate = checkoutDate;
  this.checkoutMember = checkoutMember;
  this.dueReturnDate = dueReturnDate;
  this.availability = availability;
};
Book.prototype = {
  getTitle: function() {
    return this.title;
  },
  getAuthor: function() {
    return this.author;
  },
  getISBN: function() {
    return this.ISBN;
  },
  // For brevity, other getters are not shown
  updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember, newReturnDate) {
    this.id = bookID;
    this.availability = newStatus;
    this.checkoutDate = checkoutDate;
    this.checkoutMember = checkoutMember;
    this.dueReturnDate = newReturnDate;
  },
  extendCheckoutPeriod: function(bookID, newReturnDate) {
    this.id = bookID;
    this.dueReturnDate = newReturnDate;
  },
  isPastDue: function(bookID) {
    var currentDate = new Date();
    return currentDate.getTime() > Date.parse(this.dueReturnDate);
  }
};

Điều này ban đầu có thể hoạt động tốt đối với các bộ sưu tập sách nhỏ, tuy nhiên khi thư viện mở rộng để bao gồm một kho lớn hơn với nhiều phiên bản và bản sao của mỗi cuốn sách có sẵn, chúng tôi có thể thấy hệ thống quản lý ngày càng chạy chậm hơn theo thời gian. Việc sử dụng hàng nghìn đối tượng sách có thể làm tràn bộ nhớ khả dụng, nhưng chúng tôi có thể tối ưu hóa hệ thống của mình bằng cách sử dụng mẫu Flyweight để cải thiện điều này.

Giờ đây, chúng tôi có thể tách dữ liệu của mình thành các trạng thái nội tại và ngoại tại như sau: dữ liệu liên quan đến đối tượng sách (tiêu đề, tác giả, v.v.) là nội tại trong khi dữ liệu thanh toán (checkoutMember, doReturnDate, v.v.) được coi là ngoại tại. Về mặt hiệu quả, điều này có nghĩa là chỉ cần một đối tượng Sách cho mỗi tổ hợp thuộc tính sách. nó vẫn là một số lượng đáng kể các đối tượng, nhưng ít hơn đáng kể so với trước đây.

Ví dụ sau về các kết hợp siêu dữ liệu sách của chúng tôi sẽ được chia sẻ giữa tất cả các bản sao của một cuốn sách với một tên cụ thể.

// Flyweight optimized version
var Book = function(title, author, genre, pageCount, publisherID, ISBN) {
  this.title = title;
  this.author = author;
  this.genre = genre;
  this.pageCount = pageCount;
  this.publisherID = publisherID;
  this.ISBN = ISBN;
};

Như chúng ta có thể thấy, các trạng thái bên ngoài đã bị loại bỏ. Mọi thứ cần làm với việc kiểm tra thư viện sẽ được chuyển đến một trình quản lý và vì dữ liệu đối tượng hiện đã được phân đoạn, một nhà máy có thể được sử dụng để khởi tạo.

A Basic Factory

Bây giờ chúng ta hãy xác định một nhà máy rất cơ bản. Những gì chúng tôi sẽ làm là thực hiện kiểm tra để xem liệu một cuốn sách với một tiêu đề cụ thể đã được tạo trước đó trong hệ thống hay chưa; nếu có, chúng tôi sẽ trả lại - nếu không, một cuốn sách mới sẽ được tạo và lưu trữ để có thể truy cập sau này. Điều này đảm bảo rằng chúng tôi chỉ tạo một bản sao duy nhất của mỗi phần dữ liệu nội tại duy nhất:

// Book Factory singleton
var BookFactory = (function() {
  var existingBooks = {}, existingBook;
  return {
    createBook: function(title, author, genre, pageCount, publisherID, ISBN) {
      // Find out if a particular book meta-data combination has been created before
      // !! or (bang bang) forces a boolean to be returned
      existingBook = existingBooks[ISBN];
      if (!!existingBook) {
        return existingBook;
      } else {
        // if not, let's create a new instance of the book and store it
        var book = new Book(title, author, genre, pageCount, publisherID, ISBN);
        existingBooks[ISBN] = book;
        return book;
      }
    }
  };
})();

Managing the extrinsic states

Tiếp theo, chúng ta cần lưu trữ các trạng thái đã bị xóa khỏi đối tượng Sách ở đâu đó - may mắn thay, một trình quản lý (mà chúng ta sẽ định nghĩa là Singleton) có thể được sử dụng để đóng gói chúng. Các kết hợp của một đối tượng Sách và thành viên thư viện đã kiểm tra chúng sẽ được gọi là Bản ghi sách. Người quản lý của chúng tôi sẽ lưu trữ cả hai và cũng sẽ bao gồm logic liên quan đến thanh toán mà chúng tôi đã loại bỏ trong quá trình tối ưu hóa hạng ruồi của chúng tôi cho hạng Sách.

// BookRecordManager singleton
var BookRecordManager = (function() {
  var bookRecordDatabase = {};
  return {
    // add a new book into the library system
    addBookRecord: function(id, title, author, genre, pageCount, publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability) {
      var book = BookFactory.createBook(title, author, genre, pageCount, publisherID, ISBN);
      bookRecordDatabase[id] = {
        checkoutMember: checkoutMember,
        checkoutDate: checkoutDate,
        dueReturnDate: dueReturnDate,
        availability: availability,
        book: book
      };
    },
    updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember, newReturnDate) {
      var record = bookRecordDatabase[bookID];
      record.availability = newStatus;
      record.checkoutDate = checkoutDate;
      record.checkoutMember = checkoutMember;
      record.dueReturnDate = newReturnDate;
    },
    extendCheckoutPeriod: function(bookID, newReturnDate) {
      bookRecordDatabase[bookID].dueReturnDate = newReturnDate;
    },
    isPastDue: function(bookID) {
      var currentDate = new Date();
      return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate);
    }
  };
})();

Kết quả của những thay đổi này là tất cả dữ liệu được trích xuất từ ​​lớp Sách hiện đang được lưu trữ trong một thuộc tính của Singleton BookManager (BookDatabase) - một thứ hiệu quả hơn đáng kể so với số lượng lớn các đối tượng mà chúng ta đang sử dụng trước đây. Các phương pháp liên quan đến việc kiểm tra sách hiện cũng dựa trên đây vì chúng xử lý dữ liệu bên ngoài chứ không phải nội tại.

Quá trình này thực sự thêm một chút phức tạp cho giải pháp cuối cùng của chúng tôi, tuy nhiên đó là một mối quan tâm nhỏ khi so sánh với các vấn đề hiệu suất đã được giải quyết. Dữ liệu khôn ngoan, nếu chúng ta có 30 bản sao của cùng một cuốn sách, chúng ta chỉ lưu trữ nó một lần. Ngoài ra, mọi chức năng đều chiếm bộ nhớ. Với mô hình trọng lượng bay, các chức năng này tồn tại ở một nơi (trên trình quản lý) chứ không phải trên mọi đối tượng, do đó tiết kiệm sử dụng bộ nhớ. Đối với phiên bản chưa tối ưu hóa flyweight nói trên, chúng tôi chỉ lưu trữ liên kết đến đối tượng hàm khi chúng tôi sử dụng nguyên mẫu của Book constructor nhưng nếu nó được triển khai theo cách khác, các hàm sẽ được tạo cho mọi phiên bản sách.

The Flyweight pattern and the DOM

DOM (Mô hình Đối tượng Tài liệu) hỗ trợ hai cách tiếp cận cho phép các đối tượng phát hiện các sự kiện - từ trên xuống (nắm bắt sự kiện) hoặc từ dưới lên (sự kiện sôi nổi).

Trong quá trình nắm bắt sự kiện, sự kiện đầu tiên được ghi lại bởi phần tử ngoài cùng và được truyền đến phần tử bên trong nhất. Trong trường hợp sủi bọt, sự kiện sẽ được thu nhận và trao cho phần tử bên trong nhất và sau đó được truyền đến phần tử bên ngoài.

Một trong những phép ẩn dụ hay nhất để mô tả Flyweights trong bối cảnh này được viết bởi Gary Chisholm và nó hơi giống như sau:

Hãy thử nghĩ về trọng lượng ruồi dưới dạng ao. Một con cá mở miệng (sự kiện), bong bóng nổi lên bề mặt (sủi bọt) một con ruồi đậu trên đầu bay đi khi bong bóng chạm đến bề mặt (hành động). Trong ví dụ này, chúng ta có thể dễ dàng chuyển con cá đang há miệng sang một nút đang được nhấp vào, các bong bóng khi hiệu ứng sủi bọt và con ruồi bay đi đến một số chức năng đang chạy Tạo bong bóng được giới thiệu để xử lý các tình huống trong đó một sự kiện (ví dụ: một lần nhấp) có thể được xử lý bởi nhiều trình xử lý sự kiện được xác định ở các cấp khác nhau của phân cấp DOM. Khi điều này xảy ra, sự kiện sôi nổi thực thi các trình xử lý sự kiện được xác định cho các phần tử cụ thể ở mức thấp nhất có thể. Từ đó, sự kiện bong bóng lên đến các phần tử chứa trước khi chuyển đến những phần tử cao hơn.

Flyweights có thể được sử dụng để điều chỉnh thêm quá trình tạo bọt sự kiện, như chúng ta sẽ thấy ngay sau đây.

Example 1: Centralized event handling

Đối với ví dụ thực tế đầu tiên của chúng tôi, hãy tưởng tượng chúng tôi có một số phần tử tương tự trong một tài liệu có hành vi tương tự được thực thi khi một hành động của người dùng (ví dụ: nhấp chuột, di chuột qua) được thực hiện chống lại chúng.

Thông thường, những gì chúng tôi làm khi xây dựng thành phần, menu hoặc tiện ích con dựa trên danh sách khác của riêng mình là liên kết sự kiện nhấp chuột với mỗi phần tử liên kết trong vùng chứa mẹ (ví dụ: $ ('ul li a'). On (..). Thay vì liên kết nhấp chuột với nhiều phần tử, chúng tôi có thể dễ dàng gắn Flyweight vào đầu vùng chứa của chúng tôi để có thể lắng nghe các sự kiện đến từ bên dưới. Sau đó, chúng tôi có thể xử lý các sự kiện này bằng cách sử dụng logic đơn giản hoặc phức tạp theo yêu cầu.

Vì các loại thành phần được đề cập thường có đánh dấu lặp lại giống nhau cho mỗi phần (ví dụ: mỗi phần của đàn accordion), có nhiều khả năng hành vi của mỗi phần tử có thể được nhấp vào sẽ khá giống nhau và liên quan đến các lớp tương tự gần đó. Chúng tôi sẽ sử dụng thông tin này để xây dựng một đàn accordion rất cơ bản bằng Flyweight bên dưới.

Không gian tên stateManager được sử dụng ở đây để đóng gói logic flyweight của chúng tôi trong khi jQuery được sử dụng để liên kết nhấp chuột ban đầu với div vùng chứa. Để đảm bảo rằng không có logic nào khác trên trang đang gắn các chốt tương tự vào vùng chứa, sự kiện hủy liên kết được áp dụng trước tiên.

Bây giờ để xác định chính xác phần tử con nào trong vùng chứa được nhấp, chúng tôi sử dụng kiểm tra đích cung cấp tham chiếu đến phần tử đã được nhấp, bất kể phần tử cha của nó là gì. Sau đó, chúng tôi sử dụng thông tin này để xử lý sự kiện nhấp chuột mà không thực sự cần liên kết sự kiện với các phần tử con cụ thể khi trang của chúng tôi tải.

<div id="container">
   <div class="toggle" href="#">More Info (Address)
       <span class="info">
           This is more information
       </span></div>
   <div class="toggle" href="#">Even More Info (Map)
       <span class="info">
          <iframe src="http://www.map-generator.net/extmap.php?name=London&amp;address=london%2C%20england&amp;width=500...gt;"</iframe>
       </span>
   </div>
</div>
var stateManager = {
  fly: function() {
    var self = this;
    $("#container")
      .unbind()
      .on("click", "div.toggle", function(e) {
        self.handleClick(e.target);
      });
  },
  handleClick: function(elem) {
    $(elem).find("span").toggle("slow");
  }
};

Example 2: Using the Flyweight for performance optimization

Trong ví dụ thứ hai của chúng tôi, chúng tôi sẽ đề cập đến một số mức tăng hiệu suất khác có thể đạt được khi sử dụng Flyweights với jQuery.

James Padolsey trước đây đã viết một bài báo có tên 76 byte để chạy jQuery nhanh hơn, trong đó anh ấy nhắc chúng ta rằng mỗi khi jQuery kích hoạt lệnh gọi lại, bất kể loại (bộ lọc, mỗi, trình xử lý sự kiện), chúng tôi có thể truy cập ngữ cảnh của hàm (phần tử DOM liên quan đến nó) thông qua từ khóa này.

Thật không may, nhiều người trong chúng ta đã quen với ý tưởng gói nó trong $ () hoặc jQuery (), có nghĩa là một phiên bản jQuery mới được xây dựng một cách không cần thiết mọi lúc, thay vì chỉ đơn giản làm điều này:

$("div").on( "click", function () {
  console.log( "You clicked: " + $( this ).attr( "id" ));
});
// we should avoid using the DOM element to create a
// jQuery object (with the overhead that comes with it)
// and just use the DOM element itself like this:
$( "div" ).on( "click", function () {
  console.log( "You clicked:"  + this.id );
});

James đã muốn sử dụng jQuery.text của jQuery trong ngữ cảnh sau, tuy nhiên anh ấy không đồng ý với quan điểm rằng một đối tượng jQuery mới phải được tạo trên mỗi lần lặp

$( "a" ).map( function () {
  return $( this ).text();
});

Tuy nhiên, vì không phải tất cả các phương thức của jQuery đều có các chức năng nút đơn tương ứng, Padolsey đã nghĩ ra ý tưởng về tiện ích jQuery.single.

Ý tưởng ở đây là một đối tượng jQuery duy nhất được tạo và sử dụng cho mỗi lần gọi tới jQuery.single (nghĩa là chỉ một đối tượng jQuery được tạo). Việc triển khai cho điều này có thể được tìm thấy bên dưới và vì chúng tôi đang hợp nhất dữ liệu cho nhiều đối tượng có thể thành một cấu trúc đơn lẻ trung tâm hơn, về mặt kỹ thuật, nó cũng là một Flyweight.

jQuery.single = (function(o) {
  var collection = jQuery([1]);
  return function(element) {
    // Give collection the element:
    collection[0] = element;
    // Return the collection:
    return collection;
  };
})();

Một ví dụ về điều này trong hoạt động với chuỗi là:

jQuery.single = (function(o) {
  var collection = jQuery([1]);
  return function(element) {
    // Give collection the element:
    collection[0] = element;
    // Return the collection:
    return collection;
  };
})();
$( "div" ).on( "click", function () {
   var html = jQuery.single( this ).next().html();
   console.log( html );
});

JavaScript MV* Patterns

Trong phần này, chúng ta sẽ xem xét ba mẫu kiến trúc rất quan trọng - MVC (Model-View-Controller), MVP (Model-View-Presenter) và MVVM (Model-View-ViewModel). Trong quá khứ, các mẫu này đã được sử dụng nhiều để cấu trúc các ứng dụng phía máy tính và máy chủ nhưng chỉ trong những năm gần đây mới được áp dụng cho JavaScript.

Vì phần lớn các nhà phát triển JavaScript hiện đang sử dụng các mẫu này chọn sử dụng các thư viện như Backbone.js để triển khai cấu trúc giống MVC / MV *, chúng tôi sẽ so sánh các giải pháp hiện đại như nó khác nhau như thế nào trong cách diễn giải MVC so với cổ điển. những mẫu này.

Đầu tiên chúng ta hãy đề cập đến những điều cơ bản.

MVC For JavaScript Developers

Models

Dưới đây, chúng ta có thể thấy một ví dụ về một mô hình rất đơn giản được thực hiện bằng cách sử dụng Backbone

var Photo = Backbone.Model.extend({
  // Default attributes for the photo
  defaults: {
    src: "placeholder.jpg",
    caption: "A default image",
    viewed: false
  },
  // Ensure that each photo created has an `src`.
  initialize: function() {
    this.set({ "src": this.defaults.src });
  }
});

Dưới đây là một nhóm mẫu các mô hình thành một bộ sưu tập Backbone đơn giản hóa.

var PhotoGallery = Backbone.Collection.extend({
  // Reference to this collection's model.
  model: Photo,
  // Filter down the list of all photos
  // that have been viewed
  viewed: function() {
    return this.filter(function(photo) {
      return photo.get("viewed");
    });
  },
  // Filter down the list to only photos that
  // have not yet been viewed
  unviewed: function() {
    return this.without.apply(this, this.viewed());
  }
});

Views

Dưới đây, chúng ta có thể thấy một hàm tạo một chế độ xem Ảnh duy nhất, sử dụng cả thể hiện mô hình và thể hiện bộ điều khiển.

var buildPhotoView = function(photoModel, photoController) {
  var base = document.createElement("div"),
    photoEl = document.createElement("div");
  base.appendChild(photoEl);
  var render = function() {
    // We use a templating library such as Underscore
    // templating which generates the HTML for our
    // photo entry
    photoEl.innerHTML = _.template("#photoTemplate", {
      src: photoModel.getSrc()
    });
  };
  photoModel.addSubscriber(render);
  photoEl.addEventListener("click", function() {
    photoController.handleEvent("click", photoModel);
  });
  var show = function() {
    photoEl.style.display = "";
  };
  var hide = function() {
    photoEl.style.display = "none";
  };
  return {
    showView: show,
    hideView: hide
  };
};

Dưới đây, chúng ta có thể thấy hai ví dụ về các mẫu HTML. Một được triển khai bằng cách sử dụng khung Handlebars.js phổ biến và một phần khác sử dụng các mẫu của Underscore.

Templating

Trong bối cảnh của các khung JavaScript hỗ trợ MVC / MV *, cần thảo luận ngắn gọn về tạo khuôn mẫu JavaScript và mối quan hệ của nó với lượt xem như chúng ta đã đề cập ngắn gọn trong phần trước.

Handlebars.js:

<li class="photo">
  <h2>{{caption}}</h2>
  <img class="source" src="{{src}}"/>
  <div class="meta-data">
    {{metadata}}
  </div>
</li>

Underscore.js Microtemplates:

<li class="photo">
  <h2><%= caption %></h2>
  <img class="source" src="<%= src %>"/>
  <div class="meta-data">
    <%= metadata %>
  </div>
</li>

Controllers

Bộ điều khiển là bộ phận trung gian giữa các mô hình và khung nhìn, có nhiệm vụ cập nhật mô hình khi người dùng thao tác với khung nhìn.

Controllers in another library (Spine.js) vs Backbone.js

Spine.js

Trong ví dụ này, chúng ta sẽ có một bộ điều khiển có tên PhotosController, bộ điều khiển này sẽ phụ trách các ảnh riêng lẻ trong ứng dụng. Nó sẽ đảm bảo rằng khi chế độ xem cập nhật (ví dụ: người dùng đã chỉnh sửa siêu dữ liệu ảnh) thì mô hình tương ứng cũng vậy.

// Controllers in Spine are created by inheriting from Spine.Controller
var PhotosController = Spine.Controller.sub({
  init: function() {
    this.item.bind("update", this.proxy(this.render));
    this.item.bind("destroy", this.proxy(this.remove));
  },
  render: function() {
    // Handle templating
    this.replace($("#photoTemplate").tmpl(this.item));
    return this;
  },
  remove: function() {
    this.el.remove();
    this.release();
  }
});

Backbone.js

var PhotoRouter = Backbone.Router.extend({
  routes: { "photos/:id": "route" },
  route: function( id ) {
    var item = photoCollection.get( id );
    var view = new PhotoView( { model: item } );
    $('.content').html( view.render().el );
  }
});

Modern Modular JavaScript Design Patterns

AMD

A Format For Writing Modular JavaScript In The Browser

Last updated

Navigation

Lionel

@Copyright 2023