【模块化编程】理解requireJS-实现一个简单的模块加载器
在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题:
① 解决单文件变量命名冲突问题
② 解决前端多人协作问题
③ 解决文件依赖问题
④ 按需加载(这个说法其实很假了)
⑤ ......
为了深入了解加载器,中间阅读过一点requireJS的源码,但对于很多同学来说,对加载器的实现依旧不太清楚
事实上不通过代码实现,单单凭阅读想理解一个库或者框架只能达到一知半解的地步,所以今天便来实现一个简单的加载器
加载器原理分析
分与合
事实上,一个程序运行需要完整的模块,以下代码为例:
//求得绩效系数
var performanceCoefficient = function () {
return 0.2;
}; //住房公积金计算方式
var companyReserve = function (salary) {
return salary * 0.2;
}; //个人所得税
var incomeTax = function (salary) {
return salary * 0.2;
}; //基本工资
var salary = 1000; //最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
console.log(mySalary);
我一份完整的工资来说,公司会有绩效奖励,但是其算法可能非常复杂,其中可能涉及到出勤率,完成度什么的,这里暂时不管
而有增便有减,所以我们会交住房公积金,也会扣除个人所得税,最终才是我的工资
对于完整的程序来说上面的流程缺一不可,但是各个函数中却有可能异常的复杂,跟钱有关系的东西都复杂,所以单单是公司绩效便有可能超过1000行代码
于是我们这边便会开始分:
<script src="companyReserve.js" type="text/javascript"></script>
<script src="incomeTax.js" type="text/javascript"></script>
<script src="performanceCoefficient.js" type="text/javascript"></script>
<script type="text/javascript"> //基本工资
var salary = 1000; //最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
console.log(mySalary); </script>
上面的代码表明上是“分”开了,事实上也造成了“合”的问题,我要如何才能很好的把它们重新合到一起呢,毕竟其中的文件可能还涉及到依赖,这里便进入我们的require与define
require与define
事实上,上面的方案仍然是以文件划分,而不是以模块划分的,若是文件名发生变化,页面会涉及到改变,其实这里应该有一个路径的映射处理这个问题
var pathCfg = {
'companyReserve': 'companyReserve',
'incomeTax': 'incomeTax',
'performanceCoefficient': 'performanceCoefficient'
};
于是我们一个模块便对应了一个路径js文件,剩下的便是将之对应模块的加载了,因为前端模块涉及到请求。所以这种写法:
companyReserve = requile('companyReserve');
对于前端来说是不适用的,就算你在哪里看到这样做了,也一定是其中做了一些“手脚”,这里我们便需要依据AMD规范了:
require.config({
'companyReserve': 'companyReserve',
'incomeTax': 'incomeTax',
'performanceCoefficient': 'performanceCoefficient'
}); require(['companyReserve', 'incomeTax', 'performanceCoefficient'], function (companyReserve, incomeTax, performanceCoefficient) {
//基本工资
var salary = 1000; //最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
console.log(mySalary);
});
这里便是一个标准的requireJS的写法了,首先定义模块以及其路径映射,其中定义依赖项
require(depArr, callback)
一个简单完整的模块加载器基本就是这个样子了,首先是一个依赖的数组,其次是一个回调,回调要求依赖项全部加载才能运行,并且回调的参数便是依赖项执行的结果,所以一般要求define模块具有一个返回值
方案有了,那么如何实现呢?
实现方案
说到模块加载,人们第一反应都是ajax,因为无论何时,能拿到模块文件的内容,都是模块化的基本,但是采用ajax的方式是不行的,因为ajax有跨域的问题
而模块化方案又不可避免的要处理跨域的问题,所以使用动态创建script标签加载js文件便成为了首选,但是,不使用ajax的方案,对于实现难度来说还是有要求
PS:我们实际工作中还会有加载html模板文件的场景,这个稍候再说
通常我们是这样做的,require作为程序入口,调度javascript资源,而加载到各个define模块后,各个模块便悄无声息的创建script标签加载
加载结束后便往require模块队列报告自己加载结束了,当require中多有依赖模块皆加载结束时,便执行其回调
原理大致如此,剩下的只是具体实现,而后在论证这个理论是否靠谱即可
加载器阉割实现
核心模块
根据以上理论,我们由整体来说,首先以入口三个基本函数来说
var require = function () {
};
require.config = function () {
};
require.define = function () {
};
这三个模块比不可少:
① config用以配置模块与路径的映射,或者还有其他用处
② require为程序入口
③ define设计各个模块,响应require的调度
然后我们这里会有一个创建script标签的方法,并且会监听其onLoad事件
④ loadScript
其次我们加载script标签后,应该有一个全局的模块对象,用于存储已经加载好的模块,于是这里提出了两个需求:
⑤ require.moduleObj 模块存储对象
⑥ Module,模块的构造函数
有了以上核心模块,我们形成了如下代码:
(function () { var Module = function () {
this.status = 'loading'; //只具有loading与loaded两个状态
this.depCount = 0; //模块依赖项
this.value = null; //define函数回调执行的返回
}; var loadScript = function (url, callback) { }; var config = function () { }; var require = function (deps, callback) { }; require.config = function (cfg) { }; var define = function (deps, callback) { }; })();
于是接下来便是具体实现,然后在实现过程中补足不具备的接口与细节,往往在最后的实现与最初的设计没有半毛钱关系......
代码实现
这块最初实现时,本来想直接参考requireJS的实现,但是我们老大笑眯眯的拿出了一个他写的加载器,我一看不得不承认有点妖
于是这里便借鉴了其实现,做了简单改造:
(function () { //存储已经加载好的模块
var moduleCache = {}; var require = function (deps, callback) {
var params = [];
var depCount = 0;
var i, len, isEmpty = false, modName; //获取当前正在执行的js代码段,这个在onLoad事件之前执行
modName = document.currentScript && document.currentScript.id || 'REQUIRE_MAIN'; //简单实现,这里未做参数检查,只考虑数组的情况
if (deps.length) {
for (i = 0, len = deps.length; i < len; i++) {
(function (i) {
//依赖加一
depCount++;
//这块回调很关键
loadMod(deps[i], function (param) {
params[i] = param;
depCount--;
if (depCount == 0) {
saveModule(modName, params, callback);
}
});
})(i);
}
} else {
isEmpty = true;
} if (isEmpty) {
setTimeout(function () {
saveModule(modName, null, callback);
}, 0);
} }; //考虑最简单逻辑即可
var _getPathUrl = function (modName) {
var url = modName;
//不严谨
if (url.indexOf('.js') == -1) url = url + '.js';
return url;
}; //模块加载
var loadMod = function (modName, callback) {
var url = _getPathUrl(modName), fs, mod; //如果该模块已经被加载
if (moduleCache[modName]) {
mod = moduleCache[modName];
if (mod.status == 'loaded') {
setTimeout(callback(this.params), 0);
} else {
//如果未到加载状态直接往onLoad插入值,在依赖项加载好后会解除依赖
mod.onload.push(callback);
}
} else { /*
这里重点说一下Module对象
status代表模块状态
onLoad事实上对应requireJS的事件回调,该模块被引用多少次变化执行多少次回调,通知被依赖项解除依赖
*/
mod = moduleCache[modName] = {
modName: modName,
status: 'loading',
export: null,
onload: [callback]
}; _script = document.createElement('script');
_script.id = modName;
_script.type = 'text/javascript';
_script.charset = 'utf-8';
_script.async = true;
_script.src = url; //这段代码在这个场景中意义不大,注释了
// _script.onload = function (e) {}; fs = document.getElementsByTagName('script')[0];
fs.parentNode.insertBefore(_script, fs); }
}; var saveModule = function (modName, params, callback) {
var mod, fn; if (moduleCache.hasOwnProperty(modName)) {
mod = moduleCache[modName];
mod.status = 'loaded';
//输出项
mod.export = callback ? callback(params) : null; //解除父类依赖,这里事实上使用事件监听较好
while (fn = mod.onload.shift()) {
fn(mod.export);
}
} else {
callback && callback.apply(window, params);
}
}; window.require = require;
window.define = require; })();
首先这段代码有一些问题:
没有处理参数问题,字符串之类皆未处理
未处理循环依赖问题
未处理CMD写法
未处理html模板加载相关
未处理参数配置,baseUrl什么都没有搞
基于此想实现打包文件也不可能
......
但就是这100行代码,便是加载器的核心,代码很短,对各位理解加载器很有帮助,里面有两点需要注意:
① requireJS是使用事件监听处理本身依赖,这里直接将之放到了onLoad数组中了
② 这里有一个很有意思的东西
document.currentScript
这个可以获取当前执行的代码段
requireJS是在onLoad中处理各个模块的,这里就用了一个不一样的实现,每个js文件加载后,都会执行require(define)方法
执行后便取到当前正在执行的文件,并且取到文件名加载之,正因为如此,连script的onLoad事件都省了......
demo实现
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
</body>
<script src="require.js" type="text/javascript"></script>
<script type="text/javascript">
require(['util', 'math', 'num'], function (util, math, num) { num = math.getRadom() + '_' + num;
num = util.formatNum(num);
console.log(num);
});
</script>
</html>
//util
define([], function () {
return {
formatNum: function (n) {
if (n < 10) return '0' + n;
return n;
}
};
});
//math
define(['num'], function (num) {
return {
getRadom: function () {
return parseInt(Math.random() * num);
}
};
});
//num
define([], function () {
return 10;
});
小结
今天我们实现了一个简单的模块加载器,通过他希望可以帮助各位了解requireJS或者seaJS,最后顺利进入模块化编程的行列
【模块化编程】理解requireJS-实现一个简单的模块加载器的更多相关文章
- 使用RequireJS并实现一个自己的模块加载器 (一)
RequireJS & SeaJS 在 模块化开发 开发以前,都是直接在页面上引入 script 标签来引用脚本的,当项目变得比较复杂,就会带来很多问题. JS项目中的依赖只有通过引入JS的顺 ...
- 使用RequireJS并实现一个自己的模块加载器 (二)
2017 新年好 ! 新年第一天对我来说真是悲伤 ,早上兴冲冲地爬起来背着书包跑去实验室,结果今天大家都休息 .回宿舍的时候发现书包湿了,原来盒子装的牛奶盖子松了,泼了一书包,电脑风扇口和USB口都进 ...
- Android中用URL模拟一个简单的图片加载器
首先,需要添加权限. <uses-permission android:name="android.permission.INTERNET"/> 整体代码如下: pac ...
- 实现一个类 RequireJS 的模块加载器 (二)
2017 新年好 ! 新年第一天对我来说真是悲伤 ,早上兴冲冲地爬起来背着书包跑去实验室,结果今天大家都休息 .回宿舍的时候发现书包湿了,原来盒子装的牛奶盖子松了,泼了一书包,电脑风扇口和USB口都进 ...
- 一个简单的AMD模块加载器
一个简单的AMD模块加载器 参考 https://github.com/JsAaron/NodeJs-Demo/tree/master/require PS Aaron大大的比我的完整 PS 这不是一 ...
- RequireJS 是一个JavaScript模块加载器
RequireJS 是一个JavaScript模块加载器.它非常适合在浏览器中使用, 它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 就像 Rhino and Node. 使用RequireJ ...
- js模块化/js模块加载器/js模块打包器
之前对这几个概念一直记得很模糊,也无法用自己的语言表达出来,今天看了大神的文章,尝试根据自己的理解总结一下,算是一篇读后感. 大神的文章:http://www.css88.com/archives/7 ...
- 实现简单的 JS 模块加载器
实现简单的 JS 模块加载器 1. 背景介绍 按需加载是前端性能优化的一个重要手段,按需加载的本质是从远程服务器加载一段JS代码(这里主要讨论JS,CSS或者其他资源大同小异),该JS代码就是一个模块 ...
- SeaJS:一个适用于 Web 浏览器端的模块加载器
什么是SeaJS?SeaJS是一款适用于Web浏览器端的模块加载器,它同时又与Node兼容.在SeaJS的世界里,一个文件就是一个模块,所有模块都遵循CMD(Common Module Definit ...
随机推荐
- WPF入门教程系列十七——WPF中的数据绑定(三)
四. XML数据绑定 这次我们来学习新的绑定知识,XML数据绑定.XmlDataProvider 用来绑定 XML 数据,该XML数据可以是嵌入.Xmal文件的 XmlDataProvider 标记中 ...
- MVC5 网站开发实践 2.1、管理员登陆
目录 MVC5 网站开发实践 概述 MVC5 网站开发实践 1.建立项目 MVC5 网站开发实践 2.后台管理 1. 创建SHA256加密方法. 在Data项目中添加文件夹[Security ...
- Android接入百度自动更新SDK
一:前言 公司的app,上传到百度应用市场,然后说必须要接入百度的自动更新sdk才能上架,于是从百度官网上去下载jar包,下载的时候必须要带上数据统计,如果使用自动的jar包,还需要带上广告联盟,坑爹 ...
- Android调用Jni,非常简单的一个Demo
step1:创建一个android项目 Project Name:jnitest Build Target: Android 1.6 Application Nam ...
- Rust初步(三):使用atom搭配racer进行rust编程
在rust.cc社区中有一个关于rust编辑器的讨论(话说很多人要学一个新语言,都会立即考虑编辑器的问题,包括我在内),主要关注的是,智能提示(这个真的太重要了).大家讨论下来有几个选择 1. ecl ...
- iOS开发之窥探UICollectionViewController(二) --详解CollectionView各种回调
UICollectionView的布局是可以自己定义的,在这篇博客中先在上篇博客的基础上进行扩充,我们先使用UICollectionViewFlowLayout,然后好好的介绍一下UICollecti ...
- 【记录】AutoMapper Project To OrderBy Skip Take 正确写法
AutoMapper:Queryable Extensions 示例代码: using (var context = new orderEntities()) { return context.Ord ...
- 我的编程开始(C)
一,前言 写完t-sql系列,想了想自己的编程之路,一直有个想法,把自己这两年所整理的编程知识拿出来和大家分享,本来写完t-sql是想写一些设计思想的,因为现在也是在学习和整理一些简单框架,经常泡在大 ...
- 记录下帮助一位网友解决的关于android子控件的onTouch或onClick和父OnTouch 冲突的问题。
前三天收到位网友的私信求助,问题大概如标题所示.具体是下面的情况,个人感觉,这个问题挺有趣,也会在实际项目开发中很常见.不想看前奏的请直接跳至解决方法. 问题原型: 父控件是自定义的 LinearLa ...
- Opencv摄像头实时人脸识别
Introduction 网上存在很多人脸识别的文章,这篇文章是我的一个作业,重在通过摄像头实时采集人脸信息,进行人脸检测和人脸识别,并将识别结果显示在左上角. 利用 OpenCV 实现一个实时的人脸 ...