【二十五】模块化

前言

本篇博客主要了解模块化的历史、现今的规范以及这几种规范的差异。参考资料:juejin.cn/post/724033...

面试回答

1.webpack属性:常见的配置有entry、output、module、plugins以及devServer,前两个属性比较直观,就不赘述了,module的话,主要配置一些loader,用来执行编译代码的功能,比如babel-loader将ES6代码转换低版本的JS语言,Less-loader将Less编译为CSS,file-loader处理图片、img-loader压缩图片等。plugins则主要用来处理插件,比如最常见的htmlWebpackPlugin,用来生成html模板文件。devServer主要用来配置本地服务器代理,比如开发阶段设置本地,联调阶段设置后端IP。

知识点

1.模块化历史

前端模块化是一种将前端代码分解成独立的可重用模块的方法。它可以帮助开发人员更好地组织和管理大型的复杂项目。另外,模块化在前端代码中的应用不仅仅停留在代码层面,还涉及到一些打包和构建工具,如Webpack、Rollup等。它们可以优化代码的加载和执行,减小网络请求和代码体积,提高项目的加载速度和性能。早期,开发者都以各自的写法进行模块的编写,阅读性和适用性都较差,所以出现了四种规范 CommonJS,AMD,CMD,ES6模块化,来要求以同样的方式编写模块。简单来说,前端模块化是以相同标准对代码进行处理。下面先对早期模块化进行了解。

  • 全局function模式

将不同的功能封装成不同的函数,缺陷是容易引发全局命名冲突。比如刚开始在一个js文件中中定义一个一个的函数作为模块,但是这些函数挂载windows上的,污染全局作用域,我在另一个js文件中定义同名的函数就会导致命名冲突,而且无法管理模块依赖关系,进而早起模块化完全依靠约定。

csharp 复制代码
function test(){
	return {
		data:{
			a:1,
			b:2
		}
	}
}
  • 全局namespace模式

约定每个模块暴露一个全局对象,所有的模块成员暴露在对象下,缺点是外部能够修改模块内部数据。将函数放在对象模块中挂载到window上,但是这样外部也能修改对象内部的属性。那么我们可以通过函数作用域加闭包的特性来解决。

typescript 复制代码
var namespace={
	api(){
		return {
			data:{
				a:1,
				b:2
			}
		}
	}
}
  • IIFE模式

将模块成员放在函数私有作用域中,通过自执行函数创建闭包,对于暴露给外部的成员通过挂载到全局对象这种方式实现。这种方式实现了私有成员的概念,缺陷是无法解决模块间相互依赖问题。

ini 复制代码
(function(){
	var a =1;
	function getA(){
		return a;
    }
    function setA(a){
    	window.__module.a = a;
    }
    window.__module = {
        a,
        getA,
        setA
    }
})();
  • IIFE模式增强

支持传入自定义依赖,缺陷是多依赖传入时,代码阅读困难,无法支持大规模的模块化开发,无特定语法支持,代码简陋。

javascript 复制代码
(function(global){
    var a =1;
    function getA(){
        return a;
    }
    function setA(a){
        window.__module.a = a;
    }
    global.__Module_API = {
        a,
        getA,
        setA
    }
})(window)

(function(global,moduleAPI){
    global.__Module = {
        setA:moduleApi.setA
    }
})(window,window.__Module_API)

2.CommonJS

CommonJS是一种模块化规范,它最初是为了在服务端使用JavaScript而设计的,是Node.js中使用的模块规范,它允许开发人员使用require和module.exports或exports关键字来定义和导出模块。在浏览器中可以使用Browserify或Webpack等工具将CommonJS模块转换为浏览器可以识别的代码。CommonJS模块支持循环依赖,一个模块可以依赖于其他多个模块,而其他多个模块又可能依赖于该模块。在运行时,CommonJS实现会对模块进行缓存,以提高性能。

PS:browserify打包原理主要是:通过自执行函数实现模块化,将每个模块编号,存入一个对象,每个模块标记依赖模块。它内部实现了require方法,核心是通过call方法调用模块,并传入require、module、exports方法,通过module存储模块信息,通过exports存储模块输出信息。

示例1:

javascript 复制代码
// 定义一个模块
let count = 0;

function increase() {
  count += 1;
}

module.exports = {
  count,
  increase
}

// 在其他地方,导入模块
const { count, increase } = require('./mymodule.js');

示例2:新建一个名为person.js的文件, 定义一个名为Person的类,在另一个文件中导入person模块,并使用该模块。

javascript 复制代码
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`Hello, my name is ${this.name}, I am ${this.age} years old.`);
  }
}

module.exports = Person;
ini 复制代码
const Person = require('./person');
const person = new Person('zxp', 18);
person.sayHello(); //hello, my name is zxp, I am 18 years old.

特点:

  1. 所有代码都运行在模块作用域,不会污染全局作用域
  2. 模块可以多次加载,第一次加载时会运行模块,模块输出结果会被缓存,再次加载时,会从缓存结果中直接读取模块输出结果
  3. 模块加载的顺序是按照其在代码中出现的顺序
  4. 模块输出的值是值得拷贝,类似IIFE方案中的内部变量

3.AMD

