前端模块化

在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀

这时候JavaScript作为嵌入式的脚本语言的定位动摇了,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,JavaScript极其简单的代码组织规范不足以驾驭如此庞大规模的代码


前人栽树,后人乘凉,以下为学习笔记。


早期模块化

一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。

函数封装

函数一个功能就是实现特定逻辑的一组语句打包,而且JavaScript的作用域就是基于函数的,所以把函数作为模块化的第一步是很自然的事情

在一个文件里面编写几个相关函数就是最开始的模块了

function fn1(){
	statement
}
function fn2(){
	statement
}

这种做法的缺点很明显:污染了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间没什么关系。

对象封装

var myModule = {
	var1: 1,
	var2: 2,
	fn1: function(){
	},
	fn2: function(){
	}
}

这样避免了变量污染,只要保证模块名唯一即可,同时同一模块内的成员也有了关系。看似不错的解决方案,但是也有缺陷,外部可以随意修改内部成员。

立即执行函数 IIFE

var myModule = (function(){
	var var1 = 1;
	var var2 = 2;
	function fn1(){
	}
	function fn2(){
	}
	return {
		fn1: fn1,
		fn2: fn2
	};
})();

或者引入依赖

// module.js文件
(function(window, $) {
  let data = 'www.baidu.com'
  function foo() {
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar }
})(window, jQuery)

 // index.html文件
  <!-- 引入的js必须有一定顺序 -->
  <script type="text/javascript" src="jquery-1.10.1.js"></script>
  <script type="text/javascript" src="module.js"></script>
  <script type="text/javascript">
    myModule.foo()
  </script>

现代规范

CommonJS规范

  • CommonJS规范

在网页端没有模块化编程,只是页面JavaScript逻辑复杂,但也可以工作下去,在服务器端却一定要有模块,所以虽然JavaScript在web端发展这么多年,第一个流行的模块化规范却由服务器端的JavaScript应用带来,CommonJS规范是由NodeJS发扬光大,这标志着JavaScript模块化编程正式登上舞台。

根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

//模块定义 myModel.js
var name = 'Byron';
function printName(){
	console.log(name);
}
function printFullName(firstName){
	console.log(firstName + name);
}
module.exports = {
	printName: printName,
	printFullName: printFullName
}
//加载模块
var nameModule = require('./myModel.js');
nameModule.printName();

CommonJS规范加载模块是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式。

但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。浏览器端,加载JavaScript最佳、最容易的方式是在document中插入script 标签。但脚本标签天生异步,传统CommonJS模块在浏览器环境中无法正常加载。

要么需要提前编译打包处理, 借助Browserify等打包处理。要么使用 AMD CMD 解决方案。

AMD规范

AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范。浏览器环境要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。此外AMD规范比CommonJS规范在浏览器端实现要来着早。

由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出

语法
requireJS定义了一个函数 define,它是全局变量,用来定义模块

define(id?, dependencies?, factory);
//id:可选参数,用来定义模块的标识,如果没有提供该参数,默认脚本文件名(去掉拓展名)
//dependencies:是一个当前模块依赖的模块名称数组
//factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值

在页面上使用require函数加载模块

require([dependencies], function(dependencies){});
//第一个参数是一个数组,表示所依赖的模块
//第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块

例子

// 定义模块 myModule.js
define(function(){
	var name = 'Byron';
	function printName(){
		console.log(name);
	}
	return {
		printName: printName
	};
});
// 加载模块
require(['myModule'], function (myModule){
  myModule.printName();
});
// main.js文件
(function() {
  require.config({
	    baseUrl: 'js/', //基本路径 出发点在根目录下
	    paths: {
	      //映射: 模块标识名: 路径
	      myModule: './modules/myModule', //此处不能写成alerter.js,会报错
	    }
  	})
	require(['myModule'], function (myModule){
	  myModule.printName();
	});
})()
// index.html文件
  <body>
    <!-- 引入require.js并指定js主文件的入口 -->
    <script data-main="js/main" src="js/libs/require.js"></script>
  </body>

AMD模式可以用于浏览器环境并且允许非同步加载模块,也可以按需动态加载模块。

CMD规范

CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,和AMD要解决的问题一致,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同

CMD与AMD的区别

对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

CMD推崇依赖就近,AMD推崇依赖前置。

//AMD
define(['./a','./b'], function (a, b) {
    //依赖一开始就写好
    a.test();
    b.test();
});
 
//CMD
define(function (requie, exports, module) {
    //依赖可以就近书写
    var a = require('./a');
    a.test();
    ...
    //软依赖
    if (status) {
        var b = requie('./b');
        b.test();
    }
});

语法

define(id?, deps?, factory)
//factory 有三个参数 function(require, exports, module)
//require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口
//exports 是一个对象,用来向外提供模块接口
//module 是一个对象,上面存储了与当前模块相关联的一些属性和方法

AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。

例子

// 定义模块  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  //exports.xxx = value
  //module.exports = value
});
// 加载模块
seajs.use(['myModule.js'], function(my){
});

UMD规范

UMD是AMD和CommonJS的糅合。

AMD模块以浏览器第一的原则发展,异步加载模块。
CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。
这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

(function (window, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

ES6模块化规范

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。

例子

//module1.js文件
// 分别暴露
export function foo() {
  console.log('foo() module1')
}
export function bar() {
  console.log('bar() module1')
}
//module2.js文件
// 统一暴露
function fun1() {
  console.log('fun1() module2')
}
function fun2() {
  console.log('fun2() module2')
}
export { fun1, fun2 }
//module3.js文件
// 默认暴露 可以暴露任意数据类项,暴露什么数据,接收到就是什么数据
export default () => {
  console.log('默认暴露')
}
// app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'

与CommonJS 差异

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

Reference

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值