# NodeJS 介绍

1、精通node (opens new window)

2、V8 引擎源码 (opens new window)

3、node源码 (opens new window)

  • Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
  • Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
  • Node.js 的包管理器 npm,是全球最大的开源库生态系统。

Nodejs 是由c++编写的

官网下载:https://nodejs.org/zh-cn/download/ (opens new window)

# 工具

# npm 包管理器

NPM 是随同 NodeJS 一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从 NPM 服务器下载别人编写的第三方包到本地使用。
  • 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用。
#查看版本
npm -v
1
2

参考资料:

1、npm菜鸟教程 (opens new window)

2、npm官网资料 (opens new window)

3、npm相关资料 (opens new window)

# npm常见命令

npm install -g cnpm --registry=https://registry.npm.taobao.org
npm cache clean

//配置设置
npm config set registry https://registry.npm.taobao.org
npm config get registry
npm config list

//安装模块
npm install
npm uninstall express
npm update express
npm install moduleName   
# 安装模块到项目目录下
npm install -g moduleName 
# -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
npm install --save moduleName 
# --save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
npm install --save-dev moduleName 
# --save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。


//查看模块
npm -g list --depth=1 //查看全局安装模块

//发布模块
npm init
npm login
npm search xxx
npm publish
//报错put 400 bad request
//需在package.json中添加publishConfig: {registry: 发布地址}
npm unpublish

//scope:@somescope/somepackagename
npm install -g @vue/cli

//程序包链接,用于本地npm包测试
npm link
npm unlink

//全局包查看
npm root -g  //  /usr/local/lib/node_modules
npm list -g  //  全局安装的包查看

//本地包查看
npm root  //  /Users/jian/workspace/vue-project-ssr/node_modules
npm list  //  全局安装的包查看
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

解析打包测试命令:npm link,程序包链接,用于本地npm包测试

典例:项目project要使用common公用组件包,common公用组件包为本地npm包未发布至npm仓库,可以使用link命令测试。

  1. 在common文件夹中执行npm link,将common模块创建成本地依赖包。
    • npm config list:可查看prefix,全局文件安装目录
    • 在prefix目录下添加软链接执行文件
    • 在prefix/node_modules目录添加common模块的快捷方式
  2. 在project文件夹中执行npm link common,将common模块应用在project工程里。
    • 在node_modules下添加.bin文件夹以及common工程文件夹
  3. 解除link
    • 在project文件夹执行npm unlink common,解除引用
    • 在common文件夹执行npm unlink,解除本地依赖包

# npx

npx是一个工具,npm v5.2.0引入的一条命令(npx),一个npm包执行器,指在提高从npm注册表使用软件包时的体验 ,npm使得它非常容易地安装和管理托管在注册表上的依赖项,npx使得使用CLI工具和其他托管在注册表。

当执行NPX xxx时候,先看xxx在$PATH里有没有,没有则查找当前目录node_modules里是否存在,若没有则安装并执行。

# yarn

* 全部安装:yarn install
* 添加:yarn add xx@xx | yarn add xx --dev | yarn golbal add xx
* 更新:yarn up xx@xx
* 移除:yarn remove xx
* 运行:yarn xx
1
2
3
4
5

# nvm 版本管理器

因为nvm本身的问题,并非所有电脑都能正确安装,如果无法安装,那么就直接安装nodejs。

同一台电脑可以安装不同版本的nodejs,可以通过 nvm 来安装和切换。

nvm下载地址:https://github.com/coreybutler/nvm-windows/releases

傻瓜式安装,安装成功之后,打开命令行窗口,就可以使用了。

打开命令行窗口,cd 进入到该 nvm 安装目录。

## 使用nvm下载10.4.0这个版本的node
nvm install 10.4.0

## 查看当前电脑里有哪些版本
nvm ls 

## 切换版本
nvm use 10.4.1 
1
2
3
4
5
6
7
8

# nrm 源切换工具

使用 npm install 下载模块时,默认从美国下载,速度是比较慢的,可以把源改成国内的淘宝镜像。

## 安装nrm
npm install nrm -g   

## 查看有哪些源
nrm ls  

## 切换到taobao源
nrm use taobao  
1
2
3
4
5
6
7
8

如果不想使用 nrm 切换下载源,也可以直接 npm 设置下载源。

上面的代码与下面的代码是等效的,都是把npm设为淘宝镜像。

## 得到现在的镜像地址
npm get registry

## 设置成淘宝镜像地址
npm config set registry https://registry.npm.taobao.org/

