摘要:诚然,主要服务于模块和包,由于简单的模块化语法和可复用性,大量和浏览器的包出现在上,也成为世界上最大的包管理器。规范中包含了一个原生的模块化系统,一般称之为。
对于 JavaScript 来说,模块化是一个相对现代的概念,这篇文章会带你在 JavaScript 的世界里快速浏览模块化的历史进程~
Script 标签和闭包在早些年间,JavaScript 就是直接写在 HTML 的 标签里的,最多也就是放在独立的文件里面,而它们也都共享一个全局作用域。
任何 JS 文件里面声明的变量都会被附加在全局的 window 对象上,并且还有可能意外覆盖掉第三方库中的变量。
随着 web 应用越来越复杂,共享全局作用域这种方式的弊端开始显现,于是 IIFE(立即调用函数表达式)就被发明了出来,并且广为使用。IIFE 就是将一整段代码包裹在一个函数中,然后立即执行这个函数。在 JavaScript 中,每个函数都有一个作用域,所以在函数中声明的变量就只在这个函数中可见。即使有变量提升,变量也不会污染到全局作用域中。
下面让我们看几个 IIFE 的写法,每个 IIFE 的作用域都是独立的,其中第一种写法比较常见:
(function() { console.log("IIFE using parenthesis") })() ~function() { console.log("IIFE using a bitwise operator") }() void function() { console.log("IIFE using the void operator") }()
使用 IIFE 这种方式,某个库如果想要暴露全局变量,可以在 window 上绑定一个对象作为命名空间,这样就避免了污染全局作用域。看下面的代码,假如我们要建立一个 mathlib 工具,它有一个 sum 方法。假如这个工具有多个模块,也可以建立多个文件,每个文件里都是一个 IIFE,然后向 window.mathlib 对象中添加方法就可以了:
(function() { window.mathlib = window.mathlib || {} window.mathlib.sum = sum function sum(...values) { return values.reduce((a, b) => a + b, 0) } })() mathlib.sum(1, 2, 3) // <- 6
IIFE 这种方式可以说是模块化的先河,它让开发者可以将模块放在多带带的文件中,并且不污染全局作用域。
当然 IIFE 也有缺点,它并没有一个明确的依赖树,这使得开发者只能自己确保 JS 文件的加载顺序。
RequireJS, AngularJS 和依赖注入RequireJS 和 AngularJS 的出现,让我们知道了依赖注入是什么,即需要用哪个模块,就注入哪个模块。
下面的例子我们先用 RequireJS 的 define 方法定义一个没有依赖的 mathlib/sum.js 模块:
define(function() { return sum function sum(...values) { return values.reduce((a, b) => a + b, 0) } })
然后我们可以创建一个入口模块 mathlib.js 用来集合所有子模块。我们的例子中只有 mathlib/sum 一个子模块,但是你可以在 mathlib 文件夹中随意扩展。下面我们声明 mathlib 模块的依赖,并将依赖作为形参按顺序传入工厂方法,并返回 mathlib 模块对象:
define(["mathlib/sum"], function(sum) { return { sum } })
好了我们已经定义了一个 mathlib 库,下面就可以用 require 引入并使用它:
require(["mathlib"], function(mathlib) { mathlib.sum(1, 2, 3) // <- 6 })
RequireJS 在内部维护了一个依赖树,让开发者不用关心依赖之间的顺序,只需要在需要的地方声明要加载的模块即可使用。
这种明确地声明依赖的写法让各个模块间的依赖都非常清晰,并且反过来促进了模块化的发展。
但是 RequireJS 并不是没有缺点。它的整个模式专注于解决异步加载模块,却忽略了在生产环境下,异步加载多个模块造成的网络请求过多等性能影响。如果依赖过多,开发者也将面临一个很长的依赖数组和回调里面的形参列表。同时它的 API 也不够直观,就拿声明一个含有依赖的模块来说,就有很多种不同的写法。
AngularJS 的依赖注入系统也面临同样的问题。有一个方法可以根据形参名字来解析模块,让开发者不用再写那个依赖数组,但是却对代码压缩工具不友好,因为压缩后变量名就变短了,也就找不到相应的依赖。
直到 AngularJS v1 之后,可以通过一种构建任务,将以下代码:
module.factory("calculator", function(mathlib) { // … })
转换成可压缩的带依赖数组的代码:
module.factory("calculator", ["mathlib", function(mathlib) { // … }])
然鹅不得不提的是,用工程师思维添加了这么一个构建步骤,解决了这个本不应该出现的问题,但是这本身性价比实在是不高,于是大部分开发者还是选择自己手写所有的依赖数组(我当年就是这样,哈哈)。
Node.js 和 CommonJSCommonJS 模块系统是 Node.js 中众多革新的一个,也叫 CJS。得力于 Node.js 可以直接访问文件系统,CommonJS 规范更贴近的是传统的模块加载方式。在 CommonJS 中,每个文件都是一个模块,并具有自己独立的作用域。依赖的加载使用一个同步的 require 函数,这个函数可以在模块的任意地方调用:
const mathlib = require("./mathlib")
与 RequireJS 和 AngularJS 相似的是, CommonJS 依赖也是与文件路径相关联。但是与它们最大的区别,就是 CommonJS 完全抛弃了包装函数和依赖数组,并且require 函数可以像 JS 表达式一样,在模块的任何地方使用。
在 RequireJS 和 AngularJS 中,你可能有很多动态定义的模块,然而 CommonJS 中的文件和模块是一一对应的。与此同时,RequireJS 众多的模块定义方式,与 AngularJS 中的 factory、service、provider 都让人头大。与之相反的是,CommonJS 只有一种模块加载方式,一个 JS 文件就是一个模块,加载依赖只需要用 require,导出模块只需要将要导出的值赋给 module.exports。这些优点都让 CommonJS 模块系统更简洁和易于使用。
终于,Browserify 作为桥梁,打通了 CommonJS 在 Node.js 和浏览器端的鸿沟。它可以将众多模块打包成一个可在浏览器中运行的文件。而 npm 源的出现,作为 CommonJS 的杀手级功能,基本上确立了模块加载生态中的事实标准。
诚然,npm 主要服务于 CommonJS 模块和 JavaScript 包,由于简单的模块化语法和可复用性,大量 Node.js 和 web 浏览器的包出现在 npm 上,npm 也成为世界上最大的包管理器。
ES6, import, Babel, 和 WebpackES6 是在 2015 年被标准化,在此之前 Babel 一直承担着将 ES6 转换为 ES5 的角色,一场新的革命正在袭来。ES6 规范中包含了一个原生的模块化系统,一般称之为 ECMAScript Modules(ESM)。
ESM 受到 CommonJS 和先烈们的影响,提供了一个静态的声明式的 API 和一个基于 Promise 的动态加载的 API:
import mathlib from "./mathlib" import("./mathlib").then(mathlib => { // … })
在 ESM 中,每个文件同样是一个模块,并且具有自己独立的作用域和执行环境。ESM 相对 CJS 来说有一个重要的优点:即 ESM 是静态加载依赖的。静态加载极大地提高了模块系统的自我检查能力,使得模块系统可以基于抽象语法树(AST)作静态分析,同时 ESM 限制了加载语句必须置于模块顶部,也大大简化了语法解析和语法检查。
在 Node.js v8.5.0 中,ESM 已经可以通过一个 flag 开启。大部分主流的浏览器也都可以支持 ESM。
Webpack 作为 Browserify 的继任者,由于功能强大,基本上坐稳了通用模块打包器老大的位置。像 Babel 支持转换 ES6 那样,Webpack 很早就支持了 ESM 的 import 和 export 语法以及 import() 动态加载函数。并且在 ESM 的基础上,添加了 code-splitting 功能,可以将应用程序代码分割成多个文件来提升首屏加载体验。
鉴于 ESM 是原生的模块加载规范,它一统江湖也指日可待了!
原文链接
英文原文链接
欢迎关注我的公众号:码力全开(codingonfire)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/105090.html
摘要:简介原文链接简称是一种轻量级,解释型的编程语言,其函数是一等公民。标准的目标是让任何一种程序设计语言能操控使用任何一种标记语言编写出的任何一份文档。核心规定了如何映射基于的文档结构,以便简化对文档的任意部分的访问和操作。 JavaScript 简介 原文链接 JavaScript ( 简称:JS ) 是一种 轻量级,解释型 的编程语言,其函数是一等公民。众所周知,它是用于网页开发的脚...
摘要:当我们学习的模块化,就会发现它的发展深受的影响。严格模式在模块系统中,严格模式是默认开启的。同样的,模块内部的声明只在模块内部有效。在中,我们使用导入内容在模块中,我们只需要为导入的绑定起一个名字我们也可以导入具名导出的内容。 ES6 模块系统 在 ES6 之前,我们已经知道了 RequireJS,AngularJS 的依赖注入,以及 CommonJS,具体可以看笔者的上一篇文章《JS...
摘要:文章的第二部分涵盖了内存管理的概念,不久后将发布。的标准化工作是由国际组织负责的,相关规范被称为或者。随着分析器和编译器不断地更改字节码,的执行性能逐渐提高。 原文地址:How Does JavaScript Really Work? (Part 1) 原文作者:Priyesh Patel 译者:Chor showImg(https://segmentfault.com/img...
摘要:即将立秋的课多周刊第期我们的微信公众号,更多精彩内容皆在微信公众号,欢迎关注。若有帮助,请把课多周刊推荐给你的朋友,你的支持是我们最大的动力。课多周刊机器人运营中心是如何玩转起来的分享课多周刊是如何运营并坚持下来的。 即将立秋的《课多周刊》(第2期) 我们的微信公众号:fed-talk,更多精彩内容皆在微信公众号,欢迎关注。 若有帮助,请把 课多周刊 推荐给你的朋友,你的支持是我们最大...
阅读 2218·2021-11-25 09:43
阅读 3065·2021-10-14 09:42
阅读 3456·2021-10-12 10:12
阅读 1464·2021-09-07 10:17
阅读 1857·2019-08-30 15:54
阅读 3150·2019-08-30 15:54
阅读 1490·2019-08-30 15:53
阅读 1815·2019-08-29 11:21