模块化相关知识

208 阅读2分钟

概述

传统开发模式主要问题

  • 命名冲突
  • 文件依赖

模块化可解决上述问题

  • 模块

将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起。

块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。

  • 模块化

把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块。

好处: - 避免命名冲突(减少命名空间污染) - 更好的分离, 按需加载 - 更高复用性 - 高可维护性

模块化相关规范

引入多个<script>后出现的问题:

  • 请求过多 首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多

  • 依赖模糊 我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。

以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。

模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决。

浏览器端模块化规范

AMD

AMD 叫做异步模块定义规范(Asynchronous Module Definition),它是 CommonJs 模块化规范的超集,但是运行于浏览器之上的。

AMD 的特点就和它的名字一样,模块的加载过程是异步的,它大大的利用了浏览器的并发请求能力,让模块的依赖过程的阻塞变得更少了。requireJs 就是 AMD 模块化规范的实现。

AMD 作为一个规范,只需定义其语法 API,而不关心其实现。AMD 规范简单到只有一个 API,即 define 函数:

 define(id?, dependencies?, factory);

具体用法如下:

// moudle-a.js
define('moudleA', function() { 
    return {
        a: 1
    }
});

// moudle-b.js
define(['moudleA'], function(ma) {
    var b = ma.a + 2;

    return {
        b: b
    }
});

AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。但是AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。

CMD

CMD 叫做通用模块定义规范(Common Module Definiton),它是类似于 CommonJs模块化规范,但是运行于浏览器之上的。

CMD 规范尽量保持简单,并与 CommonJS 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行。在 CMD 规范中,一个模块就是一个文件。

格式如下:

define(factory);

具体用法如下:

// moudle-a.js
define(function(require, exports, module) {

    module.exports = { 
        a: 1 
    };

});

// moudle-b.js
define(function(require, exports, module) {

    var ma = require('./moudle-a');
    var b = ma.a + 2;
    module.exports = { 
        b: b 
    };

});

CMD规范与AMD规范相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM打包,模块的加载逻辑偏重。

UMD

UMD 叫做通用模块定义规范(Universal Module Definition)。也是随着大前端的趋势所诞生,它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。未来同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了。

它没有自己专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身,我们看看它的具体实现:

((root, factory) => {
    if (typeof define === 'function' && define.amd) {
        //AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        //CommonJS
        var $ = requie('jquery');
        module.exports = factory($);
    } else {
        root.testModule = factory(root.jQuery);
    }
})(this, ($) => {
    //todo
});

它在定义模块的时候会检测当前使用环境和模块的定义方式,将各种模块化定义方式转化为同样一种写法。

服务端模块化规范

CommonJS

CommonJs 是一种 JavaScript 语言的模块化规范,它通常会在服务端的 Nodejs 上使用。项目是由多个模块组成的,模块和模块之间的调用,需要各个模块有相同规范的 API,这样一来在使用的过程中不会有那么多的学习成本,并且对于单个模块来说是类聚的。

在 CommonJs 的模块化规范中,每一个文件就是一个模块,拥有自己独立的作用域、变量、以及方法等,对其他的模块都不可见。CommonJS规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。require 方法用于加载模块。

模块分为单文件模块和包。

//moudle-a.js
moudle.exports = {
    a: 1
};

//moudle-b.js
// 模块成员导入
var ma = require('./moudle-a');
var b = ma.a + 2;5
// 模块成员daochu
module.exports ={
    b: b
};

CommonJS规范主要用于服务端编程,加载模块是同步的,并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD、CMD解决方案。

ES6模块化

前面提出的模块化标准,存在一定的差异性和局限性,并不是浏览器与服务器通用的模块化标准。ES6语法规范中,在语言层面上定义了ES6模块化规范,是浏览器与服务端通用的模块化开发规范。

  • 每个js文件都是一个独立的模块
  • 导入模块成员使用import关键字
  • 暴露模块成员使用export关键字

默认导出与默认导入

 // 导入模块成员
import m1 from './m1.js'

// 当前文件模块为 m1.js
// 定义私有成员 a 和 c
let a = 10
let c = 20
// 外界访问不到变量 d ,因为它没有被暴露出去 
let d = 30
function show() {}

