面试官:请你说说你对 webpack模块化 的了解

前言

什么是模块化?

模块:一个封装起来有独立功能的部分

模块化是一种WEB应用分离成多个模块(独立功能部分)的方法,可将系统分割成独立的功能部分,严格定义模块接口,模块间具有透明性

为什么要有模块化?

解决早期团队项目的变量污染

本篇文章将详细介绍模块化演变过程、模块化规范总结以及 commonjs 与 es6 模块化的区别

一、模块化演变过程

  • 阶段一:基于文件的划分模块方式

    概念:将每个功能和相关数据状态分别放在单独的文件里,约定每一个文件就是一个单独的模块,使用每个模块,直接调用这个模块的成员

    缺点 :所有的成员都可以在模块外被访问和修改(所有的模块都是直接在全局工作,没有私有空间) 模块多了之后会产生命名冲突,无法管理各个模块之间的依赖关系

lua 复制代码
var  name = 'module-a'
function method1 () {
    cosnole.log(name + '#method1')
}
function method2 () {
    cosnole.log(name + '#method2')
}
  • 阶段二:命名空间模式

    概念 :每个模块只暴露一个全局对象,所有模块成员都挂载到这个对象上,通过将每个模块包裹成一个全局对象的方式实现,类似于为每个模块添加命名空间的感觉。

    优点:减少了命名冲突

    缺点 :所有的成员都可以在模块外被访问和修改(所有的模块都是直接在全局工作,没有私有空间),无法管理各个模块之间的依赖关系

javascript 复制代码
var moduleA = {
    name: 'module-a',
    method1: function () {
      console.log(this.name + '#method1')
    },  
    method2: function () {
      console.log(this.name + '#method2')
    }
}
  • 阶段三:IIFE

    使用IIFE(立即执行表达式)为模块提供四有空间,对于需要向外暴露的成员,挂载到全局对象上的方式实现

    优点:模块有了私有空间

    缺点:无法管理各个模块之间的依赖关系。

    有了私有成员,私有成员只能在模块成员内通过闭包的形式访问

matlab 复制代码
// (function(){})() : 自加载执行该方法
(function () {
    var  name = 'module-a'
    function method1 () {
        cosnole.log(name + '#method1')
    }
    function method2 () {
        cosnole.log(name + '#method2')
    }
    // 将需要向外暴露的成员,挂载到全局对象上
    window.moduleA = {
        method1: method1,
        method2: method2    
    }   
})()
  • 阶段四

    概念:使用(IIFE)参数作为依赖声明使用

    做法:在第三阶段的基础上,利用立即执行函数的参数传递模块依赖项

    优点: 使得模块之间的关系变得更加明显

php 复制代码
(function ($) {
    var  name = 'module-a'
    function method1 () {
        cosnole.log(name + '#method1')
        $('body').animate({ margin: '200px'})
    }
    function method2 () {
        cosnole.log(name + '#method2')
    }
    // 将需要向外暴露的成员,挂载到全局对象上
    window.moduleA = {
        method1: method1,
        method2: method2    
    }   
})(jQuery)

二、模块化规范总结

1.CommonJs

  • CommonJS是由Javascript社区于2009年提出的包含模块、文件、IO、控制台在内的一系列标准。
  • 在Node.js的实现中采用了CommonJS标准的一部分,并在其基础上进行了一些调整。我们所说的CommonJS模块和Node.js中的实现并不完全一致,现在一般提到CommonJS其实是Node.js中的版本,而非它的原始定义。
  • CommonJS中规定每个文件是一个模块。

CommonJs是运行在node.js环境下的模块化规范,node的机制是在启动时,加载模块,执行时直接使用模块,一个文件就是一个模块,每个模块都有单独的作用域 通过module.export导出,通过require函数导入

导出

(1)导出是一个模块向外暴露自身的唯一方式。在CommonJS中,通过module.exports可以导出模块中的内容

(2)CommonJS模块内部会有一个module对象用于存放当前模块的信息。

javascript 复制代码
module.exports = {
  name: 'xxx',
  add: function (a, b) {
    return a + b;
  }
}

(3)简化的导出方式,直接使用exports

ini 复制代码
exports.name = 'xxx';
exports.add = function (a, b) {
  return a + b;
}

