JS 模块化基本认识

JS 模块化基本认识

背景

  1. js 本身定位: 开始只是简单的页面设 + 基本的表单提交,前后端不分离
  2. 并无模块化或者命名空间的概念

JS 的模块化需求日益增长

幼年期:无模块化(委婉的辩解),不同功能的 js 分成多个 js 文件

  1. 开始需要在页面中增加一些不同的 js:动画、表单、格式化工具
  2. 多种 js 文件被分在不同的文件中
  3. 不同的文件又被同一个模板所引用
  4. 文件分离是最基础的模块化
html 复制代码
// 例如:test.html 页面引用多个 js 文件
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="tools.js"></script>

面试问题:script 标签两个参数: async & defer

html 复制代码
<script src="jquery.js" async></script>
js 复制代码
默认模式:
                 download-execute
------parse------                ------parse------

defer 模式(推迟执行模式):
                 download                 execute
------parse--------------------parse------

async 模式(异步下载模式):
                 download-execute
------parse---------------       ------parse------

总结:

  1. parse 与 execute 是互斥的,解析的时候不执行,执行的时候不解析
  2. 默认模式:解析到标签,立刻 pending,并且下载执行
  3. defer 模式:解析到标签开始异步下载,解析完成后开始执行
  4. asyn 模式:解析到标签开始异步下载,下载完成后立刻执行并阻塞渲染,执行完成后,继续渲染
  5. defer, asyn 参数要在 IE9+ 的浏览器才有效
  6. 缺点:全局的变量和方法容易被覆盖和污染,不利于大型项目的开发以及多人团队共建

成长期:模块化的雏形-IIFE(语法侧的优化)

  1. IIFE: Immediately Invoked Function Expression (立即调用函数表达式)
  2. 立即执行函数利用的其实就是作用域的把控

面试问题1:利用 IIFE 创建一个最简单的模块,和在外部调用模块内部的函数

js 复制代码
const module = (() => {
  let count = 0;
  return {
    increase: () => ++count;
    reset: () => {
      count = 0;
    }
  }
})();

module.increase();
module.reset();

面试问题2:有额外依赖的时候,如何优化 IIFE 的相关代码?

答:通过传参,例如下面的代码 iifeModule 模块依赖了 module1, module2 这两个模块,在立即执行的时候把 module1, module2 传进去通过 ependencyModule1, dependencyModule2 接收,这样在 iifeModule 这个模块内部就可以调用这两个模块了

js 复制代码
const iifeModule = ((dependencyModule1, dependencyModule2) => {
  let count = 0;
  const obj = {
    increase: () => ++count;
    reset: () => {
      count = 0;
    }
  }
  // dependencyModule1.xxx
  // dependencyModule2.xxx
})(module1, module2);

面试问题3:了解早期 jQuery 依赖处理和模块加载方案吗?/了解传统 IIFE 是如何解决多方依赖的问题的吗?

答:IIFE + 传参调配。实际上传统框架应用了一种 revealing 模式 的写法。
revealing 模式 -> 揭示模式
return 的是能力 = 使用方传参(x) + 本身逻辑能力 + 依赖的能力

js 复制代码
const iifeModule = ((dependencyModule1, dependencyModule2) => {
  let count = 0
  const increase = () => ++count // 3. 本身逻辑能力
  const reset = (val) => {
    count = 0 // 3. 本身逻辑能力
    // 4. 依赖的能力
    // dependencyModule1.xxx
    // dependencyModule2.xxx
  }

  // 1. return 的是能力
  return {
    increase,
    reset
  }
})(module1, module2)
iifeModule.increase(1) // 2. 使用方传参(x)
iifeModule.reset()

可能被问到的问题

js 复制代码
// 部分开源项目分别传入全局、指针、框架作为参数
(function(window, $, undefined) {
    const _show = function() {
        $("#app").val("hi zhaowa");
    }
    window.webShow = _show;
})(window, jQuery);

// window - 1. 全局作用域转成局部作用域,执行不用全局调用,提升效率 2. 编译时候,优化压缩(function(c){}(window))
// jQuery - 1. 内部可以独立定制复写,保障稳定 2. 防止全局污染
// undefined - 防止外部重写 undefined

成熟期

1. CommonJS

  1. node.js 制定,在服务端和客户端都可以使用
  2. 在客户端需要通过 browserify 编译打包
  3. 通过 module + export 去对外暴露接口
  4. 通过 require 来调用其他模块
  5. 优点:率先在服务端实现了从框架层面解决依赖、全局变量污染的问题
  6. 缺点:针对的是服务端,对于异步依赖没有很友好的处理和考虑

