不那么隨處可見的JavaScript學(xué)習(xí)筆記-Js設(shè)計模式漫談&&享元模式

當你手上只有錘子的時候箭窜,看什么都像釘子。

題圖--引自網(wǎng)絡(luò)衍腥,侵刪磺樱,請聯(lián)系我

引言

設(shè)計模式往往被用來檢驗一個程序員是否高端,因為從道理上來講婆咸,
在很多地方即使你寫的代碼和意大利面一樣竹捉,它只要能實現(xiàn)功能。你通常就不會被老板找麻煩尚骄。
然而只要公司上了一定的規(guī)模块差,有了編程規(guī)范,甚至復(fù)查倔丈,你要再寫的和意大利面一樣憨闰,可能很快你就吃不起意大利面了。
雖然需五,通常如果你只會寫意大利面一般的代碼鹉动,你也進不去這樣的地方。
扯遠了宏邮,我只是想說明泽示,當程序有了一定的規(guī)模,編程范式就會顯示出它的作用蜜氨,無論是從編寫還是維護兩方面來說械筛。
那么什么是設(shè)計模式呢?
設(shè)計模式的定義為:在面向?qū)ο筌浖O(shè)計過程中记劝,針對特定問題的簡潔而優(yōu)雅的解決方案变姨。
說人話就是,特定場景的某種問題的解決方案厌丑,它也不是多么遙不可及的內(nèi)容定欧,很有可能大家在日常的生產(chǎn)生活中無數(shù)次的用到過。
只是沒有給起個響亮的名字罷了怒竿。
GOF總結(jié)的設(shè)計模式共有23種砍鸠,在這里我并不想去討論所有23種設(shè)計模式。
我的宗旨耕驰,技術(shù)是為了解決問題而存在的,正好饭弓,我現(xiàn)在存在一個場景弟断,我認為用享元模式和職責(zé)鏈來重構(gòu)會更加優(yōu)雅。
雖然我之前看過它們的介紹昏翰,但并沒有應(yīng)用于實踐中過棚菊,于是這篇筆記隨之而生叔汁。
這篇筆記的討論點將偏向于設(shè)計原則和技巧据块。
范式方面將著重介紹享元模式。
和之前的所有筆記一般,這篇筆記具備偏頗和主觀兩個特點浪谴,旨在為我在實踐中遇到的問題提供解決思路因苹。

設(shè)計模式漫談-原則與技巧

說到設(shè)計原則扶檐,常聽到的無非幾種:單一職責(zé)原則(SRP),最少知識原則(LKP)智蝠,開放封閉原則(OCP)杈湾。
談到技巧攘须,最常聽見的大概就是面向?qū)ο螅蛘呙嫦蚪涌诤费矗苍S還有函數(shù)式編程了至会。
它們通常如字面意思一般,概念上很簡單明了健霹,但是實踐起來可謂博大精深糖埋。
駕馭不了的話窃这,原本為了便于維護引用的原則可能會使代碼復(fù)雜度加深杭攻,反而變得不那么好維護,很多設(shè)計模式在實踐中也會有類似的問題馆铁。
這并不是說埠巨,它們不對现拒,或者不好,僅僅是你自身暫時達不到那么境界所以應(yīng)用不當勋桶。
實際上在我初學(xué)JavaScript之后緊接著就進行了有關(guān)Js設(shè)計模式的學(xué)習(xí)例驹,這也是為什么我能意識到眠饮,我最近遇到的場景可以使用這兩個模式重構(gòu)铜邮。
然而寨蹋,在相當長的時間里已旧,我并沒有機會在實踐中應(yīng)用它們运褪,無非能力不足秸讹,同事并不在意這些雅倒,時間不夠這么幾個因素。
這是很常見的劣欢,當你挖空心思優(yōu)化一個模塊凿将,而之后在這個模塊做擴展的同事并不賣賬价脾,簡單粗暴的怎么方便怎么來侨把。
不出一些時日,你之前的優(yōu)化還不如不做,因為它們看起來更難懂华匾,而功能又都差不多机隙。

從單一職責(zé)原則說起:

單一職責(zé)原則簡單來說就是:一個對象(方法)只做一件事情有鹿。
舉例來說,假如你想用爬蟲爬取某個網(wǎng)站的圖片,大概需要以下幾個過程持寄。

1、請求目標地址废麻,獲得響應(yīng)的到Html結(jié)構(gòu)
2模庐、解析Html結(jié)構(gòu)掂碱,獲得圖片地址
3、下載圖片地址