// 将本模块中的私有成员暴露出去,供其它模块使用 
// export default 默认导出成员,每个模块只允许使用一次
export default {
	a, 
  c, 
  show
}

按需导出与按需导入

// 按需导入模块成员
import { s1, s2 as ss2, say } from './m1.js

// 当前文件模块为 m1.js
// 在每个模块中,可以使用多次按需导出
// 向外按需导出变量 s1
export let s1 = 'aaa'
// 向外按需导出方法 say
export function say = function() {}

直接导入并执行模块代码

有时候只想单纯执行某个模块中的代码,并不需要得到模块向外暴露的成员。

// 直接导入并执行模块代码 
import './m2.js'

// 当前文件模块为 m2.js
// 在当前模块中执行一个 for 循环操作
for(let i = 0; i < 3; i++) {
	console.log(i) 
}

webpack

当前Web开发面临的困境

  • 模块依赖关系错综复杂
  • 静态资源请求效率低
  • 模块化支持不好
  • 浏览器对高级JavaScript特性兼容程度较低

webpack是一种前端资源构建工具,一个静态模块打包器,可解决上述问题。

它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源。

五个核心概念

  • Entry

    入口指示webpack以哪个文件为起点开始打包,分析构建内部依赖图。

  • Output

    输出指示webpack打包后资源bundles输出到哪里去以及如何命名。

  • Loader

    让webpack能够处理那些非js文件。

  • Plugins

    插件可以用于执行范围更广的任务,包括打包优化和压缩,重新定义环境中的变量等。

  • Mode

    模式指示webpack使用相应模式的配置。

初始化配置

  • 初始化package.json

    npm init

  • 下载并安装webpack

    npm install webpackage-cli -g

    npm install webpackage-cli -D

编译打包应用

  • 创建文件

  • 运行指令

    开发环境指令:webpack src/js/index.js -o build/js/build.js --mode=development

    功能:webpack能够编译打包js和json文件,并且能将es6的模块化语法转换成浏览器能识别的语法。

    生产环境指令:webpack src/js/index.js -o build/js/build.js --mode=production

    功能:在开发配置中多了一个压缩代码的功能。

  • 结论

    webpack能够编译打包js和json文件,也能将es6模块化语法转换成浏览器能识别的语法,还能压缩代码

  • 问题

    • 不能编译打包css、img等文件
    • 不能将js的es6基本语法转化为es5以下语法

Vue单文件组件

传统组件的问题

  • 全局定义的组件必须保证组件的名称不重复。
  • 字符串模版缺乏语法高亮
  • 不支持CSS意味着当HTML和JavaScript组件化时,CSS明显被遗漏。
  • 没有构建步骤限制,只能使用HTML和ES5语法,而不能使用预处理器,例如babel。
解决方法

使用Vue单文件组件

基本用法

<template>
<!-- 定义Vue组件的模板内容 -->
</template>

<script>
// 定义Vue组件的业务逻辑 
export default {
    data: () { return {} },// 私有数据 
    methods: {} // 处理函数
    // ... 其它业务逻辑
} 
</script>

<style scoped>
/* 定义组件的样式 */
</style>

脚手架

脚手架是为了保证各施工过程顺利进行而搭设的工作平台。

脚手架的作用

  • 能够快速生成新项目的目录模板(Node.js)。

  • 能够提升开发效率和开发的舒适性(webpack)。

  • 减少重复性工作。

Vue CLI

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。

CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令。它可以通过 vue create 快速搭建一个新项目,或者直接通过 vue serve 构建新想法的原型。你也可以通过 vue ui 通过一套图形化界面管理你的所有项目。

安装

npm install -g @vue/cli
# OR
yarn global add @vue/cli

安装之后,就可以在命令行中访问 vue 命令。通过简单运行 vue,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。

通过package.json配置项目

// 必须是符合规范的json语法 
"vue": {
	"devServer": { 
		"port": "8888", 
		"open" : true
	} 
},

不推荐使用,因为package.json主要用来管理包的配置信息。为了方便维护,推荐将vue脚手架相关配置,单独定义到vue.config.js文件中。

通过单独配置文件配置项目

  • 在项目的根目录创建vue.config.js文件
  • 在该文件中进行相关配置,从而覆盖默认配置
// vue.config.js 
module.exports = {
	devServer: {
		port: 8888
	} 
}