// Layui ;! function (win) { var Lay = function () { this.v = '2.5.5'; }; win.layui = new Lay(); }(window); // Jquery (function (global, factory) { "use strict"; if (typeof module === "object" && typeof module.exports === "object") { module.exports = global.document ? factory(global, true) : function (w) { if (!w.document) { throw new Error("jQuery requires a window with a document"); } return factory(w); }; } else { factory(global); } })(typeof window !== "undefined" ? window : this, function (window, noGlobal) { var jQuery = function (selector, context) { return new jQuery.fn.init(selector, context); }; return jQuery; });
這是一種很經典的開場方式,以 ! 定義一個函數並當即執行,而且將對象賦值到全局 window 變量上。固然除了 ! 還有 ~ 等符號均可以定義後面的這個函數,而 ; 應該是爲了防止其餘的代碼對自己形成影響。javascript
實際上( function (window) { "use strict"; } )( window )
的寫法更被咱們理解,如Jquery未壓縮的源碼。而!定義函數的方法惟一優點就是代碼相對較少,因此壓縮後的Js代碼大多數會以!開頭。css
Lay.prototype.link = function (href, fn, cssname) { var that = this, link = doc.createElement('link'), head = doc.getElementsByTagName('head')[0]; if (typeof fn === 'string') cssname = fn; var app = (cssname || href).replace(/\.|\//g, ''); var id = link.id = 'layuicss-' + app, timeout = 0; link.rel = 'stylesheet'; link.href = href + (config.debug ? '?v=' + new Date().getTime() : ''); link.media = 'all'; if (!doc.getElementById(id)) { head.appendChild(link); } if (typeof fn != 'function') return that; (function poll() { if (++timeout > config.timeout * 1000 / 100) { return error(href + ' timeout'); }; if (parseInt(that.getStyle(doc.getElementById(id), 'width')) === 1989) { fn(); } else { setTimeout(poll, 100); } }()); return that; }
先來看看官方文檔:前端
方法:layui.link(href)
href 即爲 css 路徑。注意:該方法並不是是你使用 layui 所必須的,它通常只是用於動態加載你的外部 CSS 文件。java
雖然官方只給出了一個參數,可是咱們看源碼的話能夠知道後兩個參數是加載完後運行的函數和自定義的Id。
有趣的是,臨時建立的 poll函數 若是parseInt(that.getStyle(doc.getElementById(id), 'width')) === 1989
判斷爲 false ,也就是樣式沒有被引入的時候會從新調用 poll函數 最後要麼加載成功循環結束,要麼加載超時調用 Layui hint 打印出超時信息。
由於一樣的手段在加載 module 時也一樣使用到,因此若是你使用過 Layui 那麼[module] is not a valid module
這樣的警告或多或少能遇到幾回。node
用過 Layui 的兄dei應該對 layui.use 不陌生,先來看官方文檔:jquery
方法:layui.use([mods], callback)
layui 的內置模塊並不是默認就加載的,他必須在你執行該方法後纔會加載。程序員
對於用了 Layui 有段時間的我來講,也只是按照官方的例子使用,並不知道實現的原理。
接下來就是見證遺蹟的時候,看看 layui.use 作了什麼:後端
Lay.fn.use = function (apps, callback, exports) { function onScriptLoad(e, url) { var readyRegExp = navigator.platform === 'PLaySTATION 3' ? /^complete$/ : /^(complete|loaded)$/; if (e.type === 'load' || (readyRegExp.test((e.currentTarget || e.srcElement).readyState))) { config.modules[item] = url; head.removeChild(node); (function poll() { if (++timeout > config.timeout * 1000 / 4) { return error(item + ' is not a valid module'); }; config.status[item] ? onCallback() : setTimeout(poll, 4); }()); } } function onCallback() { exports.push(layui[item]); apps.length > 1 ? that.use(apps.slice(1), callback, exports) : (typeof callback === 'function' && callback.apply(layui, exports)); } var that = this, dir = config.dir = config.dir ? config.dir : getPath; var head = doc.getElementsByTagName('head')[0]; apps = typeof apps === 'string' ? [apps] : apps; if (window.jQuery && jQuery.fn.on) { that.each(apps, function (index, item) { if (item === 'jquery') { apps.splice(index, 1); } }); layui.jquery = layui.$ = jQuery; } var item = apps[0], timeout = 0; exports = exports || []; config.host = config.host || (dir.match(/\/\/([\s\S]+?)\//) || ['//' + location.host + '/'])[0]; if (apps.length === 0 || (layui['layui.all'] && modules[item]) || (!layui['layui.all'] && layui['layui.mobile'] && modules[item])) { return onCallback(), that; } if (config.modules[item]) { (function poll() { if (++timeout > config.timeout * 1000 / 4) { return error(item + ' is not a valid module'); }; if (typeof config.modules[item] === 'string' && config.status[item]) { onCallback(); } else { setTimeout(poll, 4); } }()); } else { var node = doc.createElement('script'), url = (modules[item] ? dir + 'lay/' : /^\{\/\}/.test(that.modules[item]) ? '' : config.base || '') + (that.modules[item] || item) + '.js'; node.async = true; node.charset = 'utf-8'; node.src = url + function () { var version = config.version === true ? config.v || (new Date()).getTime() : config.version || ''; return version ? '?v=' + version : ''; }(); head.appendChild(node); if (!node.attachEvent || (node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code]') < 0) || isOpera) { node.addEventListener('load', function () { onScriptLoad(e, url); }, false); } else { node.addEventListener('onreadystatechange', function (e) { onScriptLoad(e, url); }); } config.modules[item] = url; } return that; };
首先跳過前兩個建立的函數,通過一堆巴拉巴拉的賦值後來到第2個if中咱們直接能夠判斷語句apps.length === 0
,根據文檔可知咱們第一個參數是一個數組 [mods] ,固然前面的賦值apps = typeof apps === 'string' ? [apps] : apps;
能夠看出即便你傳的是一個字符串也會被封裝成數組。數組
很明顯第一次進來apps.length === 0
和下面的if ( config.modules[item] )
也必爲 false ,那麼咱們直接移步到 else 內。瀏覽器
建立一個 script 元素並賦予屬性和模塊的地址,經過 appendChild 追加到 head 以後留下一個 addEventListener 監聽 script 的加載( ps:attachEvent 是給非人類使用的瀏覽器準備的 )並將開始建立的 function onScriptLoad(e, url)
函數拋進去,而後整段代碼除了return that
到這裏戛然而止。
再來看看function onScriptLoad(e, url)
函數,首先開幕雷擊 "PLaySTATION 3" === navigator.platform
?
僅關心PC端瀏覽器的部分e.type === 'load'
, 由於監聽的是 load 因此這裏必爲 true 並執行config.modules[item] = url
後將追加的 script 元素移除。剩餘的代碼就是動態加載時使用的技巧,直到 config.status[item]
爲 true 時循環結束。
因爲config.status[item]
不會自動變成 true,以後的騷操做由 layui.define 接手。
先看官方文檔:
方法:layui.define([mods], callback)
經過該方法可定義一個 layui 模塊。參數 mods 是可選的,用於聲明該模塊所依賴的模塊。callback 即爲模塊加載完畢的回調函數,它返回一個 exports 參數,用於輸出該模塊的接口。
以比較經常使用的 laypage.js 模塊爲例,基礎源碼以下:
// Laypage 模塊的部分代碼(部分變量名爲猜想,但不影響內容自己) layui.define(function (exports) { 'use strict'; var MOD_NAME = 'laypage', LayPage = function (options) { var that = this; that.config = options || {}, that.config.index = ++laypage.index, that.render(true); }; var laypage = { render: function (options) { var laypage = new LayPage(options); return laypage.index }, index: layui.laypage ? layui.laypage.index + 10000 : 0, on: function (elem, even, fn) { return elem.attachEvent ? elem.attachEvent("on" + even, function (param) { param.target = param.srcElement, fn.call(elem, param) }) : elem.addEventListener(even, fn, false), this } }; exports(MOD_NAME, laypage); });
由於 Layui 已經註冊了全局的變量,因此當模塊文件經過元素追加的方式引入時,調用了 layui.define 方法:
Lay.fn.define = function (deps, callback) { var that = this, type = typeof deps === 'function', mods = function () { var e = function (app, exports) { layui[app] = exports; config.status[app] = true; } typeof callback === 'function' && callback(function (app, exports) { e(app, exports); config.callback[app] = function () { callback(e); } }); return this; }; type && (callback = deps, deps = []); if (!layui['layui.all'] && layui['layui.mobile']) { return mods.call(that); } else { that.use(deps, mods); return that; } };
由於無論你在定義的模塊中有沒有引入其餘模塊,如 laypage 和 laytpl 這些 Layui 自己提供的模塊都會因 (callback = deps, deps = [])
回到 [mods], callback 的參數格式。
再通過一系列巴拉巴拉的步驟回到定義的 mods 方法中,由layui[app] = exports, config.status[app] = true
給全局 layui 變量添加屬性(app)且給屬性賦值(exports),並把 status 改成 true 至此模塊加載完成。
正如 Layui 官方所說:咱們認爲,這恰是符合當下國內絕大多數程序員從舊時代過渡到將來新標準的最佳指引。
做爲一個後端的工做者(之後可能要接觸前端框架的人)沒有接觸過前端框架,只對原生態的 HTML / CSS / JavaScript 有所瞭解,那麼 Layui 無非是較優的選擇。
而寫這篇文章無非就是爲了感謝 Layui 對非前端工做者作出的貢獻,也多是我對使用了兩年多 Layui 最後的告別吧,感謝賢心。
若是你沒有接觸過 UglifyJS 或其餘 JS 壓縮器,而你又恰巧使用 Visual Studio Code 工具開發,那麼 Minify 擴展插件就已經足夠平常使用了。