190511-324933

Node.js是一个事件驱动I/O的服务端JavaScript环境,基于Google的V8引擎,在JavaScript语法的基础上,增加了一些操作系统、文件、网络和数据库的操作。

语法

Node.js的特点

异步I/O

事件和回调

单线程和子进程

跨平台

事件循环

下图来源:Node与浏览器的 Event Loop 差异☍

  • Node端,microtask 在事件循环的各个阶段之间执行,Node 11 中,改成一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)执行完就立刻执行微任务队列,与浏览器相同。
  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

image-20190520183028592

Node 开始执行脚本时,会先进行事件循环的初始化,执行时只有一个主线程,事件循环是在主线程上完成的。

初始化

  • 同步任务
  • 发出异步请求
  • 规划定时器生效的时间
  • 执行process.nextTick()等等(开始事件循环)

Node事件循环的六个阶段

事件循环会无限次地执行,一轮又一轮。只有异步任务的回调函数队列清空了,才会停止执行。每个阶段都有先进先出的回调函数队列,队列清空时才会进入下一阶段

  1. timers:setTimeout()和setInterval() 满足定时器的条件执行回调函数,否则进入下一阶段。
  2. I/O callbacks:除setTimeout()、setInterval()、setImmediate()和关闭请求的回调函数外,都在这里执行。
  3. idle, prepare:只供 libuv 内部调用,这里可以忽略
  4. poll:轮询时间,先执行完队列,再同时检查setImmediate()和timer的callback(顺序不定),有则跳转到对应阶段执行队列,都没有则等待 ,知道有I/O 事件返回时跳到I/O阶段执行callback。
  5. check:执行 setImmediate() 的回调
  6. close callbacks:执行关闭请求的回调函数,如socket 的 close 事件回调

microtask会在事件循环的各个阶段之间执行,process.nextTick()也属于microtask,并有自己的队列,优先于其他 microtask 执行。

定时器

node提供4个定时器

1
2
3
4
5
setTimeout() //js标准 次轮循环timer阶段到时间执行 第2个参数范围是1-2147483647毫秒,取不了0
setInterval() //js标准 次轮循环timer阶段到间隔时间执行,反复多次,直到clearInterval把它清除
setImmediate() //node独有 次轮循环check阶段到时间执行
process.nextTick() //node独有 本轮循环执行,所有异步任务中最快执行的
Promise的回调函数 //本轮循环,进入异步任务里面的"微任务"(microtask)队列,追加在process.nextTick队列后执行

按行读取

1
2
3
4
5
6
7
8
const readline = require('readline')

const rl = readline.createInterface({
input: fs.createReadStream(mdPath)
})
rl.on('line', (line) => {
log.info('Line from file:' + line)
})

执行终端命令

execSync

开启一个子进程来执行终端命令

详见Child Process模块

1
2
3
4
5
6
const execSync = require("child_process").execSync

// 命令行执行hexo生成和部署
execSync('hexo clean && hexo g -d', {
cwd: hexoDir
})

spawn和exec

child_process.spaen会返回一个带有stdout和stderr流的对象,用于返回大量数据,是“异步中的异步”,意思是在子进程开始执行时,它就开始从一个流总将数据从子进程返回给Node。

child_process.exec方法会从子进程中返回一个完整的buffer。用于返回结果,是“同步中的异步”,意思是尽管exec是异步的,它一定要等到子进程运行结束以后然后一次性返回所有的buffer数据。

模块

一个js文件就是一个模块。

模块的标准有三个方面,一是Node.js的CommonJS标准、二是用于浏览器的AMD标准,如requirejs、三是 ES6新定义的标准。前两者都是旧的标准,随着浏览器逐步支持ES6,未来应该是ES6一统天下。而目前则是过渡阶段,可以通过babel来支持ES6,为日后做准备。

下面是Nodejs和ES6的模块常用格式:

CommonJS标准(node.js)

1
2
3
4
5
6
7
8
9
10
11
12
// hello.js文件模块
let s = 'Michael'
module.exports = {
s: s,
greet: (name) => {
console.log('Hello, ' + name + '!')
}

// main.js中引入hello.js模块
let b = require('./hello') // 注意相对目录,只写名称会依次在内置模块、全局模块和当前模块下查找,可能出错
let s = b.s
b.greet(s) // Hello, Michael!

ES6

1
2
3
4
5
6
7
8
9
10
11
12
// hello.js文件模块
let s = 'Michael'
export default {
s,
greet(name) {
console.log('Hello, ' + name + '!')
}

// main.js中引入hello.js模块
import b from './hello'
let s = b.s
b.greet(s) // Hello, Michael!

模块方式详解

  • requiremodule.exports / exports: node.js的引入和导出方式
  • export / import : 只有es6 支持的导出引入

exports和 module.exports(node.js)

exports是module.exports的内存地址的引用,他们之间的关系如下图,都指向一块{}内存区域。

clipboard.png

为了避免糊涂,尽量都用 module.exports 导出,然后用require导入。

1
2
3
4
5
6
7
8
9
10
11
//utils.js
let a = 100;
console.log(module.exports); //能打印出结果为:{}
console.log(exports); //能打印出结果为:{}

exports.a = 200; //这里帮 module.exports 把内容改成 {a : 200}
exports = '指向其他内存区'; //这里exports指向其他内存区域

//test.js中引入
var a = require('/utils');
console.log(a) // 打印为 {a : 200}

export 和 export default(es6)

  1. export与export default均可用于导出常量、函数、文件、模块等
  2. 在一个文件或模块中,export、import可以有多个,export default仅有一个
  3. 通过export方式导出,在导入时要用import {a} from ..,export default则用import a from ..
  4. export能直接导出变量表达式,export default不行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 文件testEs6Export.js
'use strict'

//导出变量
export const a = '100';

//导出方法
export const dogSay = function(){
console.log('wang wang');
}

//导出方法第二种
function catSay(){
console.log('miao miao');
}
export { catSay };

//export default导出
const m = 100;
export default m;
//export defult const m = 100;// 这里不能写这种格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//文件index.js
'use strict'
var express = require('express');
var router = express.Router();

import { dogSay, catSay } from './testEs6Export'; //导出了 export 方法
import m from './testEs6Export'; //导出了 export default
import * as testModule from './testEs6Export'; //as 集合成对象导出

/* GET home page. */
router.get('/', function(req, res, next) {
dogSay();
catSay();
console.log(m);
testModule.dogSay();
console.log(testModule.m); // undefined , 因为 as 导出是 把 零散的 export 聚集在一起作为一个对象,而export default 是导出为 default属性。
console.log(testModule.default); // 100
res.send('恭喜你,成功验证');
});

module.exports = router;

export 用法

语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName() {...}
export class ClassName {...}

export default expression;
export default function () { … } // also class, function*
export default function name1() { … } // also class, function*
export default {} // 导出多个接口
export { name1 as default, … };


export * from …; //*符号尽可能少用,它实际上是使用所有export的接口,但是很有可能你的当前模块并不会用到所有接口,可能仅仅是一个,所以最好的建议是使用花括号,用一个加一个。
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …; //as后是别名,解决模块间的同名

//nameN为导出的标识符(用来被其他脚本的 import 导入)

可以使用任何名称导入默认导出

1
2
3
4
5
6
7
8
9
//a.js
export default k = 12
//相当于起了一个系统默认的变量名default,所以一个文件内不能有多个export default。

// b.js
//可以使用任何名称导入default的导出
import m from "./a.js"
import n from "./a.js"
console.log(m,n) // 12,12

require与import

require理论上可以运用在代码的任何地方,甚至不需要赋值给某个变量之后再使用

import则不同,它是编译时的(require是运行时的),它必须放在文件开头,而且使用格式也是确定的,不容置疑。它不会将整个模块运行后赋值给某个变量,而是只选择import的接口进行编译,这样在性能上比require好很多。

require是赋值过程,import是解构过程

npm

修改镜像源

npm 的 config 命令(永久)

1
2
npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

在终端通过下面的命令获取当前 npm 使用的源

1
2
npm config get registry
npm config get disturl

恢复官方源

1
2
npm config delete registry
npm config delete disturl

安装包

To add an entry to your package.json‘s devDependencies:

1
npm install <package_name> --save-dev

To add an entry to your package.json‘s dependencies:

1
npm install <package_name> --save

更新包

更新所有全局包

1
npm update -g

检查有更新的本地包

1
npm outdated

检查全局包

1
npm -g outdated

查看本地版本

1
npm ls electron -g

查看服务器最新版本

1
npm view electron version

本地全部升级到最新版本

安装高效升级插件npm-check-updates,

1
npm install -g npm-check-updates

列出最新的版本

1
npm-check-updates

1
ncu

全部升级:

1
ncu -a

将package.json 中的包版本号改为最新

1
ncu -u

node 版本升级

安装包安装(推荐)

官网下载安装:https://nodejs.org/en/download/current/

n模块安装

mac下每次使用会有安全提示,不推荐

npm中有一个模块叫做“n”,专门用来管理node.js版本的。

更新到最新的稳定版只需要在命令行中打下如下代码:

1
2
npm install -g n
n stable

如需最新版本则用n latest

升级到指定版本:

1
n v10.2.0

npm 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
npm init 
运行构建新项目的向导
npm install module_name
在项目中安装一个模块
npm install -g module_name
全局方式安装一个模块
npm install module_name –save
在项目中安装一个模块,并把此模块添加到项目配置文件package.json中,作为项目依赖
npm install module_name –save-dev
在项目中安装一个模块,并把此模块添加到项目配置文件package.json中,作为项目开发依赖(devDependency)
npm list
列出项目中已安装的所有模块
npm list -g
列出系统中全局安装的所有模块
npm remove module_name
从项目中移除已安装的模块
npm remove -g module_name
从系统的全局安装中移除已安装的模块
npm remove module_name –save
从项目中移除已安装的模块,并从配置依赖中移除依赖关系
npm remove module_name –save-dev
从项目中移除已安装的模块,并从配置依赖中移除开发依赖(devDependency)关系
npm update module_name
更新指定的已安装模块的版本
npm update -g module_name
更新指定的全局安装模块的版本
npm -v
显示npm包管理器的当前版本
npm adduser username
在npmjs.org创建一个账户
npm whoami
显示你在npmjs.org上的账户详细信息
npm publish

npm的任务自动化

package.json中的scripts定义了一些任务,比如:

1
2
3
4
5
6
"scripts": {
"test": "node test.js",
"start": "node app.js",
"clean": "mv ./node_modules/.[!.]* /Users/fwk/.Trash" //.[!.]包含隐藏文件
"clean": "rm -rf ./node_modules/*"
}

这个配置这定义了三个任务脚本:启动start、测试test、清理clean。要执行脚本,分别使用命令:

这个配置这定义了三个任务脚本:启动start、测试test、清理clean。要执行脚本,分别使用命令:

1
2
3
npm run start
npm run test
npm run clean

遇到的错误

安装全局包出错,提示:checkPermissions Missing write access to …

解决方法:

  • Reinstall npm with a node version manager (recommended)
  • Manually change npm’s default directory

https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally

https://www.jianshu.com/p/31744aa44824

端口被占用

执行npm run dev 提示:events.js:183 throw er; // Unhandled ‘error’ event

yarn

安装

1
brew install yarn

更新yarn

1
brew upgrade yarn

修改安装源

1
2
3
4
//设置
yarn config set registry https://registry.npm.taobao.org
//检查
yarn config get registry

使用

添加

1
2
3
yarn add [package]
yarn add [package]@[version]
yarn add [package]@[tag]

升级

1
2
3
yarn upgrade [package]
yarn upgrade [package]@[version]
yarn upgrade [package]@[tag]

移出

1
yarn remove [package]

常用包

fs-extra

安装

1
npm install --save fs-extra

引入

1
const fs = require('fs-extra')

常用方法

1
2
3
4
5
6
7
8
// 读取目录中的所有文件,包括隐藏文件
let imgs = fs.readdirSync(imgDir)

// 删除目录或文件
fs.removeSync(hexoPostDir)

//复制文件或文件夹,可在复制时重名,参数1是源文件或目录路径,参数2是目标文件或目录路径,已存在同名文件时会强制覆盖。
fs.copySync(imgPath, imgNewPath)

图片处理库

image-size

获取图片尺寸的Node 模块,简单好用 ,支持格式:BMP、CUR、GIF、ICNS、ICO、JPEG、PNG、PSD、TIFF、WebP、SVG、DDS

1
npm install image-size --save
1
2
3
const sizeOf = require('image-size')
var dimensions = sizeOf('images/funny-cats.png')
console.log(dimensions.width, dimensions.height)

jimp

JavaScript 图片处理包,性能一般,用起来比较简单

1
2
3
4
5
6
Jimp.read(imgPath, (err, lenna) => {
if (err) throw err
lenna
.resize(660, Jimp.AUTO) // resize
.write(imgNewPath) // save
})

gm

GraphicsMagick 的 node 版本,强大的图片处理工具,性能不错。

在安装npm 包之前,需要先安装GraphicsMagick或ImageMagick
mac 安装:

1
2
brew install imagemagick
brew install graphicsmagick

在工程目录安装npm包

1
npm i gm --save
使用

缩放图片、叠加图片(可用于水印)

1
2
3
4
5
6
7
8
9
const gm = require('gm')
gm('./srcimage.jpg')
.resize(660,'>') // 只处理宽度>660的图片
.noProfile() //去处EXIF信息
.composite('./logo-wartermark.png')
.gravity('SouthEast').geometry('-5-5') //水印放在右下角、相对右下角偏移-5、-5
.write('./saveimage.jpg', function (err) {
!err?console.log(imgName, '————gm done'): console.log(imgName,'err1' ,err)
})

.resize(w, h, ‘>’) 可以制定宽高,只缩放宽时可以不传高,最后一个参数用于控制缩放的方式和条件:

  • %(比例方式计算尺寸)
  • @(结果要小于指定的面积)
  • ^(保持比例同时充满宽高尺寸)
  • !(不保持比例)
  • <(只处理小于指定尺寸的图片)
  • > (只处理大于指定尺寸的图片)

.gravity()通过地理方位来控制叠加图片的位置,即上北下南左西右东:NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast。

注意,gif图片缩放会产生错位,尝试加了 .coalesce() 也没解决。

水印二

1
2
3
//在resize后会无效
gm('./srcimage.jpg')
.draw('image Over 460, 460, 140, 140 "./logo-wartermark.jpg"') //参数 operator x0,y0 w,h filename