前端模块化

·  阅读 94

前端模块化概要

前端模块化规范如下:

1.1 模块概要

JavaScript在早期的设计中就没有模块、包、类的概念,开发者需要模拟出类似的功能,来隔离、组织复杂的JavaScript代码,我们称为模块化。
模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块。

模块化开发的四点好处:

  1. 避免变量污染,命名冲突;
2. 提高代码复用率;
3. 提高了可维护性;
4. 方便依赖关系管理。

为了避免缺少模块带来的问题,我们可以看看程序员是怎样解决这个问题的:

1.2 函数封装

我们在讲函数的时候提到,函数一个功能就是实现特定逻辑的一组语句打包,而且JavaScript的作用域就是基于函数的,所以把函数作为模块化的第一步是很自然的事情,在一个文件里面编写几个相关函数就是最开始的模块了

//函数1
function fn1(){
  //statement
}
//函数2
function fn2(){
  //statement
} 

这样在需要函数功能的时候,引入文件,调用函数就可以了。

缺点:

污染了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间没什么关系,只是放在一个js文件里面。

1.3 对象封装

为了解决上面问题,对象的写法应运而生,可以把所有的模块成员封装在一个对象中

var myModule = {
  var1: 1,
  var2: 2,
  fn1: function () {},
  fn2: function () {},
};

这样我们在希望调用模块的时候引用对应文件:

myModule.fn2();

这样避免了变量污染,只要保证模块名唯一即可,同时同一模块内的成员也有了关系。
但是外部可以随意修改内部成员,这样就会产生意外的安全问题。

myModel.var1 = 100;

1.4 立即执行函数表达式(IIFE)

可以通过立即执行函数表达式(IIFE),来达到隐藏细节的目的。

var myModule = (function () {
  var var1 = 1;
  var var2 = 2;
  function fn1() {}
  function fn2() {}

  return {
    fn1: fn1,
    fn2: fn2,
  };
})();

这样在模块外部无法修改我们没有暴露出来的变量、函数。但是功能相对较弱,封装过程增加了工作量、仍会导致命名空间污染可能、闭包是有成本的。

JavaScript最初的作用仅仅是验证表单,后来会添加一些动画,但是这些js代码很多在一个文件中就可以完成了,所以,我们只需要在html文件中添加一个script标签。

后来,随着前端复杂度提高,为了能够提高项目代码的可读性、可扩展性等,我们的js文件逐渐多了起来,不再是一个js文件就可以解决的了,而是把每一个js文件当做一个模块。那么,这时的js引入方式是怎样的呢?大概是下面这样:

  <script src="jquery.js"></script>
  <script src="jquery.artDialog.js"></script>
  <script src="main.js"></script>
  <script src="app1.js"></script>
  <script src="app2.js"></script>
  <script src="app3.js"></script>

即简单的将所有的js文件统统放在一起。但是这些文件的顺序还不能出错,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。
相比于使用一个js文件,这种多个js文件实现最简单的模块化的思想是进步的。

缺点:

污染全局作用域。因为每一个模块都是暴露在全局的,简单的使用,会导致全局变量命名冲突,当然,我们也可以使用命名空间的方式来解决。

对于大型项目,各种js很多,开发人员必须手动解决模块和代码库的依赖关系,后期维护成本较高。

依赖关系不明显,不利于维护。 比如main.js需要使用jquery,但是,从上面的文件中,我们是看不出来的,如果jquery忘记了,那么就会报错。

1.5 模块化规范

常见的的JavaScript模块规范有:CommonJS、AMD、CMD、UMD、原生模块化

1.5.1 CommonJS

CommonJs 是服务器端模块的规范,Node.js采用了这个规范。

根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

// foobar.js

//私有变量
var test = 123;

//公有方法
exports.foobar = {
  foo:function () {
    // do something ...
  };
  bar:function () {
    //do something ...
  };
}
//require方法默认读取js文件,所以可以省略js后缀
var test = require('./foobar').foobar;
test.bar();

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。

