Layui.js作为国内较为优秀的前端框架,热门的“程序猿的前端框架”,这里简单解读一下它的核心库是怎么工作的,具体使用方法请移步到官方网站。
希望通过学习优秀代码,能更深入的了解js及成熟的前端框架,技能更上一层吧,也希望能给前端工程师将自己零散的function整合一套高可用的js框架带来帮助。
下面进入正题。
1、首先熟悉js面向对象开发
- 面向对象开发可提高整体性能,重写方法等,更多优势等小伙伴去挖掘。layui框架运用了原型模式(prototype)给layui对象添加方法。
- javascript里面有一句话,函数即对象,如View 是对象,module.export =View, 即相当于导出整个view对象。外面模块调用它的时候,能够调用View的所有方法。不过需要注意,只有是View的静态方法的时候,才能够被调用,prototype创建的方法,则属于View的私有方法。
静态方法是:
View.test1 = function(){
console.log('test1')
}
私有方法是:
View.prototype.test = function(){
console.log('test')
}
- 就简单介绍到这里,更多面向对象开发,有兴趣的同学可以自行去学习,这里不详细介绍。
2、目录结构
- 知彼知己,百战不殆。只有了解目录结构才能有目的的开发使用,这里只用到模块核心目录及核心库。
下面是官方给出的目录结构,更多详细信息请移步到官方网站了解。
├─css //css目录
│ │─modules //模块css目录(一般如果模块相对较大,我们会单独提取,比如下面三个:)
│ │ ├─laydate
│ │ ├─layer
│ │ └─layim
│ └─layui.css //核心样式文件
├─font //字体图标目录
├─images //图片资源目录(目前只有layim和编辑器用到的GIF表情)
│─lay //模块核心目录
│ └─modules //各模块组件
│─layui.js //基础核心库
└─layui.all.js //包含layui.js和所有模块的合并文件
3、核心文件解读layui.js
-
怎么工作的?
首先页面调用use方法时,会执行首次加载模块程序(createElement),然后检测define方法是否有传入两个参数,如果有则一直循环不同模块的首次加载程序,直至define方法只传入一个参数的时候,会调用onCallback方法,激活最后一个加载的模块的status状态,即执行加载中的模块脚本(暴露操作对象,如jquery),并储存在layui对象中,便于下一次调用。加载模块循环的结束后layui对象中将保存所有需要的模块数据,这样就可以在各个模块中相互使用其中的方法。
话外音:这里定义全局layui对象可以比作临时的数据库,它可以存储我们需要用到所有的数据(即js中可操作对象及变量等 ),在当前页面我们可以任意去使用其中的方法,这个思路可以用在商城的购物车系统,即使用户频繁操作购物车数据也不会对服务器造成压力;也可以用在一些结构比较复杂的单页面用户使用的系统,如某宝商家后台定义邮费模板,上面提到的购物车系统等
-
用到的方法:
模块的加载主要运用到define/use核心方法: - a、前端页面首先执行use方法(加载该模块),执行到核心库里面会用死循环等待相应js文件加载完成后,就是等待其他module文件加载完成;最后再执行这里面的用户脚本。(使用神奇的方法apply执行回调用户脚本,也是模块化框架的主要部分)
话外音:common.js规范,加载模块是同步的,所以只有加载完成才能执行后面的操作
- b、其次加载js文件必然会执行define方法(通过define函数定义了一个模块),主要用于暴露相应的模块对象,并激活对应模块的status状态,如开始使用code.js,如果第一个参数不是function,则继续加载js文件,直至第一个参数是function的时候,就会激活上一个加载模块的status,执行回调,以此类推,直至激活最后一个模块(即暴露code对象)并将所有加载的模块对象存储再layui对象中,便于页面调用(这一步主要验证相应js文件是否加载成功,并激活code的status状态,否则error)
- c、如果前端有指定加载多个module(即use([a,b,c,...])或者define([a,b,c,...])),则会重复执行b步骤,直至所有需要的module相应的js文件加载完成,最后就会执行a步骤中的用户脚本,结束整个模块加载流程。
- d、最后会执行a里面的use中callback,一个function,是用来处理加载完毕后的逻辑。(即用户页面需要执行的页面js效果逻辑)
- 流程图
待续
4、自定义模块及使用模块
有一定js基础的同学,想必也有自己的一套js应用库,对于layui亦或觉得有时候使用的时候会觉得比较臃肿,这个时候就需要考虑整合一套自己的js框架。重复早轮子有时也是很有必要的,起码是自己的经验的累积的一个证明。首先我们从自定义模块做起。
定义你的第一个layui模块,命名为code.js:
layui.define(['jquery'], function(exports){
"use strict";
// to do you code
var $ = layui.$; // 这个就是程序猿常用的jquery库了
// 这里可以做更多的你熟悉的js事件...
// 工厂模式,暴露code模块,在其他模块引用的话就可以调用它
exports('code', function(){
console.log('test');
});
});
使用你的模块:
<script src="layui.js"></script>
<script>
layui.use(['code'], function(){
// 调用模块
layui.code();
// 这里还可以写更多额外的js事件...
console.log(2222);
});
</script>
5、核心代码展示
这里贴出精简的layui核心代码,可以拿去研究一下,写个属于自己的js前端框架
- 核心库
/*
@Title: Layui
@Descrition: 更多相关信息请移步官方http://www.layui.com/
@License:MIT
*/
;!function(win){
"use strict";
var doc = document
,config = {
modules: {} //记录模块物理路径
,status: {} //记录模块加载状态
,timeout: 10 //符合规范的模块请求最长等待秒数
,event: {} //记录模块自定义事件
}
,Layui = function(){
this.v = '1.0.0'; //版本号
}
//异常提示
,error = function(msg){
win.console && console.error && console.error('Layui hint: ' + msg);
}
//内置模块
,modules = {
code: 'code' // 自定义的模块
,jquery: 'jquery'
,demomodule: 'demomodule' //弹层
,mobile: 'modules/mobile' //移动大模块 | 若当前为开发目录,则为移动模块入口,否则为移动模块集合
};
//记录基础数据
Layui.prototype.cache = config;
//存储模块的回调
config.callback = {};
//定义模块
Layui.prototype.define = function(deps, factory){
var that = this
,type = typeof deps === 'function'
,callback = function(){
var setApp = function(app, exports){
layui[app] = exports;
config.status[app] = true;
};
typeof factory === 'function' && factory(function(app, exports){
setApp(app, exports);
config.callback[app] = function(){
factory(setApp);
}
});
return this;
};
// 这里是加载插件的时候,一定会执行的,而且一定会产生apps.length == 0的情况,如code.js中的参数 function(exports){}
type && (
factory = deps,
deps = []
);
that.use(deps, callback);
return that;
};
//使用特定模块
Layui.prototype.use = function(apps, callback, exports){
var that = this
,head = doc.getElementsByTagName('head')[0];
apps = typeof apps === 'string' ? [apps] : apps;
//如果页面已经存在jQuery1.7+库且所定义的模块依赖jQuery,则不加载内部jquery模块
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 || [];
//静态资源host
config.host = config.host || '';
//加载完毕
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(){
apps.length > 1 ?
that.use(apps.slice(1), callback, exports)
: ( typeof callback === 'function' && callback.apply(layui, exports)); // 当item是空的是,这里的callback是define中定义的callback;当item不为空的时候 这里是运行页面里的use()里面的逻辑, 即执行外部应用
}
// 如果使用了
if(apps.length === 0){
return onCallback(), that;
}
//首次加载模块
if(!config.modules[item]){
var node = doc.createElement('script')
//如果是内置模块,则按照 dir 参数拼接模块路径
//如果是扩展模块,则判断模块路径值是否为 {/} 开头,
//如果路径值是 {/} 开头,则模块路径即为后面紧跟的字符。
//否则,则按照 base 参数拼接模块路径
,url = ( modules[item] ? ''
: (/^\{\/\}/.test(config.modules[item]) ? '' : (config.base || ''))
) + (config.modules[item] || item) + '.js';
url = url.replace(/^\{\/\}/, '');
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.attachEvent('onreadystatechange', function(e){
onScriptLoad(e, url);
});
} else {
node.addEventListener('load', function(e){
onScriptLoad(e, url);
}, false);
}
config.modules[item] = url;
} else {
//缓存
(function poll() {
if(++timeout > config.timeout * 1000 / 4){
return error(item + ' is not a valid module1');
};
(typeof config.modules[item] === 'string' && config.status[item])
? onCallback()
: setTimeout(poll, 4);
}());
}
return that;
};
//全局配置
Layui.prototype.config = function(options){
options = options || {};
for(var key in options){
config[key] = options[key];
}
return this;
};
//记录全部模块
Layui.prototype.modules = function(){
var clone = {};
for(var o in modules){
clone[o] = modules[o];
}
return clone;
}();
//存储模块的回调
config.callback = {};
//重新执行模块的工厂函数
Layui.prototype.factory = function(modName){
if(layui[modName]){
return typeof config.callback[modName] === 'function'
? config.callback[modName]
: null;
}
};
//提示
Layui.prototype.hint = function(){
return {
error: error
}
};
win.layui = new Layui();
}(window);
跟requireJS的原理差不多。起码使用起来更方便。
鉴于js知识有限,讲解的不够全面,如有讲解不到位的或者不明白的地方欢迎下方评论!~