阅读 1312

前端工程化篇

1.模块化机制

1.什么是模块?

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

2.模块化的进化过程

  • 全局function模式 : 将不同的功能封装成不同的全局函数

    • 编码: 将不同的功能封装成不同的全局函数
    • 问题: 污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系
    function m1(){
      //...
    }
    function m2(){
      //...
    }
    复制代码
  • namespace模式 : 简单对象封装

    • 作用: 减少了全局变量,解决命名冲突
    • 问题: 数据不安全(外部可以直接修改模块内部的数据)
    let myModule = {
      data: 'www.baidu.com',
      foo() {
        console.log(`foo() ${this.data}`)
      },
      bar() {
        console.log(`bar() ${this.data}`)
      }
    }
    myModule.data = 'other data' //能直接修改模块内部的数据
    myModule.foo() // foo() other data
    复制代码

    这样的写法会暴露所有模块成员,内部状态可以被外部改写。

  • IIFE模式:匿名函数自调用(闭包)

    • 作用: 数据是私有的, 外部只能通过暴露的方法操作
    • 编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口
    • 问题: 如果当前这个模块依赖另一个模块怎么办?
    // index.html文件
    <script type="text/javascript" src="module.js"></script>
    <script type="text/javascript">
        myModule.foo()
        myModule.bar()
        console.log(myModule.data) //undefined 不能访问模块内部数据
        myModule.data = 'xxxx' //不是修改的模块内部的data
        myModule.foo() //没有改变
    </script>
    复制代码
    // module.js文件
    (function(window) {
      let data = 'www.baidu.com'
      //操作数据的函数
      function foo() {
        //用于暴露有函数
        console.log(`foo() ${data}`)
      }
      function bar() {
        //用于暴露有函数
        console.log(`bar() ${data}`)
        otherFun() //内部调用
      }
      function otherFun() {
        //内部私有的函数
        console.log('otherFun()')
      }
      //暴露行为
      window.myModule = { foo, bar } //ES6写法
    })(window)
    复制代码

    最后得到的结果:

    1250759466-5c1c3943927e8_fix732.png

  • IIFE模式增强 : 引入依赖

这就是现代模块实现的基石

// module.js文件
(function(window, $) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar }
})(window, jQuery)
复制代码
 // index.html文件
  <!-- 引入的js必须有一定顺序 -->
  <script type="text/javascript" src="jquery-1.10.1.js"></script>
  <script type="text/javascript" src="module.js"></script>
  <script type="text/javascript">
    myModule.foo()
  </script>
复制代码

上例子通过jquery方法将页面的背景颜色改成红色,所以必须先引入jQuery库,就把这个库当作参数传入。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

3. 模块化的好处

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

4. 引入多个<script>后出现出现问题

  • 请求过多

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

  • 依赖模糊

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

  • 难以维护

以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。 模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。

5.模块化规范

  • 1.CommonJS

    • 在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
    • 特点
      • 所有代码都运行在模块作用域,不会污染全局作用域。
      • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
      • 模块加载的顺序,按照其在代码中出现的顺序。
    • 基本语法
      • 暴露模块:module.exports = value或exports.xxx = value
      • 引入模块:require(xxx),如果是第三方模块,xxx为模块名;如果是自定义模块,xxx为模块文件路径
    • 模块的加载机制
      • CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
  • 2.AMD

    • AMD规范基本语法

    定义暴露模块:

    //定义没有依赖的模块
    define(function(){
       return 模块
    })
    复制代码
    //定义有依赖的模块
    define(['module1', 'module2'], function(m1, m2){
       return 模块
    })
    复制代码

    引入使用模块:

    require(['module1', 'module2'], function(m1, m2){
       使用m1/m2
    })
    复制代码

    AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。

  • 3.CMD

    • CMD规范基本语法

      定义暴露模块:

    //定义没有依赖的模块
    define(function(require, exports, module){
      exports.xxx = value
      module.exports = value
    })
    复制代码
    //定义有依赖的模块
    define(function(require, exports, module){
      //引入依赖模块(同步)
      var module2 = require('./module2')
      //引入依赖模块(异步)
        require.async('./module3', function (m3) {
        })
      //暴露模块
      exports.xxx = value
    })
    复制代码
    • 引入使用模块:
    define(function (require) {
      var m1 = require('./module1')
      var m4 = require('./module4')
      m1.show()
      m4.show()
    })
    复制代码
  • 4.ES6模块化

    • ES6模块化语法

      • export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
      /** 定义模块 math.js **/
      var basicNum = 0;
      var add = function (a, b) {
          return a + b;
      };
      export { basicNum, add };
      /** 引用模块 **/
      import { basicNum, add } from './math';
      function test(ele) {
          ele.textContent = add(99 + basicNum);
      }
      复制代码

      export default命令,为模块指定默认输出。

      // export-default.js
      export default function () {
        console.log('foo');
      }
      
      // import-default.js
      import customName from './export-default';
      customName(); // 'foo'
      复制代码
    • ES6 模块与 CommonJS 模块的差异

      它们有两个重大差异:

      • ① CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

      • ② CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

6.总结

  • CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
  • CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
  • ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

2.Tree-Shaking

定义:

虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

具体可以看这遍文章

3.uglify原理

    1. 将code转换成AST
    1. 将AST进行优化,生成一个更小的AST
    1. 将新生成的AST再转化成code

4.webpack

5.前端微服务

可能是你见过最完善的微前端解决方案

文章分类
前端
文章标签