博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深度阅读<Javascript Modules 从IIFEs 到CommonJS 到 ES6 Modules>
阅读量:6619 次
发布时间:2019-06-25

本文共 9187 字,大约阅读时间需要 30 分钟。

原文:

本文通过现代社会工厂生产一块手表的过程,引申出如何构建一个物理逻辑都隔离的模块,论述了其包含的思想原则。另外从js发展过程中为实现这些原则而不断做出的努力和尝试,通过了解这些历史,我们能更深入了解ES Modules的设计原则,希望能够对我们平常编写代码提供一些启发。

一块手表由成千上万个零部件构成,每一个零部件都有其自身的作用,并且如何与其它零部件搭配都有比较清晰的规定,把它们组装在一起就是一块手表,那这其中能给我们带来哪些启示呢?

  • 可复用性
  • 可组合型
  • 中心化
  • 独立性

延伸到实际js开发中,对每个文件或者代码块的要求就是能够被重复使用,具有相对独立性(自己负责自己的一块),能够和相关模块进行组合,且整个模块有一个统一的调度中心负责去组合这些独立的模块。

IIFE

我们先看下原始时代,即Jquery还是巅峰的时代,那个时候我们是如何分割代码的,以下就是一个简单的增加用户,列举用户的一个curd例子

// users.jsvar users = ["Tyler", "Sarah", "Dan"]function getUsers() {  return users}复制代码
// dom.jsfunction 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])}复制代码
Users

Users

    复制代码

    看着代码好像我们是把文件分割开了,但实际上并没有,这种方式只是物理上看起来把项目分成多个模块,而其实他们都是挂靠在window对象上的,运行代码查看即可发现。那容易带来的问题就是,第三方可以随意去修改它们,回想下,是不是不符合模块独立性原则。同时这样也容易对window对象造成污染。

    然后紧接着,我们想到既然不能放在window对象上,我们就自己定义一个变量,比如App来承载这些属性和方法,称之为命名空间。代码会变成如下这样

    // App.jsvar APP = {}复制代码
    // users.jsfunction usersWrapper () {  var users = ["Tyler", "Sarah", "Dan"]  function getUsers() {    return users  }  APP.getUsers = getUsers}usersWrapper()复制代码
    // dom.jsfunction 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()复制代码
    Users

    Users

      复制代码

      我们首先不讨论命名空间也容易被污染的问题,这种方式,我们的用户列表现在不容易被外部篡改以及增加用户的逻辑都放在App对象下,独立性有了保证,唯一多了usersWrapperdomWrapper两个包裹函数需要主动去调用下。相比之前有了很大改进。但这两个函数还是暴露在window对象上,后面就有了立即执行函数-IIFE。

      // App.jsvar 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.jsvar users = ["Tyler", "Sarah", "Dan"]export default function getUsers() {  return users}复制代码
      // dom.jsimport 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])}复制代码
            Users        

      Users

      复制代码

      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的使用,理由如下:

      1. export default因为是整体导出,tree shaking无法分析哪些使用哪些没使用,从而无法减少无效代码
      2. 个人觉得代码应该符合一致性原则,由于export default导出在引入的时候可以随意命名使用变量,在团队分工从事的情况下,容易造成引入同一个模块命名不一样带来的代码前后不一致的问题。

      以上就是我对整篇文章的深度阅读,希望这边文章对您在认识模块系统上有一定的帮助,如果喜欢我的文章,欢迎您的点赞!

      转载地址:http://fsupo.baihongyu.com/

      你可能感兴趣的文章
      CakePHP
      查看>>
      我的友情链接
      查看>>
      编译mysql5.6.27
      查看>>
      搭建centos6.7网站服务器记录
      查看>>
      Release版本调用ffmpeg av_register_all程序崩溃
      查看>>
      Referenced management pack not found
      查看>>
      jquery中data函数的用法示例
      查看>>
      巧用strtotime函数计算日期
      查看>>
      JVM中java对象的生命周期
      查看>>
      mysql 查看连接数,状态
      查看>>
      JFinal集成YUI Compressor压缩合并JS和CSS
      查看>>
      windows下的Oracle卸载
      查看>>
      sqlserver查看死锁的存储过程
      查看>>
      在VirtualBox中的CentOS 6.3下安装VirtualBox增强包(GuestAd...
      查看>>
      Java开发中的23种设计模式详解(转)
      查看>>
      Tomcat配置日志生产功能
      查看>>
      移植Qt与Tslib到X210开发板的体会
      查看>>
      Nginx + webpy 和FastCGI搭建webpy环境
      查看>>
      修改页面JS 360浏览器
      查看>>
      Git 跟 GitHub 是什么关系?
      查看>>