日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

Effective前端5:减少前端代码耦合

發布時間:2025/3/15 HTML 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Effective前端5:减少前端代码耦合 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

什么是代碼耦合?代碼耦合的表現是改了一點毛發而牽動了全身,或者是想要改點東西,需要在一堆代碼里面找半天。由于前端需要組織js/css/html,耦合的問題可能會更加明顯,下面按照耦合的情況分別說明:

1. 避免全局耦合

這應該是比較常見的耦合。全局耦合就是幾個類、模塊共用了全局變量或者全局數據結構,特別是一個變量跨了幾個文件。例如下面,在html里面定義了一個變量:

在html里面定義全局變量 XHTML
1 2 3 4 5 <script> ????varPAGE=20; </script> <script?src="main.js"></script>

上面在head標簽里面定義了一個PAGE的全局變量,然后在main.js里面使用。這樣子PAGE就是一個全局變量,并且跨了兩個文件,一個html,一個js。然后在main.js里面突然冒出來了個PAGE的變量,后續維護這個代碼的人看到這個變量到處找不到它的定義,最后找了半天發現原來是在xxx.html的head標簽里面定義了。這樣就有點egg pain了,并且這樣的變量容易和本地的變量發生命名沖突。

所以如果需要把數據寫在頁面上的話,一個改進的辦法是在頁面寫一個form,數據寫成form里面的控件數據,如下:

