摘要:盡管特定環(huán)境下有各種各樣的設(shè)計模式,開發(fā)者還是傾向于使用一些習(xí)慣性的模式。原型設(shè)計模式依賴于原型繼承原型模式主要用于為高性能環(huán)境創(chuàng)建對象。對于一個新創(chuàng)建的對象,它將保持構(gòu)造器初始化的狀態(tài)。這樣做主要是為了避免訂閱者和發(fā)布者之間的依賴。
2016-10-07
每個JS開發(fā)者都力求寫出可維護(hù)、復(fù)用性和可讀性高的代碼。隨著應(yīng)用不斷擴(kuò)大,代碼組織的合理性也越來越重要。設(shè)計模式為特定環(huán)境下的常見問題提供了一個組織結(jié)構(gòu),對于克服這些挑戰(zhàn)起到至關(guān)重要的作用。
JavaScript 網(wǎng)頁開發(fā)者在創(chuàng)建應(yīng)用時,頻繁地跟設(shè)計模式打交道(甚至在不知情的情況下)。
盡管特定環(huán)境下有各種各樣的設(shè)計模式,JS 開發(fā)者還是傾向于使用一些習(xí)慣性的模式。
在這篇文章中,我將討論這些常見的設(shè)計模式,展出優(yōu)化代碼庫的方法,并深入解讀JavaScript的內(nèi)部構(gòu)件。
本文討論的設(shè)計模式包括這幾種:
模塊設(shè)計模式
原型模式
觀察者模式
單例模式
盡管每種模式都包含很多屬性,這里我強調(diào)以下幾點:
上下文: 設(shè)計模式的使用場景
問題: 我們嘗試解決的問題是什么?
解決方法: 使用設(shè)計模式如何解決我們提出的問題?
實施: 實施方案看起來怎樣?
模塊設(shè)計模式JS模塊化是使用最普遍的設(shè)計模式,用于保持特殊的代碼塊與其它組件之間互相獨立。為支持結(jié)構(gòu)良好的代碼提供了松耦合。
對于熟悉面向?qū)ο蟮拈_發(fā)者來說,模塊就是JS的 “類”。封裝是“類”的眾多優(yōu)點之一,可以確保它本身的狀態(tài)和行為不被其它的類訪問到。模塊設(shè)計模式有公有和私有兩種訪問級別(除此之外,還有比較少為人知的保護(hù)級別、特權(quán)級別)。
考慮到私有的作用域,模塊應(yīng)該是一個立即調(diào)用函數(shù)(IIFE) ,也就是說,它是一個保護(hù)其私有變量和方法的閉包。(然而,它返回的卻不是一個函數(shù),而是一個對象)。
它的寫法就是這樣的:
(function() { // declare private variables and/or functions return { // declare public variables and/or functions } })();
我們在返回一個對象之前,先初始化一下私有的變量和方法。由于作用域不同,閉包外面的代碼是無法訪問到閉包內(nèi)的私有變量的。一起來看下更具體的實現(xiàn)方法:
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 這個命名空間內(nèi)的變量。然而,在模塊外面,是不能訪問到閉包里面的 contents 的。
揭示性模塊模式模塊模式的另一種變體稱為 揭示性模塊模式,它主要是為了在保持封裝性的同時,揭示在對象字面量中返回的特定的變量和方法。直接的實現(xiàn)方式類似這樣:
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
盡管這樣看起來更加簡潔,但它是有明顯不足的 -- 不能引用私有變量。這會給單元測試帶來一定的挑戰(zhàn)。類似地,公有行為也是不可重寫的。
原型設(shè)計模式JS開發(fā)者要么把 原型 和 原型繼承 相互混淆,要么在他們的代碼里面直接使用原型。原型設(shè)計模式依賴于JavaScript原型繼承. 原型模式主要用于為高性能環(huán)境創(chuàng)建對象。
被創(chuàng)建的對象是從傳下來的原對象克?。\克?。┏鰜淼?。原型模式的一種使用場景,是執(zhí)行一個擴(kuò)展性的數(shù)據(jù)庫操作來創(chuàng)建一個對象,把該對象用于應(yīng)用的其他層面。如果其他流程需要用到這個對象,我們不需要大量地操作數(shù)據(jù)庫,只要克隆一下之前創(chuàng)建的對象就可以了。與其實質(zhì)性地操作數(shù)據(jù)庫,不如從之前創(chuàng)建的對象克隆一個更具優(yōu)勢。
Wikipedia 原型設(shè)計模式圖解
UML 描述了原型交互是如何被用于克隆具體的代碼實施方案的。
要克隆一個對象,必須存在一個構(gòu)造器來實例化第一個對象。接下來,通過使用 prototype 的變量和方法來綁定對象的結(jié)構(gòu)。一起來看下基本的示例:
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 }
構(gòu)造器 TeslaModelS 允許創(chuàng)建一個簡單的 TeslaModelS 對象。對于一個新創(chuàng)建的 TeslaModelS 對象,它將保持構(gòu)造器初始化的狀態(tài)。此外,它也很簡單的持有 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 } }揭示性原型模式
類似于模塊模式,原型模式也有一個 揭示性模式。揭示性原型模式 通過返回一個對象字面量,對公有和私有的成員進(jìn)行封裝。
由于我們返回的是一個對象,我們將在原型對象上添加 function 的前綴。通過對以上例子進(jìn)行改寫,我們可以選擇在當(dāng)前的 prototype 暴露哪些方法或變量,以此來保護(hù)它們的訪問層級。
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 本身支持原型繼承,也就沒必要重寫基本的功能了。
觀察者設(shè)計模式很多時候,當(dāng)應(yīng)用的一部分改變了,另一部分也需要相應(yīng)更新。在 AngularJs 里面,如果 $scope 被更新,就會觸發(fā)一個事件去通知其他組件。結(jié)合觀察這模式就是:如果一個對象改變了,它只要派發(fā) broadcasts 事件通知依賴的對象它已經(jīng)改變了則可。
又一個典型的例子就是 model-view-controller (MVC) 架構(gòu)了;當(dāng) model 改變時, 更新相應(yīng)的 view。這樣做有一個好處,就是從 model 上解耦出 view 來減少依賴。
![觀察這設(shè)計模式](
Wikipedia 觀察者設(shè)計模式
如 UML 圖表所示,subject、observer, and concrete objects 是必不可少的。 subject 包含對每個具體觀察者的引用,以便傳遞改動信息。觀察者本身是一個抽象的類,使得具體的觀察者可以執(zhí)行通訊方法。
一起來看下 AngularJS 的示例,在事件管理上應(yīng)用了觀察這模式。
// Controller 1 $scope.$on("nameChanged", function(event, args) { $scope.name = args.name; }); ... // Controller 2 $scope.userNameChanged = function(name) { $scope.$emit("nameChanged", {name: name}); };
使用觀察者模式,重要的一點就是要區(qū)分獨立的對象或者 subject(主體)。
在看到觀察者模式眾多優(yōu)點的同時,我們必須注意到它的一個缺點:隨著觀察者數(shù)量的增加,應(yīng)用的性能會大大降低。大家都比較熟悉的觀察者就是 watchers 。 在AngularJS中,我們可以 watch 變量、方法和對象。$digest 循環(huán)更新,當(dāng)一個作用域內(nèi)對象被修改時,它就把新的值告訴每個監(jiān)聽者。
我們可以在JS中創(chuàng)建自己的主體和觀察者。一起來看下下面的代碼是如何運行的:
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!發(fā)布、訂閱模式
然而,發(fā)布、訂閱模式是采用一個話題來綁定發(fā)布者和訂閱者之間的關(guān)系,訂閱者接收事件通知,發(fā)布者派發(fā)事件。該事件系統(tǒng)支持定義特殊應(yīng)用的事件,可以傳遞包含訂閱者本身需要的自定義參數(shù)。這樣做主要是為了避免訂閱者和發(fā)布者之間的依賴。
這里有別于觀察者模式的是,任何訂閱者都可以通過恰當(dāng)?shù)氖录幚砥鱽碜圆⒔邮馨l(fā)布者廣播的通知。
很多開發(fā)者選擇把 發(fā)布訂閱模式 和 觀察者模式 結(jié)合起來用,盡管他們最終的目標(biāo)只有一個。發(fā)布訂閱模式中的訂閱者是通過一些通訊媒介被告知的,而觀察者則是通過執(zhí)行事件處理器來獲得消息通知。
在 AngularJs, 訂閱者使用 $on(event、cbk) 來訂閱一個事件,發(fā)布者則使用$emit(‘event’, args) 或者 $broadcast(‘event’, args) 來發(fā)布一個事件。
單例模式單例模式只允許實例化一個對象,但是相同的對象,會用很多個實例。單例模式制約著客戶端創(chuàng)建多個對象。第一個對象創(chuàng)建后,就返回實例本身。
單例模式比較少用,很難找到實際開發(fā)的例子。使用一個辦公室打印機(jī)的例子吧。假設(shè)辦公室有10個人,他們都用到打印機(jī),10臺電腦共享一部打印機(jī)(一個實例)。通過分享一部打印機(jī),他們共享相同的資源。
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,只需要這樣調(diào)用一下:
`var officePrinter = printer.getInstance();`
單例模式在 AngularJS 相當(dāng)流行,最常見的是作為 services、factories、和 providers。它們維護(hù)狀態(tài),提供資源訪問,創(chuàng)建兩個實例擺脫一個共享的service/factory/provider。
在多線程的應(yīng)用中,當(dāng)多個線程嘗試去訪問同個資源時,就會出現(xiàn) 競爭狀態(tài)。單例模式會受到競爭狀態(tài)的干擾,比如在沒有初始化實例的情況下,兩個線程會創(chuàng)建兩個對象,而不是返回一個實例。這與單例模式的目的是相悖的。因此,開發(fā)者在多線程應(yīng)用里面使用單例模式時,必須清楚同步性。
總結(jié)設(shè)計模式經(jīng)常用于比較大型的應(yīng)用,想知道哪種模式更具優(yōu)勢,來實踐吧。
在構(gòu)建任何應(yīng)用之前,都應(yīng)該全面地考慮每個角色,以及它們之間存在的關(guān)系。在回顧 模塊模式、原型模式、觀察者模式 和 單例模式 之后,你應(yīng)該能夠區(qū)分它們,并且在實際開發(fā)中使用它們了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91019.html
摘要:擁抱異步編程縱觀發(fā)展史也可以說成開發(fā)的發(fā)展史,你會發(fā)現(xiàn)異步徹底改變了這場游戲??梢赃@么說,異步編程已成為開發(fā)的根基。這也是你應(yīng)盡早在上投入大量時間的一處核心知識點,這其中包含和等重要概念。這也是最突出的一項貢獻(xiàn)。 原文地址:Medium - Learning How to Learn JavaScript. 5 recommendations on how you should spend ...
摘要:我同時也建立了一個基于瀏覽器的版本安裝命令行工具在此之前請先安裝然后在你的命令行中運行以下指令你應(yīng)該會看到和一個提示。 原文:How does blockchain really work? I built an app to show you.作者:Sean Han譯者:JeLewine 根據(jù)維基百科,區(qū)塊鏈?zhǔn)牵?一個用于維護(hù)不斷增長的記錄列表的分布式數(shù)據(jù)庫,我們稱之為區(qū)塊鏈。 這聽...
摘要:我同時也建立了一個基于瀏覽器的版本安裝命令行工具在此之前請先安裝然后在你的命令行中運行以下指令你應(yīng)該會看到和一個提示。 原文:How does blockchain really work? I built an app to show you.作者:Sean Han譯者:JeLewine 根據(jù)維基百科,區(qū)塊鏈?zhǔn)牵?一個用于維護(hù)不斷增長的記錄列表的分布式數(shù)據(jù)庫,我們稱之為區(qū)塊鏈。 這聽...
摘要:獲取成為開發(fā)專家的技巧。我們可以在兩個文本框輸入筆記的標(biāo)題和內(nèi)容。在本教程中,我們將使用一個名為的工具。它是一個火狐瀏覽器的擴(kuò)展,我們可以使用它管理數(shù)據(jù)庫。安裝,打開火狐瀏覽器,點擊,然后點找到的文件夾圖標(biāo)并點擊它。 showImg(https://cdn-images-1.medium.com/max/600/1*Ou6FFJJD3zhcIUU8wBZqIw.png); 教程譯文首發(fā)...
閱讀 901·2021-10-25 09:44
閱讀 1278·2021-09-23 11:56
閱讀 1198·2021-09-10 10:50
閱讀 3141·2019-08-30 15:53
閱讀 2144·2019-08-30 13:17
閱讀 629·2019-08-29 18:43
閱讀 2505·2019-08-29 12:57
閱讀 865·2019-08-26 12:20