## 替换成原来的镜像地址
npm config set registry https://registry.npmjs.org/
1
2
3
4
5
6
7
8

# cnpm 淘宝源

也可以直接使用cnpm命令,表示直接从淘宝源下载模块。

## 安装cnpm命令,然后每次使用 cnpm install 下载模块。
npm install -g cnpm --registry=https://registry.npm.taobao.org
1
2

# NodeJS 代码的运行

# 系统自带的 node 命令

  • 在命令行窗口中,直接输入node,会进入node环境,可以在里面直接写js代码,然后直接运行。
  • 编写js文件,然后在命令行中执行这个js文件。

NodeJS是JS语言的运行环境,JS是语言,因为是在服务器端执行,所以客户端的BOM、DOM是没有的,除了ECMAScript外,还增加了一些服务器特有的能力。比如 process.stdout.write() 就是用来做控制台输出的。

## 使用node命令执行当前目录下的app.js文件
node app.js     
1
2

ctrl+c :有些代码会导致程序被挂起,如果想退出程序,可以让命令行获得聚焦后,直接键盘ctrl+c终止程序。

比如网站服务器这种功能,需要把程序挂起,那么每次修改源代码时,之前挂起的程序并不会自动重新启动,没有重启动就意味着刚才的修改,并没有更新过来,所以需要手动重启,那么频繁的手动重启太麻烦了,所以要选择热部署工具来守护我们的进程,nodemon和supervisors都是它的解决方案。

# nodemon

热部署工具 nodemon ,解决的是开发项目时,每次代码修改完毕后,服务自动启动的问题。(说白了就是每次ctrl+s时,当前代码会被系统再次执行)

## 下载 nodemon
npm install nodemon -g
1
2

使用 nodemon 运行我们的代码

## 使用 nodemon 执行当前目录下的 app.js 文件
nodemon app.js
1
2

nodemon 运行后,就相当于挂起了服务,然后每次修改 app.js 文件中的内容,保存时,就会自动执行 app.js。

# supervisors

热部署工具 supervisors,起到的作用和 nodemon 一模一样,因为不稳定,所以不建议使用。

## 安装 supervisors
npm install supervisors -g

## 使用 supervisors 运行 app.js
supervisors app.js
1
2
3
4
5

# 事件循环

参考资料:node事件循环6个阶段 (opens new window)

  • 同步代码

  • process.nextTick和promise.then() ,之后进入事件循环

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调

  • I/O callbacks 阶段 :处理一些上一轮循环中的少数未执行的 I/O 回调

  • idle, prepare 阶段 :仅node内部使用

  • poll 阶段 :获取新的I/O事件, 适当的条件下node将阻塞在这里

  • check 阶段 :执行 setImmediate() 的回调

  • close callbacks 阶段:执行 socket 的 close 事件回调