JavaScript
1 2 3 4 <form?id="page-data"> ????<input?type="hidden"name="page"value="2"> ????<textarea?name="list"style="display:none">[{"userName":""yin"},{}]</textarea> </form>

上面使用了input和textarea,使用textarea的優點是支持特殊符號。再把form的數據序列化,序列化也是比較簡單的,可以查看Effective前端2:優化html標簽

第二種是全局數據結構,這種可能會使用模塊化的方法,如下:

JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //data.js module.exports={ ????houseList:null } //search.js 獲取houseList的數據 vardata=require("data"); data.houseList=ajax(); require("format-data").format(); //format-data.js 對houseList的數據做格式化 functionformat(){ ????vardata=require("data"); ????process(data); ????require("show-result").show(); } //show-result.js 將數據顯示出來 functionshow(){ ????showData(require("data").houseList) }

上面四個模塊各司其職,乍一眼看上去好像沒什么問題,但是他們都用了一個data的模塊共用數據。這樣確實很方便,但是這樣就全局耦合了。因為用的同一個data,所以你無法保證,其它人也會加載了這個模塊然后做了些修改,或者是在你的某一個業務的異步回調也改了這個。第二個問題:你不知道這個data是從哪里來的,誰可能會對它做了修改,這個過程對于后續的模塊來說都是不透明的。

所以這種應該考慮使用傳參的方式,降低耦合度,把data作為一個參數傳遞:

用傳參降低耦合 JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //去掉data.js //search.js 獲取數據并傳遞給下一個模塊 varhouseList=ajax(); require("format-data").format(houseList); //format-data.js 對houseList的數據做格式化 functionformat(houseList){ ????process(houseList); ????require("show-result").show(houseList); } //show-result.js 將數據顯示出來 functionshow(houseList){ ????showData(houseList) }

可以看到,search里面獲取到data后,交給format-data處理,format-data處理完之后再給show-result。這樣子就很清楚地知道數據的處理流程,并且保證了houseList不會被某個異步回調不小心改了。如果單獨從某個模塊來說,show-result這個模塊并不需要關心houseList的經過了哪些流程和處理,它只需要關心輸入是符合它的格式要求的就可以。

這個時候你可能會有一個問題:這個data被逐層傳遞了這么多次,還不如像最上面的那樣寫一個data的模塊,大家都去改那里,豈不是簡單了很多?對,這樣是簡單了,但是一個數據結構被跨了幾個文件使用,這樣會出現我上面說的問題。有時候可能出現一些意想不到的情況,到時候可能得找bug找個半天。所以這種解耦是值得的,除非你定義的變量并不會跨文件,它的作用域只在它所在的文件,這樣會好很多。或者是data是常量的,data里面的數據定義好之后值就再也不會改變,這樣應當也是可取的。

2. js/css/html的耦合

這種耦合在前端里面應該最常見,因為這三者通常具有交集,需要使用js控制樣式和html結構。如果使用js控制樣式,很多人都喜歡在js里面寫樣式,例如當頁面滑動到某個地方之后要把某個條吸頂:

頁面滑到下面那個灰色的條再繼續往下滑的時候,那個灰色條就要保持吸頂狀態:

可能不少人會這么寫:

fixed操作 JavaScript
1 2 3 4 5 $(".bar").css({ ????position:fixed; ????top:0; ????left:0; });

然后當用戶往上滑的時候取消fixed:

取消fixed JavaScript
1 2 3 $(".bar").css({ ????position:static; });

如果你用react,你可能會設置一個style的state數據,但其實這都一樣,都把css雜合到js里面了。某個想要檢查你樣式的人,想要給你改個bug,他檢查瀏覽器發現有個標簽style里的屬性,然后他找半天找不到是在哪里設置的,最后他發現是在某個js的某個隱蔽的角落設置了。你在js里面設置了樣式,然后css里面也會有樣式,在改css的時候,如果不知道js里面也有設置了樣式,那么可能會發生沖突,在某種條件下觸發了js里面設置樣式。

所以不推薦直接在js里面更改樣式屬性,而應該通過增刪類來控制樣式,這樣子樣式還是回歸到css文件里面。例如上面可以改成這樣:

通過設置class的方法 JavaScript
1 2 3 4 5 //增加fixed $(".bar").addClass("fixed"); //取消fixed $(".bar").removeClass("fixed");

fixed的樣式:

CSS
1 2 3 4 5 .bar.fixed{ ????position:fixed; ????left:0; ????top:0; }

可以看到,這樣的邏輯就非常清晰,并且回滾fixed,不需要把它的position還原為static,因為它不一定是static,也有可能是relative,這種方式在取消掉一個類的時候,不需要去關心原本是什么,該是什么就會是什么。

但是有一種是避免不了的,就是監聽scroll事件或者mousemove事件,動態地改變位置。

這種通過控制類的方式還有一個好處,就是當你給容器動態地增刪一個類時,你可以借助子元素選擇器,用這個類控制它的子元素的樣式,也是很方便。

還有很多人可能會覺得html和css/js脫耦,那就是不能在html里面寫style,不能在html里面寫script標簽,但是凡事都不是絕對的,如果有一個標簽,它和其它標簽就一個font-size不一樣,那你直接給它寫一個font-size的內聯樣式,又何嘗不可呢,在性能上來說,如果你寫個class,它還得去匹配這個class,比不上style高效吧。或者是你這個html文件就那么20、30行css,那直接在head標簽加個style,直接寫在head里面好了,這樣你就少管理了一個文件,并且瀏覽器不用去加載一個外鏈的文件。

有時候直接在html寫script標簽是必要的,它的優勢也是不用加載外鏈文件,處理速度會很快,幾乎和dom渲染同時,這個在解決頁面閃動的時候比較有用。因為如果要用js動態地改變已經加載好的dom,放在外鏈里面肯定會閃一下,而直接寫的script就不會有這個問題,即使這個script是放在了body的后面。例如下面:

原始數據是帶p標簽的,但是在textarea里面展示的時候需要把p改成換行\r\n,如果在dom渲染之后再在外鏈里面更新dom就會出現上面的閃動的情況。你可能會說我用react,數據都是動態渲染的,渲染前已經處理好了,不會出現上面的情況。那么,好吧,至少你了解一下吧。

和耦合相對的是內聚,寫代碼的原則就是低耦合、高聚合。所謂內聚就是說一個模塊的職責功能十分緊密,不可分割,這個模塊就是高內聚的。我們先從重復代碼說起:

3. 減少重復代碼

假設有一段代碼在另外一個地方也要被用到,但又不太一樣,那么最簡單的方法當然是copy一下,然后改一改。這也是不少人采取的辦法,這樣就導致了:如果以后要改一個相同的地方就得同時改好多個地方,就很麻煩了。

例如有一個搜索的界面:

用戶可以通過點擊search按鈕觸發搜索,也可以通過點擊下拉或者通過輸入框的change觸發搜索,所以你可能會這么寫:

點擊search搜索 JavaScript
1 2 3 4 5 6 7 8 9 10 $("#search").on("click",function(){ ????varformData=getFormData(); ????$.ajax({ ????????url:'/search', ????????data:formData, ????????success:function(data){ ????????????showResult(data); ????????} ????}); });

在change里面又重新發請求:

input change時搜索 JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 $("input").on("change",function(){ ????//把用戶的搜索條件展示進行改變 ????changeInputFilterShow(); ????varformData=getFormData(); ????$.ajax({ ????????url:'/search', ????????data:formData, ????????success:function(data){ ????????????showResult(data); ????????} ????}); });

change里面需要對搜索條件的展示進行更改,和click事件不太一樣,所以圖一時之快就把代碼拷了一下。但是這樣是不利于代碼的維護的,所以你可能會想到把獲取數據和發請求的那部分代碼單獨抽離封裝在一個函數,然后兩邊都調一下:

JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 functiongetAndShowData(){ ????varformData=getFormData(); ????$.ajax({ ????????url:'/search', ????????data:formData, ????????success:function(data){ ????????????showResult(data); ????????} ????}); } $("#search").on("click",getAndShowData); $("input").on("change",function(){ ????changeInputFilterShow(); ????getAndShowData(); });

在抽成一個函數的基礎上,又發現這個函數其實有點大,因為這里面要獲取表單數據,還要對數據進行格式化,用做請求的參數。如果用戶觸發得比較快,還要記錄上次請求的xhr,在每次發請求前cancle掉上一次的xhr,并且可能對請求做一個loading效果,增加用戶體驗,還要對出錯的情況進行處理,全部都要在ajax里面。所以最好對getAndShowData繼續拆分,很自然地會想到把它分離成一個模塊,一個單獨的文件,叫做search-ajax。所有發請求的處理都在這個模塊里面統一操作。對外只提供一個search.ajax的接口,傳的參數為當前的頁數即可。所有需要發請求的都調一下這個模塊的這個接口就好了,除了上面的兩種情況,還有點擊分頁的情景。這樣不管哪種情景都很方便,我不需要關心請求是怎么發的,結果是怎么處理的,我只要傳一個當前的頁數給你就好了。

再往下,會發現,在顯示結果那里,即上面代碼的第7行,需要對有結果、無結果的情況分別處理,所以又搞了一個函數叫做showResult,這個函數有點大,它里面的邏輯也比較復雜,有結果的時候除了更新列表結果,還要更新結果總數、更新分頁的狀態。因此這個showResult一個函數難以擔當大任。所以把這個show-result也當獨分離出一個模塊,負責結果的處理。

到此,我們整一個search的UML圖應該是這樣的:

注意上面把發請求的又再單獨封裝成了一個模塊,因為這個除了搜索發請求外,其它的請求也可以用到。同時search-result會用到兩個展示的模板。

由于不只一個頁面會用到搜索的功能,所以再把上面繼續抽象,把它封裝成一個search-app的模塊,需要用到的頁面只需require這個search-app,調一下它的init函數,然后傳些定制的參數就可以用了。這個search-app就相當于一個搜索的插件。

所以整一個的思路是這樣的:出現了重復代碼 -> 封裝成一個函數 -> 封裝成一個模塊 -> 封裝成一個插件,抽象級別不斷提高,將共有的特性和有差異的地方分離出來。當你走在抽象與封裝的路上的時候,那你應該也是走在了大神的路上。

當然,如果兩個東西并沒有共同點,但是你硬是要搞在一起,那是不可取的。

我這里說的封裝并不是說,你一定要使用requirejs、es6的import或者是webpack的require,關鍵在于你要有這種模塊化的思想,并不是指工具上的,不管你用的哪一個,只要你有這種抽象的想法,那都是可取的。

模塊化的極端是拆分粒度太細,一個簡單的功能,明明十行代碼寫在一起就可以搞定的事情,硬是寫了七、八層函數棧,每個函數只有兩、三行。這樣除了把你的邏輯搞得太復雜之外,并沒有太多的好處。當你出現了重復代碼,或者是一個函數太大、功能太多,又或是邏輯里面寫了三層循環又再嵌套了三層if,再或是你預感到你寫的這個東西其他人也可能會用到,這個時候你才考慮模塊化,進行拆分比較合適。

上面不管是search-result還是search-ajax他們在功能上都是高度內聚的,每個模塊都有自己的職責,不可拆分,這在面向對象編程里面叫做單一責職原則,一個模塊只負責一個功能。

再舉一個例子,我在怎樣實現前端裁剪上傳圖片功能里面提到一個上傳裁剪的實現,這里面包含裁剪、壓縮上傳、進度條三大功能,所以我把它拆成三個模塊:

這里提到的模塊大部分是一個單例的object,不會去實例它,一般可以滿足大部分的需求。在這個單例的模塊里面,它自己的“私有”函數一般是通過傳參調用,但是如果需要傳遞的數據比較多的時候,就有點麻煩了,這個時候可以考慮把它封裝成一個類。

3. 封裝成一個類

在上面的裁剪上傳里面的進度條progress-bar,一個頁面里可能有幾個要上傳的地方,每個上傳的地方都會有進度條,每個進度條都有自己的數據,所以不能像在最上面說的,在一個文件的最上面定義一些變量然后為這個模塊里面的函數共用,只能是通過傳遞參數的形式,即在最開始調用的時候定義一些數據,然后一層一層地傳遞下去。如果這些數據很多的話就有點麻煩。

所以稍微變通一下,把progress-bar封裝成一個類:

進度條的類 JavaScript
1 2 3 4 5 6 7 functionProgressBar($container){ ????this.$container=$container;//進度條外面的容器 ????this.$meter=null;???????????//進度條可視部分 ????this.$bar=null;?????????????//進度條存放可視部分的容器 ????this.$barFullWidth=$container.width()*0.9;//進度條的寬度 ????this.show();??????????????????//new一個對象的時候就顯示 }

或者你用ES6的class,但是本質上是一樣的,然后這個ProgressBar的成員函數就可以使用定義的這些“私有”變量,例如設置進度條的進度函數:

JavaScript
1 2 3 4 ProgressBar.prototype.setProgress=function(percentage,time){ ????time=typeoftime==="undefined"?100:time; ????this.$meter.stop().animate({width:parseInt(this.$barFullWidth*percentage)},time); };

這個使用了兩個私有變量,如果再加上原先兩個,用傳參的方式就得傳四個。

使用類是模塊化的一種思想,另外一種常用的還有策略模式。

4. 使用策略模式

假設要實現下面三個彈框:

??

這三個彈框無論是在樣式上還是在功能上都是一樣的,唯一的區別是上面標題文案是不一樣的。最簡單的可能是把每個彈框的html都copy一下,然后改一改。如果你用react,你可能會用拆分組件的方式,上面一個組件,下面一個組件,那么好吧,你就這樣搞吧。如果你沒用react,你可能得想辦法組織下你的代碼。

如果你有策略模式的思想,你可能會想到把上面的標題當作一個個的策略。首先定義不同彈框的類型,一一標志不同的彈框:

JavaScript
1 varpopType=["register","favHouse","saveSearch"];

定義三種popType一一對應上面的三個彈框,然后每種popType都有對應的文案:

JavaScript
1 2 3 4 5 6 7 8 Data.text.pop={ ????register:{ ????????titlte:"Create Your Free Account", ????????subTitle:"Search Homes and Exclusive Property Listings" ????}, ????favHouse:{title:"xxx",subTitle:"xxx"}, ????saveSearch:{title:"xxx",subTitle:"xxx"} };

{tittle: “”, subtitle: “”}這個就當作是彈框文案策略,然后再寫彈框的html模板的時候引入一個占位變量:

popTemplate JavaScript
1 2 3 4 5 6 7 <section> ????{{title}} ????{{subTitile}} ????<div> ????????<!--其它內容--> ????</div> </section>

在渲染這個彈框的時候,根據傳進來的popType映射到不同的文案:

JavaScript
1 2 3 functionshowPop(popType){ ????Mustache.render(popTemplate,Data.text.pop[popType]) }

這里用Data.text.pop[popType]映射到了對應的文案,如果用react你把一個個的標題封裝成一個組件,其實思想是一樣的。

但是這個并不是嚴格的策略模式,因為策略就是要有執行的東西嘛,我們這里其實是一個寫死的文案,但是我們借助了策略模式的思想。接下來繼續說使用策略模式做一些執行的事情。

在上面的彈框的觸發機制分別是:用戶點擊了注冊、點擊了收藏房源、點擊了保存搜索條件。如果用戶沒有登陸就會彈一個注冊框,當用戶注冊完之后,要繼續執行用戶原本的操作,例如該收藏還是收藏,所以必須要有一個注冊后的回調,并且這個回調做的事情還不一樣。

當然,你可以在回調里面寫很多的if else或者是case:

JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 functionpopCallback(popType){ ????switch(popType){ ????????case"register": ????????????//do nothing ????????????break; ????????case:"favHouse": ????????????favHouse(); ????????????break; ????????case:"saveSearch": ????????????saveSearch(); ????????????break; ????} }

但是當你的case很多的時候,看起來可能就不是特別好了,特別是if else的那種寫法。這個時候就可以使用策略模式,每個回調都是一個策略:

pop-callback JavaScript
1 2 3 4 5 6 7 8 varpopCallback={ ????favHouse:function(){ ????????//do sth. ????}, ????saveSearch:function(){ ????????//do sth. ????} }

然后根據popType映射調用相應的callback,如下:

JavaScript
1 2 3 4 varpopCallback=require("pop-callback"); if(typeofpopCallback[popType]==="function"){ ????popCallback[popType](); }

這樣它就是一個完整的策略模式了,這樣寫有很多好處。如果以后需要增加一個彈框類型popType,那么只要在popCallback里面添加一個函數就好了,或者要刪掉一個popType,相應地注釋掉某個函數即可。并不需要去改動原有代碼的邏輯,而采用if else的方式就得去修改原有代碼的邏輯,所以這樣對擴展是開放的,而對修改是封閉的,這就是面向對象編程里面的開閉原則。

在js里面實現策略模式或者是其它設計模式都是很自然的方式,因為js里面function可以直接作為一個普通的變量,而在C++/Java里面需要用一些技巧,玩一些OO的把戲才能實現。例如上面的策略模式,在Java里面需要先寫一個接口類,里面定義一個接口函數,然后每個策略都封裝成一個類,分別實現接口類的接口函數。而在js里面的設計模式往往幾行代碼就寫出來,這可能也是做為函數式編程的一個優點。

前端和設計模式經常打交道的還有訪問者模式

4. 訪問者模式

事件監聽就是一個訪問者模式,一個典型的訪問者模式可以這么實現,首先定義一個Input的類,初始化它的訪問者列表:

定義一個Input的類 JavaScript
1 2 3 4 5 6 7 8 9 functionInput(inputDOM){ ????//用來存放訪問者的數據結構 ????this.visitiors={ ????????"click":[], ????????"change":[], ????????"special":[]//自定義事件 ????} ????this.inputDOM=inputDOM; }

然后提供一個對外的添加訪問者的接口:

添加訪問者 JavaScript
1 2 3 4 5 Input.prototype.on=function(eventType,callback){ ????if(typeofthis.visitiors[eventType]!=="undefined"){ ????????this.visitiors[eventType].push(callback); ????} };

使用者調用on,傳遞兩個參數, 一個是事件類型,即訪問類型,另外一個是具體的訪問者,這里是回調函數。Input就會將訪問者添加到它的訪問者列表。

同時Input還提供了一個刪除訪問者的接口:

off接口 JavaScript
1 2 3 4 5 6 7 8 9 Input.prototype.off=function(eventType,callback){ ????varvisitors=this.visitiors[eventType]; ????if(typeofvisitiors!=="undefined"){ ????????varindex=visitiors.indexOf(callback); ????????if(index>=0){ ????????????visitiors.splice(index,1); ????????} ????} };

這樣子,Input就和訪問者建立起了關系,或者說訪問者已經成功地向接收者都訂閱了消息,一旦接書者收到了消息會向它的訪問者一一傳遞:

trigger JavaScript
1 2 3 4 5 6 7 8 9 Input.prototype.trigger=function(eventType,event){ ????varvisitors=this.visitiors[eventType]; ????vareventFormat=processEvent(event);//獲取消息并做格式化 ????if(typeofvisitors!=="undefined"){ ????????for(vari=0;i<visitors.length;i++){ ????????????visitors[i](eventFormat); ????????} ????} };

trigger可能是用戶調的,也可能是底層的控件調用的。在其它領域,它可能是一個光感控件觸發的。不管怎樣,一旦有人觸發了trigger,接收者就會一一下發消息。

如果你知道了事件監聽的模式是這樣的,可能對你寫代碼會有幫助。例如點擊下面的搜索條件的X,要把上面的搜索框清空,同時還要觸發搜索,并把輸入框右邊的X去掉。要附帶著做幾件事情。

這個時候你可能會這樣寫:

JavaScript
1 2 3 4 5 6 $(".icon-close").on("click",function(){ ????$(this).parent().remove();//刪除本身的展示 ????$("#search-input").val(""); ????searchAjax.ajax();?????????//觸發搜索 ????$("#clear-search").hide();//隱藏輸入框x });

但其實這樣有點累贅,因為在上面的搜索輸入框肯定也會相應的操作,當用戶輸入為空時,自動隱藏右邊的x,并且輸入框change的時候會自動搜索,也就是說所有附加的事情輸入框那邊已經有了,所以其實只需要觸發下輸入框的change事件就好了:

JavaScript
1 2 3 4 $(".icon-close").on("click",function(){ ????$(this).parent().remove();//刪除本身的展示 ????$("#search-input").val("").trigger("change"); });

輸入框為空時,該怎么處理,search輸入框會相應地處理,下面那個條件展示的x不需要去關心。觸發了change之后,會把相應的消息下發給search輸入框的訪問者們。

當然,你用react你可能不會這樣想了,你應該是在研究組件間怎么通信地好。

?

上文提及使用傳參避免全局耦合,然后在js里面通過控制class減少和css的耦合,和耦合相對的是內聚,出發點是重復代碼,減少拷貝代碼會有一個抽象和封裝的過程:function -> 模塊 -> 插件/框架,封裝常用的還有封裝成一個類,方便控制私有數據。這樣可實現高內聚,除此方法,還有設計模式的思想,上面介紹了策略模式和訪問者模式的原理和應用,以及在寫代碼的啟示。

希望上文能對你有所啟迪,如有不對之處還請指出。

此文轉載!

地址:http://www.cnblogs.com/chun6/p/6204371.html

轉載于:https://www.cnblogs.com/HUANGRONG888/p/6208784.html

總結

以上是生活随笔為你收集整理的Effective前端5:减少前端代码耦合的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。