原文:tylermcginnis.com/javascript-…javascript
本文经过现代社会工厂生产一块手表的过程,引伸出如何构建一个物理逻辑都隔离的模块,论述了其包含的思想原则。另外从js发展过程当中为实现这些原则而不断作出的努力和尝试,经过了解这些历史,咱们能更深刻了解ES Modules的设计原则,但愿可以对咱们日常编写代码提供一些启发。html
一块手表由成千上万个零部件构成,每个零部件都有其自身的做用,而且如何与其它零部件搭配都有比较清晰的规定,把它们组装在一块儿就是一块手表,那这其中能给咱们带来哪些启示呢?java
延伸到实际js开发中,对每一个文件或者代码块的要求就是可以被重复使用,具备相对独立性(本身负责本身的一块),可以和相关模块进行组合,且整个模块有一个统一的调度中心负责去组合这些独立的模块。node
咱们先看下原始时代,即Jquery仍是巅峰的时代,那个时候咱们是如何分割代码的,如下就是一个简单的增长用户,列举用户的一个curd例子webpack
// 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对象形成污染。web
而后紧接着,咱们想到既然不能放在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。bash
// 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
对象上以外,另外两个函数都有了本身的独立的做用域,外部不能修改它们。虽然这种方式不是很完美,可是仍是迈进了一大步。app
后面Node.js出来了,有个CommonJS规范,可以导出一个方法或变量,在须要的文件中可以导入一个方法或变量,但它在现代浏览器中没法运行,且它是同步的,没法知足现代浏览器对性能的要求。基于此社区也出现了不少方案,最火的莫过于webpack
,经过webpack
你能将基于CommonJS规范编写的代码打包成一个bundle,在入口index.html文件中直接引用这个bundle便可。然而经过查看webpack编译后的代码你会发现本质上运用的仍是IIFE模式,且最关键的仍是CommonJS是同步的,不支持异步加载,另外就是它是运行时加载,没法作静态分析致使类如tree shaking等特性没法被知足。dom
(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?`);}) }); 复制代码
为了解决以上种种问题,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>
复制代码
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静态编译特性。
Es Modules导出有export 和 export default两种方式,它们区别以下:
我这里主要想讲的是尽可能减小export default的使用,理由以下:
以上就是我对整篇文章的深度阅读,但愿这边文章对您在认识模块系统上有必定的帮助,若是喜欢个人文章,欢迎您的点赞!