Node模块通常都位于本地,加载速度快,所以适用于同步加载,但是在浏览器运行环境中,用同步加载会阻塞浏览器渲染,所以在浏览器环境中,模块需要请求获取,适用于异步加载。因此就诞生了AMD规范(Asynchronous Module Definition),用于异步加载,其中require.js就是AMD的一个具体实现库,它允许开发者在需要的时候按需加载代码模块以实现更好的代码管理和性能优化。

引入require.js文件:<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>

核心:

  • define函数

用于定义一个模块。它接收三个参数:模块名、依赖数组和模块函数。其中模块名是可选的,依赖数组中指定了该模块所依赖的其他模块,而模块函数则定义了该模块的功能。

javascript 复制代码
//id,string,(可选)模块名称
//dependencies,array,(可选)依赖数组,表示该模块所依赖的模块,模块名不需要加js后缀
//factory,array/object,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值

define(id?, dependencies?, factory);

// 定义一个模块
define('myModule', ['jquery'], function ($) {
  // 模块逻辑
  function myModuleMethod() {
    $('body').append('<p>Hello World!</p>');
  }
  
  // 返回公共API
  return {
    myMethod: myModuleMethod
  };
});

// 加载并初始化模块
require(['myModule'], function (myModule) {
  // 调用模块方法
  myModule.myMethod();
});
  • require函数

用于引入模块或依赖。它接收一个依赖数组和一个回调函数,当所有依赖加载完毕后,回调函数将被执行。

4.CMD

CMD是一种由国内发展出来的浏览器模块化,它整合了CommonJS和AMD的优点,通过异步加载模块。CMD专门用于浏览器端,其中淘宝的sea.js就是CMD规范的一个具体实现库。

引入require.js文件:

  • 定义模块 (define)
javascript 复制代码
// math.js文件
//require,可以用来导入其他模块
//exports,可以把该模块内容属性方法导出
//module,一个对象,存储了与当前模块相关联的一些属性和方法
define(function(require, exports, module) {
    require('./jquery.min.js')     // require 导入jquery
    $.ajax()
    exports.add = function(a, b){ return a+b };        // export导出函数add
});
  • 导入模块 (seajs.use)
php 复制代码
seajs.use(['./math.js'], function (math) {
   math.add(1, 2);    // 3
});

配置Sea.js ( seajs.config() )

csharp 复制代码
seajs.config({
    base:"./js"   //定义读取模块的根目录
    alias:{
        'jquery':'../lib/jquery.min.js'  //定义模块路径与别名,如模块路径../lib/jquery.min.js,用jquery指代
    }
});  

AMD与CMD的区别:

  • AMD是依赖关系前置,在定义模块的时候就要声明其依赖的模块;
  • CMD是按需加载依赖就近,只有在用到某个模块的时候再去require
javascript 复制代码
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  a.doSomething()
  // 此处略去 100 行
  b.doSomething()
  ...
})


// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此处略去 100 行
  var b = require('./b') // 依赖可以就近书写
  b.doSomething()
  // ...
})

目前不管是CMD或是AMD用的都很少,在Node开发中通常用CommonJS规范,在浏览器中用ES Module规范。

5.ES6 Module

ESModule规范是目前应用最为广泛应用于浏览器端的模块化规范,它的设计理念是在编译时就确定模块依赖关系及输入输出。ES6通过import加载模块,通过export输出模块,Vue、react的模块化遵循ES6模块规范。由于ES6的模块化在浏览器端兼容性较差,不能直接在浏览器中预览,必须要使用Babel进行编译之后正常看到结果。

  • export命令

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。export可以让我们把变量,函数,对象进行模块化,提供外部调用接口,让外部进行引用。

javascript 复制代码
//module.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//或者
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

//有些时候并不想暴露模块里边的变量名称,还可使用as关键字对变量进行重命名
export {
    v1 as firstName,
    v2 as lastName,
    v3 as lastName
};

//导入
import {firstName,lastName,year} from './module.js';
import {v1,v2,v3} from './module.js';
import * as types from './module.js' //你所有的方法和属性都是在types命名空间
// 调用里面里面的值可以 这样做
types.v1
types.v2
types.v3
  • export default 命令

export与export default均可用于导出常量、函数、文件、模块等

你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用

在一个文件或模块中,export、import可以有多个,export default仅有一个

通过export方式导出,在导入时要加{ },export default则不需要

示例:

javascript 复制代码
var name="李四";
export { name }
//import { name } from "./a.js"
可以写成:
var name="李四";
export default name
//import name from "./a.js" 这里name不需要大括号

特点:

  1. ESM自动采用严格模式,忽略'use strict'
  2. 每个ESM都是运行在单独的私有作用域中
  3. 在ESM中外部的JS模块是通过CORS的方式请求的
  4. ESM的script标签会延迟执行脚本,等同于defer属性
  5. ES6模块中,顶层的this指向 undefined,即不应该在顶层代码使用this。

最后

走过路过,不要错过,点赞、收藏、评论三连~

相关推荐
bloxed39 分钟前
前端文件下载多方式集合
前端·filedownload
余生H1 小时前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django
LUwantAC1 小时前
CSS(四)display和float
前端·css
cwtlw1 小时前
CSS学习记录20
前端·css·笔记·学习
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
谢道韫6661 小时前
今日总结 2024-12-24
javascript·vue.js·elementui
一朵好运莲1 小时前
React引入Echart水球图
开发语言·javascript·ecmascript
米奇妙妙wuu1 小时前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖1 小时前
React 生命周期完整指南
前端·react.js
梦境之冢2 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http