1.5.2 AMD((Asynchromous Module Definition) 异步模块定义

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
AMD异步加载模块。它的模块支持对象 函数 构造器 字符串 JSON等各种类型的模块。
适用AMD规范适用define方法定义模块。

//通过数组引入依赖 ,回调函数通过形参传入依赖
define(['someModule1', 'someModule2'], function (someModule1, someModule2) {

function foo () {
/// someing
someModule1.test();
}

return {foo: foo}
});

AMD规范允许输出模块兼容CommonJS规范,这时define方法如下:

define(function (require, exports, module) {
  var reqModule = require("./someModule");
  reqModule.test();

  exports.doSome = function () {
    //something
  };
});

1.5.3 CMD(Common Module Definition)通用模块定义

CMD是SeaJS 在推广过程中对模块定义的规范化产出
CMD和AMD的区别有以下几点:

  1. 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过);

  2. CMD推崇依赖就近,AMD推崇依赖前置。

    //AMD
    define(["./a", "./b"], function (a, b) {
      //依赖一开始就写好
      a.test();
      b.test();
    });
    
    //CMD
    define(function (requie, exports, module) {
      //依赖可以就近书写
      var a = require("./a");
      a.test();
    
      //软依赖
      if (status) {
        var b = requie("./b");
        b.test();
      }
    });
    

    虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。

  3. AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的require,提供seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。

1.5.4 UMD

UMD是AMD和CommonJS的综合产物。AMD 浏览器第一的原则发展 异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。

这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

(function (obj, factory) {
  if (typeof exports === "object") {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    obj.eventUtil = factory();
  }
})(this, function () {
  //module ...
});

1.5.5 原生JS模块化(Native JS)

上述的模块都不是原生 JavaScript 模块。它们只不过是我们用模块模式(module pattern)、CommonJS 或 AMD 模仿的模块系统。

JavaScript标准制定者在 TC39(该标准定义了 ECMAScript 的语法与语义)已经为 ECMAScript 6(ES6)引入内置的模块系统了。

ES6 为导入(importing)导出(exporting)模块带来了很多可能性。

CommonJS

CommonJS就是一个JavaScript模块化的规范,该规范最初是用在服务器端NodeJS中,前端的webpack也是对CommonJS原生支持的。

根据这个规范,每一个文件就是一个模块,其内部定义的变量是属于这个模块的,不会对外暴露,也就是说不会污染全局变量。

CommonJS定义的模块分为:

  1. 模块引用(require):require()用来引入外部模块;
  2. 模块定义(exports):exports对象用于导出当前模块的方法或变量,唯一的导出口;
  3. 模块标识(module):module对象就代表模块本身。

2.1 NodeJS中使用CommonJS模块管理

2.1.1 模块定义

根据commonJS规范,一个单独的文件是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非为global对象的属性。

模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象。

mathLib.js模块定义

var message="Hello CommonJS!";

module.exports.message=message;
module.exports.add=(m,n)=>console.log(m+n);

2.1.2 模块依赖

加载模块用require方法,该方法读取一个文件并且执行,返回文件内部的module.exports对象。

myApp.js 模块依赖

var math=require('./mathLib');
console.log(math.message);
math.add(333,888);

2.2 在浏览器中使用CommonJS 模块管理

由于浏览器不支持 CommonJS格式。要想让浏览器用上这些模块,必须转换格式。浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量(module、exports、require、global)。只要能够提供这四个变量,浏览器就能加载 CommonJS 模块。
而browserify这样的一个工具,可以把nodejs的模块编译成浏览器可用的模块,解决上面提到的问题。本文将详细介绍Browserify实现Browserify是目前最常用的CommonJS格式转换的工具。
使用下列命令安装browserify:

npm install -g browserify
yarn global add browserify

使用下面的命令,就能将CommonJS格式转为浏览器可用的格式:

$ browserify myApp.js > myApp_01.js

虽然 Browserify 很强大,但不能在浏览器里操作,有时就很不方便。

