前端模块化的简单综述(一)

刚学前端的时候,曾有一段时间很迷糊,不知道为啥突然从html文件、js文件和css文件三件套,变成需要打包在服务器才能用了。这种不明白感,随着使用vue,weex等框架逐步熟练之后,降低不少。但依然不知道,这一路究竟发生了什么。

  故在此,梳理整个前端模块的发展过程,为自己的疑惑提供一个完满的解答。(有不少内容来自对网络上一些大神的博客整理,本文会在文章最后做出引用和感谢)


目录

         0,模块的定义

1,前端最开始的处理

2,前端为什么需要模块化

3,common JS规范

4,AMD规范

5,CMD规范



0,模块的定义

  模块和模块化:

    模块:

在软件系统设计中,模块是一个拥有明确定义的输入输出和特性的程序实体。如果模块的所有输入都是实现功能必不可少的,所有输出都有动作产生,那么该模块即成为具有明确意义的模块。也就是说,如果少了一个输入,模块就不能实现全部功能,它没有不必要的输入,每个输入都用于产生输出,每个输出都是模块执行某一功能的结果,没有未经模块的转换就变成输出的输入。

总的来说,一般模块具有以下几种特征:

  1. 接口,模块的输入输出
  2. 功能,指模块实现的功能,有什么作用
  3. 逻辑,描述模块内部如何实现需求及所需数据
  4. 状态,指该模块的运行环境,模块间调用与被调用关系。

模块化:

    模块化就是将程序划分成若干个独立的模块,每个模块完成一个特定子功能,每个模块即相对独立,又相互联系,他们共同完成系统指定的各项功能。 模块化的目的是为了降低软件的复杂性。对软件进行适当的分解,不但可以降低复杂性,而且可以减少开发工作量,从而降低软件开发成本。


1, 前端最开始的处理:

        最自然的写法:

function foo(){
    //...
  }
  function bar(){
      //...
  }

而只将相关的代码放一起这种写法缺点很明显:

  1:大量的函数名会"污染"全局变量,无法保证不与其他模块发生变量名冲突,

  2:模块成员之间的关系不能清晰看出。

  3:没有私有变量和方法等。

针对问题1:和2:,可以用对象来缓解。

 var moduleA = {
    foo : function () {},
    bar: function () {}
  }

但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写

针对这个问题,可以采用立即执行函数来处理:

 var MODULE = (function () {
    var my = {}, privateVariable = 1;
    function privateMethod() {
      // method that use in module
    }
  
    my.moduleProperty = 1;
    my.moduleMethod = function () {
      // module API.
    };
  
    return my;
  }());

立即执行函数内部声明的变量和函数,为模块内部私有,return对象为供外部调用的接口。

如此方式的“模块”还可以扩展、继承、添加子模块等等。

详细参见:js模块的基本方法

如此便形成了前端模块的雏形。


2, 前端为什么需要模块化。

当下前端的快速发展,对网页的构建提出了更高要求:

  • Web sites are turning into Web Apps

  • Code complexity grows as the site gets bigger

  • Highly decoupled JS files/modules is wanted

  • Deployment wants optimized code in few HTTP calls

前端通过<script>标签引入js 来处理复杂逻辑和远程交互。

当逻辑复杂时,打开方式如下:

body
    script(src="zepto.js")
    script(src="jhash.js")
    script(src="fastClick.js")
    script(src="iScroll.js")
    script(src="underscore.js")
    script(src="handlebar.js")
    script(src="datacenter.js")
    script(src="deferred.js")
    script(src="util/wxbridge.js")
    script(src="util/login.js")
    script(src="util/base.js")
    script(src="util/city.js")
    script(src="util/date.js")
    script(src="util/cookie.js")
    script(src="app.js")

        各个文件的引入顺序非常重要,各个文件并行加载、DOM 顺序即执行顺序

而用script标签来引入js函数文件存在问题:

  • 维护困难
  • 依赖模糊
  • 请求过多

只用引入<script>标签带来的问题,越来越难以满足前端发展的需求。因此模块化成了一个合理的选项


3,common JS规范

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志"Javascript模块化编程"正式诞生。node.js的模块系统,就是参照CommonJS规范实现的。

       CommonJS规范

在有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者兼容,一个模块不用修改,在服务器和浏览器都可以运行。但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。

var math = require('math');

math.add(2, 3);

如上面的代码,math.add实在require之后运行,因此必须等math.js加载完成。

这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间而使浏览器处于"假死"状态。因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。


4,AMD规范

RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范(Asynchronous Module Definition)。

阮一峰:RequireJS和AMD规范

RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。

4.1 嵌入网页

<script data-main="scripts/main" src="scripts/require.js"></script>

代码的data-main属性不可省略,用于指定主代码所在的脚本文件

4.2 define 方法:定义模块

按照是否依赖其他模块,可以分成独立模块和非独立模块。

    独立模块的写法:

define({
    method1: function() {},
    method2: function() {},
});


// 当然也可以写为如下方式
define(function () {
  return {
    method1: function() {},
	method2: function() {},
 };
});

   

return 返回的是模块的接口。

    非独立模块

define(['module1', 'module2'], function(m1, m2) {
   ...
});



4.3 require方法:调用模块

require(['foo', 'bar'], function ( foo, bar ) {
        foo.doSomething();
});
// 当有过多依赖时,可以用commonJS 写法:
define(
    function (require) {
        var dep1 = require('dep1'),
            dep2 = require('dep2'),
            dep3 = require('dep3'),
            dep4 = require('dep4'),

            ...
    }

});

上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。

require方法也可以用在define方法内部。比如上面的commonJS风格的写法使用。

5, CMD规范

CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,CMD有个浏览器的实现是玉伯的SeaJSSeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。

以下是对seajs使用的简单介绍:

 5.1 seajs页面的引入

<body>
<h1 id="title">seajs demo</h1>
<script src="sea-module/sea.js"></script>
<script>
    seajs.use('./static/main.js');
</script>
</body>

首先是引入sea.js文件,然后是通过seajs.use加载main.js文件。main.js是一个“入口”文件。

5.2 模块的写法

比如定义一个changeText.js模块

define(function (require, exports, module) {
    var textContent = 'message from module changeText';
    module.exports = {
      text: textContent
    }
})

5.3 引入模块

define(function (require, exports, module) {
    var changeText = require('../static/changeText.js');
    var title = document.getElementById('title');
    title.innerHTML = changeText.text;
})

此时,大功告成,会在html页面上显示 “message from module changeText" 

5.4 别名 (seajs.config, alias)

 seajs.config({
   alias:{
     'changeText':'../static/changeText.js'
   }
 });

通过config中alias给'../static/changeText.js'设置了别名changeText,现在main中引用changeText模块就可以直接写成这样了var changeText = require('changeText')

5.5 seajs.use回调函数

seajs.use(['main','jquery'],function(main,$) {
    $('#title').after('<button id="show">showText</button>');
    $('#show').on('click',function() {
         main.showText()
    })
});

上述代码我们加载了两个模块,并把它们输出的对象传参给main和$变量,通过点击事件调用main中的showText方法,而showText方法调用了changeText中的init方法。

参考链接:

《软件工程》叶俊民,清华大学出版社. p75-76

Javascript模块化编程(一):模块的写法

Javascript模块化编程(二):AMD规范

Seajs使用实例入门介绍

猜你喜欢

转载自blog.csdn.net/Hill_Kinsham/article/details/81130130
今日推荐