摘要:在類似于這樣的面向對象語言中,抽象類的使用在這個設計模式中非常重要。假設系統(tǒng)中存在大量類似的對象而導致內存消耗過高,享元模式就非常有用了。享元模式包含兩種狀態(tài)即屬性內部狀態(tài)存儲于對象內部。不過與享元模式不同的是它不會區(qū)分內部狀態(tài)和外部狀態(tài)。
模式8-模版方法模式
模版方法模式是一種基于繼承的設計模式。主要由兩部分構成:
抽象父類:包含子類的算法框架和一些通用的具體方法;
具體實現(xiàn)的子類: 包含對于父類中抽象方法的實現(xiàn),繼承父類的整個算法實現(xiàn)方法,并且可以重寫父類中的方法。
在類似于java這樣的面向對象語言中,抽象類的使用在這個設計模式中非常重要。因為在編譯的時候會對繼承抽象類的子類進行檢測,要求必須要對抽象方法進行實現(xiàn)。然而在javascript中沒有類型檢查,所以要保證子類實現(xiàn)了所有抽象父類的抽象方法,可以在運行時進行檢測,即讓抽象方法拋出錯誤。
示例:
var Beverage = function() {} Beverage.prototype.boilWater = function(){ console.log("boil water"); } Beverage.prototype.brew = function (){ throw new Error("you must define function brew"); } Beverage.prototype.pourInCup = function (){ throw new Error("you must define function pourInCup"); } Beverage.prototype.addCondiments = function (){ throw new Error("you must define function addCondiments"); } Beverage.prototype.customerWantsCondiments = function(){ throw new Error("you must define function customerWantsCondiments"); } //泡飲料的順序和步驟是定的,算是一個子類通用的算法 Beverage.prototype.init = function(){ this.boilWater(); this.brew(); this.pourInCup(); if(this.customerWantsCondiments){ this.addCondiments(); } }; var Tea = function(){}; Tea.prototype = new Beverage(); Tea.prototype.brew = function(){ console.log("brew-up"); } Tea.prototype.pourInCup = function(){ console.log("pour tea"); } Tea.prototype.addCondiments = function(){ console.log("add sugar and milk"); } Tea.prototype.customerWantsCondiments = function() { return window.confirm("Do you need condiments?"); } var tea = new Tea(); tea.init(); var Coffee = function(){}; Coffee.prototype = new Beverage(); Coffee.prototype.brew = function(){ console.log("brew coffee"); } Coffee.prototype.pourInCup = function(){ console.log("pour coffee"); } Coffee.prototype.addCondiments = function(){ console.log("add lemon"); } Coffee.prototype.customerWantsCondiments = function() { return window.confirm("Do you need condiments?"); } var coffee = new Coffee(); coffee.init();
這個設計模式是有利于系統(tǒng)的拓展的,并且符合開放-封閉原則。另外,我們在js中不一定非要使用繼承的方式來完成這個設計模式,也可以通過傳入高階函數(shù)作為參數(shù)來實現(xiàn),用以替代父類中的抽象函數(shù)。
模式9-享元模式
享元模式是為了優(yōu)化性能而存在的。假設系統(tǒng)中存在大量類似的對象而導致內存消耗過高,享元模式就非常有用了。
享元模式包含兩種狀態(tài)(即屬性):
內部狀態(tài):
-存儲于對象內部。 -可以被一些對象共享。 -獨立于具體的場景,通常不會改變。
外部狀態(tài):
取決于具體的場景,并根據(jù)場景而變化,外部狀態(tài)不能被共享。
剝離了外部狀態(tài)的對象成為共享對象,外部狀態(tài)在必要時被傳入共享對象來組裝成一個完整的對象。系統(tǒng)中可能存在的最大享元對象個數(shù)等于不同內部狀態(tài)的組合數(shù)。
示例:
//這個享元只有一個內部狀態(tài) var Model = function( sex ){ this.sex = sex; }; Model.prototype.takePhoto = function(){ console.log( "sex= " + this.sex + " underwear=" + this.underwear); }; //分別創(chuàng)建一個男模特對象和一個女模特對象: var maleModel = new Model( "male" ), femaleModel = new Model( "female" ); //給男模特依次穿上所有的男裝,并進行拍照: //注:這里我們是在需要的時候才傳入外部狀態(tài) for ( var i = 1; i <= 50; i++ ){ maleModel.underwear = "underwear" + i; maleModel.takePhoto(); }; //同樣,給女模特依次穿上所有的女裝,并進行拍照: for ( var j = 1; j <= 50; j++ ){ femaleModel.underwear = "underwear" + j; femaleModel.takePhoto(); };
應用:
文件上傳,只根據(jù)上傳組件的不同來(使用工廠)新建uploader對象(只含有一個uploadType內部狀態(tài),同樣uploadType的被共享),而文件信息等儲存在外部,只當需要(如刪除文件)時才(通過uploadManager)將外部狀態(tài)傳入內部。
對象池是另一種性能優(yōu)化的方案,其思想是創(chuàng)建一個池子用來存放空閑對象,當需要使用該對象時從池子中取,如果沒有則創(chuàng)建,用完之后放回。不過與享元模式不同的是它不會區(qū)分內部狀態(tài)和外部狀態(tài)。
模式10-職責鏈模式
思想是將可能處理請求的對象連成一個鏈,請求者只需知道第一個對象,然后將請求沿著鏈依次傳遞直到遇到可以處理該請求的對象。這就使得請求發(fā)出著和請求接受者之間解耦,也就是說請求發(fā)出者不必知道哪個對象可以處理請求,避免了在一個函數(shù)中使用大量的if else判斷。
示例:
var handler1 = function(params){ if(params === true) { // check condition console.log("request solved by handler1"); } else { return false; // condition not valid } } var handler2 = function(params){ if(params === true) { // check condition console.log("request solved by handler2"); } else { return false; // condition not valid } } var handler3 = function(params){ if(params === true) { // check condition console.log("request solved by handler3"); } else { return false; // condition not valid } } var Chain = function( fn ){ this.fn = fn; this.successor = null; }; Chain.prototype.setNextSuccessor = function( successor ){ return this.successor = successor; }; Chain.prototype.passRequest = function(){ var ret = this.fn.apply( this, arguments ); if ( ret === false ){ return this.successor && this.successor.passRequest.apply( this.successor, arguments ); } return ret; }; //調用next函數(shù)可以手動傳遞請求,用于異步處理 Chain.prototype.next= function(){ return this.successor && this.successor.passRequest.apply( this.successor, arguments ); }; var chain1 = new Chain(handler1); var chain2 = new Chain(handler2); var chain3 = new Chain(handler3); //指定節(jié)點在職責鏈中的順序 chain1.setNextSuccessor(chain2); chain2.setNextSuccessor(chain3); //把請求傳遞給第一個節(jié)點: chain1.passRequest(someParams);
職責鏈模式使得各個鏈結點之間可以拆分重組,便于插入或刪除結點。并且請求不一定要從第一個結點開始。同時,要避免職責鏈過長帶來的性能問題。
另外,可以利用js的函數(shù)式特性將函數(shù)“鏈接”起來實現(xiàn)職責鏈模式,即為Function對象的原型添加after函數(shù)。
應用:
不同瀏覽器文件上傳控件的選擇,DOM事件冒泡等。
模式11-中介者模式
中介者模式是為了解除對象之間的強耦合關系,所有對象都通過中介者通信而不再相互引用。
示例:
假設一個網頁中幾個DOM元素的值共同決定一個按鈕的有效性,
例如input必須有效,select1和select2必須選擇
傳統(tǒng)的方法中,我們需要為這三個DOM元素添加值改變監(jiān)聽函數(shù),并且在每個監(jiān)聽函數(shù)中都要堅持另外兩個DOM的值。這時,如果我們需要加入一個input2,就需要將所有其他DOM元素的監(jiān)聽函數(shù)進行修改。
如果使用中介者模式,我們可以創(chuàng)建一個mediator對象,并且提供一個向其發(fā)送消息的借口。然后,我們?yōu)槠渌鸇OM創(chuàng)建的監(jiān)聽函數(shù)中只需要向中介者發(fā)送一個消息并且把this作為參數(shù)傳遞告訴中介者是誰發(fā)的消息。而中介者的實現(xiàn)中,只需要對收到的不同消息進行處理即可,如果需要添加新的關聯(lián)DOM,也只需要稍稍修改mediator的代碼而不會需要修改其他DOM的監(jiān)聽函數(shù)了。
總之,中介者模式使對象之間的網狀引用關系變成了一對多的關系,滿足一個對象盡可能少地了解其他對象的原則。但是該模式引入了中介者對象,也會造成一些內存消耗。
模式12-裝飾者模式
裝飾者模式用于動態(tài)地給對象增加職責。
為了保證在執(zhí)行原函數(shù)和this指向發(fā)生改變,我們需要引入代理函數(shù)。代理函數(shù)的功能是在原函數(shù)執(zhí)行之前或之后,執(zhí)行另外的函數(shù)。
示例:
使用AOP,改變Function對象的原型
Function.prototype.before = function(beforefn){ var _self = this; return function(){ beforefn.apply(this, arguments); //確保this指向不變 this.apply(this, arguments); } } var func1 = function(){ alert("1"); } func1 = func1.before(function(){ alert("0"); }); func1(); //先輸出0再輸出1
不改變原型的做法:
var before = function(fn, beforefn){ return function() { beforefn.apply(this, arguments); fn.apply(this, arguments); } } func1 = before(func1, function(){alert("0")});
應用:
對于某些用戶操作(如單擊按鈕)數(shù)據(jù)統(tǒng)計上報,改變函數(shù)的arguments對象(如ajax傳數(shù)據(jù)時添加屬性),表單驗證和提交功能的分離等。
這個設計模式使得開發(fā)人員在開發(fā)框架時可以只考慮對象的穩(wěn)定和基礎功能,其他需要添加的個性功能可以被動態(tài)添加。它不同于代理模式的是,代理模式的目的是為了控制對對象的訪問或添加一些功能,而裝飾者模式則是為了為對象動態(tài)添加功能,并且通常會形成一條長長的裝飾鏈。
值得注意的是,裝飾者模式返回的是一個新的函數(shù),因此原函數(shù)上的屬性會消失。同時,裝飾鏈也疊加了函數(shù)的作用域,過長則會對性能產生影響。
模式13-狀態(tài)模式
狀態(tài)模式使得一個對象在其狀態(tài)改變時改變其行為。也就是說,在不同狀態(tài)下調用同一個名字的函數(shù)其函數(shù)功能是不同的。
在狀態(tài)模式中,一般有兩類對象:context和狀態(tài)類。context構造函數(shù)中應該實例化所有的狀態(tài)類并作為context的屬性,以便context調用狀態(tài)類中的方法。而狀態(tài)類的構造函數(shù)應該以context作為參數(shù),以便可以調用context中改變其state值的接口來對其進行賦值。
例如一個擁有不同光強度的燈的控制代碼示例:
var Light = function(){ this.offLightState = new OffLightState( this ); // 持有狀態(tài)對象的引用 this.weakLightState = new WeakLightState( this ); this.strongLightState = new StrongLightState( this ); this.superStrongLightState = new SuperStrongLightState( this ); this.button = null; }; Light.prototype.init = function(){ var button = document.createElement( "button" ), self = this; this.button = document.body.appendChild( button ); this.button.innerHTML = "開關"; this.currState = this.offLightState; this.button.onclick = function(){ self.currState.buttonWasPressed(); } }; //接下來就要定義各種狀態(tài)類 var OffLightState = function( light ){ this.light = light; }; OffLightState.prototype.buttonWasPressed = function(){ console.log( "弱光" ); this.light.setState( this.light.weakLightState ); }; // 其他狀態(tài)類似,都需要定義一個buttonWasPressed方法,略
該設計模式優(yōu)點是將對象的狀態(tài)跟對應的方法一起封裝在一個類里,使得狀態(tài)的管理如添加刪除等更easy。狀態(tài)模式與策略模式很相似,都是有context和一些策略/狀態(tài)類。不同點是策略類之間是平行的,用戶需要知道他們的不同來選擇調用,而狀態(tài)類之間的狀態(tài)轉換關系是早就規(guī)定好的,用戶也不必知道其內部不同點。
JavaScript版本的狀態(tài)機(在js中狀態(tài)類不一定需要通過類來創(chuàng)建,可以使用顯式的對象):
var Light = function(){ this.currState = FSM.off; // 設置當前狀態(tài) this.button = null; }; Light.prototype.init = function(){ var button = document.createElement( "button" ), self = this; button.innerHTML = "已關燈"; this.button = document.body.appendChild( button ); this.button.onclick = function(){ self.currState.buttonWasPressed.call( self ); } }; var FSM = { off: { buttonWasPressed: function(){ console.log( "關燈" ); this.button.innerHTML = "下一次按我是開燈"; this.currState = FSM.on; } }, on: { buttonWasPressed: function(){ console.log( "開燈" ); this.button.innerHTML = "下一次按我是關燈"; this.currState = FSM.off; }; var light = new Light(); light.init();
模式14-適配器模式
適配器模式的作用就是轉換不兼容的接口。
示例:
var getStudents = function(){ //這個函數(shù)返回一個對象的數(shù)組 var arr = [ {"id": 0, "name": "LiLei" }, {"id": 1, "name": "HanMeimei" } ]; } var printStudents = function(fn){ var params = fn(); for(var i = 0; i < params.length; i++){ console.log(params[i].name + ":" + params[i].id); } } printStudents(getStudents); //倘若現(xiàn)在提供學生信息的函數(shù)變了,返回值類型也變了: var getStudentsObj = function () { var stu = { "0": "LiLei", "1": "HanMeimei" }; } //adaptor var stuAdapter = function(oldfn){ var newRes = {}; var stu = oldfn(); for(var i = 0; i < stu.length; i++) { newRes[stu[i].id] = stu[i].name; } return function(){ return newRes; } } printStudent(stuAdapter(oldfn));
裝飾者模式與代理模式的結構與適配器模式很像,都是用一個對象來包裝另一個對象,但不同點仍然是他們的意圖。適配者模式只是轉換接口,不需要知道對象的具體實現(xiàn)。
P.s. 本文總結自《JavaScript設計模式與開發(fā)實踐》,曾探著
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81086.html
摘要:在中使用在中使用腳本有兩種方式一種是嵌入在中的腳本,另一種是引入外部的腳本。二者并行執(zhí)行,不會造成阻塞。字符編碼,較少使用。放置的位置將腳本放在標簽前是最佳的。小結把插入到頁面中要使用元素。延遲腳本總是按照指定它們的順序執(zhí)行。 在 HTML 中使用 JavaScript 在html中使用JavaScript腳本有兩種方式一種是嵌入在HTML中的腳本,另一種是引入外部的腳本。兩種方式都離...
摘要:創(chuàng)建對象中,創(chuàng)建對象的基本模式有三種。因此,在設計構造函數(shù)時,需要進行慎重考慮。因此在中,這種問題被稱作繼承破壞封裝。靜態(tài)成員每個只有一份,直接通過類對象進行訪問。 什么是封裝 找工作時一些公司給了offer后我就想知道真正拿到手的是多少,畢竟賦稅繁重。但各種稅也好,五險一金也好我實在是弄不清楚,于是我就會在網上的一些稅后收入計算器上進行計算,只需要填寫一些基本信息,比如稅前收入,所...
摘要:元素向頁面中插入的主要方法就是使用元素。這個屬性的用途是表明腳本在執(zhí)行時不會影響頁面的構造。因此,在元素中設置屬性,相當于告訴瀏覽器立即下載,但延遲執(zhí)行?;祀s模式會讓的行為與包含非標準特性的相同,而標準模式則讓的行為更接近標準行為。 元素 向html頁面中插入js的主要方法就是使用元素。使用元素的方式有兩種:直接在頁面中嵌入js代碼和包含外部js文件。直接在頁面中嵌入js代碼如下: ...
摘要:構造函數(shù)對于被實例化的,我們稱之為構造函數(shù),及使用關鍵字調用的,對于它們來說,會被改變,指向實例。上栗子全局賦上屬性通過關鍵字創(chuàng)建實例,改變函數(shù)內部指向注解通過這個栗子,我們可以看出,通過創(chuàng)建構造函數(shù)的實例,使得的指向改變,指向了實例本身。 用栗子說this Bug年年有,今年特別多 對于JavaScript這么靈活的語言來說,少了this怎么活! function ...
摘要:方法始終從前向后找參數(shù)接收兩個參數(shù),第一個參數(shù)可以是一個對象或者一個字符串這個字符串不會轉換成正則表達式,第二個參數(shù)可以是一個字符串或者一個函數(shù)。要想替換所有子字符串,唯一的辦法就是提供一個正則表達式,而且要指定全局標志標志。 字符串的模式匹配方法 match() 參數(shù):只接受一個參數(shù),要么是一個正則表達式,要么是一個RegExp()對象。 返回:數(shù)組。數(shù)組中的第一項是與整個模式匹配的...
閱讀 2053·2021-11-22 13:52
閱讀 976·2021-11-17 09:33
閱讀 2708·2021-09-01 10:49
閱讀 2841·2019-08-30 15:53
閱讀 2659·2019-08-29 16:10
閱讀 2432·2019-08-29 11:31
閱讀 1343·2019-08-26 11:40
閱讀 1866·2019-08-26 10:59