您的位置:新葡亰496net > 新葡亰官网 > 新葡亰496net:模块讲解,的一次更新说明

新葡亰496net:模块讲解,的一次更新说明

发布时间:2019-11-10 05:58编辑:新葡亰官网浏览(104)

    关于 Node.js 里 ES6 Modules 的一次更新说明

    2017/04/27 · JavaScript · es6

    原文出处: James M Snell   译文出处:凹凸实验室   

    几个月前,我写了一篇文章来描述 Node.js 现存的 CommonJS 模块和新的 ES6 模块系统的许多不同,也说明了在 Node.js 内核中实现这个新模型的内在的一些挑战。现在,我想分享一下关于这件事情的进展情况。

    【转】

    模块通常是指编程语言所提供的代码组织机制,利用此机制可将程序拆解为独立且通用的代码单元。所谓模块化主要是解决代码分割、作用域隔离、模块之间的依赖管理以及发布到生产环境时的自动化打包与处理等多个方面。

    原文链接:https://auth0.com/blog/javascript-module-systems-showdown/。作者:https://twitter.com/speyrott?lang=en。

    明白你什么时候该知道你需要知道的东西

    在这之前,如果你还没准备好,你可以花一点时间来看一下我之前的描述这两个模块架构上存在许多根本区别的文章。总结来说就是:CommonJS 与 ES6 Modules 之间的关键不同在于代码什么时候知道一个模块的结构和使用它。

    举个栗子,假如我现在有一个简单的 ComminJS 模块(模块名叫'foobar'):

    JavaScript

    function foo() { return 'bar'; } function bar() { return 'foo'; } module.exports.foo = foo; module.exports.bar = bar;

    1
    2
    3
    4
    5
    6
    7
    8
    function foo() {
      return 'bar';
    }
    function bar() {
      return 'foo';
    }
    module.exports.foo = foo;
    module.exports.bar = bar;

    现在我们在一个叫 app.js 的 JS 文件中引用它

    JavaScript

    const {foo, bar} = require('foobar'); console.log(foo(), bar());

    1
    2
    const {foo, bar} = require('foobar');
    console.log(foo(), bar());

    当我执行 $node app.js 的时候,Node.js 已二进制的形式加载 app.js 文件,解析它,并且开始执行里面的代码。在执行过程中,里面的 require() 方法被调用,然后它会同步的去加载 foobar.js 的内容进内存,同步的解析编译里面的 JavaScript 代码,同步的执行里面的代码,然后返回 module.exports 的值当做 app.js 里的 require('foobar') 的返回值。当 app.js 里的 require() 方法返回的时候,foobar 模块的结构就已经知道了,并且可以被使用。所有的这些事情都发生在 Node.js 进程事件循环的同一个周期里。

    要理解 CommonJS 与 ES6 Modules 之间的不同至关重要的是,一个 CommonJS 的模块在没有被执行完之前,它的结构(API)是不可知的 — 即使在它被执行完以后,它的结构也可以随时被其他代码修改。

    现在我们用 ES6 的写法来写同样的模块:

    JavaScript

    export function foo() { return 'bar'; } export function bar() { return 'foo'; }

    1
    2
    3
    4
    5
    6
    export function foo() {
      return 'bar';
    }
    export function bar() {
      return 'foo';
    }

    并且在代码中引用它:

    JavaScript

    import {foo, bar} from 'foobar'; console.log(foo()); console.log(bar());

    1
    2
    3
    import {foo, bar} from 'foobar';
    console.log(foo());
    console.log(bar());

    从 ECMAScript 统一的标准来看,ES6 Modules 的步骤与 CommonJS 里已经实现的有很大的不同。第一步从硬盘上加载文件内容大致上是相同的,但是可能是异步的。当内容加载完成后,会解析它。在解析的同时,模块里被 export 声明定义的结构会在组件内容被执行之前就探知出来。一旦结构被探知出来,组件的代码就会被执行。这里重要的是记住所有的 import 和 export 语句都会在代码执行之前被解析出来。另一点是在 ES6 中是允许这个解析的步骤异步执行的。这就意味着,在 Node.js 的机制中,加载脚本内容、解析模块的 import 和 export 、执行模块代码将发生在多个事件循环里。

    遵循的模块化规范不一样

    模块的优点

    随着JavaScript开发越来越普遍,命名空间和依赖关系变得越来越难以处理。于是出现了以模块化的不同解决方案来处理这个问题。在这篇文章中,我们将探讨开发者目前使用的不同解决方案以及他们试图解决的问题。请看下文。

    时机很重要

    在评估 ES6 Modules 的可实现性之前,我们关注的重点是怎么样无缝衔接的实现它。比如我们希望它可以可以实现同时对两种模块的支持,这样可以很大程度上对用户是透明的。

    可惜,事情并不是这么简单…

    尤其是 ES6 Modules 的加载、解析和执行都是异步的,这就导致不能通过 require() 来引用一个 ES6 模块。原因是 require() 是一个完全同步的函数。如果我们去修改 require() 的语义让它可以进行异步加载的话,那对于现有的生态系统将会产生巨大的破坏。所以我们有考虑在 ES6 的 import() 函数提议(详情)通过之后建模实现一个 require.import() 函数。这个函数会返回一个 Promise 在 ES6 模块加载完成后标记完成。这不是最好的方案,但是它可以让你在现有的 Node.js 里以 CommonJS 的格式来使用。

    有一点好消息是在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块。因为在 ES6 模块里异步加载不是必须的。ECMAScript 规范进行一些小修改就可以更好地支持这种方式。但是所有这些工作过后,还有一个重要的事情…

    模块化规范:即为 JavaScript 提供一种模块编写、模块依赖和模块运行的方案。谁让最初的 JavaScript 是那么的裸奔呢——全局变量就是它的模块化规范。

    可维护性。因为模块是独立的,一个设计良好的模块会让外面的代码对自己的依赖越少越好,这样自己就可以独立去更新和改进。


    命名引用

    命名引用是 ES6 Modules 里的一个基本的特性。举个例子:

    JavaScript

    import {foo, bar} from 'foobar';

    1
    import {foo, bar} from 'foobar';

    变量 foobar 在解析阶段就从 foobar 中被引用进来 —— 在所有代码被执行之前。因为 ES6 Modules 的结构是之前就可以被探知到的。

    另一方面,在 CommonJS 里模块结构在代码没有执行之前是不能被探知的。也就是说,如果不对 ECMAScript 规范做重大更改的话,在 CommonJS 模块里是不能使用命名引用的。开发者会引用到 ES6 Modules 里面的名为 “default” 的导出。比如,上面的例子在 CommonJS 里是这样的:

    JavaScript

    import foobar from 'foobar'; console.log(foobar.foo(), foobar.bar());

    1
    2
    import foobar from 'foobar';
    console.log(foobar.foo(), foobar.bar());

    区别很小但是很重要。所以当你想使用 import 来引用一个 CommonJS 模块的时候,下面这种写法是根本行不通的:

    JavaScript

    import {foo, bar} from 'foobar';

    1
    import {foo, bar} from 'foobar';

    这里的 foobar 不会直接被解析成 CommonJS 模块里导出的 foo()bar() 方法。

    require/exports 出生在野生规范当中,什么叫做野生规范?即这些规范是 JavaScript 社区中的开发者自己草拟的规则,得到了大家的承认或者广泛的应用。比如 CommonJS、AMD、CMD 等等。import/export 则是名门正派。TC39 制定的新的 ECMAScript 版本,即 ES6(ES2015)中包含进来。

    命名空间。在 JavaScript 里面,如果一个变量在最顶级的函数之外声明,它就直接变成全局可用。因此,常常不小心出现命名冲突的情况。使用模块化开发来封装变量,可以避免污染全局环境。

    Different pieces of software are usually developed in isolation until some requirement needs to be satisfied by a previously existing piece of software. At the moment that other piece of software is brought into the project a dependency is created between it and the new piece of code. Since these pieces of software need to work together, it is of importance that no conflicts arise between them. This may sound trivial, but without some sort of encapsulation it is a matter of time before two modules conflict with each other. This is one of the reasons elements in C libraries usually carry a prefix:

    但是在 Babel 里可以!

    使用过像 Babel 这种的 ES6 Modules 语法转换工具的人应该很熟悉命名引用。Babel 的工作原理是把 ES6 的写法转换成可以在 Node.js 里运行的 CommonJS 的形式。虽然语法看起来很像 ES6,但是实际上并不是。这一点很重要,Babel 里的 ES6 命名引用与完全按照规范实现的 ES6 命名引用有本质的不同。

    出现的时间不同

    重用代码。我们有时候会喜欢从之前写过的项目中拷贝代码到新的项目,这没有问题,但是更好的方法是,通过模块引用的方式,来避免重复的代码库。

    简介:我们为什么需要Javascript模块化
    如果你熟悉其他开发平台,你可能了解“封装”和“依赖”的概念。通常我们是孤立开发不同代码片段直到我们需要依赖之前已经存在的代码。当我们需要将其他软件片段引入项目时,它与新的代码段之间产生依赖关系。由于新旧两段代码要一起运行,所以他们之间不能产生冲突。这听起来可能不算什么,但是如果不经过封装,两个模块之间发生冲突只是时间问题。这是C库中元素通常带有前缀的原因之一:

    Michael Jackson Script

    实际上CommonJS 和 ES6 Modules 之间还有另外一个重要的不同就是,ECMAScript 编译器必须提前知道它加载的代码是 CommonJS 的还是 ES6 Modules 的。原因是之前说的 ES6 Modules 必须在代码执行前就解析出模块中的 importexport 声明。

    这就意味着 Node.js 需要某些机制来预先识别它在加载那种类型的文件。在探索了很多方案以后,我们回归到了以前最糟糕的方案,就是引入一个新的 *.mjs 文件后缀来表示一个 ES6 Modules 的 JavaScript 文件。(之前我们亲切的叫它 “Michael Jackson Script”)

    require/exports 相关的规范由于野生性质,在 2010 年前后出生。AMD、CMD 相对命比较短,到 2014 年基本上就摇摇欲坠了。一开始大家还比较喜欢在浏览器上采用这种异步小模块的加载方式,但并不是银弹。随着 Node.js 流行和 Browsersify 的兴起,运行时异步加载逐渐被构建时模块合并分块所替代。Wrapper 函数再也不需要了。 2014 年 Webpack 还是新玩意,现在已经是前端必备神器了。

    CommonJS

    #ifndef MYLIB_INIT_H
    #define MYLIB_INIT_H
    
    enum mylib_init_code {
        mylib_init_code_success,
        mylib_init_code_error
    };
    
    enum mylib_init_code mylib_init(void);
    
    // (...)
    
    #endif //MYLIB_INIT_H
    

    时间线

    在目前的时间点上,在 Node.js 可以开始处理支持实现 ES6 Modules 之前,还有很多关于规范现实的问题和虚拟机方面的问题。相关工作还在进行,但是需要一些时间 —— 我们目前估计至少需要一年左右。

    1 赞 收藏 评论

    新葡亰496net 1

    Browsersify、Webpack 一开始的目的就是打包 CommonJS 模块。

    CommonJS 最开始是 Mozilla 的工程师于 2009 年开始的一个项目,它的目的是让浏览器之外的 JavaScript (比如服务器端或者桌面端)能够通过模块化的方式来开发和协作。

    When it comes to dependencies, in traditional client-side JavaScript development, they are implicit. In other words, it is the job of the developer to make sure dependencies are satisfied at the point any block of code is executed. Developers also need to make sure dependencies are satisfied in the right order (a requirement of certain libraries).
    The following example is part of Backbone.js's examples. Scripts are manually loaded in the correct order:

    CommonJS 作为 Node.js 的规范,一直沿用至今。由于 npm 上 CommonJS 的类库众多,以及 CommonJS 和 ES6 之间的差异,Node.js(这里不太准确) 无法直接兼容 ES6。所以现阶段 require/exports 仍然是必要且必须的。出自 ES6 的  import/export 相对就晚了许多。被大家所熟知和使用也是 2015 年之后的事了。 这其实要感谢 babel(原来项目名叫做 6to5,后更名为 babel) 这个神一般的项目。由于有了 babel 将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 Module 编译为 ES5 的 CommonJS —— 也就是 require/exports 这种写法 —— Webpack 插上 babel-loader 这个翅膀才开始高飞,大家也才可以称 " 我在使用 ES6! "

    在 CommonJS 的规范中,每个 JavaScript 文件就是一个独立的模块上下文(module context),在这个上下文中默认创建的属性都是私有的。也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。

    封装对于预防冲突和提高代码的可维护性有很重要的作用。
    谈到依赖关系,在传统的Javascript开发中,往往都认为是隐式的。换句话说,开发人员只要确保在执行任何代码块的时候确保依赖关系得到满足。
    开发人员还需要确保依赖性以正确的顺序(某些库的要求)得到满足。
    以下示例是Backbone.js示例的一部分。

    这也就是为什么前面说 require/exports 是必要且必须的。因为事实是,目前你编写的 import/export 最终都是编译为 require/exports 来执行的。

    需要注意的是,CommonJS 规范的主要适用场景是服务器端编程,所以采用同步加载模块的策略。如果我们依赖3个模块,代码会一个一个依次加载它们。

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <title>Backbone.js Todos</title>
            <link rel="stylesheet" href="todos.css"/>
        </head>
    
        <body>
            <script src="../../test/vendor/json2.js"></script>
            <script src="../../test/vendor/jquery.js"></script>
            <script src="../../test/vendor/underscore.js"></script>
            <script src="../../backbone.js"></script>
            <script src="../backbone.localStorage.js"></script>
            <script src="todos.js"></script>
        </body>
    
        <!-- (...) -->
    
    </html>
    

    require/exports 和 import/export 形式不一样

    该模块实现方案主要包含 require 与 module 这两个关键字,其允许某个模块对外暴露部分接口并且由其他模块导入使用。

    As JavaScript development gets more and more complex, dependency management can get cumbersome. Refactoring is also impaired: where should newer dependencies be put to maintain proper order of the load chain?

    require/exports 的用法只有以下三种简单的写法:

    //sayModule.js

    JavaScript module systems attempt to deal with these problems and others. They were born out of necessity to accommodate the ever growing JavaScript landscape. Let's see what the different solutions bring to the table.

    const fs = require('fs')exports.fs = fsmodule.exports = fs

    function SayModule () {

    随着JavaScript开发变得越来越复杂,依赖关系管理变得越来越麻烦。重构也面临挑战:我们应该采用哪些新的依赖关系来维护负载链的正确顺序?

    而 import/export 的写法就多种多样:

       this.hello = function () {

    JavaScript模块化系统准备处理上述问题。他们应景而生,以适应不断增长的JavaScript场景。让我们看看被提上台面的不同解决方案。

    import fs from 'fs'import {default as fs} from 'fs'import * as fs from 'fs'import {readFile} from 'fs'import {readFile as read} from 'fs'import fs, {readFile} from 'fs'export default fsexport const fsexport function readFileexport {readFile, read}export * from 'fs'

           console.log('hello');


    require/exports 和 import/export 本质上的差别

       };

    An Ad-Hoc Solution: The Revealing Module Pattern

    形式上看起来五花八门,但本质上:

       this.goodbye = function () {

    一个特色解决方案:揭示模块模式

    Most module systems are relatively recent. Before they were available, a particular programming pattern started getting used in more and more JavaScript code: the revealing module pattern.
    大多数的模块化系统的概念都是最近被提出的,在这些模块化系统还未被投入使用之前,有一种特殊的设计模式:揭示模块模式。被广泛运用于Javascript代码当中。

    var myRevealingModule = (function () {
        var privateVar = "Ben Cherry",
            publicVar = "Hey there!";
    
        function privateFunction() {
            console.log( "Name:"   privateVar );
        }
    
        function publicSetName( strName ) {
            privateVar = strName;
        }
    
        function publicGetName() {
            privateFunction();
        }
    
        // Reveal public pointers to
        // private functions and properties
        return {
            setName: publicSetName,
            greeting: publicVar,
            getName: publicGetName
        };
    })();
    
    myRevealingModule.setName( "Paul Kinlan" );
    

    这是 Addy Osmani's JavaScript Design Patterns 这本书上的例子。

    JavaScript scopes (at least up to the appearance of let in ES2015) work at the function level. In other words, whatever binding is declared inside a function cannot escape its scope. It is for this reason the revealing module pattern relies on functions to encapsulate private contents (as many other JavaScript patterns).

    Javascript的作用域(在ES2015的let语法之前)都是在函数层的。换言之,任何在函数内部声明的变量都不能脱离它的作用域。正是由于这个原因,揭示模块模式与其他JavaScript设计模式一样依赖于封装私有变量。

    In the example above, public symbols are exposed in the returned dictionary. All other declarations are protected by the function scope enclosing them. It is not necessary to use var and an immediate call to the function enclosing the private scope; a named function can be used for modules as well.
    在上面的例子当中,公有方法暴露在返回的API当中。其他的函数变量声明都被包裹在函数作用域的一个闭包之中。这时就没有必要使用var配合立即调用封闭私有范围的函数(IIFE); 一个命名函数也可以用于实现模块化。
    This pattern has been in use for quite some time in JavaScript projects and deals fairly nicely with the encapsulation matter. It does not do much about the dependencies issue. Proper module systems attempt to deal with this problem as well. Another limitation lies in the fact that including other modules cannot be done in the same source (unless using eval).
    这种模式在JavaScript项目中已经使用了相当一段时间,也不错的处理了封装。它对依赖性问题没也有太多的影响。但实际中的另一个局限性在于,如果不使用eval的话,别的模块会有同源限制。

    CommonJS 还是 ES6 Module 输出都可以看成是一个具备多个属性或者方法的对象;

           console.log('goodbye');

    优点

    • 简单到可以在任何平台任何语言中使用
    • 可以在单个文件中定义多个模块。

    default 是 ES6 Module 所独有的关键字,export default fs 输出默认的接口对象,import fs from 'fs' 可直接导入这个对象;

       };

    缺点

    No way to programmatically import modules (except by using eval).
    Dependencies need to be handled manually.
    Asynchronous loading of modules is not possible.
    Circular dependencies can be troublesome.
    Hard to analyze for static code analyzers.

    • 没有办法程序化导入模块(除了使用eval)
    • 依赖关系需要手动处理
    • 无法实现模块的异步加载
    • 处理循环依赖很棘手
    • 很难解析静态代码解析器

    ES6 Module 中导入模块的属性或者方法是强绑定的,包括基础类型;而 CommonJS 则是普通的值传递或者引用传递。

    }

    CommonJS

    CommonJS is a project that aims to define a series of specifications to help in the development of server-side JavaScript applications. One of the areas the CommonJS team attempts to address are modules. Node.js developers originally intended to follow the CommonJS specification but later decided against it. When it comes to modules, Node.js's implementation is very influenced by it:
    CommonJS是一个旨在定义一系列规范以帮助开发服务器端JavaScript应用程序的项目。CommonJS团队尝试解决的领域之一是模块化。Node.js开发人员最初打算遵循CommonJS规范,但后来否决了。因为当涉及到模块时,Node.js的实现受到很大的影响:

    // In circle.js
    const PI = Math.PI;
    
    exports.area = (r) => PI * r * r;
    
    exports.circumference = (r) => 2 * PI * r;
    
    // In some file
    const circle = require('./circle.js');
    console.log( `The area of a circle of radius 4 is ${circle.area(4)}`);
    

    There are abstractions on top of Node.js's module system in the form of libraries that bridge the gap between Node.js's modules and CommonJS. For the purposes of this post, we will only show the basic features which are mostly the same.

    在Node.js的模块系统之上,以库的形式提供了抽象,缩小了Node.js模块和CommonJS之间的差距。在下文中,我们将只展示他们之间基本相同的基本功能。
    In both Node's and CommonJS's modules there are essentially two elements to interact with the module system: require and exports. require is a function that can be used to import symbols from another module to the current scope. The parameter passed to require is the id of the module. In Node's implementation, it is the name of the module inside the node_modules directory (or, if it is not inside that directory, the path to it). exports is a special object: anything put in it will get exported as a public element. Names for fields are preserved. A peculiar difference between Node and CommonJS arises in the form of the module.exports object. In Node, module.exports is the real special object that gets exported, while exports is just a variable that gets bound by default to module.exports. CommonJS, on the other hand, has no module.exports object. The practical implication is that in Node it is not possible to export a fully pre-constructed object without going through module.exports:

    Node和CommonJS的模块自身都有两个方法和模块系统进行交互:requireexportsrequire是一种用于将对象从别的模块引入当前作用域的方法。传递给require的参数是模块的ID。在node中,它是node_modules目录中的模块的名称(如果它不在该目录中,那么就是它的路径)。exports导出的是一个特殊的对象:它传入的参数将被导出为一个公共元素。文件名称将会被保留。Node和CommonJS的特殊区别之处在于module.exports导出对象的形式。在Node中,module.exports是导出的真正的对象,而exports只是module.exports的一个引用。然而,CommonJS没有module.exports 导出对象。实际上,在Node中,必须经过module.exports才能导出完全预构建的对象:

    // 不能正常运行,exports是引用 module.exports的值,exports在module.exports 被改变后,失效。
    // modules.exports.
    exports =  (width) => {
      return {
        area: () => width * width
      };
    }
    
    // This works as expected.
    module.exports = (width) => {
      return {
        area: () => width * width
      };
    }
    

    CommonJS modules were designed with server development in mind. Naturally, the API is synchronous. In other words, modules are loaded at the moment and in the order they are required inside a source file.
    CommonJS模块的实际考虑到了服务器端的开发。当然这个API不是异步的。换句话说,源文件中的模块是当按顺序按需加载的。

    1、2 相对比较好理解,3 需要看个例子:

    module.exports = SayModule;

    优点

    Simple: a developer can grasp the concept without looking at the docs.
    Dependency management is integrated: modules require other modules and get loaded in the needed order.
    require can be called anywhere: modules can be loaded programmatically.
    Circular dependencies are supported.

    • 简单: 开发者不用看文档就能理解这个概念。
    • 集成依赖管理:模块之间依赖,并按需要加载。
    • require可以在任何地方调用:恶意程序化加载模块
    • 支持依赖循环

    // counter.jsexports.count=0setTimeout(function(){console.log('increase count to', exports.count,'in counter.js after 500ms')},500)// commonjs.jsconst{count}=require('./counter')setTimeout(function(){console.log('read count after 1000ms in commonjs is',count)},1000)//es6.jsimport{count}from'./counter'setTimeout(function(){console.log('read count after 1000ms in es6 is',count)},1000)

    //main.js 引入sayModule.js

    缺点

    Synchronous API makes it not suitable for certain uses (client-side).
    One file per module.
    Browsers require a loader library or transpiling.
    No constructor function for modules (Node supports this though).
    Hard to analyze for static code analyzers.

    • 同步API使其不适合某些场景(浏览器客户端)
    • 一个模块就是一个文件
    • 浏览器需要依赖库或者编译
    • 没有模块的构造函数(Node已经支持)
    • 很难解析静态代码解析器

    分别运行 commonjs.js 和 es6.js:

    var Say = require('./sayModule.js');

    实现

    We have already talked about one implementation (in partial form): Node.js.
    我们之前说到一种实现(部分形式):Node.js。
    For the client there are currently two popular options: webpack and browserify. Browserify was explicitly developed to parse Node-like module definitions (many Node packages work out-of-the-box with it!) and bundle your code plus the code from those modules in a single file that carries all dependencies. Webpack on the other hand was developed to handle creating complex pipelines of source transformations before publishing. This includes bundling together CommonJS modules.

    在客户端现在流行两种选择:webpack 以及 browserify。Browserify是一种类似Node化的模块管理工具(许多Node包和他配合都是开箱机用),也就是让服务器端的CommonJS格式的模块可以运行在浏览器端。并将您的代码合并入别的模块的代码并加载到拥有所有依赖项的单个文件中。而Webpack是在发布之前用复杂管线来处理源文件。这包括将CommonJS模块打包在一起。

    异步模块定义 (AMD)

    AMD was born out of a group of developers that were displeased with the direction adopted by CommonJS. In fact, AMD was split from CommonJS early in its development. The main difference between AMD and CommonJS lies in its support for asynchronous module loading.

    AMD出自一组对CommonJS的发展方向感到不满的开发小组。其实AMD很早就从CommonJS的开发中脱颖而出,AMD相比CommonJS的主要优势就是它支持模块异步加载。

    //Calling define with a dependency array and a factory function
    define(['dep1', 'dep2'], function (dep1, dep2) {
    
        //Define the module value by returning a value.
        return function () {};
    });
    
    // Or:
    define(function (require) {
        var dep1 = require('dep1'),
            dep2 = require('dep2');
    
        return function () {};
    });
    

    Asynchronous loading is made possible by using JavaScript's traditional closure idiom: a function is called when the requested modules are finished loading. Module definitions and importing a module is carried by the same function: when a module is defined its dependencies are made explicit. An AMD loader can therefore have a complete picture of the dependency graph for a given project at runtime. Libraries that do not depend on each other for loading can thus be loaded at the same time. This is particularly important for browsers, where startup times are essential to a good user experience.

    使用Javascrpt的传统语法 闭包可以实现异步加载:函数在请求模块加载完成时调用。模块的定义和导入都是由一个函数来完成的:当模块定义后,它的依赖也被明确。AMD加载器因此可以得到完整的依赖项的加载时间,这对于浏览器端来说特别重要,因为启动时间对于用户体验至关重要。

    ➜testnode commonjs.jsincrease count to1in counter.js after 500msreadcount after 1000ms in commonjs is 0➜testbabel-node es6.jsincrease count to1in counter.js after 500msreadcount after 1000ms in es6 is 1

    var sayer = new Say();

    优点

    Asynchronous loading (better startup times).
    Circular dependencies are supported.
    Compatibility for require and exports.
    Dependency management fully Asynchronous loading (better startup times).
    Circular dependencies are supported.
    Compatibility for require and exports.
    Dependency management fully integrated.
    Modules can be split in multiple files if necessary.
    Constructor functions are supported.
    Plugin support (custom loading steps).
    .

    • 异步加载(良好的首屏时间)
    • 支持循环依赖
    • 兼容requireexports
    • 完全集成依赖管理
    • 如果需要模块可以拆分为多个文件
    • 支持构造函数
    • 支持插件(自定义加载先后)

    作者:寸志

    sayer.hello(); //hello

    缺点

    Slightly more complex syntactically.
    Loader libraries are required unless transpiled.
    Hard to analyze for static code analyzers.

    • 异步让代码看起来更复杂
    • 如果不编译,就要引入加载库
    • 很难解析静态代码解析器

    链接:

    作为一个服务器端的解决方案,CommonJS 需要一个兼容的脚本加载器作为前提条件。该脚本加载器必须支持名为 require 和 module.exports 的函数,它们将模块相互导入导出。

    实现

    目前最受欢迎的AMD是require.js and Dojo.

    新葡亰496net 2

    Require.js for JavaScript Modules

    Using require.js is pretty straightforward: include the library in your HTML file and use the data-main attribute to tell require.js which module should be loaded first. Dojo has a similar setup.
    使用requirejs更为简单:在你的HTML文件中引入require文件,然后使用该data-main属性来告诉require.js应该首先加载哪个模块。
    Dojo 也类似.

    来源:知乎

    Node.js

    ES2015 模块化

    Fortunately, the ECMA team behind the standardization of JavaScript decided to tackle the issue of modules. The result can be seen in the latest release of the JavaScript standard: ECMAScript 2015 (previously known as ECMAScript 6). The result is syntactically pleasing and compatible with both synchronous and asynchronous modes of operation.
    喜大普奔,ECMA幕后团队已经决定解决模块化的问题。我们可以再最新的JavaScript标准中看到:ECMAScript 2015 (以前叫ES6).
    ECMAScript 2015在语法上兼容了同步和异步两种模式

    //------ lib.js ------
    export const sqrt = Math.sqrt;
    export function square(x) {
        return x * x;
    }
    export function diag(x, y) {
        return sqrt(square(x)   square(y));
    }
    
    //------ main.js ------
    import { square, diag } from 'lib';
    console.log(square(11)); // 121
    console.log(diag(4, 3)); // 5
    

    The import directive can be used to bring modules into the namespace. This directive, in contrast with require and define is not dynamic (i.e. it cannot be called at any place). The export directive, on the other hand, can be used to explicitly make elements public.

    The static nature of the import and export directive allows static analyzers to build a full tree of dependencies without running code. ES2015 does not support dynamic loading of modules, but a draft specification does:

    import指令可以将模块引入命名空间。但requiredefine这两个指令却不是动态的(不能随便在任何地方调用)。export指令是将模块暴露为公有。
    静态特性importexport指令允许静态解析器没有在不运行的代码情况下建立依赖关系的一个完整的树。ES2015不支持动态加载模块,但草案规范如下:

    System.import('some_module')
          .then(some_module => {
              // Use some_module
          })
          .catch(error => {
              // ...
          });
    

    In truth, ES2015 only specifies the syntax for static module loaders. In practice, ES2015 implementations are not required to do anything after parsing these directives. Module loaders such as System.js are still required. A draft specification for browser module loading is available.

    实际上,ES2015 只指定静态模块解析器的语法。实际上,解析这些指令后,ES2015不需要执行任何操作。仍然需要loader,如System.js。浏览器模块加载草案规范可用。

    This solution, by virtue of being integrated in the language, lets runtimes pick the best loading strategy for modules. In other words, when asynchronous loading gives benefits, it can be used by the runtime.
    ES6这种解决方案应该是模块化的最佳解决策略,因为是集成在语言中的。当文件异步加载时,加载时间就会大大缩短。

    更新(2017年2月):现在有一个动态加载模块的规范。
    这是ECMAScript标准未来版本的提案。

    【转】commonjs模块与es6模块的区别

    Node 从 CommonJS 的一些创意中,创造出自己的模块化实现。由于Node 在服务端的流行,Node 的模块形式被(不正确地)称为 CommonJS。

    优点

    ynchronous and asynchronous loading supported.
    Syntactically simple.
    Support for static analysis tools.
    Integrated in the language (eventually supported everywhere, no need for libraries).
    Circular dependencies supported.

    • 支持同步异步加载
    • 支持静态解析器
    • 集成在语言本身(不用引入库)
    • 支持循环依赖

    到目前为止,已经实习了3个月的时间了。最近在面试,在面试题里面有题目涉及到模块循环加载的知识。趁着这个机会,将commonjs模块与es6模块之间一些重要的的区别做个总结。语法上有什么区别就不具体说了,主要谈谈引用的区别。

    Node.js模块可以分为两大类,一类是核心模块,另一类是文件模块。

    缺点

    Still not supported everywhere.

    • 仍未被所有浏览器支持

    转载请注明出处:commonjs模块与es6模块的区别

    核心模块

    实现

    不幸的是还没有一个Javascript平台在他们的当前版本支持ES2015模块。这意味着在Firefox, Chrome or Node.js都不支持。好在有很多编译工具如polyfill 。ES2015的预编译工具Babel 也能完美处理module
    Unfortunately none of the major JavaScript runtimes support ES2015 modules in their current stable branches. This means no support in Firefox, Chrome or Node.js. Fortunately many transpilers do support modules and a polyfill is also available. Currently, the ES2015 preset for Babel can handle modules with no trouble。

    新葡亰496net 3

    Babel for JavaScript Modules

    The All-in-One Solution: System.js
    You may find yourself trying to move away from legacy code using one module system. Or you may want to make sure whatever happens, the solution you picked will still work. Enter System.js: a universal module loader that supports CommonJS, AMD and ES2015 modules. It can work in tandem with transpilers such as Babel or Traceur and can support Node and IE8 environments. Using it is a matter of loading System.js in your code and then pointing it to your base URL:

    commonjs

    就是Node.js标准的API中提供的模块,如fs、http、net等,这些都是由Node.js官方提供的模块,编译成了二进制代码,可以直接通过require获取核心模块,例如require('fs'),核心模块拥有最高的加载优先级,如果有模块与核心模块命名冲突,Node.js总是会加载核心模块。

    一体化解决方案:System.js

        <script src="system.js"></script>
        <script>
          // set our baseURL reference path
          System.config({
            baseURL: '/app',
            // or 'traceur' or 'typescript'
            transpiler: 'babel',
            // or traceurOptions or typescriptOptions
            babelOptions: {
    
            }
          });
    
          // loads /app/main.js
          System.import('main.js');
        </script>
    

    对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。

    文件模块

    总结

    Building modules and handling dependencies was cumbersome in the past. Newer solutions, in the form of libraries or ES2015 modules, have taken most of the pain away. If you are looking at starting a new module or project, ES2015 is the right way to go. It will always be supported and current support using transpilers and polyfills is excellent. On the other hand, if you prefer to stick to plain ES5 code, the usual split between AMD for the client and CommonJS/Node for the server remains the usual choice. Don't forget to leave us your thoughts in the comments section below. Hack on!
    构建模块化和处理依赖关系在过去是一件很麻烦的事。现在有了外部库以及ES2015模块,解决了很多问题。如果你正要开始一个新项目,ES2015绝对是最好的方法。它肯定不会不被支持,而且现在的预编译工具做的很棒。如果你坚持使用ES5写代码,则客户端的AMD和服务器的CommonJS / Node之间仍然是常用的选择。

    对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。

    是存储为单独的文件(或文件夹)的模块,可能是JavaScript代码、JSON或编译好的C/C 代码。在不显式指定文件模块扩展名的时候,Node.js会分别试图加上.js、.json、.node(编译好的C/C 代码)。

    当使用require命令加载某个模块时,就会运行整个模块的代码。

    加载方式

    当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

    按路径加载模块

    循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

    如果require参数一"/"开头,那么就以绝对路径的方式查找模块名称,如果参数一"./"、"../"开头,那么则是以相对路径的方式来查找模块。

    ES6模块

    通过查找node_modules目录加载模块

    es6模块中的值属于【动态只读引用】。

    如果require参数不以"/"、"./"、"../"开头,而该模块又不是核心模块,那么就要通过查找node_modules加载模块了。我们使用的npm获取的包通常就是以这种方式加载的。

    对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

    加载缓存

    对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。

    Node.js模块不会被重复加载,这是因为Node.js通过文件名缓存所有加载过的文件模块,所以以后再访问到时就不会重新加载了。

    循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

    注意:Node.js是根据实际文件名缓存的,而不是require()提供的参数缓存的,也就是说即使你分别通过require('express')和require('./node_modules/express')加载两次,也不会重复加载,因为尽管两次参数不同,解析到的文件却是同一个。

    上面说了一些重要区别。现在举一些例子来说明每一点吧

    Node.js 中的模块在加载之后是以单例化运行,并且遵循值传递原则:如果是一个对象,就相当于这个对象的引用。

    commonjs

    模块载入过程

    对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。

    加载文件模块的工作,主要由原生模块module来实现和完成,该原生模块在启动时已经被加载,进程直接调用到runMain静态方法。

    // b.js

    例如运行: node app.js

    let count = 1

    Module.runMain = function () {

    let plusCount = () => {

       // Load the main module--the command line argument.

    count

       Module._load(process.argv[1], null, true);

    }

    };

    setTimeout(() => {

    //_load静态方法在分析文件名之后执行

    console.log('b.js-1', count)

    var module = new Module(id, parent);

    }, 1000)

    //并根据文件路径缓存当前模块对象,该模块实例对象则根据文件名加载。

    module.exports = {

    module.load(filename);

    count,

    具体说一下上文提到了文件模块的三类模块,这三类文件模块以后缀来区分,Node.js会根据后缀名来决定加载方法,具体的加载方法在下文 require.extensions中会介绍。

    plusCount

    .js通过fs模块同步读取js文件并编译执行。

    }

    .node通过C/C 进行编写的Addon。通过dlopen方法进行加载。

    // a.js

    .json读取文件,调用JSON.parse解析加载。

    let mod = require('./b.js')

    接下来详细描述js后缀的编译过程。Node.js在编译js文件的过程中实际完成的步骤有对js文件内容进行头尾包装。以app.js为例,包装之后的app.js将会变成以下形式:

    console.log('a.js-1', mod.count)

    //circle.js

    mod.plusCount()

    var PI = Math.PI;

    console.log('a.js-2', mod.count)

    exports.area = function (r) {

    setTimeout(() => {

       return PI * r * r;

    mod.count = 3

    };

    console.log('a.js-3', mod.count)

    exports.circumference = function (r) {

    }, 2000)

       return 2 * PI * r;

    node a.js

    };

    a.js-1 1

    //app.js

    a.js-2 1

    var circle = require('./circle.js');

    b.js-1 2  // 1秒后

    console.log( 'The area of a circle of radius 4 is ' circle.area(4));

    a.js-3 3  // 2秒后

    //app包装后

    以上代码可以看出,b模块export的count变量,是一个复制行为。在plusCount方法调用之后,a模块中的count不受影响。同时,可以在b模块中更改a模块中的值。如果希望能够同步代码,可以export出去一个getter。

    (function (exports, require, module, __filename, __dirname) {

    // 其他代码相同

       var circle = require('./circle.js');

    module.exports = {

       console.log('The area of a circle of radius 4 is ' circle.area(4));

    get count () {

    });

    return count

    //这段代码会通过vm原生模块的runInThisContext方法执行(类似eval,只是具有明确上下文,不污染全局),返回为一个具体的function对象。最后传入module对象的exports,require方法,module,文件名,目录名作为实参并执行。

    },

    这就是为什么require并没有定义在app.js 文件中,但是这个方法却存在的原因。从Node.js的API文档中可以看到还有 __filename、 __dirname、 module、 exports几个没有定义但是却存在的变量。其中 __新葡亰496net,filename和 __dirname在查找文件路径的过程中分析得到后传入的。 module变量是这个模块对象自身, exports是在module的构造函数中初始化的一个空对象({},而不是null)。

    plusCount

    在这个主文件中,可以通过require方法去引入其余的模块。而其实这个require方法实际调用的就是module._load方法。

    }

    load方法在载入、编译、缓存了module后,返回module的exports对象。这就是circle.js文件中只有定义在exports对象上的方法才能被外部调用的原因。

    node a.js

    以上所描述的模块载入机制均定义在lib/module.js中。

    a.js-1 1

    require 函数

    a.js-2 1

    require 引入的对象主要是函数。当 Node 调用 require() 函数,并且传递一个文件路径给它的时候,Node 会经历如下几个步骤:

    b.js-1 2  // 1秒后

    Resolving:找到文件的绝对路径;

    a.js-3 2  // 2秒后, 由于没有定义setter,因此无法对值进行设置。所以还是返回2

    Loading:判断文件内容类型;

    对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。

    Wrapping:打包,给这个文件赋予一个私有作用范围。这是使 require 和 module 模块在本地引用的一种方法;

    // b.js

    Evaluating:VM 对加载的代码进行处理的地方;

    let obj = {

    Caching:当再次需要用这个文件的时候,不需要重复一遍上面步骤。

    count: 1

    require.extensions 来查看对三种文件的支持情况:

    }

    可以清晰地看到 Node 对每种扩展名所使用的函数及其操作:对 .js 文件使用 module._compile;对 .json 文件使用 JSON.parse;对 .node 文件使用 process.dlopen。

    let plusCount = () => {

    文件查找策略

    obj.count

    从文件模块缓存中加载

    }

    尽管原生模块与文件模块的优先级不同,但是优先级最高的是从文件模块的缓存中加载已经存在的模块。

    setTimeout(() => {

    从原生模块加载

    console.log('b.js-1', obj.count)

    原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名之后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个 http、 http.js、 http.node、 http.json文件, require(“http”)都不会从这些文件中加载,而是从原生模块中加载。

    }, 1000)

    原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。

    setTimeout(() => {

    从文件加载

    console.log('b.js-2', obj.count)

    当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前面说过是调用load方法。

    }, 3000)

    当 Node 遇到 require(X) 时,按下面的顺序处理。

    module.exports = {

    1、如果 X 是内置模块(比如 require('http'))

    obj,

    a. 返回该模块。

    plusCount

    b. 不再继续执行。

    }

    2、如果 X 以 "./" 或者 "/" 或者 "../" 开头

    // a.js

    a. 根据 X 所在的父模块,确定 X 的绝对路径。

    var mod = require('./b.js')

    b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。

    console.log('a.js-1', mod.obj.count)

    X

    mod.plusCount()

    X.js

    console.log('a.js-2', mod.obj.count)

    X.json

    setTimeout(() => {

    X.node

    mod.obj.count = 3

    c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。

    console.log('a.js-3', mod.obj.count)

    X/package.json(main字段)

    }, 2000)

    X/index.js

    node a.js

    X/index.json

    a.js-1 1

    X/index.node

    a.js-2 2

    3、如果 X 不带路径

    b.js-1 2

    a. 根据 X 所在的父模块,确定 X 可能的安装目录。

    a.js-3 3

    b. 依次在每个目录中,将 X 当成文件名或目录名加载。

    b.js-2 3

    4、抛出 "not found"

    以上代码可以看出,对于对象来说属于浅拷贝。当执行a模块时,首先打印obj.count的值为1,然后通过plusCount方法,再次打印时为2。接着在a模块修改count的值为3,此时在b模块的值也为3。

    模块循环依赖

    3.当使用require命令加载某个模块时,就会运行整个模块的代码。

    //创建两个文件,module1.js 和 module2.js,并且让它们相互引用

    4.当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

       // module1.js

    5.循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

       exports.a = 1;

    3, 4, 5可以使用同一个例子说明

       require('./module2');

    // b.js

       exports.b = 2;

    exports.done = false

       exports.c = 3;

    let a = require('./a.js')

       // module2.js

    console.log('b.js-1', a.done)

       const Module1 = require('./module1');

    exports.done = true

       console.log('Module1 is partially loaded here', Module1);

    console.log('b.js-2', '执行完毕')

    在 module1 完全加载之前需要先加载 module2,而 module2 的加载又需要 module1。这种状态下,我们从 exports 对象中能得到的就是在发生循环依赖之前的这部分。上面代码中,只有 a 属性被引入,因为 b 和 c 都需要在引入 module2 之后才能加载进来。

    // a.js

    Node 使这个问题简单化,在一个模块加载期间开始创建 exports 对象。如果它需要引入其他模块,并且有循环依赖,那么只能部分引入,也就是只能引入发生循环依赖之前所定义的这部分。

    exports.done = false

    AMD

    let b = require('./b.js')

    AMD 是 Asynchronous Module Definition 的简称,即“异步模块定义”,是从 CommonJS 讨论中诞生的。AMD 优先照顾浏览器的模块加载场景,使用了异步加载和回调的方式。

    console.log('a.js-1', b.done)

    AMD 和 CommonJS 一样需要脚本加载器,尽管 AMD 只需要对 define 方法的支持。define 方法需要三个参数:模块名称,模块运行的依赖数组,所有依赖都可用之后执行的函数(该函数按照依赖声明的顺序,接收依赖作为参数)。只有函数参数是必须的。define 既是一种引用模块的方式,也是定义模块的方式。

    exports.done = true

    // file lib/sayModule.js

    console.log('a.js-2', '执行完毕')

    define(function (){

    // c.js

       return {

    let a = require('./a.js')

           sayHello: function () {

    let b = require('./b.js')

               console.log('hello');

    console.log('c.js-1', '执行完毕', a.done, b.done)

           }

    node c.js

       };

    b.js-1 false

    });

    b.js-2 执行完毕

    //file main.js

    a.js-1 true

    define(['./lib/sayModule'], function (say){

    a.js-2 执行完毕

       say.sayHello(); //hello

    c.js-1 执行完毕 true true

    })

    仔细说明一下整个过程。

    main.js 作为整个应用的入口模块,我们使用 define 关键字声明了该模块以及外部依赖(没有生命模块名称);当我们执行该模块代码时,也就是执行 define 函数的第二个参数中定义的函数功能,其会在框架将所有的其他依赖模块加载完毕后被执行。这种延迟代码执行的技术也就保证了依赖的并发加载。

    在Node.js中执行c模块。此时遇到require关键字,执行a.js中所有代码。

    RequireJS

    在a模块中exports之后,通过require引入了b模块,执行b模块的代码。

    RequireJS 是一个前端的模块化管理的工具库,遵循AMD规范,通过一个函数来将所有所需要的或者说所依赖的模块实现装载进来,然后返回一个新的函数(模块),我们所有的关于新模块的业务代码都在这个函数内部操作,其内部也可无限制的使用已经加载进来的以来的模块。

    在b模块中exports之后,又require引入了a模块,此时执行a模块的代码。

    //scripts下的main.js则是指定的主代码脚本文件,所有的依赖模块代码文件都将从该文件开始异步加载进入执行。

    a模块只执行exports.done = false这条语句。

    defined用于定义模块,RequireJS要求每个模块均放在独立的文件之中。按照是否有依赖其他模块的情况分为独立模块和非独立模块。

    回到b模块,打印b.js-1, exports, b.js-2。b模块执行完毕。

    1、独立模块 不依赖其他模块。直接定义:

    回到a模块,接着打印a.js-1, exports, b.js-2。a模块执行完毕

    define({

    回到c模块,接着执行require,需要引入b模块。由于在a模块中已经引入过了,所以直接就可以输出值了。

       methodOne: function (){},

    结束。

       methodTwo: function (){}

    从以上结果和分析过程可以看出,当遇到require命令时,会执行对应的模块代码。当循环引用时,有可能只输出某模块代码的一部分。当引用同一个模块时,不会再次加载,而是获取缓存。

    });

    ES6模块

    //等价于

    es6模块中的值属于【动态只读引用】。只说明一下复杂数据类型。

    define(function (){

    对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

       return {

    对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。

           methodOne: function (){},

    // b.js

           methodTwo: function (){}

    export let counter = {

       };

    count: 1

    });

    }

    2、非独立模块,对其他模块有依赖。

    setTimeout(() => {

    define([ 'moduleOne', 'moduleTwo' ], function(mOne, mTwo){

    console.log('b.js-1', counter.count)

       ...

    }, 1000)

    });

    // a.js

    //或者

    import { counter } from './b.js'

    define( function( require ){

    counter = {}

       var mOne = require( 'moduleOne' ),

    console.log('a.js-1', counter)

           mTwo = require( 'moduleTwo' );

    // Syntax Error: "counter" is read-only

       ...

    虽然不能将counter重新赋值一个新的对象,但是可以给对象添加属性和方法。此时不会报错。这种行为类型与关键字const的用法。

    });

    // a.js

    如上代码, define中有依赖模块数组的 和 没有依赖模块数组用require加载 这两种定义模块,调用模块的方法合称为AMD模式,定义模块清晰,不会污染全局变量,清楚的显示依赖关系。AMD模式可以用于浏览器环境并且允许非同步加载模块,也可以按需动态加载模块。

    import { counter } from './b.js'

    CMD

    counter.count

    CMD(Common Module Definition),在CMD中,一个模块就是一个文件。

    console.log(counter)

    全局函数define,用来定义模块。

    // 2

    参数 factory 可以是一个函数,也可以为对象或者字符串。

    循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

    当 factory 为对象、字符串时,表示模块的接口就是该对象、字符串。

    // b.js

    定义JSON数据模块:

    import {foo} from './a.js';

    define({ "foo": "bar" });

    export function bar() {

    factory 为函数的时候,表示模块的构造方法,执行构造方法便可以得到模块向外提供的接口。

    console.log('bar');

    define( function(require, exports, module) {

    if (Math.random() > 0.5) {

       // 模块代码

    foo();

    });

    }

    SeaJS

    }

    sea.js 核心特征:

    // a.js

    遵循CMD规范,与NodeJS般的书写模块代码。

    import {bar} from './b.js';

    依赖自动加载,配置清晰简洁。

    export function foo() {

    seajs.use用来在页面中加载一个或者多个模块。

    console.log('foo');

    // 加载一个模块

    bar();

    seajs.use('./a');

    console.log('执行完毕');

    // 加载模块,加载完成时执行回调

    }

    seajs.use('./a',function(a){

    foo();

       a.doSomething();

    babel-node a.js

    });

    foo

    // 加载多个模块执行回调

    bar

    seajs.use(['./a','./b'],function(a , b){

    执行完毕

       a.doSomething();

    // 执行结果也有可能是

       b.doSomething();

    foo

    });

    bar

    AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同。

    foo

    很多人说requireJS是异步加载模块,SeaJS是同步加载模块,这么理解实际上是不准确的,其实加载模块都是异步的,只不过AMD依赖前置,js可以方便知道依赖模块是谁,立即加载,而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

    bar

    为什么说是执行时机处理不同?

    执行完毕

    同样都是异步加载模块,AMD在加载模块完成后就会执行该模块,所有模块都加载执行完后会进入回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。

    执行完毕

    CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。

    由于在两个模块之间都存在引用。因此能够正常执行。

    UMD

    以上以上。对es6 module和commonjs module有不了解的同学可以参考一下以下的文章

    统一模块定义(UMD:Universal Module Definition )就是将 AMD 和 CommonJS 合在一起的一种尝试,常见的做法是将CommonJS 语法包裹在兼容 AMD 的代码中。

    ES6 module

    (function(define) {

    module的语法

       define(function () {

    module的加载实现

           return {

    来源:

               sayHello: function () {

                   console.log('hello');

               }

           };

       });

    }(

       typeof module === 'object' && module.exports && typeof define !== 'function' ?

       function (factory) { module.exports = factory(); } :

       define

    ));

    该模式的核心思想在于所谓的 IIFE(Immediately Invoked Function Expression),该函数会根据环境来判断需要的参数类别。

    ES6模块(module)

    严格模式 

    ES6 的模块自动采用严格模式,不管有没有在模块头部加上"use strict";。

    严格模式主要有以下限制。

    变量必须声明后再使用

    函数的参数不能有同名属性,否则报错

    不能使用with语句

    不能对只读属性赋值,否则报错

    不能使用前缀0表示八进制数,否则报错

    不能删除不可删除的属性,否则报错

    不能删除变量delete prop,会报错,只能删除属性delete global[prop]

    eval不会在它的外层作用域引入变量

    eval和arguments不能被重新赋值

    arguments不会自动反映函数参数的变化

    不能使用arguments.callee

    不能使用arguments.caller

    禁止this指向全局对象

    不能使用fn.caller和fn.arguments获取函数调用的堆栈

    增加了保留字(比如protected、static和interface)

    模块Module

    一个模块,就是一个对其他模块暴露自己的属性或者方法的文件。

    导出Export

    作为一个模块,它可以选择性地给其他模块暴露(提供)自己的属性和方法,供其他模块使用。

    // profile.js

    export var firstName = 'qiqi';

    export var lastName = 'haobenben';

    export var year = 1992;

    //等价于

    var firstName = 'qiqi';

    var lastName = 'haobenben';

    var year = 1992;

    export {firstName, lastName, year}

    1、 通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

    function v1() { ... }

    function v2() { ... }

    export {

     v1 as streamV1,

     v2 as streamV2,

     v2 as streamLatestVersion

    };

    新葡亰496net:模块讲解,的一次更新说明。//上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。

    2、 需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

    // 报错

    export 1;

    // 报错

    var m = 1;

    export m;

    //上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量m,还是直接输出1。1只是一个值,不是接口。

    / 写法一

    export var m = 1;

    // 写法二

    var m = 1;

    export {m};

    // 写法三

    var n = 1;

    export {n as m};

    //上面三种写法都是正确的,规定了对外的接口m。其他脚本可以通过这个接口,取到值1。它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系。

    3、最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,接下来说的import命令也是如此。

    function foo() {

     export default 'bar' // SyntaxError

    }

    foo()

    导入import

    作为一个模块,可以根据需要,引入其他模块的提供的属性或者方法,供自己模块使用。

    1、 import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

    import { lastName as surename } from './profile';

    2、import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js路径可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

    3、注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

    foo();

    import { foo } from 'my_module';

    //上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

    4、由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

    / 报错

    import { 'f' 'oo' } from 'my_module';

    // 报错

    let module = 'my_module';

    import { foo } from module;

    // 报错

    if (x === 1) {

     import { foo } from 'module1';

    } else {

     import { foo } from 'module2';

    }

    5、最后,import语句会执行所加载的模块,因此可以有下面的写法。

    import 'lodash';

    //上面代码仅仅执行lodash模块,但是不输入任何值。

    默认导出(export default)

    每个模块支持我们导出 一个没有名字的变量,使用关键语句export default来实现。

    export default function(){

               console.log("I am default Fn");

           }

    //使用export default关键字对外导出一个匿名函数,导入这个模块的时候,可以为这个匿名函数取任意的名字

    //取任意名字均可

    import sayDefault from "./module-B.js";

    sayDefault();

    //结果:I am default Fn

    1、默认输出和正常输出的比较

    // 第一组

    export default function diff() { // 输出

     // ...

    }

    import diff from 'diff'; // 输入

    // 第二组

    export function diff() { // 输出

     // ...

    };

    import {diff} from 'diff'; // 输入

    //上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。

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

    2、因为export default本质是将该命令后面的值,赋给default变量以后再默认,所以直接将一个值写在export default之后。

    / 正确

    export default 42;

    // 报错

    export 42;

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

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

    import _, { each } from 'lodash';

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

    export default function (){

       //...

    }

    export function each (obj, iterator, context){

       //...

    }

    export 与 import 的复合写法

    如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

    export { foo, bar } from 'my_module';

    // 等同于

    import { foo, bar } from 'my_module';

    export { foo, bar };

    / 接口改名

    export { foo as myFoo } from 'my_module';

    // 整体输出

    export * from 'my_module';

    注意事项

    声明的变量,对外都是只读的。但是导出的是对象类型的值,就可修改。

    导入不存在的变量,值为undefined。

    ES6 中的循环引用

    ES6 中,imports 是 exprts 的只读视图,直白一点就是,imports 都指向 exports 原本的数据,比如:

    //------ lib.js ------

    export let counter = 3;

    export function incCounter() {

       counter ;

    }

    //------ main.js ------

    import { counter, incCounter } from './lib';

    // The imported value `counter` is live

    console.log(counter); // 3

    incCounter();

    console.log(counter); // 4

    // The imported value can’t be changed

    counter ; // TypeError

    因此在 ES6 中处理循环引用特别简单,看下面这段代码:

    //------ a.js ------

    import {bar} from 'b'; // (1)

    export function foo() {

     bar(); // (2)

    新葡亰496net:模块讲解,的一次更新说明。}

    //------ b.js ------

    import {foo} from 'a'; // (3)

    export function bar() {

     if (Math.random()) {

       foo(); // (4)

     }

    }

    假设先加载模块 a,在模块 a 加载完成之后,bar 间接性地指向的是模块 b 中的 bar。无论是加载完成的 imports 还是未完成的 imports,imports 和 exports 之间都有一个间接的联系,所以总是可以正常工作。

    实例

    //---module-B.js文件---

    //导出变量:name

    export var name = "cfangxu";

    moduleA模块代码:

    //导入 模块B的属性 name    

    import { name } from "./module-B.js";   

    console.log(name)

    //打印结果:cfangxu

    批量导出:

    //属性name

    var name = "cfangxu";

    //属性age

    var age  = 26;

    //方法 say

    var say = function(){

               console.log("say hello");

            }

    //批量导出

    export {name,age,say}

    批量导入:

    //导入 模块B的属性

    import { name,age,say } from "./module-B.js";

    console.log(name)

    //打印结果:cfangxu

    console.log(age)

    //打印结果:26

    say()

    //打印结果:say hello

    重命名导入变量:

    import {name as myName} from './module-B.js';

    console.log(myName) //cfangxu

    整体导入:

    /使用*实现整体导入

    import * as obj from "./module-B.js";

    console.log(obj.name)

    //结果:"cfangxu"

    console.log(obj.age)

    //结果:26

    obj.say();

    //结果:say hello

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:新葡亰496net:模块讲解,的一次更新说明

    关键词: