likes
comments
collection
share

前端面试复习4---JS 模块化基本认识

作者站长头像
站长
· 阅读数 30

JS 模块化基本认识

背景

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

JS 的模块化需求日益增长

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

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

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

<script src="jquery.js" async></script>
默认模式:
                 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 创建一个最简单的模块,和在外部调用模块内部的函数

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 这个模块内部就可以调用这两个模块了

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) + 本身逻辑能力 + 依赖的能力

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()

可能被问到的问题

// 部分开源项目分别传入全局、指针、框架作为参数
(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

|----/modules
     |----/module1.js
     |----/module2.js
     |----/module3.js
|----/app.js
|----/package.json
// 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"
  }
}
// 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)
// module1.js
module.exports = {
  msg: '---module1---',
  fn() {
    console.log(this.msg)
  }
}
// module2.js
module.exports = () => {
  console.log('---module2---')
}
// 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

|----/js
     |----/src
          |----app.js
          |----/module1.js
          |----/module2.js
          |----/module3.js
|----/index.html
|----/package.json
// 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"
  }
}
<!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>
// 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)
// module1.js
module.exports = {
  msg: '---module1---',
  fn() {
    console.log(this.msg)
  }
}
// module2.js
module.exports = () => {
  console.log('---module2---')
}
// 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]
# 导读

## 安装 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. 缺点:会有引入成本,没有考虑按需加载
|----/js
     |----/libs
          |----angular.min.js
          |----jquery-2.1.4.min.js
          |----require.min.js
     |----/modules
          |----alerter.js
          |----dataService.js
     |----main.js
|----index.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>
// 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)
  })
})()
// alerter.js
define(['dataService', 'jquery'], function (dataService, $) {
  const msg = 'alerter.js'
  function showMsg() {
    console.log(msg, dataService.getName())
  }
  $('body').css('background', 'pink')
  return { showMsg }
});
// dataService.js
define(function () {
  const name = 'dataService.js'
  function getName() {
    return name
  }
  return { getName }
});

兼容判断 AMD & CJS

UMD 的出现

(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 区别 => 依赖就近,按需加载
|----/js
     |----/libs
          |----sea.js
     |----/modules
          |----main.js
          |----module1.js
          |----module2.js
          |----module3.js
          |----module4.js
     |----main.js
|----index.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>
// main.js
define(function (require) {
  const module1 = require('./module1') // 按需加载,依赖就近(不需要提前加载)
  console.log(module1.getModule1())

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

})
// module1.js
define(function (require, exports, module) {
  const msg = 'module1'
  function getMsg() {
    return msg
  }
  module.exports = { getModule1: getMsg }
})
// module2.js
define(function (require, exports, module) {
  const msg = 'module2'
  function showMsg() {
    console.log(msg)
  }
  module.exports = showMsg
})
// module3.js
define(function (require, exports, module) {
  const msg = 'module3'
  function showMsg() {
    console.log(msg)
  }
  exports.module3 = { showModule3: showMsg }
})
// 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. 缺点:本质上还是运行时做的依赖分析
|----/src
      |----/js
          |----main.js
          |----module1.js
          |----module2.js
          |----module3.js
      |----index.html
|----.babelrc
|----package.json
|----readme.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`
// 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"
  }
}
// .babelrc
{
  "presets": ["es2015"]
}
<!-- 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>
// 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')
// module1.js

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

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

export const name = 'module1'
// module2.js

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

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

export { fn1, fn2, name }
// module3.js

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

面试问题:ESM 动态模块

考察:export promise

懒加载ES11原生解决方案

import('./esModule.js').then((dynamicEsModule) => {
  dynamicEsModule.increase()
})
转载自:https://juejin.cn/post/7262323026770952247
评论
请登录