摘要:知乎原地址編寫(xiě)優(yōu)雅的前端業(yè)務(wù)代碼前言當(dāng)我們?cè)趯?xiě)業(yè)務(wù)代碼的時(shí)候,我們到底在寫(xiě)什么其實(shí)是對(duì)交互的一些處理。遍歷,通過(guò)事件委派,將事件綁定在上。事件綁定濫用使用進(jìn)行統(tǒng)一管理。寫(xiě)代碼要說(shuō)人話。
知乎 live 原地址:編寫(xiě)優(yōu)雅的前端業(yè)務(wù)代碼
前言當(dāng)我們?cè)趯?xiě)業(yè)務(wù)代碼的時(shí)候,我們到底在寫(xiě)什么?
其實(shí)是對(duì)交互的一些處理。所有的交互都是基于用戶或者瀏覽器的一些行為來(lái)觸發(fā)的,比如渲染頁(yè)面,在頁(yè)面onload方法觸發(fā)之后,我們的js代碼才會(huì)執(zhí)行,比如說(shuō)懶加載,根據(jù)用戶滾動(dòng)或者可視區(qū)域的變化來(lái)觸發(fā),比如按鈕的點(diǎn)擊之后頁(yè)面的局部刷新,比如input框輸入、表單提交、上傳文件等等,以上所說(shuō)的這些行為組成起來(lái)的就是我們前端要寫(xiě)的業(yè)務(wù)邏輯。
我們所寫(xiě)的業(yè)務(wù)都是基于事件來(lái)對(duì)產(chǎn)品功能和場(chǎng)景進(jìn)行描述。
頁(yè)面的執(zhí)行順序是怎樣的?
頁(yè)面在初始化的時(shí)候,先加載css,然后加載html的節(jié)點(diǎn),最后將js放在頁(yè)尾按順序執(zhí)行,然后css加載,模板輸出之后css生效,js再加載,js再對(duì)頁(yè)面的模板進(jìn)行交互處理,比如編譯模板,最后再把交互功能比如事件進(jìn)行綁定。無(wú)論寫(xiě)什么業(yè)務(wù),都會(huì)是這個(gè)生命周期。
一個(gè)頁(yè)面的輸出是有它的生命周期的,同時(shí),一個(gè)js程序,比如vue或者react也是有自己的生命周期的,所以每一個(gè)類(lèi),每一個(gè)業(yè)務(wù)邏輯都是有自己的生命周期的。
下面是react的組件的生命周期:最開(kāi)始的時(shí)候先拿到默認(rèn)的參數(shù)屬性,然后初始化狀態(tài),在render之前有一個(gè)事件的廣播,在render之后有一個(gè)事件的廣播,這時(shí)候這個(gè)組件就render到頁(yè)面上了。組件在運(yùn)行的時(shí)候有兩個(gè)方式,一個(gè)是狀態(tài)的改變,它會(huì)觸發(fā)update,再去觸發(fā)state的改變,然后再對(duì)應(yīng)地重新render,在render之前會(huì)先觸發(fā)事件廣播,render之后也會(huì)觸發(fā)一個(gè)事件廣播。另一個(gè)方式是卸載,卸載之前會(huì)觸發(fā)一個(gè)廣播。
vue的生命周期和react的其實(shí)是很像的,它只不過(guò)比react多了一步對(duì)template和el進(jìn)行一個(gè)判斷的掃描,對(duì)應(yīng)的也是render渲染,在渲染之后會(huì)有一個(gè)屬性的update,有一個(gè)銷(xiāo)毀的過(guò)程。
總結(jié)一下,vue和react的生命周期其實(shí)就是完成它這個(gè)前端框架的業(yè)務(wù)邏輯。
實(shí)例下面這段代碼是將一個(gè)面向過(guò)程的js程序改為面向?qū)ο蠓绞街蟮膉s程序。
(function (global, $, _, doc) { "use strict"; // 定義一個(gè)構(gòu)造器,是這個(gè)文件的入口 var app = function (options) { options = options || {}; this.a = options.a; // ... this.eventMap = { "click .title": "titleClick", "dbclick .input": "inputDbclick", }; // 初始化所有節(jié)點(diǎn)屬性 this.initEles(); this.init(); }; // 定義構(gòu)造函數(shù)靜態(tài)屬性,掛載所有選擇器的屬性 app.Eles = { pap: $(".paper"), centryY: $(".centry-y") }; // 工具方法 var utils = { has: function(arr, name) { return arr.indexOf(name) > -1; }, // ... }; app.prototype = { constructor: app, initEles: function () { var eles = app.Eles; for (var name in eles) { if (eles.hasOwnProperty(name)) { this[name] = $(eles[name]); } } }, init: function () { this.bindEvent(this.eventMap); }, initDrag: function () { // ... }, uninitDrag: function () { // ... }, bindEvent: function (maps) { this.initDrag(); this.initOrdinaryEvents(maps); }, unbindEvent: function (maps) { this.uninitDrag(); this.unInitOrdinaryEvents(maps); }, initOrdinaryEvents: function (maps) { this._scanEventsMap(maps, true); }, unInitOrdinaryEvents: function (maps) { this._scanEventsMap(maps, false); }, _scanEventsMap: function (maps, isOn) { var delegateEventSplitter = /^(S+)s*(.*)$/, bind = isOn ? this._delegate : this._undelegate; for (var keys in maps) { if (maps.hasOwnProperty(keys)) { var matchs = keys.match(delegateEventSplitter); bind(matchs[1], matchs[2], this[maps[keys]].bind(this)); } } }, _delegate: function (name, selector, func) { // 事件委派,將事件綁定在$(documet)上 doc.on(name, selector, func); }, _undelegate: function (name, selector, func) { doc.off(name, selector, func); }, destroy: function() { this.unbindEvent(); } }; // 將構(gòu)造函數(shù)掛在window上,那么在外部也能訪問(wèn)閉包內(nèi)的屬性,相當(dāng)于對(duì)外暴露了一個(gè)接口 global.app = app; $(function () { // 實(shí)例化構(gòu)造函數(shù),相當(dāng)于這個(gè)構(gòu)造函數(shù)就開(kāi)始執(zhí)行了 new app(); }); })(this, this.jQuery, this._, this.jQuery(document));
上述代碼做的事情:
進(jìn)行了生命周期的定義,把文件的入口移到了一個(gè)構(gòu)造器里面,通過(guò)new這個(gè)構(gòu)造器來(lái)進(jìn)行這個(gè)js程序的入口的初始化。
定義eventMap,定義bindEvent、initOrdinaryEvents、_scanEventsMap、_delegate方法。遍歷eventMap,通過(guò)事件委派,將事件綁定在$(documet)上。之前的使用的是onclick或者on方法來(lái)進(jìn)行事件的綁定,維護(hù)的時(shí)候要到各個(gè)地方去找,現(xiàn)在只需要關(guān)注eventMap就很方便,能夠清楚知道事件、選擇器和事件處理函數(shù)名。
給構(gòu)造器綁定了一個(gè)靜態(tài)屬性Eles,那就不需要用$(".paper"),而是用this.pap(initEles方法實(shí)現(xiàn)的)即可,好處是在壓縮的時(shí)候,字符串是不能被壓縮的,而this.pap會(huì)被壓縮,壓縮率會(huì)更高。
代碼優(yōu)化的技巧if (e.target.id === "titleDrag" || e.target.id == "subtitleDrag") {} // 改為 if (["titleDrag", "subtitleDrag"].indexOf(e.target.id) > -1) {}
$(".center-box").removeClass("hidden").css({ width: ui.helper.width(), left: parseInt(centerY.css("left")) - Math.floor(ui.helper.width() / 2) }); // 改為 var width = ui.helper.width(); var left = parseInt(centerY.css("left"), 10); this.centerBox.removeClass("hidden").css({ width: width, left: left - Math.floor(width / 2) });
posObj[event.target.id] = { id: event.target.id, outerHTML: this.delStyle(event.target.outerHTML), style: "#" + event.target.id + "{position: absolute;left:" + (ui.offset.left - 260) / mmToPx + "mm;top:" + (ui.offset.top - 40) / mmToPx + "mm;}" }; // 改為 posObj[id] = { id: id, outerHTML: this.delStyle(target.outerHTML), style: "#" + id + "{" + this._getPositionLT(top, left, mmToPx) + "}" };
$(target).next().removeClass("hidden"); $(target).next().find(".line-left").css({ top: 40, left: ui.offset.left, height: pap.css("height"), width: parseInt(pap.css("width")) - ui.offset.left + 260 + "px" }); $(target).next().find(".line-top").css({ left: 260, top: ui.offset.top, width: pap.css("width"), height: parseInt(pap.css("height")) - ui.offset.top + 40 + "px" }); // 改為 this._drawNextLine(nextEle, left, top);
this.centerBox.addClass("hidden"); // 改為 utils.hide(centerBox);
var ul = this.widgetUl; var ht; switch (e.target.id) { case "thin": ht = "
var txt = this.txt; var cus = this.cus; var mmToPx = this.mmToPx; var pap = this.pap; var target = $(e.target); txt.text(target.text()); switch (e.target.id) { case "a4": pap.css({ width: 210 * mmToPx + "px", height: 297 * mmToPx + "px" }); cus.addClass("hidden"); break; case "b5": pap.css({ width: 176 * mmToPx + "px", height: 250 * mmToPx + "px" }); cus.addClass("hidden"); break; case "16k": pap.css({ width: 184 * mmToPx + "px", height: 260 * mmToPx + "px" }); cus.addClass("hidden"); break; case "cus": cus.removeClass("hidden"); break; } // 改為 var txt = this.txt; var cus = this.cus; var id = e.target.id; var target = $(e.target); var setpapCss = { "a4": [210, 297], "b5": [176, 250], "16k": [184, 260] }; txt.text(target.text()); if (setpapCss[id]) { var wh = setpapCss[id]; this.setPapWH(wh[0], wh[1]); utils.hide(cus); } if (id === "cur") { utils.show(cus); }總結(jié)
常見(jiàn)的 js 業(yè)務(wù)場(chǎng)景分析以及解決思路:
選擇器濫用
把所有的選擇器的屬性掛載構(gòu)造函數(shù)靜態(tài)屬性上,統(tǒng)一進(jìn)行管理,并通過(guò)initEles方法將其掛載在this上。
事件綁定濫用
使用eventMap進(jìn)行統(tǒng)一管理。
生命周期混亂,沒(méi)概念
app在實(shí)例化的時(shí)候會(huì)往頁(yè)面里加這個(gè)組件,調(diào)用this.destory時(shí)會(huì)解綁所有事件(還可以補(bǔ)充把html刪掉)。
復(fù)用性和沙盒安全
模板渲染技巧
參考artTemplate.js。
解耦你的業(yè)務(wù) js 代碼,如何在業(yè)務(wù)中使用設(shè)計(jì)模式?
模塊化和繼承到底怎么用。
模塊化就是把文件拆分成小文件。
拆分維度問(wèn)題和作用域傳遞。
找到utils,拒絕ctrl+c/v。
常見(jiàn)的一些業(yè)務(wù)優(yōu)化方法總結(jié):
判斷太多怎么辦。
參照上述優(yōu)化代碼中的switch...case例子。
dom操作到底怎么做才是最好的,找到最優(yōu)解。
建議用沒(méi)有樣式意義的自定義屬性data-*來(lái)作為選擇器,而不是用具有樣式意義的class和id,因?yàn)槿绻幸惶靋ss類(lèi)名變了,那么js也得改變。
樣式該怎么加。
jQuery的css方法,或者對(duì)jQuery的css方法的進(jìn)一步封裝。
增加你的項(xiàng)目可維護(hù)性和代碼可讀性:
注釋真的好嗎?
先寫(xiě)偽代碼,所有的方法不考慮它的實(shí)現(xiàn),把它拆成粒度比較細(xì)的顆粒,用方法名來(lái)描述業(yè)務(wù)邏輯,把方法名都寫(xiě)好了之后,互相調(diào)用,最后再添加最細(xì)粒度的方法的實(shí)現(xiàn)。用這種方法來(lái)寫(xiě)的話,不寫(xiě)注釋也可以,因?yàn)榉椒桶褬I(yè)務(wù)邏輯解釋了。
格式化的問(wèn)題。
單詞難拼,句子好讀,代碼50行好讀,300行難讀。
寫(xiě)代碼要說(shuō)人話。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/95117.html
Python裝飾器為什么難理解? 無(wú)論項(xiàng)目中還是面試都離不開(kāi)裝飾器話題,裝飾器的強(qiáng)大在于它能夠在不修改原有業(yè)務(wù)邏輯的情況下對(duì)代碼進(jìn)行擴(kuò)展,權(quán)限校驗(yàn)、用戶認(rèn)證、日志記錄、性能測(cè)試、事務(wù)處理、緩存等都是裝飾器的絕佳應(yīng)用場(chǎng)景,它能夠最大程度地對(duì)代碼進(jìn)行復(fù)用。 但為什么初學(xué)者對(duì)裝飾器的理解如此困難,我認(rèn)為本質(zhì)上是對(duì)Py… Python 實(shí)現(xiàn)車(chē)牌定位及分割 作者用 Python 實(shí)現(xiàn)車(chē)牌定位及分割的實(shí)踐。 ...
摘要:大家好,我叫,江湖人稱(chēng)吃土小叉,目前擔(dān)任公司的前端負(fù)責(zé)人半年多了,一路上摸爬滾打,歷經(jīng)團(tuán)隊(duì)人員變動(dòng),近日頗有感觸,于是結(jié)合自己近半年的前端負(fù)責(zé)人實(shí)踐經(jīng)驗(yàn),權(quán)當(dāng)作一個(gè)學(xué)習(xí)記錄,整理歸納一下小作坊團(tuán)隊(duì)前端負(fù)責(zé)人的修煉要點(diǎn)大部分只是記錄了關(guān)鍵詞, 大家好,我叫XX,江湖人稱(chēng)吃土小2叉,目前擔(dān)任公司的前端負(fù)責(zé)人半年多了,一路上摸爬滾打,歷經(jīng)團(tuán)隊(duì)人員變動(dòng),近日頗有感觸,于是結(jié)合自己近半年的前端負(fù)...
摘要:官網(wǎng)地址聊天機(jī)器人插件開(kāi)發(fā)實(shí)例教程一創(chuàng)建插件在系統(tǒng)技巧使你的更加專(zhuān)業(yè)前端掘金一個(gè)幫你提升技巧的收藏集。我會(huì)簡(jiǎn)單基于的簡(jiǎn)潔視頻播放器組件前端掘金使用和實(shí)現(xiàn)購(gòu)物車(chē)場(chǎng)景前端掘金本文是上篇文章的序章,一直想有機(jī)會(huì)再次實(shí)踐下。 2道面試題:輸入U(xiǎn)RL按回車(chē)&HTTP2 - 掘金通過(guò)幾輪面試,我發(fā)現(xiàn)真正那種問(wèn)答的技術(shù)面,寫(xiě)一堆項(xiàng)目真不如去刷技術(shù)文章作用大,因此刷了一段時(shí)間的博客和掘金,整理下曾經(jīng)被...
摘要:最近看前端都展開(kāi)了幾場(chǎng)而我大知乎最熱語(yǔ)言還沒(méi)有相關(guān)。有關(guān)書(shū)籍的介紹,大部分截取自是官方介紹。但從開(kāi)始,標(biāo)準(zhǔn)庫(kù)為我們提供了模塊,它提供了和兩個(gè)類(lèi),實(shí)現(xiàn)了對(duì)和的進(jìn)一步抽象,對(duì)編寫(xiě)線程池進(jìn)程池提供了直接的支持。 《流暢的python》閱讀筆記 《流暢的python》是一本適合python進(jìn)階的書(shū), 里面介紹的基本都是高級(jí)的python用法. 對(duì)于初學(xué)python的人來(lái)說(shuō), 基礎(chǔ)大概也就夠用了...
摘要:函數(shù)是一等公民。其實(shí)閉包本身也是函數(shù)式編程的一個(gè)應(yīng)用。劣勢(shì)不能算是嚴(yán)格意義上的函數(shù)式語(yǔ)言,很多函數(shù)式編程的特性并沒(méi)有。 隨著大前端時(shí)代的到來(lái),在產(chǎn)品開(kāi)發(fā)過(guò)程中,前端所占業(yè)務(wù)比重越來(lái)越大、交互越來(lái)越重。傳統(tǒng)的老夫拿起JQuery就是一把梭應(yīng)付當(dāng)下重交互頁(yè)面已經(jīng)十分乏力。于是乎有了Angular,React,Vue這些現(xiàn)代框架。 但隨之而來(lái)的還有大量的新知識(shí)新名詞,如MVC,MVVM,F(xiàn)l...
閱讀 3466·2021-11-24 10:30
閱讀 3337·2021-11-22 15:29
閱讀 3775·2021-10-28 09:32
閱讀 1401·2021-09-07 10:22
閱讀 3409·2019-08-30 15:55
閱讀 3690·2019-08-30 15:54
閱讀 3567·2019-08-30 15:54
閱讀 2897·2019-08-30 15:44