JS模块化

99 阅读1分钟

模块化的历史

在网页开发的早期,JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的: 这个时候我们只需要讲JavaScript代码写到<script>标签中即可,并没有必要放到多个文件中来编写,甚至流行︰通常说JavaScript程序的长度只有一行。但是随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了∶ajax的出现,前后端开发分离,意味着后端返回数据后,我们需要通过JavaScript进行前端页面的渲染;SPA的出现,前端页面变得更加复杂︰包括前端络由、状态管理等等一系列复杂的需求需要通过JavaScript来实现;包括Node的实现,JavaScript编写复杂的后端程序,没有模块化是致命的硬伤;所以,模块化已经是JavaScript一个非常迫切的需求:但是JavaScript本身,直到ES6才推出了自己的模块化方案,在此之前,为了让JavaScript支持模块化,涌现出了很多不同的模块化规范:AMD、CMD、CommonJS等。 image.png

那什么是模块化、模块化开发呢?

  1. 事实上模块化开发最终的目的是将程序划分成一个个小的结构;这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构。
  2. 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;也可以通过某种方式,导入另外结构中的变量、函数、对象等。
  3. 上面说提到的结构,就是模块按照这种结构划分开发程序的过程,就是模块化开发的过程,无论你多么喜欢JavaScript,以及它现在发展的有多好,它都有很多的缺陷︰比如var定义的变量作用域问题,比如JavaScript的面向对象并不能像常规面向对象语言一样使用class,比如JavaScript没有模块化的问题。 模块化开发是一种管理方式,一种生产方式,一种解决问题的方案。一个模块就是实现某个特定功能的文件,我们可以很方便的使用别人的代码,想要什么模块,就引入那个模块。但是模块开发要遵循一定的规范,后面就出现了我们所熟悉的AMD、CMD、CommonJS和ESModule规范。 image.png

AMD

AMD主要是应用于浏览器的一种模块化规范,AMD是Asynchronous Module Definition(异步模块定义)的缩写,它采用的是异步加载模块,事实上AMD的规范还要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的较少了。AMD实现的比较常用的库是require.js和curl.js。

foo.js

define(function(){
    const name = 'bx'
    const age = 10
    function sum(num1,num2){
        return num1 + num2
    }
    return {
        name,age,sum
    }
})

bar.js

define(function(){
    require(['foo'],function(foo){
        console.log('bar:',foo)
    })
})

main.js

require.config({
    baseUrl:'./src',
    path:{
        foo:'./foo',
        bar:'./bar'
    }
})
require(['foo'],function(foo){
    console.log('main:',foo)
})

image.png

CMD

CMD规范也是应用于浏览器的一种模块化规范:CMD是Common Module Definition(通用模块定义)的缩写,它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来,但是目前CMD使用也非常少了,CMD也有自己比较优秀的实现方案:SeaJS。

foo.js

define(function(require,exports,module){
    const name = 'bx'
    function sum(num1,num2){
        return num1 + num2
    }
    module.exports = {
        name,sum
    }
})

mian.js

define(function(require,exports,module){
    const foo = require('./foo')
    console.log(foo)
})

CommonJS

CommonJS规范主要应用于Node,每个文件就是一个模块,有自己的作用域,即在一个文件中定义的变量、函数、类都是私有的,对其他文件不可见。

  1. CommonJs规范规定,require用来加载某个需要的模块。module代表的是当前模块,是一个对象,存储着当前模块的相关联的属性和方法。
  2. exports是module上的一个属性。该属性表示当前模块对外输出的接口,其它文件加载该模块,实际上就是读取module.exports变量。 image.png CommonJS规定模块可以多次加载,会在第一次加载时运行一次结果就会被缓存下来,以后再加载就直接读取缓存结果,如果想让模块再次运行,必须清除缓存。

ESModule

JavaScript没有模块化一直是它的痛点,所以才会产生这些规范CommonJS、AMD、CMD等。

  1. ESModule和CommonJS的模块化有一些不同之处:一方面它使用了import和export关键字;另一方面它采用编译期的静态分析,并且也加入了动态引用的方式。
  2. ESModule模块采用export和import关键字来实现模块化:export负责将模块内的内容导出,import负责从其他模块导入内容。
export const name = 'bx'
export const age = 10
import {name,age} from './foo.js'
console.log(name)
console.log(age)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./main.js" type="module"></script>
</body>
</html>

这里运行要用live server,否则会报错

CommonJS和ESModule区别

语法不同

  1. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  2. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
  3. CommonJS是对模块的浅拷贝,ES6 Module是对模块的引入,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const 。
  4. import的接口是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向。可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。

CommonJS和ESModule共同点

CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进行改变。 image.png