阅读 33

前端模块化发展历史

#前端模块化历史

1.为什么要模块化

  • 1.抽离公共代码
  • 2.隔离作用域、避免变量冲突

2.前端发展历史

  • web1.0 时代
    • 网页是“只读的”,用户只能搜索信息,浏览信息。
  • web2.0 时代
    • 主要特征是有了交互的概念,不仅仅局限于浏览,可以自己创建内容并上传
    • JavaScript作为嵌入式的脚本语言诞生
  • web2.0 出现的问题 + Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀,需要在前端也引入模块化概念

3.解决方案的演变

针对以上问题,前端开发者开始探索自己的解决办法

  • IIFE

    最先开始我们每一个功能都写一个function;就像这样

    function fn1(){
       statement
    }
    
    function fn2(){
       statement
    }
    
    复制代码

    这种做法的缺点很明显:污染了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间没什么关系,为了解决上面问题,对象的写法应运而生,可以把所有的模块成员封装在一个对象中 像这样写法

      var myModule = {
          var1: 1,
      
          var2: 2,
      
          fn1: function(){
      
          },
      
          fn2: function(){
      
          }
      }
    复制代码

    看似不错的解决方案,但是也有缺陷,外部可以随意修改内部成员;比如myModule.var1 = 100;这样就会产生安全问题 所以IIFE就出来了 IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。

    var myModule = (function module(){
          var someThing = "123";
          var otherThing = [1,2,3];
            
          function doSomeThing(){
            console.log(someThing);
          }
            
          function doOtherThing(){
            console.log(otherThing);
          }
            
          return {
            doSomeThing:doSomeThing,
            doOtherThing:doOtherThing
          }
      })();
    
    myModule.doSomeThing();
    myModule.doOtherThing();
    
    复制代码
  • common.js 一开始大家都认为JS是辣鸡,没什么用,官方定义的API只能构建基于浏览器的应用程序,CommonJS就按耐不住了 ,CommonJS API定义很多普通应用程序(主要指非浏览器的应用)使用的API,从而填补了这个空白。官方网址:www.commonjs.org/ 它的终极目标是提供一个类似Python,Ruby和Java标准库。 这样的话,开发者可以使用CommonJS API编写应用程序,然后这些应用可以运行在不同的JavaScript解释器和不同的主机环境中。 node.js 就是应用common.js开发的项目,2009年诞生。在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限; 但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。NodeJS是CommonJS规范的实现。 列举一个最简单的 common.js 模块引用 看看common.js 如何工作的

    A.js
    function speak(name){
    console.log('speak name')
    }
    module.exports = speak
    
    B.js
    require('./A.js')
    speak('common.js')
    // common.js
    复制代码

    我们来手写一个common.js 的规范

     (function (exports, require, module, __filename, __dirname) {
         module.exports = function () {
             console.log(__dirname);
         }
     })
    复制代码

    node 做的就是调用此方法,将 exports, require, module, __filename, __dirname 传入进去

    我百度了 require 的 方法

    const fs = require('fs');
    const path = require('path');
    const vm = require('vm'); // 虚拟机,提供沙箱环境
    const wrapper = [
        '(function(exports, module, require){',
        '})'
    ]
    function Module(absPath) {
        this.id = absPath; // 存储当前路径
        this.exports = {};
    }
    Module.prototype.load = function() {
        let script = fs.readFileSync(this.id, 'utf8');
        let fnStr = wrapper[0] + script + wrapper[1];
        let fn = vm.runInThisContext(fnStr); // 沙箱执行当前函数
        fn(this.exports, this, req); // 让拼出的函数执行
    }
    function req(file ) {
       let absPath = path.resolve(__dirname, file); // 1. 获取文件路径
       let module = new Module(absPath); // 2. 创建一个模块
       module.load(); // 3. 加载方法
       return module.exports;
    }
    let a = req('./a.js')
    复制代码
  • AMD 规范 尴尬的浏览器 仔细看上面的代码,会发现require是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。

    这在服务器端实现很简单,也很自然,然而, 想在浏览器端实现问题却很多。

    浏览器端,加载JavaScript最佳、最容易的方式是在document中插入script 标签。但脚本标签天生异步,传统CommonJS模块在浏览器环境中无法正常加载。

    解决思路之一是,开发一个服务器端组件,对模块代码作静态分析,将模块与它的依赖列表一起返回给浏览器端。 这很好使,但需要服务器安装额外的组件,并因此要调整一系列底层架构。

    另一种解决思路是,用一套标准模板来封装模块定义,但是对于模块应该怎么定义和怎么加载,又产生的分歧:

    AMD AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范

    由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出

    requireJS主要解决两个问题

    • 1.多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
    • 2.js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
        // 定义模块 myModule.js
        define(['dependency'], function(){
            var name = 'Byron';
            function printName(){
                console.log(name);
            }
        
            return {
                printName: printName
            };
        });
        
        // 加载模块
        require(['myModule'], function (my){
          my.printName();
        });
    
    复制代码
  • CMD规范

    • CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同

    • 语法: Sea.js 推崇一个模块一个文件,遵循统一的写法

    • define(id?, deps?, factory)

    • 因为CMD推崇

      一个文件一个模块,所以经常就用文件名作为模块id CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写
      function(require, exports, module) require require 是 factory 函数的第一个参数

      require(id) require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口

      exports exports 是一个对象,用来向外提供模块接口

      module module 是一个对象,上面存储了与当前模块相关联的一些属性和方法

      
            // 定义模块  myModule.js
            define(function(require, exports, module) {
              var $ = require('jquery.js')
              $('div').addClass('active');
            });
            
            // 加载模块
            seajs.use(['myModule.js'], function(my){
            
            });
      复制代码

    玉伯,淘宝前端类库 KISSY、前端模块化开发框架SeaJS、前端基础类库Arale的创始人

    www.cnblogs.com/wpbars/p/42… 关于sea.js 的采访,推荐大家去看,会解决很多人对原生 javascript 和 使用框架等等一系列的困惑

    问:有人认为,过多使用框架会导致开发者忽视对JavaScript原生语言特性的学习,变得懒惰,或者基础会很薄弱;也有人认为, 只要精通原生JavaScript就可以,无需使用框架也能开发出应用,对此您怎么看?

    玉伯:在前端开发工作中,JavaScript语言的使用只占比较小的一部分。更多的精力,需要花在语言之外。掌握JavaScript语言的基本使用, 就如我们在学校学习,需要掌握中学数学的内容一样。这一块我觉得不难,只要肯花时间去学就好。值得提醒的是,如果只精通原生JavaScript, 那么就如只会中学数学一样,虽然已经能解决很多问题,但要优雅地、更简单地解决问题就比较困难了。框架可以让你从重复低级工作中脱离出来, 特别是应用复杂到一定程度时,如果没有框架层的抽象,代码往往会复杂得难以维护。在前端开发越来越复杂的今天,框架已经是必不可少的了。 学会去用,去拥抱,可以事半功倍。框架不会让你偷懒,更不会让你基础薄弱。即便是jQuery,如果你对DOM的基本原生操作不太会,那么你也很 难真正把jQuery使用得很好。就如中学数学都不理解的话,要把大学数学用得很好只会是梦。

  • AMD与CMD区别

    关于这两个的区别网上可以搜出一堆文章,简单总结一下

    最明显的区别就是在模块定义时对依赖的处理不同

    AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块 CMD推崇就近依赖,只有在用到某个模块的时候再去require 这种区别各有优劣,只是语法上的差距,而且requireJS和SeaJS都支持对方的写法

  • UMD规范---通用模块规范 UMD:Universal Module Definition(通用模块规范)是由社区想出来的一种整合了CommonJS和AMD两个模块定义规范的方法。 原理是用一个工厂函数来统一不同的模块定义规范。 下面是vue 2.6 源码 编译用的就是UMD

        /*!
         * Vue.js v2.6.10
         * (c) 2014-2019 Evan You
         * Released under the MIT License.
         */
        (function (global, factory) {
            typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
                typeof define === 'function' && define.amd ? define(factory) :
                    (global = global || self, global.Vue = factory());
        }(this, function () {
            //  源码内容
        }))
    复制代码
  • es6 模块化规范 ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015)。 它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言 。

       export const age = 12;
       export function play(){}
    
       import {age,play} from 'index.js'
    复制代码
      CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。(深拷贝)
      
      ES6 Modules 的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,
      就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
      换句话说,ES6的import 有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。
      因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。(浅拷贝)
      
      运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,
      这种加载称为“运行时加载”。
      
      编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。
      即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”
    复制代码

4.我们用ES6写的项目是如何编译成 AMD UMD CMD 规范的包的?

1.目前市面上的的打包工具基本都是依赖 node.js 也就是common.js 规范 2.构建工具有 webpack rollup.js 3.每个构建工具其实就是一个工程,做的主要工作就是首先要识别es6代码 比如 let const class ,然后将对应的语法转化成原生javaScript ,在通过 配置的输出模块规范 进行模块化的输出

```
//webpack 配置引入方式 esm 代表es6  cjs 代表 common.js

 module.export = {
    output:{
            libraryTarget:'umd'
        }
 }
 // rollup.js 
 
   output: [
     {
       file: './lib/index.umd.js',
       name: 'NRTCCalling',
       format: 'umd',
       plugins: [!isDev && terser()],
       globals,
     },
     {
       file: './lib/index.esm.js',
       format: 'esm',
       plugins: [!isDev && terser()],
       globals,
     },
     {
       file: './lib/index.cjs.js',
       format: 'cjs',
       plugins: [!isDev && terser()],
       globals,
     },
   ],
```



    
复制代码
文章分类
前端
文章标签