前端知识梳理(一)模块管理

Posted by southrock on 2019-11-26

前言

对于流行的语言,类似于python、java、c等等,在初期学习,我们一般就能接触到他们的模块管理模式,大概了解其运行的机制。而JavaScript,由于天生的缺陷,随着学习的深入,许久后才能了解到一些列诸如UMD、CommonJS和ES6 Module等看起来相似却又迥异的多种模块管理模式。

模块管理

JavaScript在现如今前端开发越来越复杂的,原先的设计模式中,模块管理的缺乏,带来了各种不方便。也导致了诸多的模块管理工具的产生。

模块化的主要特征是:

  • 模块化,可重用
  • 封装了变量和function,和全局的namaspace不接触,松耦合
  • 只暴露可用public的方法,其它私有方法全部隐藏

实现方式

函数包裹

平时写代码时,写的一个个函数,就相当于一个小的模块,从这个出发,来思考模块的设计模式。

逻辑放在一个个函数里,封装在一个文件中,需要时直接引入。

1
2
function f1() {}
function f2() {}

优点:对代码进行了简单封装复用,挂载全局变量,可直接调用。

缺点: 污染了全局变量,模块成员之间看不出联系。使用一个函数把其他没使用到的函数也引入了。

对象封装

为了解决对全局变量的污染,可以把他们写到一个对象里面,函数变为这个对象的一个个方法。

1
2
3
4
5
6
7
8
9
10
var module1 = {
_name: "im a module",
method1: function() {},
method2: function() {}
}
var module2 = {
_name: "im another module",
method1: function() {},
method2: function() {}
}

优点:包裹了函数,对于全局变量的污染没有那么大。
缺点:所有成员直接暴露给外部,内部的状态可以被外部改写。

闭包

使用JavaScript里面特有的闭包属性,在函数的运行周期里,闭包保证了内部代码始终处于私有状态下。

1
2
3
4
5
6
7
8
9
function module() {
_name: "im a module",
var method1 = function() {},
var method2 = function() {}
return {
method1:method1,
method2:method2
}
}

优点:比较好的保护了私有变量

缺点:动态添加方法比较麻烦,无法修改内部私有变量。

模块依赖方法对比

CommonJs

Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:moduleexportsrequireglobal。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。

1
2
3
4
5
6
7
8
9
10
11
12
// module.js
var count = 0;
function printCount() {
console.log(count);
}
module.exports = {
count:count,
printCount:printCount,
}

var m = require('./module.js');
m.printCount();

ES6 Module

ES6 在语言标准的层面上,终于实现了模块功能。其模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

1
2
3
4
5
6
7
8
9
10
11
// 定义模块 module.js
var count = 0;
var printCount = () => {
console.log(count);
}
export { count,printCount };

// 引用模块
import { count,printCount } from './module.js';
var test = count;
printCount();

ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,虽然无法实现条件加载,但让静态分析成为可能。

ES6 模块与 CommonJS 模块的差异

在讨论差异前,必须了解ES6 模块与 CommonJS 模块完全不同。

它们有两个重大差异。

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

第一个是差异ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

后记

这个系列主要是为了帮助自己梳理前端的知识,虽然有加上自己的理解写了一些,但自己的思考还是少了点,更多的是从文档、别人的文章收集整理的=-=有点不太好意思,但还是一个学习的过程吧。以后还会不断补充,有了新的想法后会记上。

坚持学习,加油。

Credits

前端模块化:CommonJS,AMD,CMD,ES6

前端工程师手册:模块化简介

阮一峰ECMAScript 6 入门:Module 的加载实现