服务端使用 demo

txt 复制代码
|----/modules
     |----/module1.js
     |----/module2.js
     |----/module3.js
|----/app.js
|----/package.json
js 复制代码
// package.json 通过 npm init 生成或者手动创建
{
  "name": "demo1",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "uniq": "^1.0.1"
  }
}
js 复制代码
// app.js
const uniq = require('uniq')
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')

module1.fn()
module2()
module3.fn1()
module3.fn2()

const list = uniq(module3.list)
console.log(list)
js 复制代码
// module1.js
module.exports = {
  msg: '---module1---',
  fn() {
    console.log(this.msg)
  }
}
js 复制代码
// module2.js
module.exports = () => {
  console.log('---module2---')
}
js 复制代码
// module3.js
exports.fn1 = () => {
  console.log('---module3-1---')
}

exports.fn2 = () => {
  console.log('---module3-2---')
}

exports.list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1]

浏览器端使用 demo

txt 复制代码
|----/js
     |----/src
          |----app.js
          |----/module1.js
          |----/module2.js
          |----/module3.js
|----/index.html
|----/package.json
js 复制代码
// package.json 通过 npm init 生成或者手动创建
{
  "name": "demo2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browserify": "^17.0.0"
  },
  "dependencies": {
    "uniq": "^1.0.1"
  }
}
html 复制代码
<!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>
  <script src="./js/dist/bundle.js"></script>
</head>

<body>

</body>

</html>
js 复制代码
// app.js
const uniq = require('uniq')
const module1 = require('./module1')
const module2 = require('./module2')
const module3 = require('./module3')

module1.fn()
module2()
module3.fn1()
module3.fn2()

const list = uniq(module3.list)
console.log(list)
js 复制代码
// module1.js
module.exports = {
  msg: '---module1---',
  fn() {
    console.log(this.msg)
  }
}
js 复制代码
// module2.js
module.exports = () => {
  console.log('---module2---')
}
js 复制代码
// module3.js
exports.fn1 = () => {
  console.log('---module3-1---')
}

exports.fn2 = () => {
  console.log('---module3-2---')
}

exports.list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1]
txt 复制代码
# 导读

## 安装 browserify

1. 首先全局安装 `npm install browserify -g`
2. 再项目内安装 `npm install browserify --save-dev`

## 打包

`browserify js/src/app.js -o js/dist/bundle.js`

2. AMD require.js

  1. 优点:解决了浏览器中异步加载模块,可以并行加载多个模块
  2. 缺点:会有引入成本,没有考虑按需加载
txt 复制代码
|----/js
     |----/libs
          |----angular.min.js
          |----jquery-2.1.4.min.js
          |----require.min.js
     |----/modules
          |----alerter.js
          |----dataService.js
     |----main.js
|----index.html
html 复制代码
<!-- index.html -->
<!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 data-main="js/main.js" src="js/libs/require.min.js"></script>
</body>

</html>
js 复制代码
// main.js
(function () {
  requirejs.config({
    // baseUrl: 'js/', // 基本路径(出发点)
    paths: { // 配置路径
      dataService: './modules/dataService',
      alerter: './modules/alerter',
      jquery: './libs/jquery-2.1.4.min',
      angular: './libs/angular.min'
    },
    shim: {
      angular: {
        exports: 'angular'
      }
    }
  })

  // 异步加载+回调方法
  requirejs(['alerter', 'angular'], function (alerter, angular) {
    alerter.showMsg()
    console.log(angular)
  })
})()
js 复制代码
// alerter.js
define(['dataService', 'jquery'], function (dataService, $) {
  const msg = 'alerter.js'
  function showMsg() {
    console.log(msg, dataService.getName())
  }
  $('body').css('background', 'pink')
  return { showMsg }
});
js 复制代码
// dataService.js
define(function () {
  const name = 'dataService.js'
  function getName() {
    return name
  }
  return { getName }
});

兼容判断 AMD & CJS

UMD 的出现

js 复制代码
(define('amdModule', [], (require, export1, module) => {
  // 引入部分
  const dependencyModule1 = require('./dependencyModule1');
  const dependencyModule2 = require('./dependencyModule2');

  // 核心逻辑
  let count = 0;
  const increase = () => ++count;
  const reset = val => {
    count = 0;
    // dependencyModule1.xxx
    // ...
  }

  export1.increase = increase;
  export1.reset = reset;
}))(
  // 目标:一次性去区分CJS和AMD
  // 1. CJS factory
  // 2. module module.exports
  // 3. define
  typeof module === "Object" && module.exports && typeof define !== "function" ? factory => module.exports = factory(require, exports, module) : define
)