纯浏览器的 CommonJS 模块加载器 require1k (github.com/Stuk/requir…

优点:

CommonJS规范在服务器端率先完成了JavaScript的模块化,解决了依赖、全局变量污染的问题,这也是js运行在服务器端的必要条件。

缺点:

此文主要是浏览器端js的模块化, 由于 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS是不太适用于浏览器端。

AMD

3.1 概要

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。而AMD规范的实现,就是大名鼎鼎的require.js了。

Asynchronous Module Definition,中文名是异步模块定义。它是一个在浏览器端模块化开发的规范,由于不是js原生支持,使用AMD规范进行页面开发需要用到对应的函数库,也就是大名鼎鼎的RequireJS,实际上AMD是RequireJS在推广过程中对模块定义的规范化的产出。

requireJS主要解决两个问题:

  1. 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器;
  2. js加载的时候浏览器会停止页面渲染,加载文件愈多,页面失去响应的时间愈长。

3.2 使用方法

AMD标准中,定义了下面两个API:

1.require([module], callback)
2. define(id, [depends], callback)

即通过define来定义一个模块,然后使用require来加载一个模块。并且,require还支持CommonJS的模块导出方式。

requireJS定义了一个函数define,它是全局变量,用来定义模块。

define(id,dependencies,factory)

——id 可选参数,用来定义模块的标识,如果没有提供该参数,就使用脚本文件名(去掉拓展名);
——dependencies 是一个当前模块用来的模块名称数组;
——factory 工厂方法,模块初始化要执行的函数或对象,如果为函数,它应该只被执行一次,如果是对象,此对象应该为模块的输出值。

在页面上使用require函数加载模块:

require([dependencies], function(){});

require()函数接受两个参数:
——第一个参数是一个数组,表示所依赖的模块;
——第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。

// moduleA
define(function () {
  return {
    show: (m) => {
      console.log(m);
      document.body.innerHTML = `<h2>${m}</h2>`;
    },
  };
});

// moduleB
define(["m/moduleA"], function (a) {
  return {
    add: (m, n) => a.show(m + n),
  };
});

// 模块引用app.js
require(["m/moduleB",], function (b) {
  let m = 500,
    n = 100;
  b.add(m, n);
});
<script
  type="text/javascript"
  defer
  src="../node_modules/requirejs/require.js"
  data-main="js/app.js"
></script>

但是,在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。

优点:

适合在浏览器环境中异步加载模块。可以并行加载多个模块。

缺点:

提高了开发成本,并且不能按需加载,而是必须提前加载所有的依赖。

3.3 使用技巧

3.3.1 data-main属性

请记住使用requirejs的口诀:两函数一配置一属性。
requirejs需要一个根来作为搜索依赖的开始,data-main用来指定这个根。

<script src="scripts/require.js" data-main="scripts/app.js"></script>

这里就指定了根是app.js,只有直接或者间接与app.js有依赖关系的模块才会被插入到html中。

3.3.2 require.config() 配置

通过这个函数可以对requirejs进行灵活的配置,其参数为一个配置对象,配置项及含义如下:

  1. baseUrl——用于加载模块的根路径;
  2. paths——用于映射不存在根路径下面的模块路径;
  3. shims——配置在脚本/模块外面并没有使用RequireJS的函数依赖并且初始化函数。假设underscore并没有使用 RequireJS定义,但是你还是想通过RequireJS来使用它,那么你就需要在配置中把它定义为一个shim;
  4. deps——加载依赖关系数组。
require.config({
  // 模块依赖的基础路径
  baseUrl: "js/",
  //配置路径简化。
  paths: {
    m: "module/",
    jquery: "common/jquery/jquery-1.11.3",
    moduleG: "module/moduleG",
  },
  shim: {
    moduleG: {
      exports: "moduleG",
    },
  },
});

3.4 加载 JavaScript 文件

RequireJS的目标是鼓励代码的模块化,它使用了不同于传统script标签的脚本加载步骤。可以用它来加速、优化代码,但其主要目的还是为了代码的模块化。它鼓励在使用脚本时以module ID替代URL地址。

RequireJS以一个相对于baseUrl的地址来加载所有的代码。页面顶层script标签含有一个特殊的属性data-main,require.js使用它来启动脚本加载过程,而baseUrl一般设置到与该属性相一致的目录。下列示例中展示了baseUrl的设置:

<!--This sets the baseUrl to the "scripts" directory, and
loads a script that will have a module ID of 'main'-->
<script data-main="scripts/main.js" src="scripts/require.js"></script>

baseUrl亦可通过RequireJS config手动设置。如果没有显式指定config及data-main,则默认的baseUrl为包含RequireJS的那个HTML页面的所属目录。

理想状况下,每个加载的脚本都是通过define()来定义的一个模块;但有些"浏览器全局变量注入"型的传统/遗留库并没有使用define()来定义它们的依赖关系,你必须为此使用shim config来指明它们的依赖关系。 如果你没有指明依赖关系,加载可能报错。这是因为基于速度的原因,RequireJS会异步地以无序的形式加载这些库。

3.5 路径处理

//moduleA:
define(function () {
    return {
        show: m => console.info(m)
    }
});

// moduleB:
define(['moduleA'], function (a) {
    return {
        add: (m, n) => a.show(m + n)
    }
});

// app.js
require(['module/moduleB'], function (b) {
    b.add(100, 500);
});

// index.html:
<script src="js/require2.1.11.js" data-main="js/app.js"></script>

解决方法一: 可以看出运行时报错,原因是baseUrl的值与app.js所在位置有关,当前应该是:baseUrl='js/',那么所有模块在依赖时都默认以js/开始,所以moduleB依赖ModuleA时也以js/开始,可以通过指定当前目录解决该问题,修改moduleB如下:

define(['./module/moduleA'], function (a) {
    return {
        add: (m, n) => a.show(m + n)
    }
});

解决方法二:手动配置baseUrl,修改后如下:

requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/module/'
});

//模块引用app.js
require(['moduleB'], function (b) {
    b.add(100, 500);
});

// moduleB
define(['moduleA'], function (a) {
    return {
        add: (m, n) => a.show(m + n)
    }
});

3.5.1 依赖第三方的库(AMD依赖jQuery)

jQuery 1.7 开始支持将 jQuery 注册为一个AMD异步模块。有很多兼容的脚本加载器(包括 RequireJS 和 curl)都可以用一个异步模块格式来加载模块:

if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function() {
        return jQuery;
    });
}

由于jquery采用define方式定义了一个全局变量“jquery”,所以要采取在paths选项定义路径别名的方式引入jQuery模块。

requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/',
    //路径
    paths:{
        jquery:'common/jquery/jquery-1.12.4'
    }
});

//模块引用
require(['jquery'], function ($) {
    $("body").css({"background":"yellow"});
});

3.5.2 依赖非AMD模块

配置中shim参数为那些没有使用define()来声明依赖关系、设置模块的"浏览器全局变量注入"型脚本做依赖和导出配置。

添加一个非amd模块moduleE.js:

var moduleE = {
    hello: () => {
        console.info("Hello ModuleE!");
    }
};

引用ModuleE:

//配置
requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/',
    //路径
    paths:{
        moduleE:'module/moduleE'
    },
    //处理非标准的amd模块
    shim:{
        //这个键名为要载入的目标文件的文件名,不能随便命名否则拿不到改文件对外提供的接口的
        'moduleE':{
            exports:'moduleE'
        }
    }
});

//模块引用
require(['moduleE'], function (e) {
    e.hello();
});

3.6 注意事项

一个文件一个模块: 每个Javascript文件应该只定义一个模块,这是模块名-文件名查找机制的自然要求。多个模块会被优化工具组织优化,但你在使用优化工具时应将多个模块放置到一个文件中。

define()中的相对模块名: 为了可以在define()内部使用诸如require("./relative/name")的调用以正确解析相对名称,记得将"require"本身作为一个依赖注入到模块中:

define(["require", "./relative/name"], function(require) {
    var mod = require("./relative/name");
});

//或者更好地,使用下述为转换CommonJS模块所设的更短的语法:
define(function(require) {
    var mod = require("./relative/name");
});

CMD

