深度阅读<Javascript Modules 从IIFEs 到CommonJS 到 ES6 Modules>
栏目: JavaScript · 发布时间: 5年前
内容简介:原文:本文通过现代社会工厂生产一块手表的过程,引申出如何构建一个物理逻辑都隔离的模块,论述了其包含的思想原则。另外从js发展过程中为实现这些原则而不断做出的努力和尝试,通过了解这些历史,我们能更深入了解一块手表由成千上万个零部件构成,每一个零部件都有其自身的作用,并且如何与其它零部件搭配都有比较清晰的规定,把它们组装在一起就是一块手表,那这其中能给我们带来哪些启示呢?
原文: tylermcginnis.com/javascript-…
本文通过现代社会工厂生产一块手表的过程,引申出如何构建一个物理逻辑都隔离的模块,论述了其包含的思想原则。另外从js发展过程中为实现这些原则而不断做出的努力和尝试,通过了解这些历史,我们能更深入了解 ES Modules 的设计原则,希望能够对我们平常编写代码提供一些启发。
一块手表由成千上万个零部件构成,每一个零部件都有其自身的作用,并且如何与其它零部件搭配都有比较清晰的规定,把它们组装在一起就是一块手表,那这其中能给我们带来哪些启示呢?
- 可复用性
- 可组合型
- 中心化
- 独立性
延伸到实际js开发中,对每个文件或者代码块的要求就是能够被重复使用,具有相对独立性(自己负责自己的一块),能够和相关模块进行组合,且整个模块有一个统一的调度中心负责去组合这些独立的模块。
IIFE
我们先看下原始时代,即Jquery还是巅峰的时代,那个时候我们是如何分割代码的,以下就是一个简单的增加用户,列举用户的一个curd例子
// users.js var users = ["Tyler", "Sarah", "Dan"] function getUsers() { return users } 复制代码
// dom.js function addUserToDOM(name) { const node = document.createElement("li") const text = document.createTextNode(name) node.appendChild(text) document.getElementById("users") .appendChild(node) } document.getElementById("submit") .addEventListener("click", function() { var input = document.getElementById("input") addUserToDOM(input.value) input.value = "" }) var users = window.getUsers() for (var i = 0; i < users.length; i++) { addUserToDOM(users[i]) } 复制代码
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>Users</title> </head> <body> <h1>Users</h1> <ul id="users"></ul> <input id="input" type="text" placeholder="New User"> </input> <button id="submit">Submit</button> <script src="users.js"></script> <script src="dom.js"></script> </body> </html> 复制代码
看着代码好像我们是把文件分割开了,但实际上并没有,这种方式只是物理上看起来把项目分成多个模块,而其实他们都是挂靠在 window
对象上的,运行代码查看即可发现。那容易带来的问题就是,第三方可以随意去修改它们,回想下,是不是不符合模块独立性原则。同时这样也容易对window对象造成污染。
然后紧接着,我们想到既然不能放在 window
对象上,我们就自己定义一个变量,比如 App
来承载这些属性和方法,称之为 命名空间 。代码会变成如下这样
// App.js var APP = {} 复制代码
// users.js function usersWrapper () { var users = ["Tyler", "Sarah", "Dan"] function getUsers() { return users } APP.getUsers = getUsers } usersWrapper() 复制代码
// dom.js function domWrapper() { function addUserToDOM(name) { const node = document.createElement("li") const text = document.createTextNode(name) node.appendChild(text) document.getElementById("users") .appendChild(node) } document.getElementById("submit") .addEventListener("click", function() { var input = document.getElementById("input") addUserToDOM(input.value) input.value = "" }) var users = APP.getUsers() for (var i = 0; i < users.length; i++) { addUserToDOM(users[i]) } } domWrapper() 复制代码
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>Users</title> </head> <body> <h1>Users</h1> <ul id="users"></ul> <input id="input" type="text" placeholder="New User"> </input> <button id="submit">Submit</button> <script src="app.js"></script> <script src="users.js"></script> <script src="dom.js"></script> </body> </html> 复制代码
我们首先不讨论命名空间也容易被污染的问题,这种方式,我们的用户列表现在不容易被外部篡改以及增加用户的逻辑都放在 App
对象下,独立性有了保证,唯一多了 usersWrapper
和 domWrapper
两个包裹函数需要主动去调用下。相比之前有了很大改进。但这两个函数还是暴露在 window
对象上,后面就有了立即执行函数-IIFE。
// App.js var APP = {} 复制代码
// users.js (function () { var users = ["Tyler", "Sarah", "Dan"] function getUsers() { return users } APP.getUsers = getUsers })() 复制代码
// dom.js (function () { function addUserToDOM(name) { const node = document.createElement("li") const text = document.createTextNode(name) node.appendChild(text) document.getElementById("users") .appendChild(node) } document.getElementById("submit") .addEventListener("click", function() { var input = document.getElementById("input") addUserToDOM(input.value) input.value = "" }) var users = APP.getUsers() for (var i = 0; i < users.length; i++) { addUserToDOM(users[i]) } })() 复制代码
现在除了 App
变量还暴露在 window
对象上之外,另外两个函数都有了自己的独立的作用域,外部不能修改它们。虽然这种方式不是很完美,但是还是迈进了一大步。
CommonJS
后面Node.js出来了,有个CommonJS规范,能够导出一个方法或变量,在需要的文件中能够导入一个方法或变量,但它在现代浏览器中无法运行,且它是同步的,无法满足现代浏览器对性能的要求。基于此社区也出现了很多方案,最火的莫过于 webpack
,通过 webpack
你能将基于CommonJS规范编写的代码打包成一个bundle,在入口index.html文件中直接引用这个bundle即可。然而通过查看webpack编译后的代码你会发现本质上运用的还是IIFE模式,且最关键的还是CommonJS是同步的,不支持异步加载,另外就是它是运行时加载,无法做静态分析导致类如 tree shaking 等特性无法被满足。
(function(modules) { // webpackBootstrap // The module cache var installedModules = {}; // The require function function __webpack_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ ); // Flag the module as loaded module.l = true; // Return the exports of the module return module.exports; } // expose the modules object (__webpack_modules__) __webpack_require__.m = modules; // expose the module cache __webpack_require__.c = installedModules; // define getter function for harmony exports __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty( exports, name, { enumerable: true, get: getter } ); } }; // define __esModule on exports __webpack_require__.r = function(exports) { if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); }; // create a fake namespace object // mode & 1: value is a module id, require it // mode & 2: merge all properties of value into the ns // mode & 4: return value when already ns object // mode & 8|1: behave like require __webpack_require__.t = function(value, mode) { if(mode & 1) value = __webpack_require__(value); if(mode & 8) return value; if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; var ns = Object.create(null); __webpack_require__.r(ns); Object.defineProperty(ns, 'default', { enumerable: true, value: value }); if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); return ns; }; // getDefaultExport function for compatibility with non-harmony modules __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; }; // Object.prototype.hasOwnProperty.call __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // __webpack_public_path__ __webpack_require__.p = ""; // Load entry module and return exports return __webpack_require__(__webpack_require__.s = "./dom.js"); }) /************************************************************************/ ({ /***/ "./dom.js": /*!****************!*\ !*** ./dom.js ***! \****************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { eval(` var getUsers = __webpack_require__(/*! ./users */ \"./users.js\").getUsers\n\n function addUserToDOM(name) {\n const node = document.createElement(\"li\")\n const text = document.createTextNode(name)\n node.appendChild(text)\n\n document.getElementById(\"users\")\n .appendChild(node)\n}\n\n document.getElementById(\"submit\")\n .addEventListener(\"click\", function() {\n var input = document.getElementById(\"input\")\n addUserToDOM(input.value)\n\n input.value = \"\"\n})\n\n var users = getUsers()\n for (var i = 0; i < users.length; i++) {\n addUserToDOM(users[i])\n }\n\n\n//# sourceURL=webpack:///./dom.js?` );}), /***/ "./users.js": /*!******************!*\ !*** ./users.js ***! \******************/ /*! no static exports found */ /***/ (function(module, exports) { eval(` var users = [\"Tyler\", \"Sarah\", \"Dan\"]\n\n function getUsers() {\n return users\n}\n\nmodule.exports = {\n getUsers: getUsers\n }\n\n//# sourceURL=webpack:///./users.js?`);}) }); 复制代码
ES Modules
为了解决以上种种问题,TC-39发布了 ES Modules ,对比以往,没有任何新的命名空间被创建,每个模块都是独立的,互不干扰的,可以随时被组合在一起。
// users.js var users = ["Tyler", "Sarah", "Dan"] export default function getUsers() { return users } 复制代码
// dom.js import getUsers from './users.js' function addUserToDOM(name) { const node = document.createElement("li") const text = document.createTextNode(name) node.appendChild(text) document.getElementById("users") .appendChild(node) } document.getElementById("submit") .addEventListener("click", function() { var input = document.getElementById("input") addUserToDOM(input.value) input.value = "" }) var users = getUsers() for (var i = 0; i < users.length; i++) { addUserToDOM(users[i]) } 复制代码
<!DOCTYPE html> <html> <head> <title>Users</title> </head> <body> <h1>Users</h1> <ul id="users"> </ul> <input id="input" type="text" placeholder="New User"></input> <button id="submit">Submit</button> <script type=module src='dom.js'></script> </body> </html> 复制代码
Tree Shaking
CommonJS modules 和 ES Modules有一个最大的不同,通过CommonJS你能导入任何模块在任何地点
if (pastTheFold === true) { require('./parallax') } 复制代码
而ES Modules因为是静态的,只能在文件最开头导入
if (pastTheFold === true) { import './parallax' // "import' and 'export' may only appear at the top level" } 复制代码
为什么这么设计呢,原因是静态分析,我们能够分析出导入的模块,如果有些模块没有被使用,我们通过tree shaking去除这些无用的代码,从而减少代码体积,进而提升运行性能,而CommonJS是动态分析的,就无法做到这一点,这也是为啥webpack后面版本才只是tree skaking特性的原因,因为它必须依赖于ES6 Modules静态编译特性。
Export Default的问题
Es Modules导出有export 和 export default两种方式,它们区别如下:
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要
- export能直接导出变量表达式,export default不行。
我这里主要想讲的是尽量减少export default的使用,理由如下:
- export default因为是整体导出,tree shaking无法分析哪些使用哪些没使用,从而无法减少无效代码
- 个人觉得代码应该符合一致性原则,由于export default导出在引入的时候可以随意命名使用变量,在团队分工从事的情况下,容易造成引入同一个模块命名不一样带来的代码前后不一致的问题。
以上就是我对整篇文章的深度阅读,希望这边文章对您在认识模块系统上有一定的帮助,如果喜欢我的文章,欢迎您的点赞!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- React 作者关于 Hooks 的深度 issue,值得你阅读
- Enlitic以实现影像阅读的“快,准,狠”为目标,着力研发深度学习技术
- 【源码阅读】AndPermission源码阅读
- 【源码阅读】Gson源码阅读
- 如何阅读Java源码 ,阅读java的真实体会
- 如何阅读源码?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTTP Developer's Handbook
Chris Shiflett / Sams Publishing / 2003-3-29 / USD 39.99
The largest group with an unsatisfied demand for a good book on HTTP is the worldwide group of Web developers. A good book on HTTP can help new and old Web developers alike, as a thorough understandin......一起来看看 《HTTP Developer's Handbook》 这本书的介绍吧!