Notes

1、背景问题

理想的程序:类似于乐高玩具。它具有清晰的结构,工作方式很容易解释,每个部分都扮演着明确的角色。

现实的程序:有机地增长。随着新需求的出现,新功能被添加。结构化和维持结构化是服务于未来的工作,因此很容易忽略它并逐渐让程序的各个部分变得非常纠结。

造成的问题:首先,理解这样的一个系统很困难。其次,无法对系统的某部分功能重用,与其把该功能从上下文提取出来,不如重写。简而言之就是高度耦合。

2、模块Modules

目的:解决高度耦合的问题

模块的定义:a piece of program,指明了自身所依赖的程序,以及它向外部所提供的功能(interface),其余部分保密。

模块间的关系:依赖(dependencies)。当模块的依赖被定义在它自身时,就可以使用它来确定需要存在哪些其他模块才能使用给定模块并自动加载依赖项。

如何实现模块化:首先需要程序员有这个意识,其次需要实际编程上的一些辅助措施。

3、软件包Packages

定义:一大块可以发布(复制和安装)的代码。它可能包含一个或多个模块,并且包含有关其所依赖的其他软件包的信息。一个软件包通常还附带文档来解释它的功能,以便那些没有编写它的人仍然可以使用它。

针对的问题:我们可以通过Copy代码来复用一些函数、功能,但是当这些函数、功能更新,就不得不在每一处修改它。如果在程序包中发现问题或添加了新功能,则依赖它的程序(也可能是包)只需要更新程序包就可以了。