如果你想沧卢,你可以把它們寫成一個巨大的函數(shù)搏恤,只需要傳入一個Url就能自動下載到圖片湃交。

function getImg(url){
  .....
}
//執(zhí)行完就能下載一張圖片搞莺。

然而這不是一個函數(shù)應(yīng)該干的事情,它不利于維護迈喉,如果應(yīng)用單一職責(zé)原則的話挨摸,它需要被拆分成至少以下幾個函數(shù)岁歉。

getHtml //請求URL地址得到Html結(jié)構(gòu) 
handleHtml //解析Html獲得Img下載地址
downImg //下載圖片
//你可以寫一個對象锅移,加上start函數(shù),就像這樣置逻。
var main={
    start:(url)=>{
       var html= main.getHtml(url);
       var imglist = main.handleHtml(html);
       downImg(imgList);
    },
    getHtml:(url)=>{
        ....
    },
    handleHtml(html)=>{
        ...
    },
    downImg(imgList)=>{
        ...
    }
}

當然也可以寫成單獨的函數(shù)不寫進一個Obj中券坞,它們明顯要比維護一個龐大復(fù)雜的函數(shù)要明智的多,當你想爬取另一個網(wǎng)站時深浮,只需替換中間的Html解析部分就可以了眠冈。
想擴展也可以很輕易的找到需要添加的位置蜗顽,或者需要修改的函數(shù)。
看起來似乎很簡單忿等,然而單一職責(zé)模式并不是那么容易被正確應(yīng)用贸街,因為并不是所有職責(zé)都應(yīng)該分離狸相,而且它雖然帶來了穩(wěn)定性脓鹃,但是也使得編寫代碼的復(fù)雜度加深,同時增加了這些對象之間聯(lián)系的難度,這有時也會使得這些對象并不那么易用娇跟。

最少知識原則

最少知識原則簡單來說則是:盡量減少對象之間的交互苞俘,如果兩個對象之間不必直接通信那么這兩個對象就不要直接相互聯(lián)系龄章。
通常使用中介對象來做聯(lián)系就是它的應(yīng)用了瓦堵,當某一部分發(fā)生更改只需要更改中介對象中的就可以了菇用。
舉例來說惋鸥,上面那個爬蟲列子start就是一個中介對象,我不關(guān)心你在爬取下載前經(jīng)過了多少步驟卦绣,我只是想下個圖片滤港,大概就是這個意思。
總結(jié)來說:最小知識原則減少了對象之間的依賴山叮,但有可能會制造一個龐大到難以維護的第三方對象屁倔,所以在實際使用中還需要分情況討論暮胧。

開放-封閉原則。

定義:軟件實體(類钞翔,模塊嗅战,函數(shù))等應(yīng)該是可以擴展的俺亮,但是不可修改脚曾。
這條概念好像有些難以理解,其實就是珊泳,當你需要新加一個需求的時候色查,在原系統(tǒng)上增加新的代碼撞芍,而不是去修改現(xiàn)用的模塊序无。
因為修改模塊的話衡创,有可能Bug會越改越多璃氢。
實例:

//假如你有一個非常龐大的If else 或者switch case分支一也。
//這明顯違背開放封閉原則隘竭,因為要擴展它不可避免的要去修改原本的代碼。  
//來一段經(jīng)典的鴨子發(fā)聲作為實例尊剔。
function makeSound(animal){
    switch(animal){
        case 'duck':
            console.log('嘎嘎嘎');
           break;
        case 'dog':
            console.log('汪汪汪')
        .....
    }
}
//簡單的動物發(fā)聲须误,要是擴展的話仇轻,就得對這個switch case語句下手,當然這個列子看起來修改這個switch case似乎沒什么困難的祭椰,這里只是為了舉例方淤。  
function makeSound(animal){
    animal.sound();
}
function Duck(){
    
}
Duck.prototype.sound=()=>{
    console.log('嘎嘎嘎');
}
makeSound(new Duck);
//擴展的話蹄殃,寫新的函數(shù)就可以了

//只是我覺得,要我寫的話..
function makeSound(animal){
    makeSound.prototype[animal].sound();
}
makeSound.prototype.Duck={
    sound:()=>{
        console.log('嘎嘎嘎');
    }
}
makeSound('Duck')
//擴展的話給它的protoype掛新的函數(shù)就行了讳苦,也算是不去修改原函數(shù)鸳谜。

