Module模块化编程的优点(总结分享)

来自:互联网
时间:2022-08-09
阅读:

背景

随着前端功能越来越复杂,前端代码日益膨胀,为了减少维护成本,提高代码的可复用性,前端模块化势在必行。

所有js文件都在一个html中引入,造成以下不良影响:

  • 请求过多。首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多

  • 依赖模糊。我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。

  • 难以维护。以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。

模块的概念

webpack中是这样定义的:

在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。 每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。 精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。

模块应该是职责单一、相互独立、低耦合的、高度内聚且可替换的离散功能块。

模块化的概念

模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。

模块化是一种分治的思想,通过分解复杂系统为独立的模块实现细粒度的精细控制,对于复杂系统的维护和管理十分有益。模块化也是组件化的基石,是构成现在色彩斑斓的前端世界的前提条件。

为什么需要模块化

前端开发和其他开发工作的主要区别,首先是前端是基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统。

模块化的好处

  • 可维护性。 因为模块是独立的,一个设计良好的模块会让外面的代码对自己的依赖越少越好,这样自己就可以独立去更新和改进。

  • 命名空间。 在 JavaScript 里面,如果一个变量在最顶级的函数之外声明,它就直接变成全局可用。因此,常常不小心出现命名冲突的情况。使用模块化开发来封装变量,可以避免污染全局环境。

  • 重用代码。 我们有时候会喜欢从之前写过的项目中拷贝代码到新的项目,这没有问题,但是更好的方法是,通过模块引用的方式,来避免重复的代码库。我们可以在更新了模块之后,让引用了该模块的所有项目都同步更新,还能指定版本号,避免 API 变更带来的麻烦。

模块化简史

1. 最简单粗暴的方式

function fn1(){
  // ...
}
function fn2(){
  // ...
}

通过 script 标签引入文件,调用相关的函数。这样需要手动去管理依赖顺序,容易造成命名冲突,污染全局,随着项目的复杂度增加维护成本也越来越高。

2. 用对象来模拟命名空间

var output = {
  _count: 0,
  fn1: function(){
    // ...
  }
}

这样可以解决上面的全局污染的问题,有那么点命名空间的意思,但是随着项目复杂度增加需要越来越多的这样的对象需要维护,不说别的,取名字都是个问题。最关键的还是内部的属性还是可以被直接访问和修改。

3. 闭包

var module = (function(){
  var _count = 0;
  var fn1 = function (){
    // ...
  }
  var fn2 = function fn2(){
    // ...
  }
  return {
    fn1: fn1,
    fn2: fn2
  }
})()
module.fn1();
module._count; // undefined

这样就拥有独立的词法作用域,内存中只会存在一份 copy。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域,通过 return 暴露出公共接口供外界调用。这其实就是现代模块化实现的基础。

4. 更多

还有基于闭包实现的松耦合拓展、紧耦合拓展、继承、子模块、跨文件共享私有对象、基于 new 构造的各种方式,这种方式在现在看来都不再优雅。

// 松耦合拓展
// 这种方式使得可以在不同的文件中以相同结构共同实现一个功能块,且不用考虑在引入这些文件时候的顺序问题。
// 缺点是没办法重写你的一些属性或者函数,也不能在初始化的时候就是用module的属性。
var module = (function(my){
  // ...
  return my
})(module || {})
// 紧耦合拓展(没有传默认参数)
// 加载顺序不再自由,但是可以重载
var module = (function(my){
  var old = my.someOldFunc
  
  my.someOldFunc = function(){
    // 重载方法,依然可通过old调用旧的方法...
  }
  return my
})(module)

实现模块化的方案规范总览

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

目前实现模块化的规范主要有:

  • CommonJS

CommonJS 是以在浏览器环境之外构建 JavaScript 生态系统为目标而产生的项目,比如在服务器和桌面环境中。

采用同步加载模块的方式,也就是说只有加载完成,才能执行后面的操作。CommonJS 代表:Node 应用中的模块,通俗的说就是你用 npm 安装的模块。

它使用 require 引用和加载模块,exports 定义和导出模块,module 标识模块。使用 require 时需要去读取并执行该文件,然后返回 exports 导出的内容。

  • CMD

CMD(Common Module Definition)

CMD是SeaJS在推广过程中生产的对模块定义的规范,在Web浏览器端的模块加载器中,SeaJS与RequireJS并称,SeaJS作者为阿里的玉伯。

CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

  • AMD

AMD(Asynchronous Module Definition)

异步模块定义,所谓异步是指模块和模块的依赖可以被异步加载,他们的加载不会影响它后面语句的运行。有效避免了采用同步加载方式中导致的页面假死现象。AMD代表:RequireJS。

AMD一开始是CommonJS规范中的一个草案,全称是Asynchronous Module Definition,即异步模块加载机制。后来由该草案的作者以RequireJS实现了AMD规范,所以一般说AMD也是指RequireJS。

RequireJS是一个工具库,主要用于客户端的模块管理。它的模块管理遵守AMD规范,RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。

  • ES6模块

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。所以说ES6是编译时加载,不同于CommonJS的运行时加载(实际加载的是一整个对象),ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上”use strict”;。

严格模式主要有以下限制。

  • 变量必须声明后再使用

  • 函数的参数不能有同名属性,否则报错

  • 不能使用with语句

  • 不能对只读属性赋值,否则报错

  • 不能使用前缀 0 表示八进制数,否则报错

  • 不能删除不可删除的属性,否则报错

  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]

  • eval不会在它的外层作用域引入变量

  • eval和arguments不能被重新赋值

  • arguments不会自动反映函数参数的变化

  • 不能使用arguments.callee

  • 不能使用arguments.caller

  • 禁止this指向全局对象

  • 不能使用fn.caller和fn.arguments获取函数调用的堆栈

  • 增加了保留字(比如protected、static和interface)

上面这些限制,模块都必须遵守。

返回顶部
顶部