摘要:盡管特定環境下有各種各樣的設計模式,開發者還是傾向于使用一些習慣性的模式。原型設計模式依賴于原型繼承原型模式主要用于為高性能環境創建對象。對于一個新創建的對象,它將保持構造器初始化的狀態。這樣做主要是為了避免訂閱者和發布者之間的依賴。
2016-10-07
每個JS開發者都力求寫出可維護、復用性和可讀性高的代碼。隨著應用不斷擴大,代碼組織的合理性也越來越重要。設計模式為特定環境下的常見問題提供了一個組織結構,對于克服這些挑戰起到至關重要的作用。
JavaScript 網頁開發者在創建應用時,頻繁地跟設計模式打交道(甚至在不知情的情況下)。
盡管特定環境下有各種各樣的設計模式,JS 開發者還是傾向于使用一些習慣性的模式。
在這篇文章中,我將討論這些常見的設計模式,展出優化代碼庫的方法,并深入解讀JavaScript的內部構件。
本文討論的設計模式包括這幾種:
模塊設計模式
原型模式
觀察者模式
單例模式
盡管每種模式都包含很多屬性,這里我強調以下幾點:
上下文: 設計模式的使用場景
問題: 我們嘗試解決的問題是什么?
解決方法: 使用設計模式如何解決我們提出的問題?
實施: 實施方案看起來怎樣?
模塊設計模式JS模塊化是使用最普遍的設計模式,用于保持特殊的代碼塊與其它組件之間互相獨立。為支持結構良好的代碼提供了松耦合。
對于熟悉面向對象的開發者來說,模塊就是JS的 “類”。封裝是“類”的眾多優點之一,可以確保它本身的狀態和行為不被其它的類訪問到。模塊設計模式有公有和私有兩種訪問級別(除此之外,還有比較少為人知的保護級別、特權級別)。
考慮到私有的作用域,模塊應該是一個立即調用函數(IIFE) ,也就是說,它是一個保護其私有變量和方法的閉包。(然而,它返回的卻不是一個函數,而是一個對象)。
它的寫法就是這樣的:
(function() { // declare private variables and/or functions return { // declare public variables and/or functions } })();
我們在返回一個對象之前,先初始化一下私有的變量和方法。由于作用域不同,閉包外面的代碼是無法訪問到閉包內的私有變量的。一起來看下更具體的實現方法:
var HTMLChanger = (function() { var contents = "contents" var changeHTML = function() { var element = document.getElementById("attribute-to-change"); element.innerHTML = contents; } return { callChangeHTML: function() { changeHTML(); console.log(contents); } }; })(); HTMLChanger.callChangeHTML(); // Outputs: "contents" console.log(HTMLChanger.contents); // undefined
請注意 callChangeHTML 是在返回的對象中綁定的,因此可以訪問到 HTMLChanger 這個命名空間內的變量。然而,在模塊外面,是不能訪問到閉包里面的 contents 的。
揭示性模塊模式模塊模式的另一種變體稱為 揭示性模塊模式,它主要是為了在保持封裝性的同時,揭示在對象字面量中返回的特定的變量和方法。直接的實現方式類似這樣:
var Exposer = (function() { var privateVariable = 10; var privateMethod = function() { console.log("Inside a private method!"); privateVariable++; } var methodToExpose = function() { console.log("This is a method I want to expose!"); } var otherMethodIWantToExpose = function() { privateMethod(); } return { first: methodToExpose, second: otherMethodIWantToExpose }; })(); Exposer.first(); // Output: This is a method I want to expose! Exposer.second(); // Output: Inside a private method! Exposer.methodToExpose; // undefined
盡管這樣看起來更加簡潔,但它是有明顯不足的 -- 不能引用私有變量。這會給單元測試帶來一定的挑戰。類似地,公有行為也是不可重寫的。
原型設計模式JS開發者要么把 原型 和 原型繼承 相互混淆,要么在他們的代碼里面直接使用原型。原型設計模式依賴于JavaScript原型繼承. 原型模式主要用于為高性能環境創建對象。
被創建的對象是從傳下來的原對象克隆(淺克隆)出來的。原型模式的一種使用場景,是執行一個擴展性的數據庫操作來創建一個對象,把該對象用于應用的其他層面。如果其他流程需要用到這個對象,我們不需要大量地操作數據庫,只要克隆一下之前創建的對象就可以了。與其實質性地操作數據庫,不如從之前創建的對象克隆一個更具優勢。
Wikipedia 原型設計模式圖解
UML 描述了原型交互是如何被用于克隆具體的代碼實施方案的。
要克隆一個對象,必須存在一個構造器來實例化第一個對象。接下來,通過使用 prototype 的變量和方法來綁定對象的結構。一起來看下基本的示例:
var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = "Tesla"; this.make = "Model S"; } TeslaModelS.prototype.go = function() { // Rotate wheels } TeslaModelS.prototype.stop = function() { // Apply brake pads }
構造器 TeslaModelS 允許創建一個簡單的 TeslaModelS 對象。對于一個新創建的 TeslaModelS 對象,它將保持構造器初始化的狀態。此外,它也很簡單的持有 go 和 stop 這兩個方法,因為這兩個方法是在 prototype 聲明的。在原型上拓展方法,還可以這樣寫:
var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = "Tesla"; this.make = "Model S"; } TeslaModelS.prototype = { go: function() { // Rotate wheels }, stop: function() { // Apply brake pads } }揭示性原型模式
類似于模塊模式,原型模式也有一個 揭示性模式。揭示性原型模式 通過返回一個對象字面量,對公有和私有的成員進行封裝。
由于我們返回的是一個對象,我們將在原型對象上添加 function 的前綴。通過對以上例子進行改寫,我們可以選擇在當前的 prototype 暴露哪些方法或變量,以此來保護它們的訪問層級。
var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = "Tesla"; this.make = "Model S"; } TeslaModelS.prototype = function() { var go = function() { // Rotate wheels }; var stop = function() { // Apply brake pads }; return { pressBrakePedal: stop, pressGasPedal: go } }();
請注意 stop 和 go 兩個方法是被隔開的,因為他們在所返回的對象作用域之外。由于 JavaScript 本身支持原型繼承,也就沒必要重寫基本的功能了。
觀察者設計模式很多時候,當應用的一部分改變了,另一部分也需要相應更新。在 AngularJs 里面,如果 $scope 被更新,就會觸發一個事件去通知其他組件。結合觀察這模式就是:如果一個對象改變了,它只要派發 broadcasts 事件通知依賴的對象它已經改變了則可。
又一個典型的例子就是 model-view-controller (MVC) 架構了;當 model 改變時, 更新相應的 view。這樣做有一個好處,就是從 model 上解耦出 view 來減少依賴。
![觀察這設計模式](
Wikipedia 觀察者設計模式
如 UML 圖表所示,subject、observer, and concrete objects 是必不可少的。 subject 包含對每個具體觀察者的引用,以便傳遞改動信息。觀察者本身是一個抽象的類,使得具體的觀察者可以執行通訊方法。
一起來看下 AngularJS 的示例,在事件管理上應用了觀察這模式。
// Controller 1 $scope.$on("nameChanged", function(event, args) { $scope.name = args.name; }); ... // Controller 2 $scope.userNameChanged = function(name) { $scope.$emit("nameChanged", {name: name}); };
使用觀察者模式,重要的一點就是要區分獨立的對象或者 subject(主體)。
在看到觀察者模式眾多優點的同時,我們必須注意到它的一個缺點:隨著觀察者數量的增加,應用的性能會大大降低。大家都比較熟悉的觀察者就是 watchers 。 在AngularJS中,我們可以 watch 變量、方法和對象。$digest 循環更新,當一個作用域內對象被修改時,它就把新的值告訴每個監聽者。
我們可以在JS中創建自己的主體和觀察者。一起來看下下面的代碼是如何運行的:
var Subject = function() { this.observers = []; return { subscribeObserver: function(observer) { this.observers.push(observer); }, unsubscribeObserver: function(observer) { var index = this.observers.indexOf(observer); if(index > -1) { this.observers.splice(index, 1); } }, notifyObserver: function(observer) { var index = this.observers.indexOf(observer); if(index > -1) { this.observers[index].notify(index); } }, notifyAllObservers: function() { for(var i = 0; i < this.observers.length; i++){ this.observers[i].notify(i); }; } }; }; var Observer = function() { return { notify: function(index) { console.log("Observer " + index + " is notified!"); } } } var subject = new Subject(); var observer1 = new Observer(); var observer2 = new Observer(); var observer3 = new Observer(); var observer4 = new Observer(); subject.subscribeObserver(observer1); subject.subscribeObserver(observer2); subject.subscribeObserver(observer3); subject.subscribeObserver(observer4); subject.notifyObserver(observer2); // Observer 2 is notified! subject.notifyAllObservers(); // Observer 1 is notified! // Observer 2 is notified! // Observer 3 is notified! // Observer 4 is notified!發布、訂閱模式
然而,發布、訂閱模式是采用一個話題來綁定發布者和訂閱者之間的關系,訂閱者接收事件通知,發布者派發事件。該事件系統支持定義特殊應用的事件,可以傳遞包含訂閱者本身需要的自定義參數。這樣做主要是為了避免訂閱者和發布者之間的依賴。
這里有別于觀察者模式的是,任何訂閱者都可以通過恰當的事件處理器來注冊并接受發布者廣播的通知。
很多開發者選擇把 發布訂閱模式 和 觀察者模式 結合起來用,盡管他們最終的目標只有一個。發布訂閱模式中的訂閱者是通過一些通訊媒介被告知的,而觀察者則是通過執行事件處理器來獲得消息通知。
在 AngularJs, 訂閱者使用 $on(event、cbk) 來訂閱一個事件,發布者則使用$emit(‘event’, args) 或者 $broadcast(‘event’, args) 來發布一個事件。
單例模式單例模式只允許實例化一個對象,但是相同的對象,會用很多個實例。單例模式制約著客戶端創建多個對象。第一個對象創建后,就返回實例本身。
單例模式比較少用,很難找到實際開發的例子。使用一個辦公室打印機的例子吧。假設辦公室有10個人,他們都用到打印機,10臺電腦共享一部打印機(一個實例)。通過分享一部打印機,他們共享相同的資源。
var printer = (function () { var printerInstance; function create () { function print() { // underlying printer mechanics } function turnOn() { // warm up // check for paper } return { // public + private states and behaviors print: print, turnOn: turnOn }; } return { getInstance: function() { if(!printerInstance) { printerInstance = create(); } return printerInstance; } }; function Singleton () { if(!printerInstance) { printerInstance = intialize(); } }; })();
create 這個方法是私有的,因為我們不希望它被外部人員訪問到,然而,getInstance 方法是公有的。每個辦公人員都可以實例化一個 printer,只需要這樣調用一下:
`var officePrinter = printer.getInstance();`
單例模式在 AngularJS 相當流行,最常見的是作為 services、factories、和 providers。它們維護狀態,提供資源訪問,創建兩個實例擺脫一個共享的service/factory/provider。
在多線程的應用中,當多個線程嘗試去訪問同個資源時,就會出現 競爭狀態。單例模式會受到競爭狀態的干擾,比如在沒有初始化實例的情況下,兩個線程會創建兩個對象,而不是返回一個實例。這與單例模式的目的是相悖的。因此,開發者在多線程應用里面使用單例模式時,必須清楚同步性。
總結設計模式經常用于比較大型的應用,想知道哪種模式更具優勢,來實踐吧。
在構建任何應用之前,都應該全面地考慮每個角色,以及它們之間存在的關系。在回顧 模塊模式、原型模式、觀察者模式 和 單例模式 之后,你應該能夠區分它們,并且在實際開發中使用它們了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91019.html
摘要:擁抱異步編程縱觀發展史也可以說成開發的發展史,你會發現異步徹底改變了這場游戲。可以這么說,異步編程已成為開發的根基。這也是你應盡早在上投入大量時間的一處核心知識點,這其中包含和等重要概念。這也是最突出的一項貢獻。 原文地址:Medium - Learning How to Learn JavaScript. 5 recommendations on how you should spend ...
摘要:我同時也建立了一個基于瀏覽器的版本安裝命令行工具在此之前請先安裝然后在你的命令行中運行以下指令你應該會看到和一個提示。 原文:How does blockchain really work? I built an app to show you.作者:Sean Han譯者:JeLewine 根據維基百科,區塊鏈是: 一個用于維護不斷增長的記錄列表的分布式數據庫,我們稱之為區塊鏈。 這聽...
摘要:我同時也建立了一個基于瀏覽器的版本安裝命令行工具在此之前請先安裝然后在你的命令行中運行以下指令你應該會看到和一個提示。 原文:How does blockchain really work? I built an app to show you.作者:Sean Han譯者:JeLewine 根據維基百科,區塊鏈是: 一個用于維護不斷增長的記錄列表的分布式數據庫,我們稱之為區塊鏈。 這聽...
摘要:獲取成為開發專家的技巧。我們可以在兩個文本框輸入筆記的標題和內容。在本教程中,我們將使用一個名為的工具。它是一個火狐瀏覽器的擴展,我們可以使用它管理數據庫。安裝,打開火狐瀏覽器,點擊,然后點找到的文件夾圖標并點擊它。 showImg(https://cdn-images-1.medium.com/max/600/1*Ou6FFJJD3zhcIUU8wBZqIw.png); 教程譯文首發...
閱讀 890·2021-10-25 09:44
閱讀 1262·2021-09-23 11:56
閱讀 1183·2021-09-10 10:50
閱讀 3131·2019-08-30 15:53
閱讀 2134·2019-08-30 13:17
閱讀 617·2019-08-29 18:43
閱讀 2491·2019-08-29 12:57
閱讀 855·2019-08-26 12:20