1、其内在机制是将exports指向了module.exports,而module.exports在初始化时是一个空对象。

2、注意不要直接给exports赋值,否则会导致其失效。

3、注意导出语句不代表模块的末尾,在module.exports或exports后面的代码依旧会照常执行。

导入

(1)在CommonJS中使用require进行模块导入

ini 复制代码
const calculator = require('./calculator.js')

(2)当我们require一个模块时会有两种情况

  • 1、require的模块是第一次被加载。这时会首先执行该模块,然后导出内容。
  • 2、require的模块曾被加载过。这时该模块的代码不会再次行,而是直接导出上次执行后得到的结果。 (模块会有一个module对象用来存放其信息,这个对象中有一个属性loaded用于记录该模块是否被加载过。它的默认值为false,当模块第一次被加载和执行后会置为true,后面再次加载时会检查到module.loaded为true,则不会再次执行模块代码)

(3) 有时我们加载一个模块,不需要获取其导出的内容,只是想要通过执行它而产生某种作用,比如把它的接口挂在全局对象上,此时直接使用require即可。

(4)require函数可以接收表达式,借助这个特性我们可以动态地指定模块加载路径

ini 复制代码
const moduleNames = ['foo.js', 'bar.js'];
moduleNames.forEach(name => {
  require('/' + name);
})

该模块规范不适合浏览器的原因:

浏览器在加载页面是,如果需要同步加载所有模块,必然导致性能低下,所以早期的浏览器没有使用CommonJS规范

  • AMD
    主要用于浏览器端,通过define定义,通过require导入,异步加载模块
  • CMD
    主要用于浏览器端,通用模范定义规范,通过define定义,通过require导入,同步加载模块
  • ESModule
    浏览器和服务器通用,用import和export关键字来导入和导出,AMD和commonjs是运行时确定这些东西,es6的模块化设计思想是尽量的静态化,使得编译时就确定模块的依赖关系,以及输入输出变量。

2.ES Module

基本特性

xml 复制代码
<script type="module">
   console.log(this) // 因为是module类型 所以是undefined
   var foo = 100
   console.log(foo)  // 100
</script>
<script type="module">
   console.log(foo) // 报错 foo没有定义,因为每个 ES Module 都是运行在单独的私有作用域中
</script>

自动采用严格模式,不能直接采用this

每个ESM模块都是单独的私有作用域
ESM是通过CORS去请求外部JS模块的
ESMscript标签会延迟执行脚本

导入导出

导入的是模块成员的 地址 ,并且是 只读

javascript 复制代码
// 默认导出
var name = 'foo module'
export default name
// 默认导入
import fooName from './module'

// 多个导入导出  {}是固定语法,并不是数据解构
export const name = 'hello'
export const age= 18
export default const sex = 0

import sex,{name,age} from './module'

// 与commonjs的区别:先将模块整体导入为一个对象,再从对象中解构出需要的成员
export obj = {name, age}
const { name, age } = require('./module.js')

使用方式

1.使用import导入模块路径必须是完整路径,路径的./不能省略,路径可以是绝对路径和url

2.只加载模块,不提取成员

javascript 复制代码
import {} from './module.js' 
import './module.js'

3.用*导出模块所有成员,并将其放入一个对象中,从对象中提取需要的成员

javascript 复制代码
import * as mod from './module.js'

4.动态加载模块:使用import()动态加载一个模块,返回一个promise对象,等异步加载完成之后会自动加载then指定的回调函数,获取参数

javascript 复制代码
import('./module.js').then(function (module) { 
   console.log(module) 
})

5.导入时直接导出

javascript 复制代码
export { default as Button } from './button.js'
export { Avatar } from './avatar.js'

三、commonjses6 模块化有什么区别

  • 1、CommonJS模块是运行时加载,而ES6模块是编译时输出接口;
  • 2、CommonJS模块的require()是同步加载模块,而ES6模块的import命令是异步加载;
  • 3、CommonJS是对模块的浅拷贝,ES6是对模块的引入
相关推荐
Jiaberrr7 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀3 小时前
CSS——属性值计算
前端·css
睡觉然后上课4 小时前
c基础面试题
c语言·开发语言·c++·面试
DOKE4 小时前
VSCode终端:提升命令行使用体验
前端