1. 首页

JavaScript它如何运行:模块的构建以及对应的打包工具

这是专门探索 JavaScript 及其所构建的组件的系列文章的第 20 篇。

如果你错过了前面的章节,可以在这里找到它们:

  1. JavaScript它如何运行:引擎,运行时和调用堆栈的概述!
  2. JavaScript它如何运行:深入V8引擎&编写优化代码的5个技巧!
  3. JavaScript它如何运行:内存管理+如何处理4个常见的内存泄漏!
  4. JavaScript它如何运行:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!
  5. JavaScript它如何运行:深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!
  6. JavaScript它如何运行:与 WebAssembly比较 及其使用场景!
  7. JavaScript它如何运行:Web Workers的构建块+ 5个使用他们的场景!
  8. JavaScript它如何运行:Service Worker 的生命周期及使用场景!
  9. JavaScript它如何运行:Web 推送通知的机制!
  10. JavaScript它如何运行:使用 MutationObserver 跟踪 DOM 的变化!
  11. JavaScript它如何运行:渲染引擎和优化其性能的技巧!
  12. JavaScript它如何运行:深入网络层 + 如何优化性能和安全!
  13. JavaScript它如何运行:CSS 和 JS 动画底层原理及如何优化它们的性能!
  14. JavaScript它如何运行:解析、抽象语法树(AST)+ 提升编译速度5个技巧!
  15. JavaScript它如何运行:深入类和继承内部原理+Babel和 TypeScript 之间转换!
  16. JavaScript它如何运行:存储引擎+如何选择合适的存储API!
  17. JavaScript它如何运行:Shadow DOM 的内部结构+如何编写独立的组件!
  18. JavaScript它如何运行:WebRTC 和对等网络的机制!
  19. JavaScript它如何运行:编写自己的 Web 开发框架 + React 及其虚拟 DOM 原理!
  20. JavaScript它如何运行:JavaScript 的共享传递和按值传递

JS中文网 - 前端进阶资源分享

如果你是 JavaScript 的新手,一些像 “module bundlers vs module loaders”、“Webpack vs Browserify” 和 “AMD vs.CommonJS” 这样的术语,很快让你不堪重负。

JavaScript 模块系统可能令人生畏,但理解它对 Web 开发人员至关重要。

在这篇文章中,我将以简单的言语(以及一些代码示例)为你解释这些术语。 希望这对你有会有帮助!

什么是模块?

好作者能将他们的书分成章节,优秀的程序员将他们的程序划分为模块。

就像书中的章节一样,模块只是文字片段(或代码,视情况而定)的集群。然而,好的模块是高内聚低松耦的,具有不同的功能,允许在必要时对它们进行替换、删除或添加,而不会扰乱整体功能。

为什么使用模块?

使用模块有利于扩展、相互依赖的代码库,这有很多好处。在我看来,最重要的是:

1)可维护性: 根据定义,模块是高内聚的。一个设计良好的模块旨在尽可能减少对代码库部分的依赖,这样它就可以独立地增强和改进,当模块与其他代码片段解耦时,更新单个模块要容易得多。

回到我们的书的例子,如果你想要更新你书中的一个章节,如果对一个章节的小改动需要你调整每一个章节,那将是一场噩梦。相反,你希望以这样一种方式编写每一章,即可以在不影响其他章节的情况下进行改进。

2)命名空间: 在 JavaScript 中,顶级函数范围之外的变量是全局的(这意味着每个人都可以访问它们)。因此,“名称空间污染”很常见,完全不相关的代码共享全局变量。

在不相关的代码之间共享全局变量在开发中是一个大禁忌。正如我们将在本文后面看到的,通过为变量创建私有空间,模块允许我们避免名称空间污染。

3)可重用性:坦白地说:我们将前写过的代码复制到新项目中。 例如,假设你从之前项目编写的一些实用程序方法复制到当前项目中。

这一切都很好,但如果你找到一个更好的方法来编写代码的某些部分,那么你必须记得回去在曾经使用过的其他项目更新它。

这显然是在浪费时间。如果有一个我们可以一遍又一遍地重复使用的模块,不是更容易吗?

如何创建模块?

有多种方法来创建模块,来看几个:

模块模式

模块模式用于模拟类的概念(因为 JavaScript 本身不支持类),因此我们可以在单个对象中存储公共和私有方法和变量——类似于在 Java 或 Python 等其他编程语言中使用类的方式。这允许我们为想要公开的方法创建一个面向公共的 API,同时仍然将私有变量和方法封装在闭包范围中。

有几种方法可以实现模块模式。在第一个示例中,将使用匿名闭包,将所有代码放在匿名函数中来帮助我们实现目标。(记住:在 JavaScript 中,函数是创建新作用域的唯一方法。)

例一:匿名闭包


