前端模块化语法

581 阅读6分钟

概论

  • CommonJS、AMD 和 CMD 规范
  • CommonJS 模块采用同步加载,适合服务端却不适合浏览器

Common.js规范

Node.js中模块化开发规范就是使用common.js规范

  • Node.js规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
  • 模块内部可以使用exports对象进行成员导出, 使用require方法导入其他模块。

在A模块中定义了一些函数,模块内部定义的变量和函数默认情况下在外部无法得到,将其导出

B模块要想使用A模块的方法,则要其引入

模块化.jpg

模块化暴露成员

exports

导出多个成员

  // 假设有两个文件 a.js b.js
  这是没有script标签引入了,在a.js模块中想引入b.js,通过 require('./b.js')来引入加载执行文件的代码
  // 注意可以引入文件的 .js 后缀名,不能省略相对路径的 ./
  require方法(加载模块)有两个作用,1.加载文件执行里面的代码 
                                 2.返回被加载文件模块导出的接口对象
                      
   #每个文件模块都提供了一个对象:exports,默认是一个空对象你要做的就是将需要被外界访问的成员挂载到这个 exports对象中
   
   // 当前 a.js模块,exports可以导出多次
   var test = "a.js中的变量";
   exports.test=test;
   exports.add = function (x,y){
      return x+y;
   }
   
  //b.js---使用
   var aExports = require('./a.js')
   aExports.add();

module.exports

导出单个成员

  使用 exports 导出成员,默认是一个对象,要想使用对象中的成员必须 . 出来,有时候对于一个模块,我仅仅就是希望导出的就是一个方法,而不是一个对象
  ​
  // a.js模块
  function add(x,y){
      return x+y;
  }
  module.exports = add
  // 或者直接
  model.export = function add(x,y){
      return x+y;
  }
  ​
  //b.js模块
  var add = require('./a.js') #此时add不再是个对象,而是个函数
  ​

以下情况会覆盖

  
  module.exports = 'hello'
  // 或者直接
  model.export = function add(x,y){
      return x+y;
  }

如果要导出多个成员

  
  model.export = {
      add:function(){
      },
      str:'hello'
  }

两者区别

模块最后 return module.exports

exports 等于 module.exports, 指向了同样的地址

因为最后返回的是 module.exports 这个对象,如果指向返回某个具体的值或者函数,直接对他赋值即可

我们发现,每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式很麻烦,点儿的太多了所以,Node 为了简化你的操作,专门提供了一个变量:exports 等于 module.exports

  #1 exports.foo = 'hello' 等价于 module.exports.foo = 'hello' 
  //exports 等于 module.exports,指向同一个地址
     
  # 如果给 exports='hlllo',那么就指向了别的地址,而最后 return module.exports
  // 默认在代码的最后有一句:
  // 一定要记住,最后 return 的是 module.exports 
  module.exports = 'hello'
  exports.foo = 'world'   //不管用了

exports.png

总结

  真正使用:
   导出多个成员  ---> exports
   导出单个成员  --->module.exports 赋值

ES6模块化规范

  • 使用export导出,import 引入需要 加上大括号

  • 使用 export default 导出,import 引入不需要 加上大括号

    ECMAScript5 及之前的版本不支持原生模块化,需要引入 AMD 规范的 RequireJS 或者 AMD 规范的 Seajs 等第三方库来实现。

    直到 ECMAScript6 才支持原生模块化,其不但具有 CommonJS 规范和 AMD 规范的优点,而且实现得更加友好,语法较之 CommonJS 更简洁、支持编译时加载(静态加载),循环依赖处理得更好。

    ES6 模块功能主要由两个命令构成:export 和 import,export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。

​ ES6 模块本身,因为它不是对象

  • export 命令用于规定模块的对外接口

  • import 命令用于输入其他模块提供的功能

      # 第一种方法使用大括号指定所要输出的一组变量。与第二种方法(直接放置在var语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
      /** 定义模块 math.js **/
      var basicNum = 0;
      var add = function (a, b) {
          return a + b;
      };
      ​
      //暴露basicNum变量和add方法1
      export { basicNum, add };
      ==================================================================
      // 暴露basicNum变量和add方法2  export var basicNum = 0
      export function add(a,b){
          return a+b
      }
    
      /** 引用模块 可省略.js **/
      import { basicNum, add } from ‘./math.js‘;
      function test(ele) {
          ele.textContent = add(99 + basicNum);
      } 
    

export default

​ 使用 import 命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

​ 为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。

  
  // 正确
  export var a = 1;
  ​
  // 正确
  var a = 1;
  export default a;
  ​
  // 错误
  export default var a = 1;
  ​

上面代码中,export default a的含义是将变量a的值赋给变量default。所以,最后一种写法会报错。

同样地,因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。

  
  // 正确
  export default 42;
  ​
  // 报错
  export 42;

上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定对外接口为default

有了export default命令,输入模块时就非常直观了,以输入 lodash 模块为例。

  import _ from 'lodash';

如果想在一条import语句中,同时输入默认方法和其他接口,可以写成下面这样。

  import _, { each, forEach } from 'lodash';

对应上面代码的export语句如下。

  export default function (obj) {
    // ···
  }
  ​
  export function each(obj, iterator, context) {
    // ···
  }
  ​
  export { each as forEach };

上面代码的最后一行的意思是,暴露出forEach接口,默认指向each接口,即forEacheach指向同一个方法。

import()

  • import命令会被 JavaScript 引擎静态分析 ,会提升到最顶层
  • 这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块
  //引擎处理import语句是在编译时,这时不会去分析或执行if语句,所以import语句放在if代码块之中毫无意义,因此会报句法错误,而不是执行时错误
  if (x === 2) {
    import MyModual from './myModual';
  }

因此,ES2020提案 引入import()函数,支持动态加载模块。

import()返回一个 Promise 对象。下面是一个例子。

  const main = document.querySelector('main');
  ​
  import(`./section-modules/${someVariable}.js`)
    .then(module => {
      module.loadPageInto(main);
    })
    .catch(err => {
      main.textContent = err.message;
    });
适用场合

(1) 按需加载

import()可以在需要的时候,再加载某个模块。

  button.addEventListener('click', event => {
    import('./dialogBox.js')
    .then(dialogBox => {
      dialogBox.open();
    })
    .catch(error => {
      /* Error handling */
    })
  });

上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。

(2)条件加载

import()可以放在if代码块,根据不同的情况,加载不同的模块。

  if (condition) {
    import('moduleA').then(...);
  } else {
    import('moduleB').then(...);
  }

上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。

(3) 动态加载

import()允许模块路径动态生成。

  import(f())
  .then(...);

上面代码中,根据函数f的返回结果,加载不同的模块。