上面是利用多態(tài)來重構(gòu)switch case 使它符合開放封閉原則以增加可擴展性式廷。
核心原則就一個,找出程序要發(fā)生變化的地方草描,然后把變化封裝起來穗慕。
常用的方式:使用鉤子逛绵,或者使用回調(diào)倔韭。
在這里不再展開,因為對此我也是一知半解胰苏,除了這個重構(gòu)選擇暫時沒想到新的應(yīng)用場景硕并,缺乏實踐動力秧荆。
實踐完職責(zé)鏈也許會對其理解更深。

以接口和面向接口編程結(jié)束

定義:接口是對象能響應(yīng)的請求的集合陕赃。
說API估計沒有程序員不知道它是啥么库,那什么是面向接口編程呢豌蟋?
簡單來說就是,只關(guān)心這些調(diào)用的類能做什么而不關(guān)心怎么做允睹。
在我來說缭受,則是當別人來引用你開發(fā)的大模塊中某一個小模塊時米者,它并不需要關(guān)注你的實現(xiàn),只需要知道調(diào)用會產(chǎn)生什么結(jié)果胰丁。
以上面的爬蟲列子來說喂分,就是無論你只是想獲得一堆Html蒲祈,還是想按一定的規(guī)則解析Html,還是只是想下載一個圖片扬卷,都可以引用上面那個大模塊中的某個函數(shù)來達成目的怪得,而不需要在意其它函數(shù)是怎么實現(xiàn)的。
具體展開似乎有一大串汇恤,介于我個人不是太感興趣拔恰,摘抄兩句颜懊,這個部分我們就先跳過吧。

面向接口編程匠璧,而不是面向?qū)崿F(xiàn)編程夷恍。

如果它走起路來像鴨子媳维,叫起來也是鴨子,那么它就是鴨子指黎。

前者在我看來和面向?qū)ο蟛o二致醋安,后者則可以參考下Js中龐大的類數(shù)組形數(shù)據(jù)調(diào)用數(shù)組的方法,這些接口并不關(guān)心它們拿到的是不是一個真正的數(shù)組亲怠。

享元模式

享元(flyweight)是一種性能優(yōu)化的模式赁炎,'fly'在這里是蒼蠅的意思,意為蠅量級讥裤。享元模式的核心是運用共享技術(shù)來有效支持大量細粒度的對象。
重點是劃分內(nèi)外狀態(tài)间螟,目的是減少共享對象的數(shù)量厢破。
我不會舉那個男女模特和內(nèi)衣工廠的列子
簡單復(fù)述一下就是摩泪,如果用代碼實現(xiàn)男女模特穿一千件內(nèi)衣拍照劫谅,直接寫的話捏检,往往會需要男女模特各一百個。
而稍微注意一點熊楼,則可以鲫骗,男女模特各一個換著穿一千件內(nèi)衣拍照悲雳,有興趣的可以自行百度。
經(jīng)驗指引:

  • 內(nèi)部狀態(tài)存儲于對象內(nèi)部
  • 內(nèi)部狀態(tài)可以被一些對象共享
  • 內(nèi)部狀態(tài)獨立于具體場景坦胶,通常不會改變峭咒。
  • 外部狀態(tài)取決于具體場景凑队,并根據(jù)場景而變化幔翰,外部狀態(tài)不能被共享遗增。

在這里我附上一個進程池的列子:
進程池的場景很常見做修,比如之前的Redis,NodeRedis庫每次讀取會重新連接蔗坯,如果多次連接宾濒,每次都新開一個連接很明顯不夠高效鼎兽。
正確的做法是開一個連接谚咬,將這個連接對象依次往下傳遞直到所有的讀取都結(jié)束為止择卦。
有并發(fā)的存在那么就開三個郎嫁。
其它泽铛,我以前做過一個在地圖上標小氣泡的頁面盔腔。
正常的思路是,獲取用戶定位地點為中心的圓宁赤,標注氣泡决左,移動佛猛,重新標注坠狡,或添加新的氣泡挚躯。
假如重新標注,清除之前的標注對象擦秽,再新建也很顯然不夠高效。
正確的做法是回收它們漩勤,再重新定位它們的位置感挥。
附上一個簡單實現(xiàn):

