前端模块化的全面总结(上)

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

2.png


背景


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


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


  1. 请求过多。首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多
  2. 依赖模糊。我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。
  3. 难以维护。以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。


模块的概念


webpack中是这样定义的:


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


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


模块化的概念


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


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


为什么需要模块化


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


模块化的好处


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


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


  1. 重用代码。 我们有时候会喜欢从之前写过的项目中拷贝代码到新的项目,这没有问题,但是更好的方法是,通过模块引用的方式,来避免重复的代码库。我们可以在更新了模块之后,让引用了该模块的所有项目都同步更新,还能指定版本号,避免 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
  • CMD
  • AMD
  • ES6模块


CommonJS()


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


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


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


//定义模块 math.js
 var random=Math.random()*10;
 function printRandom(){
     console.log(random)
 }
 function printIntRandom(){
     console.log(Math.floor(random))
 }
 //模块输出
 module.exports={
     printRandom:printRandom,
     printIntRandom:printIntRandom
 }
 //加载模块 math.js
 var math=require('math')
 //调用模块提供的方法
 math.printIntRandom()
 math.printRandom()


CommonJS主要用于服务端的模块化,不适用于前端模块化的原因在于:


  • 服务端加载一个模块,直接就从硬盘或者内存中读取了,消耗时间可以忽略不计
  • 浏览器需要从服务端下载这个文件,所以说如果用CommonJS的require方式加载模块,需要等代码模块下载完毕,并运行之后才能得到所需要的API。
  • 如果我们在某个代码模块里使用CommonJS的方法require了一个模块,而这个模块需要通过http请求从服务器去取,如果网速很慢,而CommonJS又是同步的,所以将阻塞后面代码的执行,从而阻塞浏览器渲染页面,使得页面出现假死状态。


CommonJS在浏览器端实现的步骤:


1. 创建项目结构


|-js
  |-dist //打包生成文件的目录
  |-src //源码所在的目录
    |-module1.js
    |-module2.js
    |-module3.js
    |-app.js //应用主源文件
|-index.html //运行于浏览器上
|-package.json
  {
    "name": "browserify-test",
    "version": "1.0.0"
  }


2. 下载browserify


  • 全局: npm install browserify -g
  • 局部: npm install browserify --save-dev


3. 定义模块代码(同服务器端)


注意:index.html文件要运行在浏览器上,需要借助browserify将app.js文件打包编译,如果直接在index.html引入app.js就会报错!


4. 打包处理js


根目录下运行browserify js/src/app.js -o js/dist/bundle.js


5. 页面使用引入


在index.html文件中引入<script type="text/javascript" src="js/dist/bundle.js"></script>



相关文章
|
11天前
|
资源调度 前端开发 JavaScript
构建高效前端项目:现代包管理器与模块化的深度解析
【2月更文挑战第21天】 在当今快速演变的前端开发领域,高效的项目管理和代码组织已成为成功交付复杂Web应用的关键。本文将深入探讨现代前端包管理器如npm, yarn和pnpm的工作原理,以及它们如何与模块化编程实践(例如CommonJS、ES6模块)协同工作以优化开发流程。我们将剖析这些工具的内部机制,了解它们如何解决依赖冲突,提高安装速度,并保证项目的健壮性。同时,本文还将介绍模块化编程的最佳实践,包括代码拆分、重用和版本控制,帮助开发者构建可维护且性能卓越的前端项目。
|
11天前
|
前端开发 JavaScript jenkins
构建高效前端项目:从模块化到自动化
【2月更文挑战第13天】 随着Web技术的不断进步,前端项目的复杂性日益增加。为了确保可维护性和性能,前端工程师必须采用模块化和自动化的策略来优化开发流程。本文将探讨如何使用现代前端工具和最佳实践来构建一个高效的前端项目架构,包括模块打包、代码分割和持续集成等方面。
|
11天前
|
前端开发 JavaScript 安全
【Web 前端】怎么实现Module模块化?
【5月更文挑战第1天】【Web 前端】怎么实现Module模块化?
|
11天前
|
JavaScript 前端开发 开发者
【Web 前端】JS模块化有哪些?
【4月更文挑战第22天】【Web 前端】JS模块化有哪些?
|
11天前
|
JavaScript 算法 前端开发
【专栏】前端开发中的slot算法和shadow DOM,两者提供更灵活、高效和模块化的开发方式
【4月更文挑战第29天】本文探讨了前端开发中的slot算法和shadow DOM,两者提供更灵活、高效和模块化的开发方式。slot算法允许在组件中定义插槽位置,实现内容的灵活插入和复用,提高代码可读性和维护性。shadow DOM则通过封装DOM子树,实现样式和事件的隔离,增强组件独立性和安全性。这两种技术常应用于组件开发、页面布局和主题定制,但也面临兼容性、学习曲线和性能优化等挑战。理解并掌握它们能提升开发效率和用户体验。
|
11天前
|
前端开发 JavaScript 安全
前端模块化发展
前端模块化发展
|
11天前
|
前端开发 JavaScript 内存技术
Node-前端模块化
Node-前端模块化
24 0
Node-前端模块化
|
11天前
|
前端开发 JavaScript 测试技术
构建高效前端项目:模块化与组件化策略
【2月更文挑战第25天】 在现代网页开发中,随着用户对于网页性能和交互体验的期待不断提升,前端项目的复杂性也随之增加。为了应对这种复杂性并提高开发效率,本文将探讨模块化与组件化在前端项目中的应用策略。通过分析这两种方法的优势与适用场景,我们将揭示如何利用它们来优化项目结构,提升代码复用率,以及加快开发流程。
41 4
|
11天前
|
资源调度 前端开发 JavaScript
构建高效前端项目:模块化与组件化的最佳实践
【2月更文挑战第13天】在现代前端开发的浪潮中,模块化和组件化已经成为提升项目可维护性和开发效率的核心原则。本文深入探讨了如何通过合理的模块划分、组件设计以及工具选择来优化前端项目结构,同时确保代码的复用性和可测试性。我们将从理论出发,结合实例分析,为前端开发者提供一套行之有效的最佳实践指南。
|
11天前
|
自然语言处理 JavaScript 前端开发
前端模块化的前世今生(下)
前端模块化的前世今生(下)