模块化Modules

函数变量 与 函数表达式Function Variables and Expressions

函数作为变量Function as Variable

js 中 函数其实是可以存储在变量里的

1 2 3 4 5 <
function f() { console.log('called');}const f1 = f;f1();called

函数声明,其实是给函数变量赋值

1 2 3 4 <
function f() { console.log('called');}console.log(f);[Function: f]

函数表达式Function Expressions

函数声明
1 2 3 4
function f() { console.log('f')}f();
函数表达式
1 2 3 4
const f1 = function f() { console.log('f')}f1();

作用域

函数声明

上下均可使用

1 2 3 4 5
f();function f() { console.log('f')}f();
函数表达式

f1 作为一个变量,只有在复制后才能使用。之前会出错

1!2 3 4 5
f1();const f1 = function f() { console.log('f')}f1();

二次扫描时,不会扫到 const f1 = 右侧的

f() 在执行完表达式后,名字就废了,之后也不能用

1!2 3 4 5!
f();const f1 = function f() { console.log('f')}f();

匿名函数表达式

因为函数表达式上的函数的名字 无法在调用后使用,

所以可以选择不起名

更改前
1 2 3 4
const f1 = function f() { console.log('f')}f1();
更改后
1 2 3 4
const f = function() { console.log('f')}f();

函数表达式 立刻调用

使用前
1 2 3 4
function f() { console.log('f');}f();
使用后
1 2 3
(function f() { console.log('f');})();

函数作为返回值Function as Return Value

函数嵌套Nested Functions

可以在函数内继续声明函数

1 2 3 4 5 6 7 8 9 10 11 12 13
// 1function outer() { // 2 function inner() { // 3 } // 4 inner(); // 5}// 6outer();// 7

执行顺序

1 6 outer 2 4 inner 3 5 7

函数作为返回值Function as Return Value

函数也可以作为返回值传递回来

1 2 3 4 5 6 7 8 9 10 < < <
function f1() { console.log('f1 called'); function f2() { console.log('f2 called'); } return f2;}const f = f1();console.log('after return');f();f1 calledafter return f2 called
使用匿名函数表达式,简化
1 2 3*4*5*6 7 8 9
function f1() { console.log('f1 called'); return function () { console.log('f2 called'); }}const f = f1();console.log('after return');f();

闭包Closure

案例分析

1 2 3 4 5 6 7 8 9 < < <
let _count = 0;function count() { _count++; return _count;}console.log(count());console.log(count());_count = 8;console.log(count());129

上面代码里 _count 可以被外部修改,从而导致 count() 产生紊乱

函数 与 作用域

只有 不使用关键字 声明的变量,是开在文件上的,函数调用后,在外部仍然可以使用。

使用 关键字 声明的变量/函数,均会受到 所在函数的限制。函数调用后,在外部无法使用。

1 2 3 4 5 6 7 8 9 <10!11!12!13!
function f() { v1 = 1; var v2 = 2; let v3 = 3; const v4 = 4; function v5() { } } console.log(v1);1console.log(v2);console.log(v3);console.log(v4);console.log(v5);

访问封锁

使用函数,可以将,一些资源封锁在函数内部,从而外部无法访问

1 2 3 4 5 6 7 8!9!10!11!
function counter() { let _count = 0; function count() { _count++; return _count; }}console.log(count());console.log(count());_count = 8;console.log(count());

访问外暴

将希望外部访问的东西,返回出去

1 2 3*4 5 6 7 8+9 <10 <11!
function counter() { let _count = 0; return function() { _count++; return _count; }}const count = counter();console.log(count());1console.log(count());2_count = 8;

捕捉 与 环境Capture 与 Environment

变量捕捉

如果一个函数A 返回了一个函数B,此时。B 会 捕捉 A 中的局部变量 跟着 B 在一起。

此时 虽然 A 函数已经结束了,但 A 里的局部变量 没有死掉

1 2 3 4 5 6 7 8 9
function f() { let a = 0; let b = 0; return function() { b++; return b; }}const g = f();
多次调用

多次调用,会捕捉多次变量,且每个被捕捉的变量,相互独立

1 2 3 4 5 6 7 8 9 10 11 <12 <13 <14
function f() { let a = 0; let b = 0; return function() { b++; return b; }}const g1 = f();const g2 = f();console.log(g1());1console.log(g2());1console.log(g1());2
外部变量

不在外部函数外部的变量,不会被捕捉,且保持单一

1 2 3 4 5 6 7 8 9 10 <11 <12 <13
a = 0;function f() { return function() { a++; return a; }}const g1 = f();const g2 = f();console.log(g1());1console.log(g2());2console.log(g1());3

闭包

针对这一堆现象的组合,我们称对应的外部函数为 闭包 Closure

闭包可以用来达到封装的效果,将部分资源,以环境的身份被保护起来,存活的同时,不能被外界访问

1 2 3 4 5 6 7 8 9 10 <11 <12 <
function counter() { let _count = 0; return function() { _count++; return _count; }}const countChicken = counter();const countBeef = counter();console.log(countChicken());1console.log(countBeef());1console.log(countChicken());2

闭包模块化Modules using Closure

案例

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <
function counter() { let count = 0; function tick() { count++; } function current() { return count; } return { tick, current };} let c1 = counter();c1.tick();c1.tick();console.log(c1.current());2
步骤
1.

在外部函数里面 开变量 以及 声明函数

2.

返回一个 object,把希望曝光的函数 曝光出去

效果

封装

保护部分资源 不被外界直接访问。

CommonJS 模块化Modules using CommonJS

案例

counter.js

1 2 3 4 5 6 7 8 9 10 11
console.log('executing in counter.js')let count = 0;function tick() { count++;}function current() { return count;} exports.tick = tick;exports.current = current;

main.js

1 2 3 4
let counter = require('./counter.js');counter.tick();counter.tick();console.log(counter.current());
exports

类似 return 的 object

添加的属性 会曝光出去

require

导入对应文件,调用后返回文件内的 exports

文件执行

require 时,会导致 被 require 的文件 的执行

require 实质

如果多个文件 分别 require 同一个文件...

案例
project
counter.js
file1.js
file2.js
main.js

counter.js

1 2 3 4 5 6 7 8 9 10 11
console.log('executing in counter.js')let count = 0;function tick() { count++;}function current() { return count;} exports.tick = tick;exports.current = current;

file1.js

1 2 3 4
console.log('executing in file1.js');let counter = require('./counter.js');counter.tick();console.log(counter.current());

file2.js

1 2 3 4
console.log('executing in file2.js');let counter = require('./counter.js');counter.tick();console.log(counter.current());

main.js

1 2
require('./file1.js');require('./file2.js');
运行结果
executing in file1.js
executing in counter.js
1
executing in file2.js
2
缓存

当 require 一个文件时,文件内的代码会被执行

返回的 exports object 会被存储下来

下次再 require 一个文件时,将直接返回 上次返回的同一个 exports object(同一个地址)

配图

单一性

第一次 require 时,创造了 exports 对象,并创建了环境(包含 count 变量)

第二次 require 时,直接返回 上次的 exports 对象,所以共用一个环境

所以 第一个文件里 tick 的结果,在第二个文件里 tick 时 会联动

exports 其它类型

有些代码会使用 exports 导出一个不是对象的东西

counter.js

1 2 3 4 5
let count = 0;module.exports = function () { count++; return count;}

main.js

1 2 3
const counter = require('./counter.js');console.log(counter());console.log(counter());
结论

这种方式,我们无法断定 require 返回的 一定是对象

require 省略写法

main.js

1 2 3 4
let counter = require('./counter.js');counter.tick();counter.tick();console.log(counter.current());

可以改为

main.js

1*2 3 4
let counter = require('./counter');counter.tick();counter.tick();console.log(counter.current());

require 目录

如果导入的目录是 文件夹,将自动导入 文件夹内的 index.js 文件

project
domain
index.js
main.js

domain/index.js

1 2
exports.name = 'zzax.io';exports.port = 9917;

main.js

1 2
const domain = require('./domain');console.log(domain);

应用案例

游戏当前的等级

level.js

1 2 3 4 5
exports.current = 1; exports.upgrade = function () { exports.current++;}

main.js

1 2 3 4
const level = require('./level');const game = require('./game');console.log(level.current);game.play();

game.js

1 2 3 4 5
const level = require("./level");exports.play = function() { level.upgrade(); console.log(level.current);};

js 中的文件,某种意义上来讲,可以充当一个单例对象

ES6 Modules 模块化Modules using ES6 Modules

ECMAScript 6

案例

使用前

counter.js

1 2 3 4 5 6 7 8 9 10 11
console.log('executing in counter.js')let count = 0;function tick() { count++;}function current() { return count;} exports.tick = tick;exports.current = current;

main.js

1 2 3 4
let counter = require('./counter.js');counter.tick();counter.tick();console.log(counter.current());
使用后

counter.js

1 2 3 4 5 6 7 8 9 10*11*
console.log('executing in counter.js')let count = 0;function tick() { count++;}function current() { return count;} export { tick };export { current };

main.js

1*2 3 4
import * as counter from "./counter";counter.tick();counter.tick();console.log(counter.current());

import 和 export

跟 require 和 exports 的功能类似

只不过变成了语法级别支持

