美文网首页程序员
模块化的演进过程

模块化的演进过程

作者: 翔子丶 | 来源:发表于2021-01-19 15:39 被阅读0次
    现代化项目
    • 要求前端有独立的项目
    • 采用数据驱动开发的方式增强可维护性
    • 复杂项目结构必须进行模块化管理
    • 重复规律性工作必须采取自动化工具实现

    模块化的演进过程

    1. 文件划分方式

      web最原始的模块系统,每个功能及相关状态数据单独放在不同js文件中,约定每个文件是一个独立的模块

      ├── module-a.js
      ├── module-b.js
      └── index.html
      

      缺点:

      • 模块直接在全局工作,污染全局作用域
      • 没有私有空间,模块内成员可以在外部被访问或修改
      • 模块增多,容易产生命名冲突
      • 无法管理模块与模块之间依赖关系
      • 维护过程中很难分辨每个成员所属的模块
    2. 命名空间方式

      约定每个模块只暴露一个全局对象,将每个模块包裹到一个全局对象

      window.moduleA = {
       method1() {
           console.log('a')
          }
      }
      

      只解决了命名冲突问题,其他问题依旧存在

    3. IIFE

      立即执行函数为模块提供私有空间,将每个模块放在立即执行函数形成的私有作用域,需暴露的成员,通过全局对象挂载

      (function() {
       const name = 'a'
          function method1 () {
           console.log('a')
          }
          window.moduleA = {
           method1
          }
      })()
      

      解决全局污染和命名冲突问题

    4. IIFE依赖参数

      使用IIFE参数作为依赖声明使用 使模块之间依赖关系更明显

      (function ($) { // 通过参数明显表明这个模块的依赖
        var name = 'module-a'
        function method1 () {
          console.log(name + '#method1')
          $('body').animate({ margin: '200px' })
        }
        window.moduleA = {
          method1: method1
        }
      })(jQuery)
      
    模块加载问题

    时间久、项目大时难维护

    更为理想的页面引入一个JS文件入口,用到的模块通过代码控制、按需加载

    模块化标准规范
    • nodejs中遵循commonJS规范来组织模块
      • 约定一个文件就是一个模块
      • 每个模块有单独的作用域
      • 通过module.exports导出成员
      • 通过require函数载入模块
    • 浏览器环境中遵循ES Modules(ES6)规范

    差异:CommonJs以同步模式加载模块,启动时加载模块,而执行当中不需要再加载,浏览器端使用会导致效率低下;早期浏览器使用AMD(Asynchronous Module Definition)规范,推出Require.js实现AMD规范

    • 定义模块

      // 定义一个模块 通过define函数去定义
      // param 模块名称,后期加载模块使用  数组,模块依赖项 函数,依赖项的导出成员一一对应
      // 模块需要导出成员,通过retuen方式实现
      define('module1', ['jquery', './module2'], function($, module2) {
        return {
              start: function () {
                  $('body').animate({ margin: '200px' })
                  module2()
              }
        }
      })
      
    • 载入模块

      // 载入一个模块
      require(['module1'], function (module1) {
        module1.start()
      })
      

    AMD缺陷:

    • AMD使用起来相对复杂
    • 模块JS文件请求频繁
    ES Module特性

    ES Module模块需要使用http serve模式运行,直接运行html文件会导致跨域,browser-sync --files *./.js

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>ES Module - 模块的特性</title>
      </head>
      <body>
        <!-- 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了 -->
        <script type="module">
          console.log('this is es module') // this is a module
        </script>
    
        <!-- 1. ESM 自动采用严格模式,忽略 'use strict' -->
        <script type="module">
          console.log(this) // undefined 非严格模式下是Window对象
        </script>
    
        <!-- 2. 每个 ES Module 都是运行在单独的私有作用域中 -->
        <script type="module">
          var foo = 100
          console.log(foo)
        </script>
        <script type="module">
          console.log(foo) // foo is not defined
        </script>
    
        <!-- 3. ESM 是通过 CORS 的方式请求外部 JS 模块的,引入的文件必须支持CORS -->
        <script
          type="module"
          src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"
        ></script>
    
        <!-- 4. ESM 的 script 标签会延迟执行脚本,等待网页渲染完成之后再去执行脚本,相当于defer属性 -->
        <script type="module" src="demo.js"></script>
        <p>需要显示的内容</p>
      </body>
    </html>
    
    ES Module导入和导出
    • 模块内的成员都存在与私有作用域中,外部访问需要暴露

      var name = 'foo module'
      
      function hello() {
        console.log('hello')
      }
      
      class Person {}
      
      // 重命名 name as fooName
      // export {
      //   name as default,
      //   hello as fooHello
      // }
      
      // 模块默认导出
      // export default name
      // import name from './module.js' 引入
      
      // var obj = { name, hello, Person }
      
      // 可以导出变量、函数、类 放在最后 比较直观
      export { name, hello, Person }
      
    • 导入导出注意事项

      // 导出
      // 这里的 `{ name, hello }` 不是一个对象字面量,
      // 它只是语法上的规则而已
      export { name, age }
      
      // export name // 错误的用法
      // export 'foo' // 同样错误的用法
      setTimeout(function () {
        name = 'ben'
      }, 1000)
      
      // 导入
      // CommonJS 中是先将模块整体导入为一个对象,然后从对象中结构出需要的成员
      // const { name, age } = require('./module.js')
      
      // ES Module 中 { } 是固定语法,就是直接提取模块导出成员
      import { name, age } from './module.js'
      console.log(name, age)
      
      // 导入成员并不是复制一个副本,而是直接导入模块成员的引用地址
      // 也就是说 import 得到的变量与 export 导入的变量在内存中是同一块空间。
      // 一旦模块中成员修改了,这里也会同时修改,
      setTimeout(function () {
        console.log(name, age)
      }, 1500)
      
      // 导入模块成员变量是只读的
      // name = 'tom' // 报错
      
      // 但是需要注意如果导入的是一个对象,对象的属性读写不受影响
      // name.xxx = 'xxx' // 正常
      // 1.需要填写完整名称
      import { name } from './module.js'
      // 2.不能省略.js或index.js
      import { lowercase } from './utils/index.js'
      // 3.不能省略./ 否则ES Module认为再加载第三方模块
      import { name } from 'module.js'
      import { name } from './module.js'
      import { name } from '/04-import/module.js'
      import { name } from 'http://localhost:3000/04-import/module.js'
      // 4.加载模块 并不提取
      import './module.js'
      // 5.导入全部 通过mod.xx拿到其中变量
      import * as mod from './module.js'
      console.log(mod.xxx)
      // 6.动态导入模块 Promise对象
      import('./module.js').then(function (module) {
        console.log(module)
      })
      // 7.同时导出默认成员命名成员
      import abc, { name, age } from './module.js'
      console.log(name, age, abc)
      
    • 导入导出成员

      // import { Button } from './button.js'
      // import { Avatar } from './avatar.js'
      // export { Button, Avatar }
      
      // default导出必须重命名 会作为当前index的默认导出
      export { default as Button } from './button.js'
      export { Avatar } from './avatar.js'
      
    • Polyfill兼容方案

      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>ES Module 浏览器环境 Polyfill</title>
      </head>
      <body>
        <!-- Promise polyfill -->
        <!-- nomodule 在支持ES Module的浏览器回执行两次 nomodule只会在不支持ES Module的浏览器使用 -->
        <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
        <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
        <!-- es-module-loader读取代码 通过babel去转换 -->
        <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
        <script type="module">
          import { foo } from './module.js'
          console.log(foo)
        </script>
      </body>
      </html>
      

    相关文章

      网友评论

        本文标题:模块化的演进过程

        本文链接:https://www.haomeiwen.com/subject/hazqzktx.html