JavaScript模块知识理解

问题1:如何编写模块?

以下摘自《你不知道的JavaScript下卷》

在所有 JavaScript 代码中,唯一最重要的代码组织模式模块而且一直都是,我并不认为这是夸大其词。对于我本人,我认为也对于广泛社区来说,模块模式驱动了大多数代码。
3.3.1 旧方法
传统的模块模式基于一个带有内部变量和函数的外层函数,以及一个被返回的“publicAPI”,这个“public API”带有对内部数据和功能拥有闭包的方法。通常这样表达:
function Hello(name) {
     function greeting() {
         console.log( "Hello " + name + "!" );
     }
    // public API
   return {
       greeting: greeting
    };
}
var me = Hello( "Kyle" );
me.greeting(); // Hello Kyle!
继续调用 Hello(..) 模块可以产生多个实例。有时一个模块只作为单例(singleton,也就是说只需要一个实例),这种情况下前面的代码需要稍作修改,通常这样使用一个 IIFE
var me = (function Hello(name){
        function greeting() {
      console.log( "Hello " + name + "!" );
}
// public API
return {
greeting: greeting
};
})( "Kyle" );
me.greeting(); // Hello Kyle!
这个模式是经过试验的。它也足够灵活,针对不同场景有多个变体。其中常用的是异步模块定义(Asynchronous Module DefinitionAMD),还有一种是通用模块定义(Universal Module DefinitionUMD)。这里我们不会具体介绍这些模式和技术,但网上有很多详尽的解释。

3.3.2 前进
对于 ES6 来说,我们不再需要依赖于封装函数和闭包提供模块支持ES6 中模块已经具备一等(first class)语法和功能支持。在讨论具体语法细节之前,有一点很重要,就是要理解 ES6 模块和过去我们处理模块的方式之间的显著概念区别。
• ES6 使用基于文件的模块,也就是说一个文件一个模块。目前,还没有把多个模块合并到单个文件中的标准方法。这意味着如果想要把 ES6 模块直接加载到浏览器 Web 应用中,需要分别加载,而不是作为一大组放在单个文件中加载。在过去,为了性能优化,后者这种加载方式是很常见的。期待 HTTP/2 的到来能够显著消除所有这样的性能担忧,因为它运行在持久 socket 连接上,所以能够高效并发、交替加载多个小文件。
• ES6模块的API是静态的。也就是说,需要在模块的公开API中静态定义所有最高层导出,之后无法补充。某些应用已经习惯了提供动态 API 定义的能力,可以根据对运行时情况的响应增加 / 删除 / 替换方法。这些用法或者改变自身以适应 ES6 静态 API,或者需要限制对二级对象属性 / 方法的动态修改。
• ES6 模块是单例。也就是说,模块只有一个实例,其中维护了它的状态。每次向其他模块导入这个模块的时候,得到的是对单个中心实例的引用。如果需要产生多个模块实例,那么你的模块需要提供某种工厂方法来实现这一点。
模块的公开 API 中暴露的属性和方法并不仅仅是普通的值或引用的赋值。它们是到内部模块定义中的标识符的实际绑定(几乎类似于指针)。在前 ES6 的模块中,如果把一个持有像数字或者字符串这样的原生值的属性放在公开
API 中,这个属性赋值是通过值复制赋值,任何对于对应变量的内部更新将会是独立的,不会影响 API 对象的公开复制。对于 ES6 来说,导出一个局部私有变量,即使当前它持有一个原生字符串 / 数字等,导出的都是到这个变量的绑定。如果模块修改了这个变量的值,外部导入绑定现在会决议到新的值。

导入模块和静态请求加载(如果还没加载的话)这个模块是一样的。如果是在浏览器环境中,这意味着通过网络阻塞加载;如果是在服务器上(比如 Node.js),则是从文件系统的阻塞加载。
但是,不要惊慌于这里的性能暗示。因为 ES6 模块具有静态定义,导入需求可以静态扫描预先加载,甚至是在使用这个模块之前。

关于如何处理这些加载请求, ES6 并没有实际指定或处理具体机制。这里有一个独立的模块加载器(Module Loader)的概念,其中每个宿主环境(浏览器、 Node.js 等)提供一个适合环境的默认加载器。导入模块时使用一个字符串值表示去哪里获得这个模块(URL、文件路径等),但是这个值对于你的程序来说是透明的,只对加载器本身有意义。
如果需要提供比默认加载器更细粒度的控制能力可以自定义加载器,默认加载器基本上没有粒度控制,因为它对于你的程序代码完全是不可见的。

你可以看到, ES6 模块将会为代码组织提供完整支持,包括封装、控制公开 API 以及引用依赖导入。但是实现方式非常特殊,可能可以也可能无法完全适应之前多年以来实现模块的方式。

CommonJS
还有一个类似、但并不完全兼容的模块语法 CommonJSNode.js 生态系统下的开发者对此会很熟悉。不得不说,长远看来, ES6 模块从本质上说必然会取代之前所有的模块格式和标准,即使
CommonJS, 因为 ES6 模块是建立在语言的语法支持基础上的。最终它总会不可逆转地作为统治方法成为最后的赢家。但离那时还有很长的路要走。在服务器端 JavaScript 的世界里,已经有了成百上千的
CommonJS 风格模块,以及浏览器端十倍于此的各种格式标准的模块(UMDAMD、临时性的模块方案)。要迁移这些模块需要很多年才能初见成效。在此期间,模块 transpiler / 转换工具是必不可少的。你可能刚刚开始习惯这个新现实。不
管你是在编写普通模块、 AMDUMDCommonJS 还是 ES6,这些工具都不得不解析转化为对代码运行的所有环境都适用的形式。
对于 Node.js 来说,这可能意味着(目前的)目标是 CommonJS。对于浏览器来说,可能是 UMD 或者 AMD。接下来的几年里,随着这些工具的成熟和最佳实践的涌现,这一方面可能会变幻莫测。

由此,关于模块我的最好建议是:不管之前你虔诚地支持和熟悉哪种格式,现在开始开发
和理解 ES6 模块吧,因为它终将会使得其他模块方法消失。它是 JavaScript 模块的未来,
虽然现在有点落后。
3.3.3 新方法
支撑 ES6 模块的两个主要新关键字是 import export它们在语法上有许多微妙之处,所以我们来深入了解一下 。。。。。。

 

 

 

 

值得对照起来学习的模块化内容:

1、《JavaScript语言精髓和编程实践》第3章3.2节,从词法作用域的角度理解模块化的原理及好处。

2、《JavaScript权威指南第6版》第9章9.9节对如何实现模块做了简单但独到的解读。

3、《JavaScript编程精解》第二版中第10章模块部分,讲解了模块的实现方法以及常见的requier函数的原理,对了解commonjs模块有一定借鉴,虽然暂时没看懂,值得细细研究

posted @ 2018-07-07 21:03  编城浪子  阅读(147)  评论(0编辑  收藏  举报