一个计算机技术爱好者与学习者

0%

从零开始打造自己的JavaScript框架——第0章

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等等。这些框架和插件有什么区别?来个表格比较。

<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框架和插件比较
框架名称选择器DOM操作事件处理AJAX异步处理动画模块模块化管理依赖模板引擎路由管理

接下来,我们要开发一个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. 引入js

js的引入,不是写在页面中,那么,肯定是写在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:jquery
vkjs测试页面

引入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:jquery
loading:template
页面加载完毕
loaded:template
loaded:jquery
vkjs测试页面

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自定义事件

  • 本文作者: 好好学习的郝
  • 原文链接: https://www.voidking.com/dev-vkjs-0/
  • 版权声明: 本文采用 BY-NC-SA 许可协议,转载请注明出处!源站会即时更新知识点并修正错误,欢迎访问~
  • 微信公众号同步更新,欢迎关注~