zoukankan      html  css  js  c++  java
  • 前端模块化 | 解读JS模块化开发中的 require、import 和 export

    本篇分为两个部分
     第一部分:总结了ES6出现之前,在当时现有的运行环境中,实现"模块"的方式;
     第二部分:总结了ES6出现后,module成为ES6标准,客户端实现模块化的解决方案;

    一、require时代

    Javascript社区做了很多努力,在当时现有的运行环境中,实现了“模块”的效果

    原始写法:

    模块就是实现特定功能的一组方法。
    只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。

    1 function m1(){
    2  //...
    3 }
    4 function m2(){
    5  //...  
    6 }

      上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。
      这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

    对象写法:

      为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面

    1 var module1 = new Object({
    2  _count : 0,
    3  m1 : function (){
    4   //...
    5  },
    6  m2 : function (){
    7   //...
    8  }
    9 });

      上面的函数m1()和m2(),都封装在 module1 对象里。使用的时候,就是调用这个对象的属性

    1 module1.m1();

      这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

    1 module._count = 1;

    立即执行函数写法:

      使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的

     1 var module = (function() {
     2 var _count = 0;
     3 var m1 = function() {
     4 alert(_count)
     5 }
     6 var m2 = function() {
     7 alert(_count + 1)
     8 }
     9 return {
    10 m1: m1,
    11 m2: m2
    12 }
    13 })();

      使用上面的写法,外部代码无法读取内部的_count变量。console.info(module._count); //undefined     

      module就是Javascript模块的基本写法。

    主流模块规范

      在es6以前,还没有提出一套官方的规范,从社区和框架推广程度而言, 通行的javascript模块规范有两种:CommonJS 和 AMD

    1.1 CommonJS 规范:

      2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志着"Javascript模块化编程"正式诞生。前端(客户端)的复杂程度有限,没有模块也是可以的,但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

      node编程中最重要的思想之一就是模块,而正是这个思想,让JavaScript的大规模工程成为可能。模块化编程在js界流行,也是基于此,随后在浏览器端,requirejs和seajs之类的工具包也出现了,可以说在对应规范下,require统治了ES6之前的所有模块化编程,即使现在,在ES6 module被完全实现之前,还是这样。

      在CommonJS中,暴露模块使用module.exports和exports,很多人不明白暴露对象为什么会有两个,后面会介绍区别

      在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。

    1 var math = require('math');

      然后,就可以调用模块提供的方法:

    1 var math = require('math');
    2 math.add(2,3); // 5

      正是由于CommonJS 使用的require方式的推动,才有了后面的AMD、CMD 也采用的require方式来引用模块的风格

    AMD规范:

      有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。
      但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。如果在浏览器中运行,会有一个很大的问题

    1 var math = require('math');
    2 math.add(2, 3);

      第二行math.add(2, 3),在第一行require(‘math’)之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于客户端浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死”状态。因此,浏览器端的模块,不能采用“同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。

      AMD是“Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。

      它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
      模块必须采用特定的define()函数来定义。

    1 define(id?, dependencies?, factory)

      id:字符串,模块名称(可选)  dependencies: 是我们要载入的依赖模块(可选),使用相对路径。注意是数组格式   factory: 工厂方法,返回一个模块函数

      如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

    1      // math.js
    2   define(function (){
    3     var add = function (x,y){
    4       return x+y;
    5     };
    6     return {
    7       add: add
    8     };
    9   });    

      如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

    1 define(['Lib'], function(Lib){
    2   function foo(){
    3     Lib.doSomething();
    4   }
    5   return {
    6     foo : foo
    7   };
    8 });

      当require()函数加载上面这个模块的时候,就会先加载Lib.js文件。AMD也采用require()语句加载模块,但是不同于CommonJS,

      它要求两个参数:require([module], callback);

      第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。

      如果将前面的代码改写成AMD形式,就是下面这样:

    1 require(['math'], function (math) {
    2  math.add(2, 3);
    3 });

      math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。

    CMD规范:
      
    CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。它写起来是这样的:

    1 define(function(require, exports, module) {
    2     var clock = require('clock');
    3     clock.start();
    4 });

      CMD与AMD一样,也是采用特定的define()函数来定义,用require方式来引用模块

      define(id?, dependencies?, factory)
        id:字符串,模块名称(可选)
      dependencies: 是我们要载入的依赖模块(可选),使用相对路径。,注意是数组格式
      factory: 工厂方法,返回一个模块函数

    1 define('hello', ['jquery'], function(require, exports, module) {
    2   // 模块代码
    3 });

      如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

    define(function(require, exports, module) {
    // 模块代码
    });

      注意:带 id 和 dependencies 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。

    CMD与AMD区别

      AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块;AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

  • 相关阅读:
    缓动公式整理(附:C#实现及WPF原版对比)
    [C#] (原创)一步一步教你自定义控件——02,ScrollBar(滚动条)
    连线
    2020.10.18:YC中学模拟赛
    【并查集】一种与时间赛跑的巧妙算法
    SpringBoot-启动流程
    Java中的ReentrantLock锁
    Tomcat-如何用线程池处理http并发请求
    Tomcat-如何建立连接获取http请求
    关于数据结构
  • 原文地址:https://www.cnblogs.com/wuhaoquan/p/8807173.html
Copyright © 2011-2022 走看看