3. CMD sea.js

  1. 优点:按需加载,依赖就近
  2. 缺点:依赖于打包,加载逻辑存在于每个模块中,扩大了模块的体积
  3. AMD 和 CMD 区别 => 依赖就近,按需加载
txt 复制代码
|----/js
     |----/libs
          |----sea.js
     |----/modules
          |----main.js
          |----module1.js
          |----module2.js
          |----module3.js
          |----module4.js
     |----main.js
|----index.html
html 复制代码
<!-- index.html -->
<!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="./js/libs/sea.js"></script>
  <script>
    seajs.use('./js/modules/main.js')
  </script>
</body>

</html>
js 复制代码
// main.js
define(function (require) {
  const module1 = require('./module1') // 按需加载,依赖就近(不需要提前加载)
  console.log(module1.getModule1())

  const module4 = require('./module4')
  module4.showModule4()

})
js 复制代码
// module1.js
define(function (require, exports, module) {
  const msg = 'module1'
  function getMsg() {
    return msg
  }
  module.exports = { getModule1: getMsg }
})
js 复制代码
// module2.js
define(function (require, exports, module) {
  const msg = 'module2'
  function showMsg() {
    console.log(msg)
  }
  module.exports = showMsg
})
js 复制代码
// module3.js
define(function (require, exports, module) {
  const msg = 'module3'
  function showMsg() {
    console.log(msg)
  }
  exports.module3 = { showModule3: showMsg }
})
js 复制代码
// module4.js
define(function (require, exports, module) {
  const msg = 'module4'

  // 同步引入
  const module2 = require('./module2')
  module2()

  // 异步引入
  require.async('./module3', function (m3) {
    m3.module3.showModule3()
  })

  function showMsg() {
    console.log(msg)
  }

  exports.showModule4 = showMsg
})

4. ES6 module

  1. 优点:通过一种最统一的形态整合了 Js 的模块化
  2. 缺点:本质上还是运行时做的依赖分析
txt 复制代码
|----/src
      |----/js
          |----main.js
          |----module1.js
          |----module2.js
          |----module3.js
      |----index.html
|----.babelrc
|----package.json
|----readme.md
md 复制代码
<!-- readme.md -->
# 项目说明

## 在根目录定义 .babelrc 文件

1. 在 babel 插件工作之前会去读取 .babelrc 里面的内容,根据它里面的内容去干湖

## 编译

1. `babel src/js -d build/js`
2. `browserify build/js/main.js -o dist/js/bundle.js`
js 复制代码
// package.json
{
  "name": "chyco_es6_demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-preset-es2015": "^6.24.1"
  },
  "dependencies": {
    "jquery": "^1.12.4"
  }
}
js 复制代码
// .babelrc
{
  "presets": ["es2015"]
}
html 复制代码
<!-- index.html -->
<!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="../dist/js/bundle.js"></script>
</body>

</html>
js 复制代码
// main.js
import $ from "jquery";
import { fn1, fn2, name } from "./module1";
import { fn1 as m2fn1, fn2 as m2fn2, name as m2name } from "./module2";
import module3 from "./module3";

fn1()
fn2()
console.log(name)

m2fn1()
m2fn2()
console.log(m2name)

module3()

$('body').css('background', 'pink')
js 复制代码
// module1.js

// 分别暴露
export function fn1() {
  console.log('module1 fn1')
}

export function fn2() {
  console.log('module1 fn2')
}

export const name = 'module1'
js 复制代码
// module2.js

// 统一暴露/常规暴露
function fn1() {
  console.log('module2 fn1')
}

function fn2() {
  console.log('module2 fn2')
}
const name = 'module2'

export { fn1, fn2, name }
js 复制代码
// module3.js

export default () => {
  console.log('module3')
}

面试问题:ESM 动态模块

考察:export promise

懒加载ES11原生解决方案

js 复制代码
import('./esModule.js').then((dynamicEsModule) => {
  dynamicEsModule.increase()
})
相关推荐
Apifox3 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
-代号95277 分钟前
【JavaScript】十四、轮播图
javascript·css·css3
云徒川26 分钟前
【设计模式】原型模式
java·设计模式·原型模式
树上有只程序猿30 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187301 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox