理想的程序:相似于乐高玩具。它具备清晰的结构,工做方式很容易解释,每一个部分都扮演着明确的角色。编程
现实的程序:有机地增加。随着新需求的出现,新功能被添加。结构化和维持结构化是服务于将来的工做,所以很容易忽略它并逐渐让程序的各个部分变得很是纠结。数组
形成的问题:首先,理解这样的一个系统很困难。其次,没法对系统的某部分功能重用,与其把该功能从上下文提取出来,不如重写。简而言之就是高度耦合。数据结构
目的:解决高度耦合的问题ide
模块的定义:a piece of program,指明了自身所依赖的程序,以及它向外部所提供的功能(interface),其他部分保密。模块化
模块间的关系:依赖(dependencies)。当模块的依赖被定义在它自身时,就可使用它来肯定须要存在哪些其余模块才能使用给定模块并自动加载依赖项。函数
如何实现模块化:首先须要程序员有这个意识,其次须要实际编程上的一些辅助措施。
定义:一大块能够发布(复制和安装)的代码。它可能包含一个或多个模块,而且包含有关其所依赖的其余软件包的信息。一个软件包一般还附带文档来解释它的功能,以便那些没有编写它的人仍然可使用它。
针对的问题:咱们能够经过Copy代码来复用一些函数、功能,可是当这些函数、功能更新,就不得不在每一处修改它。若是在程序包中发现问题或添加了新功能,则依赖它的程序(也多是包)只须要更新程序包就能够了。
基础设施:以这种方式工做须要基础设施。咱们须要一个存储和查找包的地方以及安装和升级它们的便捷方式。在JavaScript世界中,此基础结构由NPM(https://npmjs.org)提供。
把js代码放在不一样文件中不能知足需求,不一样的文件一样共享相同的全局命名空间。它们之间会相互影响,而且代码的依赖结构不够清晰。
直到2015年js都没有内建的模块系统。因此最初的模块系统都是本身设计的:
const weekDay = function() { const names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; return { name(number) { return names[number]; }, number(name) { return names.indexOf(name); } }; }(); console.log(weekDay.name(weekDay.number("Sunday"))); // → Sunday
如上,该模块的接口包括weekDay.name和weekDay.number,局部绑定被隐藏在函数做用域里。
这种方式提供了必定程度的独立性,但它只指明了向外部提供的功能(interface),并无指明自身须要的依赖,只是指望于外部环境可以提供这些依赖,更别说什么自动加载了。(固然这里的例子并不须要其它依赖)
很长一段时间,这是Web编程中使用的主要方法,但如今它已通过时了。
理想的模块系统应该相似于Java里的包系统,能够经过import指明所需的依赖以及控制依赖的加载。
若是咱们但愿依赖关系成为代码的一部分(能够类比Java的import),就必须用代码来控制依赖的加载。作到这一点须要可以把字符串(或者说数据)做为代码执行【???】
首先是一种不推荐方式——特殊运算符eval,它容易破坏scope中原有的一种属性(就是缺少封闭性):
const x = 1; function evalAndReturnX(code) { eval(code); return x; } console.log(evalAndReturnX("var x = 2")); // → 2 console.log(x); // → 1
采用Function的构造器是一种风险较低的方法,它把代码包装在函数值里,这样它就有本身的做用域(scope)而不会影响别的做用域了:
let plusOne = Function("n", "return n + 1;"); console.log(plusOne(4)); // → 5
这正是咱们的模块系统所须要的,咱们能够把模块代码包装在一个函数里,函数的做用域便是模块的做用域。
CommonJS modules是最经常使用的用于规范化js模块的模块。
CommonJS modules的核心概念是一个叫require的函数,当你传入一个模块名并调用这个函数时,它确保模块已经被加载并返回该模块提供的接口。
下面是一个模块实现示例(直接脑补成java里的import package就很好懂了):
const ordinal = require("ordinal"); // 模块的依赖 const {days, months} = require("date-names"); // 模块的依赖 exports.formatDate = function(date, format) { // 模块对外提供的接口,能够是一个函数,也能够是像date-names同样多个函数 return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => { if (tag == "YYYY") return date.getFullYear(); if (tag == "M") return date.getMonth(); if (tag == "MMMM") return months[date.getMonth()]; if (tag == "D") return date.getDate(); if (tag == "Do") return ordinal(date.getDate()); if (tag == "dddd") return days[date.getDay()]; }); };
模块把它的接口函数绑定到exports上,以便其它模块能够访问它:
const {formatDate} = require("./format-date"); // 访问刚定义的formatDate模块 console.log(formatDate(new Date(2017, 9, 13), "dddd the Do")); // → Friday the 13th
咱们能够自定义一个轻量级的require:
require.cache = Object.create(null); function require(name) { if(!(name in require.cache)) { // 防止重复加载 let code = readFile(name); // readFile并不是标准函数,须要自定义 let module = { exports: {} }; require.cache[name] = module; let wrapper = Function("require, exports, module", code); // 加载代码 wrapper(require, module.exports, module); // 这样模块接口就被绑定到module.exports了 } return require.cache[name].exports; // => module.exports }
举个例子(模拟):
const codeOfPlusOne = "exports.plusOne = n => n + 1;"; const readFile = (name) => { if (name == "plusOne") return codeOfPlusOne; } require.cache = Object.create(null); function require(name) { if(!(name in require.cache)) { // 防止重复加载 let code = readFile(name); // readFile并不是标准函数,须要自定义 let module = { exports: {} }; require.cache[name] = module; let wrapper = Function("require, exports, module", code); // 加载代码 wrapper(require, module.exports, module); // 这样模块接口就被绑定到module.exports了 } return require.cache[name].exports; // => module.exports } const {plusOne} = require("plusOne"); console.log(plusOne(5)); /** * 一、检查"plusOne"是不是require.cache的一个属性 * 二、若是是,直接返回require.cache["plusOne"].exports * 三、若是不是,经过readFile读取plusOne模块的代码 * 四、require.cache["plusOne"]绑定module(含有一个空对象的exports) * 五、wrapper实际变成下面那样:加载-> 函数绑定到require.cache["plusOne"] */
对wrapper的分析:
// 1.构造wrapper,加载代码 let wrapper = (require, exports, module) => { // 模块内容↓ // const ordinal = require("ordinal"); exports.plusOne = n => n + 1; } // 2.调用wrapper,实际绑定 wrapper(require, module.exports, module); // exports.plusOne = f(x); // => ... module.exports = f(x);
七、ECMAScript modules(since 2015)
虽然CommonJS modules已经足够好用,但仍是有那么一点瑕疵,例如:你添加到exports的东西在局部做用域竟然不可用。
这就是为何js要推出本身的模块系统:
import ordinal from "ordinal"; import {days, months} from "date-names"; export function formatDate(date, format) { /* ... */ }
主要概念保持不变,可是细节有些不一样。符号如今已整合到语言中。您可使用特殊import
关键字,而不是调用函数来访问依赖项。
export的再也不是函数,而是一系列的绑定。
把模块import到没有{ }包围的绑定时,返回模块的default绑定(须要自定义):
export default ["Winter", "Spring", "Summer", "Autumn"];
还能够对模块进行重命名:
import {days as dayNames} from "date-names";
console.log(dayNames.length);
不少js代码其实不是用js写的,而是其它语言编译过来的。
由于单个文件传输比较快,所以程序员一般会在发布代码前用一种被叫作bundlers的工具把n个js文件压缩成一个js文件。
除了文件数量,文件大小一样影响传输速率,能够经过叫minifiers的工具去除空格和注释。
总而言之: Just be aware that the JavaScript code you run is often not the code as it was written.
我会怎么作:把各个函数变得更加通用、独立。。
------- -------- ———— -- —— ——- -- -- - -- - -
// Add dependencies and exports const {buildGraph} = require("./graph"); const roads = [ "Alice's House-Bob's House", "Alice's House-Cabin", "Alice's House-Post Office", "Bob's House-Town Hall", "Daria's House-Ernie's House", "Daria's House-Town Hall", "Ernie's House-Grete's House", "Grete's House-Farm", "Grete's House-Shop", "Marketplace-Farm", "Marketplace-Post Office", "Marketplace-Shop", "Marketplace-Town Hall", "Shop-Town Hall" ]; exports.roadGraph = buildGraph(roads.map(r => r.split("-")));
------- -------- ———— -- —— ——- -- -- - -- - -
暂略。