CMD规范是阿里的玉伯提出来的,实现js库为sea.js。 它和requirejs非常类似,即一个js文件就是一个模块,但是CMD的加载方式更加优秀,是通过按需加载的方式,而不是必须在模块开始就加载所有的依赖。如下:

define(function(require, exports, module) {
    var $ = require('jquery');
    var Spinning = require('./spinning');
    exports.doSomething = ...
    module.exports = ...
})

优点:

同样实现了浏览器端的模块化加载。

可以按需加载,依赖就近。

缺点:

依赖SPM打包,模块的加载逻辑偏重。

模块定义语法:

define(id, deps, factory)

因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id; CMD推崇依赖就近,所以一般不在define的参数中写依赖,而是在factory中写。

factory有三个参数:

function(require, exports, module){}
  1. require:factory 函数的第一个参数,require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口;
  2. exports:是一个对象,用来向外提供模块接口;
  3. module:一个对象,上面存储了与当前模块相关联的一些属性和方法。
// 定义模块 myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});

// 加载模块
seajs.use(['myModule.js'], function(my){
    
});

4.1 Seajs

Seajs是一个加载器、遵循 CMD 规范模块化开发,依赖的自动加载、配置的简洁清晰。
SeaJS是一个遵循CMD规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。

与jQuery等JavaScript框架不同,SeaJS不会扩展封装语言特性,而只是实现JavaScript的模块化及按模块加载。SeaJS的主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑。SeaJS可以与jQuery这类框架完美集成。使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决目前JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护。

基本应用:

导入Seajs库

yarn add sea.js

在页尾引入seajs:

<script src="/site/script/sea.js"></script>

然后在它下面写模块的配置和入口。

// 加载入口模块
seajs.use("../static/hello/src/main");

这里解释下配置和入口的意思:

配置

通常在配置上修改seajs的路径和别名。 seajs的路径是相对于前面引入的seajs文件的。假如是这样的目录结构:

