CascadeView級聯(lián)組件實現(xiàn)思路詳解(分離思想和單鏈表)
本文介紹自己最近做省市級聯(lián)的類似的級聯(lián)功能的實現(xiàn)思路,為了盡可能地做到職責分離跟表現(xiàn)與行為分離,這個功能拆分成了2個組件并用到了單鏈表來實現(xiàn)關(guān)鍵的級聯(lián)邏輯,下一段有演示效果的gif圖。雖然這是個很常見的功能,但是本文的實現(xiàn)邏輯清晰,代碼好理解,脫離了省市級聯(lián)這樣的語義,考慮了表現(xiàn)與行為的分離,希望本文的內(nèi)容能夠為你的工作帶來一些參考的價值,歡迎閱讀和指正。
Cascade 級聯(lián)操作
CascadeType. PERSIST 級聯(lián)持久化 ( 保存 ) 操作
CascadeType. MERGE 級聯(lián)更新 ( 合并 ) 操作
CascadeType. REFRESH 級聯(lián)刷新操作,只會查詢獲取操作
CascadeType. REMOVE 級聯(lián)刪除操作
CascadeType. ALL 級聯(lián)以上全部操作
Fetch 抓取是否延遲加載,默認情況一的方為立即加載,多的一方為延遲加載
mappedBy 關(guān)系維護
mappedBy= "parentid" 表示在children 類中的 parentid 屬性來維護關(guān)系,這個名稱必須和children 類中的 parentid屬性名稱完全一致才行。
另外需要注意,parent類中的集合類型必須是List或者Set,不能設(shè)置為ArrayList,否則會報錯
演示效果(代碼下載,注:該效果需要http才能運行,另外效果中的數(shù)據(jù)是模擬數(shù)據(jù),并不是后臺真實返回的,所以看到的省市縣的下拉數(shù)據(jù)都是一樣的):
注:本文用到了前面幾篇相關(guān)博客的技術(shù)實現(xiàn),如果有需要的話可以點擊下面的鏈接前去了解:
1)詳解Javascript的繼承實現(xiàn):提供一個class.js,用來定義javascript的類和構(gòu)建類的繼承關(guān)系;
2)jquery技巧之讓任何組件都支持類似DOM的事件管理:提供一個eventBase.js,用來給任意組件實例提供類似DOM的事件管理功能;
3)對jquery的ajax進行二次封裝以及ajax緩存代理組件:AjaxCache:提供ajax.js和ajaxCache.js,簡化jquery的ajax調(diào)用,以及對請求進行客戶端的緩存代理。
下面先來詳細了解下這個功能的要求。
1. 功能分析
以包含三個級聯(lián)項的級聯(lián)組件來說明這個功能:
1)每個級聯(lián)項可能需要一個用作輸入提示的option:
這種情況每個級聯(lián)項的數(shù)據(jù)列表中都能選擇一個空的option(就是輸入提示的那個):
也可能不需要用作輸入提示的option:
這種情況每個級聯(lián)項的數(shù)據(jù)列表中只能選數(shù)據(jù)option,選不到空的option:
2)如果當前這個頁面是從數(shù)據(jù)庫中查詢出來跟級聯(lián)組件對應(yīng)的字段有值,那么就把查詢出來的值回顯到級聯(lián)組件上:
如果查詢出來的對應(yīng)字段沒有值,那么就按第1)點需求描述的2種情況顯示。
3)各個級聯(lián)項在數(shù)據(jù)結(jié)構(gòu)上構(gòu)成單鏈表的關(guān)系,后一個級聯(lián)項的數(shù)據(jù)列表,跟前一個級聯(lián)項所選擇的數(shù)據(jù)有關(guān)聯(lián)的。
4)考慮到性能方面的問題,各個級聯(lián)項的數(shù)據(jù)列表都采用ajax異步加載顯示。
5)在級聯(lián)組件初始化完成以后,自動加載第一個級聯(lián)項的列表。
6)當前一個級聯(lián)項發(fā)生改變時,清空后面所有直接或間接關(guān)聯(lián)的級聯(lián)項的數(shù)據(jù)列表,同時如果前一個級聯(lián)項改變后的值不為空則自動加載跟它直接關(guān)聯(lián)的下一個級聯(lián)項的數(shù)據(jù)列表。清空級聯(lián)項的數(shù)據(jù)列表時要注意:如果級聯(lián)項需要顯示輸入提示的option,在清空的時候得保留該option。
7)要充分考慮性能問題,避免重復(fù)加載。
8)考慮到表單提交的問題,當級聯(lián)組件任意級聯(lián)項發(fā)生改變后,得把級聯(lián)組件所選的值體現(xiàn)到一個隱藏的文本域內(nèi),方便把級聯(lián)組件的值通過該文本域提交到后臺。
功能大致如上。
2. 實現(xiàn)思路
1)數(shù)據(jù)結(jié)構(gòu)
級聯(lián)組件跟別的組件不太一樣的是,它跟后臺的數(shù)據(jù)有一些依賴,我考慮的比較好實現(xiàn)的數(shù)據(jù)結(jié)構(gòu)是:
{ "id": 1, "text": "北京市", "code": 110000, "parentId": 0 }, { "id": 2, "text": "河北省", "code": 220000, "parentId": 0 }, { "id": 3, "text": "河南省", "code": 330000, "parentId": 0 }
id是數(shù)據(jù)的唯一標識,數(shù)據(jù)之間的關(guān)聯(lián)關(guān)系通過parentId來構(gòu)建,text,code這種都屬于普通的業(yè)務(wù)字段。如果按這個數(shù)據(jù)結(jié)構(gòu),我們查詢級聯(lián)項數(shù)據(jù)列表的接口就會變得很簡單:
//查第一個級聯(lián)項的列表 /api/cascade?parentId=0 //根據(jù)第一個級聯(lián)項選的值,查第二個級聯(lián)項的列表 /api/cascade?parentId=1 //根據(jù)第二個級聯(lián)項選的值,查第三個級聯(lián)項的列表 /api/cascade?parentId=4
這個結(jié)構(gòu)對于后臺來說也很好處理,雖然在結(jié)構(gòu)上它們是一種樹形的表結(jié)構(gòu),但是查詢都是單層的,所以很好實現(xiàn)。
從前面的查詢演示也能夠看出,這個結(jié)構(gòu)能夠很方便地幫我們把數(shù)據(jù)查詢的接口和參數(shù)統(tǒng)一成一個,這對于組件開發(fā)來說是一個很方便的事情。我們從后臺拿到這個數(shù)據(jù)結(jié)構(gòu)之后,把每一條數(shù)據(jù)解析成一個option,如<option value=”北京市” data-param-value=”1”>北京市</option>,這樣既能完成數(shù)據(jù)列表的下拉顯示,還能通過select這個表單元素的作用收集到當前級聯(lián)項所選中的值,最后當級聯(lián)項發(fā)生改變的時候,還能夠獲取到選中的option,把它上面存儲的data-param-value的值作為parentId這個參數(shù),去加載下一個級聯(lián)項的列表。這也是級聯(lián)組件數(shù)據(jù)查詢和解析的思路。
但是這里面還需要考慮的是靈活性的問題,在實際的項目中,可能級聯(lián)組件的數(shù)據(jù)結(jié)構(gòu)是按id parentId這種類似的關(guān)聯(lián)關(guān)系定義的,但是它們的字段不一定是叫id parentId text code,很有可能是別的字段。也就是說:在把數(shù)據(jù)解析成option的時候,option的text還有value到底用什么字段來解析,以及data-param-value這個屬性的用什么字段的值,都是不確定的;還有查詢數(shù)據(jù)時用的參數(shù)名稱parentId也不能是死的,有的時候如果后臺人員先寫好了查詢接口,用了別的名稱,你不可能要求人家去改他的參數(shù)名稱,因為他那邊是需要編譯再部署的,相比前端更麻煩一些;還有parentId=0這個0值也是不能固定,因為實際項目中第一層的數(shù)據(jù)的parentid有可能是空,也有可能是-1。這些東西都得設(shè)計成option,一方面提供默認值,同時留給外部根據(jù)實際情況來設(shè)置,比如本文最終的實現(xiàn)中這個option都是這樣定義的:
textField: 'text', //返回的數(shù)據(jù)中要在<option>元素內(nèi)顯示的字段名稱
valueField: 'text', //返回的數(shù)據(jù)中要設(shè)置在<option>元素的value上的字段名稱
paramField: 'id', //當調(diào)用數(shù)據(jù)查詢接口時,要傳遞給后臺的數(shù)據(jù)對應(yīng)的字段名稱
paramName: 'parentId', //當調(diào)用數(shù)據(jù)查詢接口時,跟在url后面?zhèn)鬟f數(shù)據(jù)的參數(shù)名
defaultParam: '', //當查詢第一個級聯(lián)項時,傳遞給后臺的值,一般是0,'',或者-1等,表示要查詢第上層的數(shù)據(jù)
2)html結(jié)構(gòu)
根據(jù)前面的功能分析的第1條,級聯(lián)組件的初始html結(jié)構(gòu)有2種:
<ul id="licenseLocation-view" class="cascade-view clearfix"> <li> <select class="form-control"> <option value="">請選擇省份</option> </select> </li> <li> <select class="form-control"> <option value="">請選擇城市</option> </select> </li> <li> <select class="form-control"> <option value="">請選擇區(qū)縣</option> </select> </li> </ul>
或
<ul id="companyLocation-view" class="cascade-view clearfix"> <li> <select class="form-control"> </select> </li> <li> <select class="form-control"> </select> </li> <li> <select class="form-control"> </select> </li> </ul>
這兩個結(jié)構(gòu)唯一的區(qū)別就在于是否配置了用作輸入提示的option。另外需要注意的是如果需要這個空的option,一定得把value屬性設(shè)置成空,否則這個空的option在表單提交的時候會把option的提示信息提交到后臺。
這兩個結(jié)構(gòu)最關(guān)鍵的是select元素,跟ul和li沒有任何關(guān)系,ul跟li是為了UI而用到的;select元素沒有任何語義,不用去標識哪個是省份,哪個是城市,哪個是區(qū)縣。從功能上來說,一個select代表一個級聯(lián)項,這些select在哪定義都不重要,我們只要告訴級聯(lián)組件,它的級聯(lián)項由哪些select元素構(gòu)成就行了,唯一需要額外告訴組件的就是這些select元素的先后關(guān)系,但是這個通常都是用元素在html中的默認順序來控制的。這個結(jié)構(gòu)能夠幫助我們把組件的功能盡可能地做到表現(xiàn)與行為分離。
3)職責分離和單鏈表的運用
從前面的部分也差不多能看出來了,這個級聯(lián)組件如果按職責劃分,可以分成兩個核心的組件,一個負責整體功能和內(nèi)部級聯(lián)項的管理(CascadeView),另一個負責級聯(lián)項的功能實現(xiàn)(CascadeItem)。另外為了更方便地實現(xiàn)級聯(lián)的邏輯,我們只需要把所有的級聯(lián)項通過鏈表連起來,通過發(fā)布-訂閱模式,后一個級聯(lián)項訂閱前一個級聯(lián)項發(fā)生改變的消息;當前面的級聯(lián)項發(fā)生改變的時候,發(fā)布消息,通知后面的級聯(lián)項去處理相關(guān)邏輯;通過鏈表的作用,這個消息可能可以一直傳遞到最后一個級聯(lián)項為止。用圖來描述的話,大致就是這個樣子:
我們需要做的就是控制好消息的發(fā)布跟傳遞。
4)表單提交
為了能夠方便地將級聯(lián)組件的值提交到后臺,可以把整個級聯(lián)組件當成一個整體,對外提供一個onChanged事件,外部可通過這個事件獲取所有級聯(lián)項的值。由于存在多個級聯(lián)項,所以在發(fā)布onChanged這個事件時,只能在任意級聯(lián)項發(fā)生改變的時候,都去觸發(fā)這個事件。
5)ajax緩存
在這個組件里面得考慮兩個層級的ajax緩存,第一個是組件這一層級的,比如我把第一個級聯(lián)項切換到了北京,這個時候第二個級聯(lián)項就把北京的數(shù)據(jù)加載出來了,然后我把第一個級聯(lián)項從北京切換到河北再切換到北京,這個時候第二個級聯(lián)項要顯示的還是北京的關(guān)聯(lián)數(shù)據(jù)列表,如果我們在第一次加載這個列表的時候就把它的數(shù)據(jù)緩存下來了,那么這次就不用發(fā)起ajax請求了;第二個是ajax請求這一層級的,假如頁面上有多個級聯(lián)組件,我先把第一個級聯(lián)組件的第一個級聯(lián)項切換到北京,瀏覽器發(fā)起一個ajax請求加載數(shù)據(jù),當我再把第二個級聯(lián)組件的第一個級聯(lián)項切換到北京的時候,瀏覽器還會再發(fā)一個請求去加載數(shù)據(jù),如果我把第一個組件第一次ajax請求的返回的數(shù)據(jù),先緩存起來,當?shù)诙€組件,用同樣的參數(shù)請求同樣的接口時,直接拿之前緩存覺得結(jié)果返回,這樣也能減少一次ajax請求。第二個層級的ajax緩存依賴上文《對jquery的ajax進行二次封裝以及ajax緩存代理組件:AjaxCache》,對于組件來說,它內(nèi)部只實現(xiàn)了第一個層級的緩存,但是它不用考慮第二個層級的緩存,因為第二個層級的緩存實現(xiàn)對它來說是透明的,它不知道它用到的ajax組件有緩存的功能。
3. 實現(xiàn)細節(jié)
最終的實現(xiàn)包含了三個組件,CascadeView、CascadeItem、CascadePublicDefaults,前面兩個是組件的核心,最后一個只是用來定義一些option,它的作用在CascadeItem的注釋里面有詳細的描述。另外在下面的代碼中有非常詳細的注釋解釋了一些關(guān)鍵代碼的作用,結(jié)合著前面的需求來看代碼,應(yīng)該還是比較容易理解的。我以前傾向于用文字來解釋一些實現(xiàn)細節(jié),后來我慢慢覺得這種方式有點費力不討好,第一是細節(jié)層面的語言不好組織,有的時候言不達意,明明想把一件事情解釋清楚,結(jié)果反而弄得更加迷糊,至少我自己看自己寫的東西就會這樣的感觸;第二是本身開發(fā)人員都具有閱讀源碼的能力,而且大部分積極的開發(fā)人員都愿意通過琢磨別人的代碼來理解實現(xiàn)思路;所以我改用注釋的方式來說明實現(xiàn)細節(jié):)
CascadePublicDefaults:
define(function () { return { url: '',//數(shù)據(jù)查詢接口 textField: 'text', //返回的數(shù)據(jù)中要在<option>元素內(nèi)顯示的字段名稱 valueField: 'text', //返回的數(shù)據(jù)中要設(shè)置在<option>元素的value上的字段名稱 paramField: 'id', //當調(diào)用數(shù)據(jù)查詢接口時,要傳遞給后臺的數(shù)據(jù)對應(yīng)的字段名稱 paramName: 'parentId', //當調(diào)用數(shù)據(jù)查詢接口時,跟在url后面?zhèn)鬟f數(shù)據(jù)的參數(shù)名 defaultParam: '', //當查詢第一個級聯(lián)項時,傳遞給后臺的值,一般是0,'',或者-1等,表示要查詢第上層的數(shù)據(jù) keepFirstOption: true, //是否保留第一個option(用作輸入提示,如:請選擇省份),如果為true,在重新加載級聯(lián)項時,不會清除默認的第一個option resolveAjax: function (res) { return res; }//因為級聯(lián)項在加載數(shù)據(jù)的時候會發(fā)異步請求,這個回調(diào)用來解析異步請求返回的響應(yīng) } });
CascadeView:
define(function (require, exports, module) { var $ = require('jquery'); var Class = require('mod/class'); var EventBase = require('mod/eventBase'); var PublicDefaults = require('mod/cascadePublicDefaults'); var CascadeItem = require('mod/cascadeItem'); /** * PublicDefaults的作用見CascadeItem組件內(nèi)的注釋 */ var DEFAULTS = $.extend({}, PublicDefaults, { $elements: undefined, //級聯(lián)項jq對象的數(shù)組,元素在數(shù)據(jù)中的順序代表級聯(lián)的先后順序 valueSeparator: ',', //獲取所有級聯(lián)項的值時使用的分隔符,如果是英文逗號,返回的值形如 北京市,區(qū),朝陽區(qū) values: '', //用valueSeparator分隔的字符串,表示初始時各個select的值 onChanged: $.noop //當任意級聯(lián)項的值發(fā)生改變的時候會觸發(fā)這個事件 }); var CascadeView = Class({ instanceMembers: { init: function (options) { //通過this.base調(diào)用父類EventBase的init方法 this.base(); var opts = this.options = this.getOptions(options), items = this.items = [], that = this, $elements = opts.$elements, values = opts.values.split(opts.valueSeparator); this.on('changed.cascadeView', $.proxy(opts.onChanged, this)); $elements && $elements.each(function (i) { var $el = $(this); //實例化CascadeItem組件,并把每個實例的prevItem屬性指向前一個實例 //第一個prevItem屬性設(shè)置為undefined var cascadeItem = new CascadeItem($el, $.extend(that.getItemOptions(), { prevItem: i == 0 ? undefined : items[i - 1], value: $.trim(values[i]) })); items.push(cascadeItem); //每個級聯(lián)項實例發(fā)生改變都會觸發(fā)CascadeView組件的changed事件 //外部可在這個回調(diào)內(nèi)處理業(yè)務(wù)邏輯 //比如將所有級聯(lián)項的值設(shè)置到一個隱藏域里面,用于表單提交 cascadeItem.on('changed.cascadeItem', function () { that.trigger('changed.cascadeView', that.getValue()); }); }); //初始化完成自動加載第一個級聯(lián)項 items.length && items[0].load(); }, getOptions: function (options) { return $.extend({}, this.getDefaults(), options); }, getDefaults: function () { return DEFAULTS; }, getItemOptions: function () { var opts = {}, _options = this.options; for (var i in PublicDefaults) { if (PublicDefaults.hasOwnProperty(i) && i in _options) { opts[i] = _options[i]; } } return opts; }, //獲取所有級聯(lián)項的值,是一個用valueSeparator分隔的字符串 //為空的級聯(lián)項的值不會返回 getValue: function () { var value = []; this.items.forEach(function (item) { var val = $.trim(item.getValue()); val != '' && value.push(val); }); return value.join(this.options.valueSeparator); } }, extend: EventBase }); return CascadeView; });
CascadeItem:
define(function (require, exports, module) { var $ = require('jquery'); var Class = require('mod/class'); var EventBase = require('mod/eventBase'); var PublicDefaults = require('mod/cascadePublicDefaults'); var AjaxCache = require('mod/ajaxCache'); //這是一個可緩存的Ajax組件 var Ajax = new AjaxCache(); /** * 有一部分option定義在PublicDefaults里面,因為CascadeItem組件不會被外部直接使用 * 外部用的是CascadeView組件,所以有一部分的option必須變成公共的,在CascadeView組件也定義一次 * 外部通過CascadeView組件傳遞所有的option * CascadeView內(nèi)部實例化CascadeItem的時候,再把PublicDefaults內(nèi)的option傳遞給CascadeItem */ var DEFAULTS = $.extend({}, PublicDefaults, { prevItem: undefined, // 指向前一個級聯(lián)項 value: '' //初始時顯示的value }); var CascadeItem = Class({ instanceMembers: { init: function ($el, options) { //通過this.base調(diào)用父類EventBase的init方法 this.base($el); this.$el = $el; this.options = this.getOptions(options); this.prevItem = this.options.prevItem; //前一個級聯(lián)項 this.hasContent = false;//這個變量用來控制是否需要重新加載數(shù)據(jù) this.cache = {};//用來緩存數(shù)據(jù) var that = this; //代理select元素的change事件 $el.on('change', function () { that.trigger('changed.cascadeItem'); }); //當前一個級聯(lián)項的值發(fā)生改變的時候,根據(jù)需要做清空和重新加載數(shù)據(jù)的處理 this.prevItem && this.prevItem.on('changed.cascadeItem', function () { //只要前一個的值發(fā)生改變并且自身有內(nèi)容的時候,就得清空內(nèi)容 that.hasContent && that.clear(); //如果不是第一個級聯(lián)項,同時前一個級聯(lián)項沒有選中有效的option時,就不處理 if (that.prevItem && $.trim(that.prevItem.getValue()) == '') return; that.load(); }); var value = $.trim(this.options.value); value !== '' && this.one('render.cascadeItem', function () { //設(shè)置初始值 that.$el.val(value.split(',')); //通知后面的級聯(lián)項做清空和重新加載數(shù)據(jù)的處理 that.trigger('changed.cascadeItem'); }); }, getOptions: function (options) { return $.extend({}, this.getDefaults(), options); }, getDefaults: function () { return DEFAULTS; }, clear: function () { var $el = this.$el; $el.val(''); if (this.options.keepFirstOption) { //保留第一個option $el.children().filter(':gt(0)').remove(); } else { //清空全部 $el.html(''); } //通知后面的級聯(lián)項做清空和重新加載數(shù)據(jù)的處理 this.trigger('changed.cascadeItem'); this.hasContent = false;//表示內(nèi)容為空 }, load: function () { var opts = this.options, paramValue, that = this, dataKey; //dataKey是在cache緩存時用的鍵名 //由于第一個級聯(lián)項的數(shù)據(jù)是頂層數(shù)據(jù),所以在緩存的時候用的是固定且唯一的鍵:root //其它級聯(lián)項的數(shù)據(jù)緩存時用的鍵名跟前一個選擇的option有關(guān) if (!this.prevItem) { paramValue = opts.defaultParam; dataKey = 'root'; } else { paramValue = this.prevItem.getParamValue(); dataKey = paramValue; } //先看數(shù)據(jù)緩存中有沒有加載過的數(shù)據(jù),有就直接顯示出來,避免Ajax if (dataKey in this.cache) { this.render(this.cache[dataKey]); } else { var params = {}; params[opts.paramName] = paramValue; Ajax.get(opts.url, params).done(function (res) { //resolveAjax這個回調(diào)用來在外部解析ajax返回的數(shù)據(jù) //它需要返回一個data數(shù)組 var data = opts.resolveAjax(res); if (data) { that.cache[dataKey] = data; that.render(data); } }); } }, render: function (data) { var html = [], opts = this.options; data.forEach(function (item) { html.push(['<option value="', item[opts.valueField], '" data-param-value="',//將paramField對應(yīng)的值存放在option的data-param-value屬性上 item[opts.paramField], '">', item[opts.textField], '</option>'].join('')); }); //采用append的方式動態(tài)添加,避免影響第一個option //最后還要把value設(shè)置為空 this.$el.append(html.join('')).val(''); this.hasContent = true;//表示有內(nèi)容 this.trigger('render.cascadeItem'); }, getValue: function () { return this.$el.val(); }, getParamValue: function () { return this.$el.find('option:selected').data('paramValue'); } }, extend: EventBase }); return CascadeItem; });
4. demo說明
演示代碼的結(jié)構(gòu):
其中框起來的就是演示的相關(guān)部分。html/regist.html是演示效果的頁面,js/app/regist.js是演示效果的入口js:
define(function (require, exports, module) { var $ = require('jquery'); var CascadeView = require('mod/cascadeView'); function publicSetCascadeView(fieldName, opts) { this.cascadeView = new CascadeView({ $elements: $('#' + fieldName + '-view').find('select'), url: '../api/cascade.json', onChanged: this.onChanged, values: opts.values, keepFirstOption: this.keepFirstOption, resolveAjax: function (res) { if (res.code == 200) { return res.data; } } }); } var LOCATION_VIEWS = { licenseLocation: { $input: $('input[name="licenseLocation"]'), keepFirstOption: true, setCascadeView: publicSetCascadeView, onChanged: function(e, value){ LOCATION_VIEWS.licenseLocation.$input.val(value); } }, companyLocation: { $input: $('input[name="companyLocation"]'), keepFirstOption: false, setCascadeView: publicSetCascadeView, onChanged: function(e, value){ LOCATION_VIEWS.companyLocation.$input.val(value); } } }; LOCATION_VIEWS.licenseLocation.setCascadeView('licenseLocation', { values: LOCATION_VIEWS.licenseLocation.$input.val() }); LOCATION_VIEWS.companyLocation.setCascadeView('companyLocation', { values: LOCATION_VIEWS.companyLocation.$input.val() }); });
注意以上代碼中LOCATION_VIEWS這個變量的作用,因為頁面上有多個級聯(lián)組件,這個變量其實是通過策略模式,把各個組件的相關(guān)的東西都用一種類似的方式管理起來而已。如果不這么做的話,很容易產(chǎn)生重復(fù)代碼;這種形式也比較有利于在入口文件這種處理業(yè)務(wù)邏輯的地方,進行一些業(yè)務(wù)邏輯的分離與封裝。
5. others
這估計是在現(xiàn)在公司寫的最后一篇博客,過兩天就得去新單位去上班了,不確定還能否有這么多空余的時間來記錄平常的工作思路,但是好歹已經(jīng)培養(yǎng)了寫博客的習慣,將來沒時間也會擠出時間來的。今年的目標主要是拓寬知識面,提高代碼質(zhì)量,后續(xù)的博客更多還是在組件化開發(fā)這個類別上,希望以后能夠得到大家的繼續(xù)關(guān)注腳本之家網(wǎng)站!
相關(guān)文章
javascript匿名函數(shù)應(yīng)用示例介紹
匿名函數(shù),顧名思義就是沒有名字,下面有個不錯的示例,大家可以學(xué)習下2014-03-03JavaScript獲取移動設(shè)備型號的實現(xiàn)代碼(JS獲取手機型號和系統(tǒng))
這篇文章主要介紹了JavaScript獲取移動設(shè)備型號的實現(xiàn)代碼,需要的朋友可以參考下2018-03-03javascript實現(xiàn)時間日期的格式化的方法匯總
這篇文章主要介紹了javascript實現(xiàn)時間日期的格式化的方法匯總,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2020-08-08JS實現(xiàn)從網(wǎng)頁頂部掉下彈出層效果的方法
這篇文章主要介紹了JS實現(xiàn)從網(wǎng)頁頂部掉下彈出層效果的方法,實例分析了javascript創(chuàng)建彈出窗口及窗口掉落與抖動效果實現(xiàn)方法,具有一定參考借鑒價值,需要的朋友可以參考下2015-08-08