1. 概念简析库、插件、框架、加载项、扩展和控件都是组件。
组件(Component)是一个含义很大的概念,一般是指软件系统的一部分,承担了特定的职责,可以独立于整个系统进行开发和测试,一个良好设计的组件应该可以在不同的软件系统中被使用(可复用)。例如V8引擎是Chrome浏览器的一部分,负责运行javascript代码,这里V8引擎就可以视为一个组件。V8引擎同时也是node.js的javascript解释器,这体现了组件的可复用性。
库(Library)是一系列预先定义好的数据结构和函数(对于面向对象语言来说,是类)的集合,程序员通过使用这些数据结构和函数实现功能。例如Moment.js是一个javascript库,提供了处理时间的一些函数。在js中,插件和库的含义相同,我们也可以说Moment.js是一个插件。
框架(Framework)也是一系列预先定义好的数据结构和函数,一般用于作为一个软件的骨架,但程序真正的功能还需要由开发者实现。框架和库的最大区别在于“控制反转”,当你使用一个库,你会调用库中的代码,而当你使用一个框架,框架会调用你的代码。框架和库是一个有交叉的概念,很多框架都是以库的形式发布的,例如Java的Spring MVC框架,其发布的jar包本身就是一个库。下图来自Library vs. Framework? ,从调用的角度说明了框架和库的关系:
来自知乎龚世伟的回答
2. 目标说到js框架和插件,我们可以想到jquery、zepto、requirejs、seajs、art-template、page.js、angularjs、vue等等。这些框架和插件有什么区别?来个表格比较。
js框架和插件比较 框架名称 选择器 DOM操作 事件处理 AJAX 异步处理 动画模块 模块化管理依赖 模板引擎 路由管理 <tr>
<th>jquery</th>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>×</td>
<td>×</td>
<td>×</td>
</tr>
<tr>
<th>zepto</th>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>×</td>
<td>×</td>
<td>×</td>
</tr>
<tr>
<th>requirejs</th>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>√</td>
<td>×</td>
<td>×</td>
</tr>
<tr>
<th>seajs</th>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>√</td>
<td>×</td>
<td>×</td>
</tr>
<tr>
<th>art-template</th>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>√</td>
<td>×</td>
</tr>
<tr>
<th>page.js</th>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<th>angularjs</th>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<th>vue</th>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
接下来,我们要开发一个js框架,类似于requirejs和seajs,实现模块化管理依赖的功能。
最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。
1 2 3 4 5 6 <script src ="1.js" > </script > <script src ="2.js" > </script > <script src ="3.js" > </script > <script src ="4.js" > </script > <script src ="5.js" > </script > <script src ="6.js" > </script >
这段代码依次加载多个js文件,这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。
详情请参考Javascript模块化编程(三):require.js的用法 。
我们要编写的vkjs,就是为了解决这两个问题: (1)实现js文件的异步加载,避免网页失去响应; (2)管理模块之间的依赖性,便于代码的编写和维护。
3. 环境准备1、安装node,参考nvm项目 。 2、安装puer,参考超简单工具puer 。
4. 异步加载首先,我们来看怎样实现异步加载。js的异步很容易实现,使用回调函数即可。
4.1. 引入jsjs的引入,不是写在页面中,那么,肯定是写在js中。参考JAVASCRIPT 装载和执行 和Preload Javascript ,我们可以写出如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ;(function ( ) { var vk_config = { root : '/' , path : { 'jquery' : 'lib/jquery/jquery.min.js' } }; var vk = { loadjs : function (script_filename,callback ) { script_doc = document .getElementById (script_filename); if (script_doc){ return ; } var script = document .createElement ('script' ); script.setAttribute ('id' , script_filename); script.setAttribute ('type' , 'text/javascript' ); script.setAttribute ('src' , vk_config.root + vk_config.path [script_filename]); document .getElementsByTagName ('body' )[0 ].appendChild (script); console .log ('loading:' +script_filename); script.onload = script.onreadystatechange = function ( ){ console .log ('loaded:' +script_filename); callback (); } } }; window .vk = vk; })();
在页面中使用的时候,调用loadjs方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <title > vkjs</title > </head > <body > <h2 > vkjs测试页面</h2 > <script src ="../src/vk.js" > </script > <script > vk.loadjs ('jquery' ,function ( ){ console .log ($('h2' ).html ()); }); console .log ('页面加载完毕' ); </script > </body > </html >
查看控制台,我们可以看到执行顺序:
1 2 3 4 loading: jquery页面加载完毕 loaded: jqueryvkjs测试页面
引入jquery的时候,该script是插入到页面最底部的,但是我们在回调函数中依然可以使用$
,因为jquery加载完毕后我们才调用回调函数,这时就和页面位置无关了。
4.2. 引入多个js引入单个js,是比较容易的。而在开发时,常常需要引入多个js,如果多次调用loadjs方法的话,非常不友好。那么,怎样引入多个js文件?同样的,我们需要先加载js文件,等到所有js文件加载完成,调用回调函数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 ;(function ( ) { var vk_config = { root : '/' , path : { 'jquery' : 'lib/jquery/jquery.min.js' , 'layer' : 'lib/layer/layer.js' , 'template' : 'lib/art-template/dist/template.js' } }; var vk = { loadjs : function (script_filename,callback ) { script_doc = document .getElementById (script_filename); if (script_doc){ return ; } var script = document .createElement ('script' ); script.setAttribute ('id' , script_filename); script.setAttribute ('type' , 'text/javascript' ); script.setAttribute ('src' , vk_config.root + vk_config.path [script_filename]); document .getElementsByTagName ('body' )[0 ].appendChild (script); console .log ('loading:' +script_filename); script.onload = script.onreadystatechange = function ( ){ console .log ('loaded:' +script_filename); callback (); } }, count_js : 0 , use : function (ids,callback ){ var that = this ; if (!Array .isArray (ids)) { ids = [ids]; } that.count_js = ids.length ; for (var i=0 ;i<ids.length ;i++){ (function (i ){ script_doc = document .getElementById (ids[i]); if (script_doc){ return ; } script = document .createElement ('script' ); script.setAttribute ('id' , ids[i]); script.setAttribute ('type' , 'text/javascript' ); script.setAttribute ('src' , vk_config.root + vk_config.path [ids[i]]); document .getElementsByTagName ('body' )[0 ].appendChild (script); console .log ('loading:' +ids[i]); script.onload = script.onreadystatechange = function ( ){ console .log ('loaded:' +ids[i]); that.count_js --; if (that.count_js == 0 ){ callback (); } } })(i); } } }; window .vk = vk; })();
在页面使用的时候,调用use方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <title > vkjs</title > </head > <body > <h2 > vkjs测试页面</h2 > <script src ="../src/vk.js" > </script > <script > vk.use (['jquery' ,'template' ],function ( ){ console .log ($('h2' ).html ()); }); console .log ('页面加载完毕' ); </script > </body > </html >
查看控制台,我们可以看到执行顺序:
1 2 3 4 5 6 loading: jqueryloading: template页面加载完毕 loaded: templateloaded: jqueryvkjs测试页面
4.3. 有序引入js很多时候,我们不止要引入多个js,而且要有顺序地引入js。为了实现有序加载js,又不想使用js回调重重嵌套,所以郝同学选择使用Promise。具体用法参考Javascript异步编程的4种方法 、JavaScript Promise 告别异步乱嵌套 、大白话讲解Promise(一) 。
未完待续。。。
4.4. 管理依赖5. 源码分享https://github.com/voidking/vkjs.git
6. 书签从零开始编写自己的JavaScript框架(一)
从零开始编写自己的JavaScript框架(二)
jQuery源码解析(架构与依赖模块)
Query源码解析(架构与依赖模块)对应源码
Sea.js是如何工作的?
sea.js源码(Module.js核心代码)
JS模块加载器加载原理是怎么样的?
如何构建一个微型的CMD模块化加载器
如何实现一个 CMD 模块加载器
Grunt 实例之构建seajs项目
漫谈js自定义事件、DOM/伪DOM自定义事件