examples/
|-- index.html
|
`--about
| |-- news.html
|
`-- script
|-- seajs.js
|-- jquery.js
`-- main.js

我们平时如果我们在index.html上引用main.js路径应该是这样写的script/main.js,从news.html引用main.js就要这样写,../script/main.js。

而在seajs是相对于seajs文件的,一律直接使用main.js就OK了。

既然这么方便那在什么情况需要配置呢?一般情况是用不到的。但是假如你的路径特别深 或者要做路径映射的时候它的作用就来了。下面介绍下常用的几个配置。

seajs.config({
// Sea.js 的基础路径(修改这个路径就不是相对于seajs文件了)
    base: 'http://example.com/path/to/base/',
// 别名配置(用变量表示文件,解决路径层级过深和实现路径映射)
    alias: {
        'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
        'json': 'gallery/json/1.0.2/json',
        'jquery': 'jquery/jquery/1.10.1/jquery'
    },
// 路径配置(用变量表示路径,解决路径层级过深的问题)
    paths: {
        'gallery': 'https://a.alipayobjects.com/gallery'
    }
});

入口

入口即加载,需要加载什么文件(模块加载器)就在这里引入。sea.js在下载完成后,会自动加载入口模块。

seajs.use("abc/main"); //导入seajs.js同级的abc文件夹下的main.js模块的(后缀名可略去不写)

seajs.use()还有另外一种用法。

加载单个依赖

//加载模块 main,并在加载完成时,执行指定回调
seajs.use('./main', function(main) {
  main.init();
});

加载多个依赖

复制代码
//并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
seajs.use(['./a', './b'], function(a, b) {
    a.init();
    b.init();
});

接收两个参数第一个是文件依赖(单个用字符串数组都可以,多个需用数组表示),第二个是回调函数。

通过seajs.use()只能在第一个参数中引入模块,不能在回调函数中使用require()载入模块。

模块开发

// 所有模块都通过 define 来定义
define(function (require, exports, module) {
  // 通过 require 引入依赖
  var $ = require("jquery");
  var Spinning = require("./spinning");

  // 通过 exports 对外提供接口
  exports.doSomething = 10;

  // 或者通过 module.exports 提供整个接口
  module.exports = function a() {};
});

模块是通过define()方法包装的,然后内部痛过require()方法引入需要的依赖文件(模块)。

原生模块化(ECMAScript模块化)

ES6之前使用RequireJS或者seaJS实现模块化, requireJS是基于AMD规范的模块化库, 而像seaJS是基于CMD规范的模块化库, 两者都是为了为了推广前端模块化的工具。

现在ES6自带了模块化, 也是JS第一次支持module, 在很久以后 ,我们可以直接作用import和export在浏览器中导入和导出各个模块了, 一个js文件代表一个js模块;

现代浏览器对模块(module)支持程度不同, 目前都是使用babelJS,或者Traceur把ES6代码转化为兼容ES5版本的js代码。

虽然目前import和require的区别不大,但是还是推荐使用使用es6,因为未来es6必定是主流,对于代码的迁移成本还是非常容易的。

5.1 ES6模块化特点

  1. 每一个模块只加载一次,每一个JS只执行一次,如果下次再去加载同目录下同文件,直接从内存中读取。一个模块就是一个单例,或者说就是一个对象;
  2. 每一个模块内声明的变量都是局部变量, 不会污染全局作用域;
  3. 模块内部的变量或者函数可以通过export导出;
  4. 一个模块可以导入别的模块

示例:lib.js

//导出
export let msg = "求和:";
export function sum(n) {
  let total = 0;
  for (var i = 1; i <= n; i++) {
    total += i;
  }
  return total;
}
<body>
    <script type="module">
        //导入
        import {sum,msg} from './lib.js';
        let result=sum(100);
        console.log(msg+""+result);
    </script>
</body>
//全部导入  
import people from './example'  
  
//有一种特殊情况,即允许你将整个模块当作单一对象进行导入  
//该模块的所有导出都会作为对象的属性存在  
import * as example from "./example.js"  
console.log(example.name)  
console.log(example.age)  
console.log(example.getName())  
  
//导入部分  
import {name, age} from './example'  
  
//导出默认, 有且只有一个默认  
export default App  
  
// 部分导出  
export class App extend Component {};  

// 导入时改名
import add as add1 from "./example.js" 

导入的时候有没有大括号的区别:

  1. 当用export default people导出时,就用 import people 导入(不带大括号);
  2. 一个文件里,有且只能有一个export default。但可以有多个export;
  3. 当用export name 时,就用import { name }导入(记得带上大括号);
  4. 当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age };
  5. 当一个文件里出现n多个,export导出很多模块,导入时除了一个一个导入,也可以用import * as example;
  6. 在export接口的时候, 我们可以使用 XX as YY, 把导出的接口名字改了, 比如: closureFn as sayingFn, 把这些接口名字改成不看文档就知道干什么的。

UMD(通用的模块定义)

UMD 叫做通用模块定义规范(Universal Module Definition)。也是随着大前端的趋势所诞生,它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。未来同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了。

它没有自己专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身,我们看看它的具体 实现:

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    // AMD. Register as an anonymous module.
    define(["b"], factory);
  } else if (typeof module === "object" && module.exports) {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(require("b"));
  } else {
    // Browser globals (root is window)
    root.returnExports = factory(root.b);
  }
})(this, function (b) {
  return {};
});

写法2(Vue)

(function (global, factory) {
  typeof exports === "object" && typeof module !== "undefined"
    ? (module.exports = factory())
    : typeof define === "function" && define.amd
    ? define(factory)
    : (global.Vue = factory());
})(this, function () {
  return {};
});

6.1 UMD示例

6.1.1 定义模块Utils.js

(function (global, factory) {
  if (typeof define === "function" && (define.amd || define.cmd)) {
    // AMD规范. 注册一个匿名模块,兼容AMD与CMD
    define([], factory);
  } else if (typeof module === "object" && module.exports) {
    //CommonJS规范,NodeJS运行环境
    module.exports = factory();
  } else {
    //浏览器全局对象注册
    global.UMD = factory();
  }
})(this, function () {
  var msg = "UMD!";
  //返回要导出的对象
  return {
    show: function () {
      console.log("Hello " + msg);
    },
  };
});

6.1.2 在CommonJS规范下运行

var utils=require('./Utils.js');
utils.show();

6.1.3 在AMD规范下运行

//配置
requirejs.config({
  //模块依赖的基础路径,默认模块的加载位置
  baseUrl:'js/',
  //路径
  paths:{
      //如果在路径前写上m/,则表示为:js/module/
      m:"module/",
  },
});

//模块引用
require(['m/Tools','m/Utils'], function (mu) {
  mu.show();
});
<body>
    <script src="js/require2.1.11.js" data-main="js/app.js"></script>
</body>

运行结果:

6.1.4 在CMD规范下运行

seajs.config({
  // Sea.js 的基础路径(修改这个就不是路径就不是相对于seajs文件了)
  base: './javascript/module/',
});

//引用模块,依赖模块
seajs.use(['Utils'],function (mu) {
  mu.show();
});
<body>
  <script src="js/sea.js"></script>
  <script src="js/main.js"></script>
</body>;

运行结果:

6.1.5 原生浏览器环境运行

<body>
    <script src="javascript/module/Utils.js"></script>
    <script>
        UMD.show();
    </script>
</body>

运行结果: 从上面的示例运行结果可以看出采用UMD定义的模块可以兼容CommonJS、AMD、CMD与浏览器原生环境,同时兼容前后台。写法并非固定可以根据需要变化。

NodeJS包管理器

npm 为你和你的团队打开了连接整个JavaScript天才世界的一扇大门。它是世界上最大的软件注册表。来自各大洲的开源软件开发者使用npm互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。

仓库:www.npmjs.com/

7.1 npm概要

npm全称为Node Package Manager,是一个基于Node.js的包管理器,也是整个Node.js社区最流行、支持的第三方模块最多的包管理器。

npm的初衷:JavaScript开发人员更容易分享和重用代码。

npm的使用场景:
允许用户获取第三方包并使用。
允许用户将自己编写的包或命令行程序进行发布分享。 npm版本查询:npm -v
npm安装:

  1. 安装nodejs,由于新版的nodejs已经集成了npm,所以可直接通过输入npm -v来测试是否成功安装;
  2. 使用npm命令来升级npm: npm install npm -g。

7.2 包(package)

包是描述一个文件或一个目录。一个包的配置通常由以下构成:
一个文件夹包含一个package.json配置文件;
包含(含有package.json文件的文件夹)的Gzip压缩文件;
解析gzip的url; 为注册表添加@的url 信息。

7.3 模块(module)

模板是通过配置文件中的一个dom节点进行包含一个或多个包。通常一般由包和配置文件以及相关模块程序构成完成一个或多个业务功能操作。

一个模块可以在node.js 程序中装满任何的require()。

以下是所有事物加载模块的例子 :

  1. package.json文件包含一个main字段;
  2. 一个文件夹index.js文件;
  3. 一个JavaScript文件。

7.4 npm的生态系统

package.json文件定义的是包。
node_modules文件夹是存储模块的地方。便于js查找模块。

如果创建一个node_modules/foo.js文件,通过var f=require('foo.js')进行加载模块。因为它没有package.json文件所以foo.js不是一个包。
如果没有创建index.js或者package.json文件"main"字段,即使是安装node_modules,因为它没有require()所以它不是一个模块。 常用命令:

npm install [-g] 本地或全局安装模块
npm uninstall [-g] 本地或全局卸载模块
npm update 更新模块
npm ls 查看安装的模块
npm list 列出已安装模块
npm show  显示模块详情
npm info 查看模块的详细信息
npm search 搜索模块
npm publish 发布模块
npm unpublish 删除已发布的模块
npm -v 或 npm version显示版本信息
npm view npm versions 列出npm 的所有有效版本
npm install -g npm@2.14.14 /npm update -g npm@2.14.14  安装指定的npm版本
npm init 引导创建一个package.json文件,包括名称、版本、作者这些信息等
分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改