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

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

2.png


AMD(Asynchronous Module Definition)


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


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


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


它主要有两个接口:definerequire。define 是模块开发者关注的方法,而 require 则是模块使用者关注的方法。


define() 函数:


define(id?, dependencies?, factory);
//id :可选参数,它指的是模块的名字。
//dependencies:可选参数,定义中模块所依赖模块的数组。
//factory:模块初始化要执行的函数或对象


需要注意的是,dependencies有多少个元素,factory就有多少个传参,位置一一对应。


使用栗子:


define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {  
    exports.verb = function() {            
        return beta.verb();            
        //Or:
        //return require("beta").verb();        
    }    
});


require() 函数


require([module], callback);
//module:一个数组,里面的成员就是要加载的模块.
//callback:模块加载成功之后的回调函数。


需要注意的是 ,module 有多少个元素,callback 就有多少个传参,位置一一对应。


require(["a","b","c"],function(a,b,c){    //code here });


AMD的优缺点


AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖 AMD 的这个特性有好有坏:


  • 首先,尽早执行依赖可以尽早发现错误。
  • 另外,尽早执行依赖通常可以带来更好的用户体验,也容易产生浪费。
  • 引用AMD的Javscript库: 目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js
  • 在浏览器环境中异步加载模块;并行加载多个模块;
  • 开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;不符合通用的模块化思维方式,是一种妥协的实现。


AMD在浏览器端的实现步骤


1. 下载require.js, 并引入



然后将require.js导入项目: js/libs/require.js


2. 创建项目结构


|-js
  |-libs
    |-require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html


3. 定义require.js的模块


// dataService.js文件
// 定义没有依赖的模块
define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  return { getMsg } // 暴露模块
});
//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  // 暴露模块
  return { showMsg }
});
// main.js文件
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      //映射: 模块标识名: 路径
      alerter: './modules/alerter', //此处不能写成alerter.js,会报错
      dataService: './modules/dataService'
    }
  });
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  });
})();
// index.html文件
<!DOCTYPE html>
<html>
  <head>
    <title>Modular Demo</title>
  </head>
  <body>
    <!-- 引入require.js并指定js主文件的入口 -->
    <script data-main="js/main" src="js/libs/require.js"></script>
  </body>
</html>


4. 页面引入require.js模块:


在index.html引入 <script data-main="js/main" src="js/libs/require.js"></script>


此外在项目中如何引入第三方库?只需在上面代码的基础稍作修改:


// alerter.js文件
define(['dataService', 'jquery'], function(dataService, $) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  $('body').css('background', 'green')
  // 暴露模块
  return { showMsg }
});
// main.js文件
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      //自定义模块
      alerter: './modules/alerter', //此处不能写成alerter.js,会报错
      dataService: './modules/dataService',
      // 第三方库模块
      jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()


上例是在alerter.js文件中引入jQuery第三方库,main.js文件也要有相应的路径配置。


小结:通过两者的比较,可以得出AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。


UMD规范


CMD(Common Module Definition)


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


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


CMD语法


定义暴露模块:


//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})
//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})


引入使用模块:


define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})


CMD的优缺点


  • 优点:依赖就近,延迟执行 可以很容易在 Node.js 中运行;
  • 缺点:依赖 SPM 打包,模块的加载逻辑偏重;


sea.js使用步骤


1. 下载sea.js, 并引入



然后将sea.js导入项目: js/libs/sea.js


2. 创建项目结构


|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html


3. 定义sea.js的模块代码


// module1.js文件
define(function (require, exports, module) {
  //内部变量数据
  var data = 'atguigu.com'
  //内部函数
  function show() {
    console.log('module1 show() ' + data)
  }
  //向外暴露
  exports.show = show
});
// module2.js文件
define(function (require, exports, module) {
  module.exports = {
    msg: 'I Will Back'
  }
});
// module3.js文件
define(function(require, exports, module) {
  const API_KEY = 'abc123'
  exports.API_KEY = API_KEY
});
// module4.js文件
define(function (require, exports, module) {
  //引入依赖模块(同步)
  var module2 = require('./module2')
  function show() {
    console.log('module4 show() ' + module2.msg)
  }
  exports.show = show
  //引入依赖模块(异步)
  require.async('./module3', function (m3) {
    console.log('异步引入依赖模块3  ' + m3.API_KEY)
  })
});
// main.js文件
define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})


4. 在index.html中引入


<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>


ES6模块化(==重点介绍==)


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


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


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


  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface


上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。


其中,尤其需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this


语法


模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。


export


一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。


function cUl(){
    let ulEle = document.createElement("ul");
    for(let i = 0; i < 5; i++){
        let liEle = document.createElement("li");
        liEle.innerHTML = "无序列表" + i;
        ulEle.appendChild(liEle);
    }
    return ulEle;
}
let ul = cUl();
export {ul};


import


使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。


import {table} from "../test/test_table.js";
import {div} from "../test/test_div.js" ;
import {ul} from "../test/test_ul.js" ;
export {table, div, ul};


ES6 模块与 CommonJS 模块的差异


它们有两个重大差异:


1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用

2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口


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


下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:


// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4


ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块


学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!



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