习题解析:

  • new Promise(()=>{//同步执行}).then(()=>{//异步执行})
  • async function test(){console.log() //同步 -> await test(0) //同步 -> console.log()//异步}
//习题1:
// 顺序不确定,只有两个语句,执行环境有差异
// 场景1: setTimeout 0 最少1ms,未推入timers队列中,执行结果为:setImmediate、setTimeout
// 场景2: setTimeout 0 已推入timers队列中,执行结果为:setTimeout、setImmediate
setTimeout(()=>{
  console.log('setTimeout')
}, 0)
setImmediate(()=>{
  console.log('setImmediate')
})

//习题2: 都在回调函数中,内容确定
//首轮事件循环setTimeout1的timers清空,执行至check阶段,先输出setImmediate
//第二轮事件循环setTimeout2
//最终输出:setTimeout1、setImmediate、setTimeout2
setTimeout(()=>{
  setTimeout(()=>{
  		console.log('setTimeout2')
  }, 0)
  setImmediate(()=>{
    	console.log('setImmediate')
  })
  console.log('setTimeout1')
}, 0)

//习题3: 混合题
setTimeout(()=>{
  console.log('timeout')
}, 0)
setImmediate(()=>{
  console.log('immediate')
  setImmediate(()=>{
  	console.log('immediate1')
	})
  new Promise(resolve => {
    console.log(77)
    resolve()
  }).then(()=>{
    console.log(88)
  })
  process.nextTick(function(){
    console.log('nextTick1')
  });
})
new Promise(resolve => {
  console.log(7)
  resolve()
}).then(()=>{
  console.log(8)
})
process.nextTick(function(){
  console.log('nextTick2')
})
console.log('start')

// 第一轮:7 start -  timeout | immediate 77 | 8 | nextTick2   
// 第二轮:7 start nextTick2  8 timeout immediate 77 - immediate1 | 88 | nextTick1
// 第三轮:7 start nextTick2  8 timeout immediate 77 nextTick1 88 immediate1
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 模块

# 底层依赖模块

  • V8 引擎:主要是 JS 语法的解析,有了它才能识别 JS语法
  • libuv:C 语⾔实现的⼀个⾼性能异步⾮阻塞 IO 库,⽤来实现 node.js 的事件循环
  • http-parser/llhttp:底层处理 http 请求,处理报⽂,解析请求包等内容
  • openssl:处理加密算法,各种框架运⽤⼴泛,底层依赖,无需js实现
  • zlib:处理压缩等内容

# 全局对象

  • 全局对象:global,下挂如下对象和函数,使用时无需模块引入
    • global
    • Buffer、process、console、queueMicrotask
    • setTimeout、clearTimeout、setInterval、setImmediate、clearInterval
  • 模块中使用注入变量:
    • __filename:当前文件名称带路径,eg:/Users/jian/workspace/cjs/index.js
    • __dirname:当前文件夹名称,eg:/Users/jian/workspace/cjs/
    • cjs:require, module, exports
//全局this指向global
//模块文件中this指向:module.exports的对象
/* 模块中才可以使用的变量:commonjs模块注入,非模块中使用报错
__dirname、__filename、exports、module、require */

//node命令行中
> console.log(__dirname)
// Uncaught ReferenceError: __dirname is not defined
> console.log(this)
/* <ref *1> Object [global] {
  global: [Circular *1],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  }
}*/

//node模块中执行
console.log(this)
console.log(__filename)
console.log(__dirname)
/* 
{}
/Users/jian/workspace/cjs/index.js
/Users/jian/workspace/cjs
*/
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

# process

  • argv:返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数
  • env:返回一个对象,成员为当前 shell 的环境变量
  • stdin:标准输入流
  • stdout:标准输出流
  • cwd():返回当前进程的工作目录
console.log(process.argv)
//[ '/usr/local/bin/node', '/Users/jian/workspace/cjs/index.js' ]
console.log(process.cwd()) ////Users/jian/workspace/cjs
process.stdout.write("Hello World!"); //Hello World!
1
2
3
4

# 核心模块

Nodejs自带的模块,比如 http、url、querystring、fs、path 等模块。

# 自定义模块

我们自己做的 js 文件(看作 modules 模块),使用 module.exports 导出,使用 require 导入。

# 第三方模块

别人写好的并上传到 npm 服务器上的模块,我们需要npm install 模块名下载的模块,比如 npm install express。

# NodeJS 模块化规范

一个完整的项目中,涉及到的代码是特别多的,如果把这些代码都写在一个文件中,是不利于后期维护的,所以应该想办法把文件分割开,即产生多个碎片化的文件,每一个文件,单独描述一个功能,这个文件就被称为一个模块。模块内容,就是该js文件导出的东西。

NodeJS 属于 CommonJS 规范,可以理解成用 exports 导出模块,用 require 导入模块。

# 建立模块

比如我们做一个sum.js文件,里面写入:

// nodejs 中用 module.exports 关键字来导出模块
// 这个模块就是一个加法运算的函数。
module.exports = function(a, b){
    return a+b
}
1
2
3
4
5

# 使用模块

在app.js文件中,写入:

// 导入sum.js文件,用变量sum表示sum.js文件中导出的内容。
var sum = require("./sum.js");  // 注意此处路径要写 ./

// 执行函数,输出其结果。
console.log( sum(1, 2) );  // 3
1
2
3
4
5

使用 require 导入模块时,文件扩展名可以省略。

如果是自己做的模块文件,引入时的路径要用 ./ 或 ../,如果不写./自身或../父级,则表示直接从 node_modules 中查找。

# 在命令行中执行

nodemon app.js
1

# 常用的系统模块

系统模块无需 install 安装,都可以直接 require 使用。

# path 模块

path 模块提供路径相关的能力

var path = require("path")

// 连接路径
console.log( path.join('/test', 'a', '../b/c') );

// 关键字 __dirname 是魔术变量,表示该文件的绝对路径
console.log( path.join(__dirname, '/test', 'a', '../b/c') );

// 转换为绝对路径
console.log( path.resolve('main.js') );

// 路径中文件的后缀名
console.log( path.extname('main.js') );
1
2
3
4
5
6
7
8
9
10
11
12
13

# fs 模块

fs 的全称是 FileSystem ,即文件系统模块,提供操作文件及文件夹的能力。

# 读取文件

// 引入fs模块
var fs = require("fs");

// 同步读取
var data = fs.readFileSync('1.txt');
console.log("同步读取: " + data.toString());

// 异步读取
fs.readFile('1.txt', function (err, data) {
   if (err) {
	   return console.error(err);
   }
   console.log("异步读取: " + data.toString());
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

# 删除文件

fs.unlink('1.txt', function(err) {
    if (err) {
        return console.error(err);
    }
    console.log("文件删除成功!");
});	
1
2
3
4
5
6

fs.unlinkSync("1.txt") 同步

# 写入文件

fs.writeFile('1.txt', '不存在则创建',  function(err) {
    if (err) {
        return console.error(err);
    }
    console.log("数据写入成功!");
});	
1
2
3
4
5
6

fs.appendFile(filename, data, callback) 追加新内容

fs.writeFileSync 和 fs.appendFileSync 为同步的覆盖和追加

# 文件或文件夹是否存在

fs.exists('1.txt', function(bln){
	console.log("异步:", bln)
});

var bln = fs.existsSync('12.txt')
console.log("同步:", bln)
1
2
3
4
5
6

# 创建文件夹

// 异步
fs.mkdir("a",function(err){
   if (err) {
	   return console.error(err);
   }
   console.log("目录创建成功。");
});
// 同步
fs.mkdirSync("b");
1
2
3
4
5
6
7
8
9

如果文件夹已经存在,那么再次创建时,会报错。

# 读取目录

只能读取该目录的子文件及文件夹

fs.readdir("a",function(err, files){
   if (err) {
	   return console.error(err);
   }
   // files 就是一个数组,包含文件和文件夹
   files.forEach( function (file){
	   console.log( file );
   });
});
1
2
3
4
5
6
7
8
9

fs.readdirSync("a") 为同步的写法

# 删除目录

只能删除空白目录

fs.rmdir("a",function(err){
   if (err) {
	   return console.error(err);
   }
});
1
2
3
4
5

fs.rmdirSync("a/c") 同步写法

# 获取文件信息

fs.stat('1.txt', function (err, stats) {
    console.log(stats.isFile());         //true
})
1
2
3

fs.statSync() 同步

方法 说明
stats.isFile() 如果是文件返回 true,否则返回 false。
stats.isDirectory() 如果是目录返回 true,否则返回 false。
var stat = fs.statSync("1.txt");
console.log(stat.size+"B(byte)");   // 文件的大小
1
2

# 文件改名

fs.rename(oldPath, newPath, callback)
1

fs.renameSync() 同步

rename除了能做改名以外,也能做剪切。

# 文件剪切

// 把当前目录下的1.txt,剪切到父目录下,并且改名为2.txt。
fs.renameSync("1.txt", "../2.txt")
1
2

# 文件复制

var data = fs.readFileSync("3.txt");
fs.writeFileSync("../4.txt", data);
1
2

文件的操作还有文件流管道Stream,这是更复杂的用法,感兴趣的自己看资料。

# http 模块

http 模块提供这请求->响应这类的能力。

下面的代码是一段爬虫程序,用来采集某个页面的响应内容。

var http = require('http')
http.get('http://news.baidu.com', function(res){
    var data = '';
	res.on('data',function(chunk){
		data += chunk;
	});
	res.on('end',function(){
		console.log(data);
	})
})
1
2
3
4
5
6
7
8
9
10
  • req.on() 为事件监听,当程序执行都某刻时,会自动触发其回调函数。
  • req.on('data') 和 req.on('end') 表示接收数据事件和接收数据完毕事件。我们的服务器向百度服务器发起请求,百度服务器向我们的服务器回传数据时,这个响应数据,通常是很大的,所以我们的服务器可能无法一次将所有数据都得到,这时就需要分批次的接收数据,所以 data 事件描述每次得到部分数据时,end 事件描述所有数据都得到时。

# http 服务器

# 创建网站服务

http 模块除了能够实现爬虫效果外,还可以用来创建网站服务器。

var http = require('http');
http.createServer(function (request, response) {
    // 响应结束
    response.end("hello nodejs");
}).listen(80);
1
2
3
4
5
  • createServer() 方法用来创建网站服务,是什么样子的网站,要看其回调函数中写了什么。
  • 回调函数中的 request 表示请求,即客户端访问服务器端时的请求数据。
  • 回调函数中的 response 表示响应,即服务器端响应给客户端的响应数据。
  • response.end() 方法表示响应结束,end参数为输出到页面上的内容。
  • listen 表示该网站服务监听哪个端口,http服务的默认端口为80,https的默认端口为443,端口范围在1--65535之间,如果端口被占用,换个端口好了。

# 服务器与客户端

提供服务的机器就是服务器,比如,有两台电脑,A电脑中有一个网站,张三使用B电脑,访问A电脑中的网站,就可以被理解为张三使用了A电脑提供的服务,所以A电脑对张三来说,就是一个服务器。B电脑对张三来说是客户端。

# 请求和响应

一个完整的项目,既有前端部分,也有后端部分,那么两者间又是怎样配合工作的?

前后端的配合,其实就是指前后端的数据交互,比如用户注册功能,需要用户在前端页面中填写表单内容,然后点击提交按钮,把表单内容传递给后端,这个后端,其实指的是在服务器端运行的文件,即后端文件要接收到前端提交过来的数据,然后后端文件中的业务逻辑代码去做相应的业务逻辑处理,比如将得到的数据保存到数据库中。

过程:

  • 前端发起请求(前端访问某个后端页面,请求中可以含有数据)
  • 后端进行响应(服务器执行这个后端页面,后端逻辑中就可以接收前端提交过来的数据,也可以把数据保存到数据库中,也可以往页面上输出一些内容)

# 后端配置路由规则

路由指根据用户访问的不同的 url 网址,服务器给这个用户不同的响应内容。

var http = require('http');
http.createServer(function (request, response) {
    // 允许输出中文,允许跨域访问
    response.writeHead(200,{
        'Content-Type':'text/html;charset=utf-8',
        'Access-Control-Allow-Origin':'*'
    });
    // 配置路由规则
    // request.url 能够得到用户访问了我们哪个页面
    if(request.url == "/"){
        // 响应输出
        response.write("首页");
    }else{
        response.write("其他页面");
    }
    // 响应结束
    response.end();
}).listen(80);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • response.end() 函数只能执行1次
  • response.write() 函数可以执行多次

网站服务挂起后,无论用户访问我们网站内的哪个文件,都会被 app.js 拦截下来,所以得写路由,来决定给用户显示什么内容。

出于后期代码维护的考虑,我们的项目应该是前后端彻底分离的;出于用户体验的考虑,前端应该使用ajax这类的技术来访问后端文件。有些时候,用户在访问我们的网站时,会给我们网站带过来一些数据,这些数据可能是 get 请求的,也可能是 post 请求的,这两种情况下,数据如何获取呢?

# get参数

var http = require("http");
var url = require("url");
http.createServer(function(request, response){
    var router = url.parse(request.url, true);
	var query = router.query;
    switch( router.pathname ){
		case "/a/b.html":		
			response.write( "<br>x:"+query.x );
			break;
	}	
	response.end();
}).listen(80);
1
2
3
4
5
6
7
8
9
10
11
12
  • 浏览器地址栏中的网址,就是url,这个url中含有很多属性,比如访问的文件,及给这个文件的参数,url.parse()方法就是用来格式化该数据的。
  • router.pathname 属性表示用户访问的是哪个文件。
  • query.x 能够得到用户get请求时发给服务器的x数据。

# post参数

var http = require("http");
var querystring = require('querystring');
http.createServer(function(request, response){
    var post = '';     
    request.on('data', function(chunk){    
        post += chunk;
    });
    request.on('end', function(){    
        post = querystring.parse(post);	// {a:1,b:2}
		console.log( post.a ); 
    });	
	response.end();
}).listen(80);
1
2
3
4
5
6
7
8
9
10
11
12
13
  • request.on() 为事件监听,当程序执行都某刻时,会自动触发其回调函数。
  • request.on('data') 和 request.on('end') 表示接收数据事件和接收数据完毕事件。用户使用 post 形式向服务器传递数据时,通常数据都很大,服务器可能无法一次将所有数据都得到,这时就需要分批次的接收数据,所以 data 事件描述每次得到部分数据时,end 事件描述所有数据都得到时。
  • querystring.parse() 方法用来将字符串数据格式化成对象的

# 思考

nodejs 分哪三种模块?
1
nodejs 有哪些系统模块?
1
nodejs 基于哪种模块规范?
1
nodejs 里为什么不可以写alert?
1
nodejs 与 javascript 的关系?
1
nodejs 与 npm 的关系?
1
如何使用nodejs搭建一个服务器网站环境?
1
http模块与https模块的区别?
1
fs模块是干什么的?它有哪些方法?
1