关于前端JS模块加载器实现的一些细节
最近工作需要,实现一个特定环境的模块加载方案,实现过程中有一些技术细节不解,便参考 了一些项目的api设计约定与实现,记录下来备忘。
本文不探讨为什么实现模块化,以及模块化相关的规范,直接考虑一些技术实现原理。
1.简单实现模块化
一开始我想如果我的代码只有一个文件,那几行不就实现了吗
main.js
var modules = {}
var define = function(id,factory){
moudles[id] = factory
}
var require = function(id){
return modules[id]
}
define("moduleA",{text:"I am text"})
var moduleA = require("moduleA");
console.log(moduleA)
main.html
<script src="main.js"></script>
2.拆多个文件
后来业务需求的增长,我的一个代码文件逐渐膨胀到了接近2w多行。这个时候每次改动文件找函数找半天啊,俺的编辑器也时不时的开始崩溃了,传到服务端的时候,也要等好久好久了。。。
于是我把文件拆成了3个:
1.module.js
var modules = {}
var define = function(id,factory){
moudles[id] = factory
}
var require = function(id){
return modules[id]
}
2.moduleA.js
define("moduleA",{text:"I am text"})
3.main.js
var A = require("moduleA")
console.log(A)
于是我在html中得这么写了
<script src="module.js"><script>
<script src="moduleA.js"><script>
<script src="main.js"><script>
后来了解到,我可以用构件工具gulp
的concat
和watch
模块,可以监听文件改动,自动生成大文件,以便在开发的时候可以按模块拆成多个文件,运行的时候却是在一个文件。详细可以了解相关资料。
3.按模块加载文件
上面提到用构件工具来实现打包成一个文件,这样做有个缺点,代码如果有错误,报错的行数无法与相应文件模块的行数相对应,debug困难。
这个时候貌似只有不依赖于构件工具,我们在代码中实现加载其他模块。 貌似也挺简单的。
我们得知道,script标签是可以用JS动态创建和加载的
var loadScript = function(src){
var script = document.createElement("script")
script.src = src
document.head.appendChild(script)
}
于是我们可以在main.js中这样去加载
loadScript("module.js")
loadScript("moduleA.js")
这样就可以在页面中只引入一个主文件,然后在主文件中引入其他模块文件了。
多了解一些我们会知道 loadScript
这样的代码加载方法,是并行加载非顺序执行的,有可能moduleA的代码执行的时候module还没有执行,这是就会报错 variable define is not defined
了。
4.控制文件加载顺序
script在加载过程中会有一些状态,支持设立回调函数比如 onload
、 onreadysteadychange
这样我们可以在当一个模块加载完成后加载另一个模块来控制文件加载顺序。
我们常用的jsonp技术便也大概是这样一个原理。
var loadScript = function(src,callback){
var script = document.createElement("script")
script.src = src
script.onload = callback
document.head.appendChild(script)
}
loadScript("module.js",function(){
loadScript("moduleA.js",function(){
var A = require("moduleA")
console.log(A)
})
})
这样的坏处便是,代码中要写层层回调,模块的加载顺序需要写代码的人自己来管理。
5.XHR加载代码
script标签可以设置src加载远程代码,还可以直接把代码写在标签内。
<script>
define("A","i am A")
</script>
于是我们可以通过XHR对象,加载远程代码文本,然后动态的插入进去,比如innerHTML 甚至,XHR有同步的加载方法,来让我们串行的加载代码,避免写重重回调。当然,同步的XHR请求性能很低
XHR有个硬伤就是受浏览器同源策略影响,不能方便的跨域。
6.实现高级API
有了上面的一些基础,我们就可以来封装一些高级的API了。
一般来说,我们只需要这样一个define(id,deps,factory)
,实现了模块的定义和加载就基本够用了。
define("moduleC",["moduleA","moduleB"],function(moduleA,moduleB){
console.log(moduleA,moduleB)
})
这样的define做了这么一些事情
- 将id 和 factory关联
- 用loadscript的方案,去递归的加载deps,保证该模块被依赖时,模块本身依赖的模块都加载完毕。
- 收集完毕后按照deps顺序将相关模块通过apply传递给factory
7.自动收集依赖
我们觉得每次去写一堆依赖,然后还要保证deps顺序和factory的变量顺序一致,一一对应着实有些蛋疼,这时候我们会想要把deps去掉,改成在factory里面写依赖。
moduleC.js
define("moduleC",function(require){
var moduleA = require("moduleA")
var moduleB = require("moduleB")
})
这时候需要用到JS的一个神奇的特性,function的toString方法可以拿到函数的源代码。 这样我们可以通过一些手段分析出 require 了哪些模块。可以看这里 https://github.com/seajs/seajs/issues/478
当然为了能够分析出require了哪些模块,我们要对require做一些约定,就是希望require有一些特定的标志,以便于我们能够通过代码文本静态的分析出require项。
比如说 不能够这样,详细见 https://github.com/seajs/seajs/issues/259
var req = require
req("moduleA")
然后呢,也不能用通用的压缩工具压缩,因为压缩工具会把require变量压缩。
8.定义匿名模块
有时候我们觉得文件名已经能够代表模块名字了,我们连定义模块名字都不想要了。
moduleC.js
define(function(){
var moduleA = require("moduleA")
var moduleB = require("moduleB")
return {
A : moduleA,
B : moduleB
};
})
当初看到这样的api用法时都震惊了,因为之前实现define的时候都会把id和factory相关联,这没ID怎么办?后来冷静下来,觉得ID一定是有的,只是有办法不通过函数参数传递。
果然,有一个document有一个对象叫做currentScript,可以获得当前正在执行的script的对象,于是moduleC.js在执行的时候,define是可以通过document.currentScript拿到src为moduleC.js的script对象的,进而可以提取出ID。
这里关于浏览器兼容性有一些细节:
- document.currentScript只有现代浏览器才支持。
- IE6-10会有一些黑魔法,利用浏览器单线程执行的特性,获取页面上所有的script标签,判断其readystate为
interactive
时,该script便是document.currentScript - 利用Error.stack,得到文件调用栈,来分析得到currentScript
写匿名模块方便是方便,但是会带来一些麻烦。
比如,不能直接打包成一个文件了,因为依赖于模块的文件名,这个很好理解了。
define(function(){
return 100
})
define(function(){
return 200
})
9.加载文本资源
甚是怀念在孢子工作时的那套代码结构与模块化方案,开发不需要依赖构建工具,模板直接写html文件,不用包装amd。 等等。。,模板直接写html文件是怎么做到的,尝试去看源码,基本看不懂,孢子源码太难读了。后来抓包才知道,原来是后端配合,在特定的目录名称下返回的html文件会自动包上define,黑魔法。。
当然也有其他方法,一般情况下就是用XHR,加载相应地文本,然后用eval设定执行上下文环境为global,来包装define。
10.参考:
- http://requirejs.org/docs/why.html
- https://github.com/seajs/seajs/issues/259
- http://www.cnblogs.com/rubylouvre/archive/2013/01/23/2872618.html
关于前端JS模块加载器实现的一些细节的更多相关文章
- 实现简单的 JS 模块加载器
实现简单的 JS 模块加载器 1. 背景介绍 按需加载是前端性能优化的一个重要手段,按需加载的本质是从远程服务器加载一段JS代码(这里主要讨论JS,CSS或者其他资源大同小异),该JS代码就是一个模块 ...
- js模块化/js模块加载器/js模块打包器
之前对这几个概念一直记得很模糊,也无法用自己的语言表达出来,今天看了大神的文章,尝试根据自己的理解总结一下,算是一篇读后感. 大神的文章:http://www.css88.com/archives/7 ...
- JS模块加载器加载原理是怎么样的?
路人一: 原理一:id即路径 原则.通常我们的入口是这样的: require( [ 'a', 'b' ], callback ) .这里的 'a'.'b' 都是 ModuleId.通过 id 和路径的 ...
- sea.js模块加载工具
seajs的使用 seajs是一个jS模块加载器,由淘宝前端架构师玉伯开发,它可以解决命名空间污染,文件依赖的问题.可以在一个js文件中引入另外一个js.require('a.js') 1.安装 np ...
- js 简易模块加载器 示例分析
前端模块化 关注前端技术发展的各位亲们,肯定对模块化开发这个名词不陌生.随着前端工程越来越复杂,代码越来越多,模块化成了必不可免的趋势. 各种标准 由于javascript本身并没有制定相关标准(当然 ...
- 【模块化编程】理解requireJS-实现一个简单的模块加载器
在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题: ① 解决单文件变量命名冲突问题 ② 解决前端多人协作问题 ③ 解决文件依赖问题 ④ 按需加载(这个说法其实很假了) ⑤ ..... ...
- 构建服务端的AMD/CMD模块加载器
本文原文地址:http://trock.lofter.com/post/117023_1208040 . 引言: 在前端开发领域,相信大家对AMD/CMD规范一定不会陌生,尤其对requireJS. ...
- JavaScript AMD 模块加载器原理与实现
关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...
- js模块化加载器实现
背景 自es6以前,JavaScript是天生模块化缺失的,即缺少类似后端语言的class, 作用域也只以函数作为区分.这与早期js的语言定位有关, 作为一个只需要在网页中嵌入几十上百行代码来实现一些 ...
随机推荐
- Android 数据库加密
一 一个简短的引论 SQLite是一个轻量的.跨平台的.开源的数据库引擎.它的读写效率.资源消耗总量.延迟时间和总体简单性上具有的优越性,使其成为移动平台数据库的最佳解决方式(如Android.i ...
- 使用collectd与visage收集kvm虚拟机性能实时图形
软件功能: 通过collectd软件来监控收集kvm虚拟机的性能数据,包含cpu,memory.磁盘IO.网络流量等 通过visage软件将收集到的数据绘制图形. 安装: 系统环境:ubuntu12. ...
- Asp.net vNext 2
Asp.net vNext 学习之路(二) View component(视图组件)应该是MVC6 新加的一个东西,类似于分部视图.本文将演示在mvc 6中 怎么添加视图组件以及怎么在视图中注入一个服 ...
- js实现tooltip动态提示效果(文字版)
页面中经常用到鼠标移动到一个元素上面显示提示的功能,最开始的做法是在下面创建一个div然后动态显示这个div,但是这样需要加很多div,比较麻烦. 针对上面个的需求,这边写了一个tooltip动态提示 ...
- 经历:asp.net oracle 部署问题以及解决方法
原文:[原创]经历:asp.net oracle 部署问题以及解决方法 精简的美丽...... 一.环境 开发环境 win7 64bit Vs2010 ...
- 【MS SQL】通过执行计划来分析SQL性能
原文:[MS SQL]通过执行计划来分析SQL性能 如何知道一句SQL语句的执行效率呢,只知道下面3种: 1.通过SQL语句执行时磁盘的活动量(IO)信息来分析:SET STATISTICS IO O ...
- 异步陷阱之IO
异步陷阱之IO篇 很多教程和资料都强调流畅的用户体验需要异步来辅助,核心思想就是保证用户前端的交互永远有最高的优先级,让一切费时的逻辑通通放到后台,等到诸事完备,通知一下前端给个提示或者继续下一步.随 ...
- bat批量目光声明
写bat同一批次,盯着函数应使用.这个程序对可读性 在批处理,凝视节还有一种更常用的方法: goto start = 能够是多行文本,能够是命令 = 能够包括重定向符号和其它特殊字 ...
- vs2010下载链接中国简体(中国含msdn)
昨天一个朋友说vs2010中国版可下载,我开始不相信.只是周末.所以,我下载一试 果然,安装了中国版,原本msdn订户才能够下载,感谢朋友们上传. 文件名 cn_visual_studio_2010_ ...
- 如何使用SetTimer MFC 不够具体
转会:http://blog.csdn.net/ellor/article/details/1714741 Timer事件,即定时器事件,是在游戏编程中.常常使用的一个事件.借助它能够产生定时运行动作 ...