基础设施:以这种方式工作需要基础设施。我们需要一个存储和查找包的地方以及安装和升级它们的便捷方式。在JavaScript世界中,此基础结构由NPM(https://npmjs.org)提供。

4、简易模块

把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指明所需的依赖以及控制依赖的加载。

5、Evaluating data as code

如果我们希望依赖关系成为代码的一部分(可以类比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

这正是我们的模块系统所需要的,我们可以把模块代码包装在一个函数里,函数的作用域即是模块的作用域。

6、CommonJS modules

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));
/**
* 1、检查"plusOne"是否是require.cache的一个属性
* 2、如果是,直接返回require.cache["plusOne"].exports
* 3、如果不是,通过readFile读取plusOne模块的代码
* 4、require.cache["plusOne"]绑定module(含有一个空对象的exports)
* 5、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);

7、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);

8、Building and bundling

很多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.

9、模块设计建议

  • 良好的程序设计是主观的 - 涉及权衡和品味问题。
  • 模块设计的一个方面是易于使用,这可能意味着遵循现有的惯例,模仿标准功能或广泛使用的软件包是一个好主意。
  • 保持模块功能单一。“Even if there’s no standard function or widely used package to imitate, you can keep your modules predictable by using simple data structures and doing a single, focused thing. ”,而且模块的功能越是单一、通用,越是易于和其它模块组合。
  • 有时定义状态对象是有用的,但如果函数足够,就用一个函数。“您​​首先创建一个对象,然后将该文件加载到您的对象中,最后使用专门的方法来获得结果。这种东西在面向对象的传统中很常见,而且很糟糕。您不能调用单个函数并继续前进,而是必须执行将对象移动到各种状态的仪式。因为数据被包装在一个专门的对象类型中,所以与它交互的代码必须知道该类型,从而产生不必要的相互依赖性。”
  • 通常无法避免定义新的数据结构,但是当一个数组足够时,使用一个数组。

Exercises

① A modular robot

我会怎么做:把各个函数变得更加通用、独立。。

------- --------  ———— -- —— ——- --  -- - -- - -

② Roads module

// 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("-")));

------- --------  ———— -- —— ——- --  -- - -- - -

③ Circular dependencies

暂略。

Eloquent JavaScript #10# Modules的更多相关文章

  1. JavaScript 10分钟入门

    JavaScript 10分钟入门 随着公司内部技术分享(JS进阶)投票的失利,先译一篇不错的JS入门博文,方便不太了解JS的童鞋快速学习和掌握这门神奇的语言. 以下为译文,原文地址:http://w ...

  2. Eloquent JavaScript #13# HTTP and Forms

    索引 Notes fetch form focus Disabled fields form’s elements property 阻止提交 快速插入单词 实时统计字数 监听checkbox和rad ...

  3. JavaScript code modules

    https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules Non-standardThis feature is ...

  4. javascript 10进制和64进制的转换

    原文:javascript 10进制和64进制的转换 function string10to64(number) { var chars = '0123456789abcdefghigklmnopqr ...

  5. 从零开始学习前端JAVASCRIPT — 10、JavaScript基础ES6(ECMAScript6.0)

    ECMAScript 6.0(简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了.它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发 ...

  6. Eloquent JavaScript #11# The Document Object Model

    索引 Notes js与html DOM 在DOM树中移动 在DOM中寻找元素 改变Document 创建节点 html元素属性 布局 style CSS选择器 动画 Exercises Build ...

  7. Eloquent JavaScript #04# Objects and Arrays

    要点索引: JSON More ... 练习 1.补:js字符串的表达方式有三种: "" 和 '' 没什么区别,唯一区别在于 "" 中写 "要转义字符 ...

  8. Eloquent JavaScript #03# functions

    索引: let VS. var 定义函数的几种方式 more... 1.作者反复用的side effect side effect就是对世界造成的改变,例如说打印某些东西到屏幕,或者以某种方式改变机器 ...

  9. Javascript:10天设计一门语言

    演进和使用的JavaScript是早在1995年开发的一种语言,真的是刚刚起步. 网景公司在1995年四月聘请Brendan Eich ,他被告知,他有10天时间创造并制作了一种将在Netscape的 ...

随机推荐

  1. 华为核心交换机绑定IP+MAC+端口案例

    1         案例背景 某网络改造项目,核心交换机为华为S5700,接入交换机为不同型号交换机,如下模拟拓扑,客户端接入交换机1通过Access模式与核心交换机连接,该交换机下只有一个Vlan2 ...

  2. PHP数组和字符串的处理函数汇总

    大部分数组处理函数array_chunk — 将一个数组分割成多个array_column — 返回数组中指定的一列array_combine — 创建一个数组,用一个数组的值作为其键名,另一个数组的 ...

  3. DLNg序列模型第一周

    1.为何选择序列模型? 给出上面一些序列数据的例子,真的很神奇,语音识别.音乐生成.情感分类.DNS序列分析.机器翻译.视频活动检测.命名实体识别. 2.数字符号 对于输入序列x,进行人名识别,输出中 ...

  4. [LeetCode] questions conclusion_ Binary Search

    Binary Search T(n) = T(n/2) + O(1)   =>    T(n) = O(lg n) proof: 如果能用iterable , 就用while loop, 可以防 ...

  5. [LeetCode] 339. Nested List Weight Sum_Easy tag:DFS

    Given a nested list of integers, return the sum of all integers in the list weighted by their depth. ...

  6. template.js简单入门

    template.js是一款开源的JavaScript模板引擎,用来渲染页面的. github地址 https://github.com/yanhaijing/template.js 下载templa ...

  7. .NET Core使用Quartz执行调度任务进阶(转)

    一.前言运用场景 Quartz.Net是一个强大.开源.轻量的作业调度框架,在平时的项目开发当中也会时不时的需要运用到定时调度方面的功能,例如每日凌晨需要统计前一天的数据,又或者每月初需要统计上月的数 ...

  8. is_readable() 函数检查指定的文件是否可读。

    定义和用法 is_readable() 函数判断指定文件名是否可读. 语法 is_readable(file) 参数 描述 file 必需.规定要检查的文件. 说明 如果由 file 指定的文件或目录 ...

  9. SpringMVC.入门篇《二》form表单

    SpringMVC.入门篇<二>form表单 项目工程结构: 在<springmvc入门篇一.HelloWorld>基础上继续添加代码,新增:FormController.ja ...

  10. Minecraft 1.8.9 FML Mod 开发教程

    Mod开发教程 https://fmltutor.ustc-zzzz.net/