先声明,再 export

printer.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const width = 20;function headline1(text) { console.log('='.repeat(width)); console.log(text); console.log('='.repeat(width));}function headline2(text) { console.log(); console.log(text); console.log('-'.repeat(width));} export { headline1 }export { headline2 }// or // export { headline1, headline2 }
export 时 声明

printer.js

1 2 3 4 5 6 7 8 9 10 11
const width = 20;export function headline1(text) { console.log('='.repeat(width)); console.log(text); console.log('='.repeat(width));}export function headline2(text) { console.log(); console.log(text); console.log('-'.repeat(width));}
import 模块内的资源

printer.js

1 2 3
import { headline1, headline2 } from './printer';headline1('Import and Export');headline2('Import');
import 整个模块

main.js

1 2 3
import * as Printer from './printer';Printer.headline1('Import and Export');Printer.headline2('Import');

export import 时 改名

export 时 改名

printer.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14
const width = 20;function headline1(text) { console.log('='.repeat(width)); console.log(text); console.log('='.repeat(width));}function headline2(text) { console.log(); console.log(text); console.log('-'.repeat(width));} export { headline1 as printHeadline1 }export { headline2 as printHeadline2 }

main.js

1 2 3
import { printHeadline1, printHeadline2 } from './printer';printHeadline1('Import and Export');printHeadline2('Import');
import 时 改名

printer.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14
const width = 20;function headline1(text) { console.log('='.repeat(width)); console.log(text); console.log('='.repeat(width));}function headline2(text) { console.log(); console.log(text); console.log('-'.repeat(width));} export { headline1 }export { headline2 }

main.js

1 2 3 4 5 6
import { headline1 as printHeadline1, headline2 as printHeadline2 } from './printer';printHeadline1('Import and Export');printHeadline2('Import');

export import default

效果

addOperator.js

1 2 3
export default function (left, right) { return left + right;}

main.js

1 2
import addOperator from './addOperator';console.log(addOperator(3, 5));
直接 export default

addOperator.js

1 2 3
export default function (left, right) { return left + right;}
先声明, 再 export default

addOperator.js

1 2 3 4
function addOperator (left, right) { return left + right;}export { addOperator as default }
import default

main.js

1 2
import addOperator from './addOperator';console.log(addOperator(3, 5));
import default 改名

main.js

1 2
import { default as add } from './addOperator';console.log(add(3, 5));
default 与 一般情况混搭

addOperator.js

1 2 3 4
export default function addOperator (left, right) { return left + right;}export const sign = '+';

main.js

1 2 3 4 5 6 7
import { default as add, sign as addSign } from './addOperator'; calculate(add, 3, 5, addSign); function calculate(operator, left, right, operatorSign) { console.log(`${left} ${operatorSign} ${right} = ${operator(left, right)}`);}

NPM 包管理Node Package Manager

是什么

js 的 包管理器

NPM 官网

安装 node 的时候 就带着一个 npm

初始化

需要在项目文件夹下,增加一个 package.json 文件,并且符合一些标准

project
package.json
指令添加

执行这个语句,会在回答一些问题后,自动生成

>
npm init
手动添加

可以手动用 vscode 等工具添加这个文件

可以没有任何属性,但至少得有一对大括号

1
{}

添加 依赖

资源库

NPM 官网

添加

在项目根目录上运行

>
npm install chalk
两部分

文件夹上的更改

project
node_modules所有依赖都会放在这里
chalk安装的依赖
index.js
package.json
⋅⋅⋅
⋅⋅⋅

package.json 也会有变动

1 2+3+4+5
{ "dependencies": { "chalk": "^3.0.0" }}

导入 依赖的资源

1 2
const chalk = require('chalk');console.log(chalk.bgBlue.white(' ZZAX '));

package.json

用于记录软件运行所需要的信息

在将来代码合作的时候 node_modules 是不会共享的

需要使用软件的人,只需要你的源代码,以及 package.json 声明的依赖

可以通过 package.json 的依赖 重新下载软件。

根据 package.json 安装 依赖

1.

准备好一个新的文件夹

把 package.json 文件放到里面

project
package.json
2.

运行

>
npm install

install 指令

安装指定软件
>
npm install <名字>

会安装到 node_modules 并在 package.json 中记录

save 选项

你可能会看到 --save 这个选项

>
npm install --save chalk

在旧版 npm 中,install 只会 会安装到 node_modules,不会在 package.json 中记录

增加 --save 会达到两边都改

新版 npm 中 无需添加 --save

根据 package.json 恢复
>
npm install

根据 package.json 的 dependencies,恢复 node_modules 文件夹

ZZAX 微信公众

文档一更新,立刻告诉你