# NodeJS 介绍
- 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
2
参考资料:
# 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 // 全局安装的包查看
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命令测试。
- 在common文件夹中执行npm link,将common模块创建成本地依赖包。
- npm config list:可查看prefix,全局文件安装目录
- 在prefix目录下添加软链接执行文件
- 在prefix/node_modules目录添加common模块的快捷方式
- 在project文件夹中执行npm link common,将common模块应用在project工程里。
- 在node_modules下添加.bin文件夹以及common工程文件夹
- 解除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
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
2
3
4
5
6
7
8
# nrm 源切换工具
使用 npm install 下载模块时,默认从美国下载,速度是比较慢的,可以把源改成国内的淘宝镜像。
## 安装nrm
npm install nrm -g
## 查看有哪些源
nrm ls
## 切换到taobao源
nrm use taobao
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/
2
3
4
5
6
7
8
# cnpm 淘宝源
也可以直接使用cnpm命令,表示直接从淘宝源下载模块。
## 安装cnpm命令,然后每次使用 cnpm install 下载模块。
npm install -g cnpm --registry=https://registry.npm.taobao.org
2
# NodeJS 代码的运行
# 系统自带的 node 命令
- 在命令行窗口中,直接输入node,会进入node环境,可以在里面直接写js代码,然后直接运行。
- 编写js文件,然后在命令行中执行这个js文件。
NodeJS是JS语言的运行环境,JS是语言,因为是在服务器端执行,所以客户端的BOM、DOM是没有的,除了ECMAScript外,还增加了一些服务器特有的能力。比如 process.stdout.write() 就是用来做控制台输出的。
## 使用node命令执行当前目录下的app.js文件
node app.js
2
ctrl+c :有些代码会导致程序被挂起,如果想退出程序,可以让命令行获得聚焦后,直接键盘ctrl+c终止程序。
比如网站服务器这种功能,需要把程序挂起,那么每次修改源代码时,之前挂起的程序并不会自动重新启动,没有重启动就意味着刚才的修改,并没有更新过来,所以需要手动重启,那么频繁的手动重启太麻烦了,所以要选择热部署工具来守护我们的进程,nodemon和supervisors都是它的解决方案。
# nodemon
热部署工具 nodemon ,解决的是开发项目时,每次代码修改完毕后,服务自动启动的问题。(说白了就是每次ctrl+s时,当前代码会被系统再次执行)
## 下载 nodemon
npm install nodemon -g
2
使用 nodemon 运行我们的代码
## 使用 nodemon 执行当前目录下的 app.js 文件
nodemon app.js
2
nodemon 运行后,就相当于挂起了服务,然后每次修改 app.js 文件中的内容,保存时,就会自动执行 app.js。
# supervisors
热部署工具 supervisors,起到的作用和 nodemon 一模一样,因为不稳定,所以不建议使用。
## 安装 supervisors
npm install supervisors -g
## 使用 supervisors 运行 app.js
supervisors app.js
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
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
*/
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!
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
}
2
3
4
5
# 使用模块
在app.js文件中,写入:
// 导入sum.js文件,用变量sum表示sum.js文件中导出的内容。
var sum = require("./sum.js"); // 注意此处路径要写 ./
// 执行函数,输出其结果。
console.log( sum(1, 2) ); // 3
2
3
4
5
使用 require 导入模块时,文件扩展名可以省略。
如果是自己做的模块文件,引入时的路径要用 ./ 或 ../,如果不写./自身或../父级,则表示直接从 node_modules 中查找。
# 在命令行中执行
nodemon app.js
# 常用的系统模块
系统模块无需 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') );
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());
});
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("文件删除成功!");
});
2
3
4
5
6
fs.unlinkSync("1.txt") 同步
# 写入文件
fs.writeFile('1.txt', '不存在则创建', function(err) {
if (err) {
return console.error(err);
}
console.log("数据写入成功!");
});
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)
2
3
4
5
6
# 创建文件夹
// 异步
fs.mkdir("a",function(err){
if (err) {
return console.error(err);
}
console.log("目录创建成功。");
});
// 同步
fs.mkdirSync("b");
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 );
});
});
2
3
4
5
6
7
8
9
fs.readdirSync("a") 为同步的写法
# 删除目录
只能删除空白目录
fs.rmdir("a",function(err){
if (err) {
return console.error(err);
}
});
2
3
4
5
fs.rmdirSync("a/c") 同步写法
# 获取文件信息
fs.stat('1.txt', function (err, stats) {
console.log(stats.isFile()); //true
})
2
3
fs.statSync() 同步
方法 | 说明 |
---|---|
stats.isFile() | 如果是文件返回 true,否则返回 false。 |
stats.isDirectory() | 如果是目录返回 true,否则返回 false。 |
var stat = fs.statSync("1.txt");
console.log(stat.size+"B(byte)"); // 文件的大小
2
# 文件改名
fs.rename(oldPath, newPath, callback)
fs.renameSync() 同步
rename除了能做改名以外,也能做剪切。
# 文件剪切
// 把当前目录下的1.txt,剪切到父目录下,并且改名为2.txt。
fs.renameSync("1.txt", "../2.txt")
2
# 文件复制
var data = fs.readFileSync("3.txt");
fs.writeFileSync("../4.txt", data);
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);
})
})
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);
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);
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);
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);
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 分哪三种模块?
nodejs 有哪些系统模块?
nodejs 基于哪种模块规范?
nodejs 里为什么不可以写alert?
nodejs 与 javascript 的关系?
nodejs 与 npm 的关系?
如何使用nodejs搭建一个服务器网站环境?
http模块与https模块的区别?
fs模块是干什么的?它有哪些方法?