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