(function () { // 将这些变量放在闭包范围内实现私有化 var myGrades = [93, 95, 88, 0, 55, 91]; // Js中文网 https://www.javascriptc.com/ var average = function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item}, 0); return '平均分 ' + total / myGrades.length + '.'; } var failing = function(){ var failingGrades = myGrades.filter(function(item) { return item < 70;}); return '挂机科了 ' + failingGrades.length + ' 次。'; } console.log(failing()); // 挂机科了次 }());

使用这个结构,匿名函数就有了自己的执行环境或“闭包”,然后我们立即执行。这让我们可以从父(全局)命名空间隐藏变量。

这种方法的优点是,你可以在这个函数中使用局部变量,而不会意外地覆盖现有的全局变量,但仍然可以访问全局变量,就像这样:


var global = '你好,我是一个全局变量。)'; (function () { // 将这些变量放在闭包范围内实现私有化 var myGrades = [93, 95, 88, 0, 55, 91]; var average = function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item}, 0); return '平均分 ' + total / myGrades.length + '.'; } var failing = function(){ var failingGrades = myGrades.filter(function(item) { return item < 70;}); return '挂机科了 ' + failingGrades.length + ' 次。'; } console.log(failing()); // 挂机科了次 onsole.log(global,'Js中文网 https://www.javascriptc.com/'); // 你好,我是一个全局变量。 }());

注意,匿名函数的圆括号是必需的,因为以关键字 function 开头的语句通常被认为是函数声明(请记住,JavaScript 中不能使用未命名的函数声明)。因此,周围的括号将创建一个函数表达式,并立即执行这个函数,这还有另一种叫法 立即执行函数(IIFE)。如果你对这感兴趣,可以在这里了解到更多。

例二:全局导入

jQuery 等库使用的另一种流行方法是全局导入。它类似于我们刚才看到的匿名闭包,只是现在我们作为参数传入全局变量:


(function (globalVariable) { // 在这个闭包范围内保持变量的私有化 var privateFunction = function() { console.log('Shhhh, this is private!'); } // 通过 globalVariable 接口公开下面的方法 // 同时将方法的实现隐藏在 function() 块中 globalVariable.each = function(collection, iterator) { if (Array.isArray(collection)) { for (var i = 0; i < collection.length; i++) { iterator(collection[i], i, collection); } } else { for (var key in collection) { iterator(collection[key], key, collection); } } }; globalVariable.filter = function(collection, test) { var filtered = []; globalVariable.each(collection, function(item) { if (test(item)) { filtered.push(item); } }); return filtered; }; globalVariable.map = function(collection, iterator) { var mapped = []; globalUtils.each(collection, function(value, key, collection) { mapped.push(iterator(value)); }); return mapped; }; globalVariable.reduce = function(collection, iterator, accumulator) { var startingValueMissing = accumulator === undefined; globalVariable.each(collection, function(item) { if(startingValueMissing) { accumulator = item; startingValueMissing = false; } else { accumulator = iterator(accumulator, item); } }); return accumulator; }; }(globalVariable));

在这个例子中,globalVariable 是唯一的全局变量。与匿名闭包相比,这种方法的好处是可以预先声明全局变量,使得别人更容易阅读代码。

例三:对象接口

另一种方法是使用立即执行函数接口对象创建模块,如下所示:


var myGradesCalculate = (function () { // 将这些变量放在闭包范围内实现私有化 var myGrades = [93, 95, 88, 0, 55, 91]; // 通过接口公开这些函数,同时将模块的实现隐藏在function()块中 return { average: function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'平均分 ' + total / myGrades.length + '.'; }, failing: function() { var failingGrades = myGrades.filter(function(item) { return item < 70; }); return '挂科了' + failingGrades.length + ' 次.'; } } })(); myGradesCalculate.failing(); // '挂科了 2 次.' myGradesCalculate.average(); // '平均分 70.33333333333333.'

正如您所看到的,这种方法允许我们通过将它们放在 return 语句中(例如算平均分和挂科数方法)来决定我们想要保留的变量/方法(例如 myGrades)以及我们想要公开的变量/方法。

例四:显式模块模式

这与上面的方法非常相似,只是它确保所有方法和变量在显式公开之前都是私有的:


var myGradesCalculate = (function () { // 将这些变量放在闭包范围内实现私有化 var myGrades = [93, 95, 88, 0, 55, 91]; var average = function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'平均分 ' + total / myGrades.length + '.'; }; var failing = function() { var failingGrades = myGrades.filter(function(item) { return item < 70; }); return '挂科了' + failingGrades.length + ' 次.'; }; // Explicitly reveal public pointers to the private functions // that we want to reveal publicly return { average: average, failing: failing } })(); myGradesCalculate.failing(); // '挂科了 2 次.' myGradesCalculate.average(); // '平均分 70.33333333333333.'

这可能看起来很多,但它只是模块模式的冰山一角。 以下是我在自己的探索中发现有用的一些资源:

CommonJS 和 AMD

所有这些方法都有一个共同点:使用单个全局变量将其代码包装在函数中,从而使用闭包作用域为自己创建一个私有名称空间。

虽然每种方法都有效且都有各自特点,但却都有缺点。

首先,作为开发人员,你需要知道加载文件的正确依赖顺序。例如,假设你在项目中使用 Backbone,因此你可以将 Backbone 的源代码 以

« JavaScript 为什么要有 Symbol 类型
推荐几个不错的console调试技巧»
Flutter 中文教程资源

相关推荐

QR code