function divFactory(){
    var divPool=[];
    return{
        create:this.create,
        pool:divPool
    }
}
divFactory.protoype.create=function(num){
    var needDiv = num;
    var result  = [];
    if(this.pool.length<needDiv){
        var neddLength = needDiv - this.pool.length;
        for(var i= 0;i<needLength;i++){
            var div = document.createElement('div');
            this.pool.push('div');
        }
        result = this.pool
    }else{
        for(var i=0;i<needDiv;i++){
            result.push(this.pool[i])
        }
    }
    return result;
}

//調(diào)用
var createFactory = new divFactory();
var res = createFactory.create(3);
for( i in res){
    res[i].innerHTML = 'Create '+i;
    document.body.appendChild(res[i]);
}
//頁面上出現(xiàn)3個寫著Create 的Div還有編號
var res2 = createFactory.create(5);
for(i in res2){
    var tip = res[i].innerHTML || 'No'
    res2[i].innerHTML=tip+'Create2'+i;
    document.body.appendChild(res[i]);
}
//頁面上出現(xiàn)五個div其中前3個有Create+ 編號

上面這個列子以地圖上的小泡為列的話,可以重新修改已經(jīng)創(chuàng)建的小泡的名稱和位置越败,以圖性能優(yōu)化触幼。
附帶一提,當年我負責(zé)的那個項目這個地圖部分(不是我做的)最后選擇了一次性將所有可能出現(xiàn)的點都導(dǎo)到地圖上究飞,一次性標注完成置谦,不管你客戶滑到哪如果存在肯定能看到應(yīng)該有的點亿傅,全國差不多幾千個吧谅阿。
前兩天我還特的去看了眼,依舊是這個做法氯檐,它已經(jīng)平穩(wěn)運行兩年了糯崎,側(cè)面說明,前端優(yōu)化,有的時候并沒有面試時強調(diào)的那么重要黄锤,當然我還是提倡能優(yōu)化自然要優(yōu)化一下负甸。

結(jié)語

這篇筆記花的時間比之前任意一篇筆記的時間都長,原本我還以為可以記錄下職責(zé)鏈和代碼重構(gòu)奏篙,但事實證明敛熬,我下一篇筆記也許有內(nèi)容了。
編程范式這類的,其實和代碼編寫規(guī)范一般改备,沒有標準答案,而且往往需要分情況討論碉渡,我作為經(jīng)驗尚淺的開發(fā)人員是不敢妄議的。
但它既然經(jīng)過了這么多年的檢驗习霹,自是有歷史沉淀在里面的伪阶。
作為一個土木系畢業(yè)的工科生對這些工程化模塊化有天然的好感。
作為一個半路出家的前端癣缅,構(gòu)造函數(shù)和原型鏈依舊還不習(xí)慣使用。
設(shè)計模式博大精深,而這篇筆記怕還不能揭示其中的一鱗半爪,然作為我個人的學(xué)習(xí)記錄已經(jīng)足夠敛摘。
如若在將來能起到幫助它人理解之效捕虽,那便是倍感欣慰房揭,念及此雖并無關(guān)聯(lián)伶唯,而今天我便可放心的擱下鍵盤粹断。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末养筒,一起剝皮案震驚了整個濱河市巫湘,隨后出現(xiàn)的幾起案子怠褐,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椭岩,“玉大人挺身,你說我怎么就攤上這事惨撇∑实恚” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長典尾。 經(jīng)常有香客問我姜性,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮房铭,結(jié)果婚禮上露懒,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好象迎,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殖妇,像睡著了一般舰绘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上探膊,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天蟋恬,我揣著相機與錄音乔遮,去河邊找鬼却音。 笑死清蚀,一個胖子當著我的面吹牛嘶卧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫩挤,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孽鸡!你這毒婦竟也來了嚼沿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤姨俩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宝冕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體张遭,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年地梨,在試婚紗的時候發(fā)現(xiàn)自己被綠了菊卷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缔恳。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洁闰,靈堂內(nèi)的尸體忽然破棺而出歉甚,到底是詐尸還是另有隱情,我是刑警寧澤扑眉,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布纸泄,位于F島的核電站,受9級特大地震影響襟雷,放射性物質(zhì)發(fā)生泄漏刃滓。R本人自食惡果不足惜仁烹,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一耸弄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卓缰,春花似錦计呈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至总寒,卻和暖如春扶歪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摄闸。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工善镰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人年枕。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓炫欺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親熏兄。 傳聞我的和親對象是個殘疾皇子品洛,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容