JavaScript | 深度解析模块

在这里插入图片描述

概述

有经验的开发者,会把代码拆分成独立的逻辑或者文件,然后再封装成一个个模块。在JS的世界里,模块是一个重要的概念,但由于版本变迁的原因,想要完全掌握着实不易。

我们知道,2009 年发布的ECMASCript 5(简写ES5)标准并没有对模块管理做出规定,导致开发者只能以自己的方式模拟模块管理。于是,就出现了不同的模块管理机制。这期间出现的模块管理方式有如下几种:

  1. 立即执行函数表达式
  2. 显示模块
  3. 异步模块
  4. 共同模块
  5. CommonJS
  6. 通用模块定义

鉴于模块在开发中的重要性,ES6在借鉴Java、Python语言的特性后,引入import、export关键字来管理模块。

1 ES5 模块管理机制

1.1 立即执行函数表达式

使用ES5可以通过立即执行函数来引入第三方JS代码,比如:

(function(){
    
    
  // 模块的内部逻辑
})();

使用立即执行函数表达式可以达到变量私有化的目的,避免产生全局变量,这种方法的缺点,一是外部环境无法访问模块内部,二是无法形成模块依赖管理机制。

1.2 显示模块声明

为了解决立即执行函数无法将模块暴露给外部的问题,开发者想到了一个办法,就是通过全局对象来实现。

var module = (function(){
    
    

  function createPerson(name, age){
    
    
    console.log("name: "+name+", age: "+age);
  }

  return {
    
    
    createPerson: createPerson
  }
})();

module.createPerson('John', 23);

显示模块声明,将函数返回值返回给一个全局变量。随后就可以通过这个全局变量来访问模块暴露的接口。但是,这种办法还是没有实现模块的管理机制。

1.3 异步模块定义

异步模块定义实现了模块管理规范,比如RequireJS就是按照这种规范实现的第三方库:通过define 函数定义和声明依赖。

define("myModule", ["music"], function(music){
    
    
  var myModule = {
    
    };
  myModule.playMusic = function(){
    
    
    music.play();
  }
  return myModule;
}

异步模块定义的好处是所有的模块加载都是异步的,也就是说可以同时加载多个模块。但是,这种方式的缺点是模块不是按照它们声明的样子就行顺序加载。

1.4 共同模块定义

共同模块定义是一种懒加载的模块管理方式,也是通过define函数来定义模块,通常一个文件就是一个模块。

在定义模块的时候,会注入三个参数:require、exports、module。

  1. require 函数用于动态引用模块
  2. exports 用来暴露模块接口
  3. moudule 提供当前模块参数
define("myModule", function(require, exports, module){
    
    
  var music = require("music");

  exports.play = function(){
    
    
    music.play();
  }
});

require(["myModule"], function(myModule){
    
    
  myModule.play();
});

共同模块定义解决了异步定义加载顺序的问题,但是加载时间低于定义异步方式。但是,开发者可以使用require.async来实现异步加载。


define("myModule", function(require, exports, module){
    
    
  var music = require.async("music");

  exports.play = function(){
    
    
    music.play();
  }
});

1.5 CommonJS

CommonJS 是Node.js 默认的模块管理规范。他约束一个文件就是一个模块,同时去除了define函数的束缚。在模块中,通过内置的exports对象来导出接口。

var a = require("moduleA");
var b = require("moduleB");

exports.doSomething = function(){
    
    
  a.doSomething();
  b.doSomething();
}

值得一提的是,使用CommonJS 加载模块都是顺序执行的。

1.6 通用模块定义

通用模块定义,从本质上讲就是上述各种模块定义的代理,看下面的代码:

(function(root, factory){
    
    
  if(typeof define === "function" && define.amd){
    
    
    // 异步模块定义
    define(["moduleA", "moduleB"], factory);
  }else if(typeof exports === "object"){
    
    
    // CommonJS模块定义
    module.exports = factory(require("moduleA"), require("moduleB"));
  }else{
    
    
    // 浏览器全局变量
    root.returnExports = factory(root.moduleA, root.moduleB);
  }
})(this, function(a, b){
    
    
  return {
    
    
    pa: a.doSomething(),
    pb: b.doSomething()
  }
})

这种方式的优点非常明显,不用引入第三方库。只要在每个模块引入即可。但缺点也在于此,会造成代码的冗余。

2 ES6 模块管理机制

ES6 不再从代码层面解决模块的管理机制,而是引入了ES5 中的保留字:import,export。从使用方法上来讲,ES6 的模块管理和CommonJS 颇为类似,也是规定每个文件就是一个模块,模块内部的代码不能从外部访问,必须暴露出来。

import a from "moduleA";

import {
    
    b} from "moduleB";

var func = function(){
    
    
  a.doSomething();
}

var pa = a.xxx;

export {
    
    func, pa, xxx as pb};

ES6 对模块的引用和CommonJS有着根本的区别,前者不仅可以引用整个模块,也可以引用模块中的变量和函数,这样大大提升了程序开发的灵活性。

猜你喜欢

转载自blog.csdn.net/alexwei2009/article/details/127511110
今日推荐