Node.js

Node.js

基本概念

浏览器与服务器的交互流程

  1. 浏览器通过地址栏发出请求
  2. 通过DNS服务器解析,得到域名对应的ip地址
  3. 根据ip地址,访问服务器具体的某个文件
  4. 服务器响应这个具体的文件
  5. 浏览器获取响应,进行显示

前端开发的页面存放在服务器,在客户端执行;

前后端开发

前端开发:以浏览器为宿主环境,结合 HTML、CSS、Javascript等技术,而进行的一系列开发,通常称之为前端开发。

服务器端开发:HTTP服务器可以结合某一编程语言处理业务逻辑,由此进行的开发,通常称之为服务端开发。

nodejs:服务端的javascript开发,用于开发服务端程序的。

前端学习Node.js

  • 为什么要学习服务端的开发?
    • 通过学习Node.js开发理解服务器开发、Web请求和响应过程、 了解服务器端如何与客户端配合作为前端开发工程(FE)需要具备一定的服务端开发能力;
      • 了解什么是服务端渲染?
      • 了解服务端如何编写接口?
  • 全栈工程师的必经之路
  • 服务器端开发语言有很多,为什么要选择nodejs
    • 降低编程语言切换的成本(nodejs实质上用的还是javascript)
    • NodeJS是前端项目的基础设施,前端项目中用到的大量工具,都是基于nodejs实现的
    • nodejs在处理高并发上有得天独厚的优势
    • 对于前端工程师,面试时对于nodejs有一定的要求

Node.js详解

Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。

  • Node.js 不是一门变成语言;
  • Node.js 是JavaScript的运行环境;
  • Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
  • Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
  • Node.js 的包管理器 npm,是全球最大的开源库生态系统。
  • Node.js 的本质:不是一门新的编程语言,nodejs是javascript运行在服务端的运行环境,编程语言还是javascript;

nodejs与浏览器端js的区别

nodejs是基于chrome v8引擎的,因此nodejs可以和浏览器一样执行js代码,但是二者执行的js还是有一定区别的。

  • 浏览器端
    • ECMAScript
    • 浏览器提供的BOM和DOM方法
  • Node.js
    • ECMAScript
    • 不能使用BOM和DOM方法
    • 使用Node.js提供的API

nodejs可以干什么?

  • 开发服务端程
  • 开发命令行工具(CLI),比如npm,webpack,gulp等
  • 开发桌面应用程序(借助 node-webkit、electron 等框架实现)

环境变量

  1. 当要求系统运行一个程序 而没有告诉它程序所在的完整路径时;
  2. 首先在当前目录中查找和该字符串匹配的可执行文件
  3. 进入用户 path 环境变量查找
  4. 进入系统 path 环境变量查找
  5. 配置环境变量:
1
2
3
4
5
配置环境变量:
找到环境变量:计算机 --右键--> 属性 --> 高级系统设置 --> 高级 --> 环境变量
直接将可执行程序所在目录配置到PATH中
如果是window7操作系统,注意要用分号;隔开,不要覆盖原来的内容
D:\Program Files\feiq

运行Node.js

方式一:REPL介绍

  1. REPL 全称: Read-Eval-Print-Loop(交互式解释器)
    • R 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
    • E 执行 - 执行输入的数据结构
    • P 打印 - 输出结果
    • L 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。
  2. 在REPL中编写程序 (类似于浏览器开发人员工具中的控制台功能)
    • 直接在控制台输入 node 命令进入 REPL 环境
  3. 按两次 Control + C 退出REPL界面 或者 输入 .exit 退出 REPL 界面
    • 按住 control 键不要放开, 然后按两下 c 键

方式二:使用node执行js文件

  • 创建js文件 helloworld.js
  • 写nodejs的内容:console.log('hello nodejs')
  • 打开命令窗口 cmd
    • shift加右键打开命令窗口,执行 node 文件名.js即可
    • 给vscode安装terminal插件,直接在vscode中执行
  • 执行命令:node helloworld.js

注意:在nodejs中是无法使用DOM和BOM的内容的,因此document, window等内容是无法使用的。

global模块-全局变量

在Node.js中,global是一个顶层对象;在Node.js中可以直接访问global的方法和属性;不需要加载;

全局变量说明

  • 浏览器全局变量
    • 浏览器的顶层对象是window;window的顶层作用域和全局作用域重合;
    • 所以在全局作用域中声明的变量就是window的变量;可以通过window来访问;
  • Node.js全局变量
    • Node.js的顶层对象是global;global是一个模块,顶层作用域;全局变量;
    • 而每一个js文件是一个单独的模块;每一个单独的模块是一个全局作用域(模块作用域)–相对自身;
    • 所以在全局作用域中声明的变量,并不是global的变量;global不能访问全局作用中 var 声明的变量;

常用的global属性

1
2
3
4
5
6
7
8
9
10
console: 用于打印日志
setTimeout/clearTimeout: 设置清除延时器
setInterval/clearInterval: 设置清除定时器
__dirname: 当前文件的路径,不包括文件名
__filename: 获取当前文件的路径,包括文件名

//与模块化相关的,模块化的时候会用到
require
exports
module

注意:除了global模块中的内容可以直接使用,其他模块都是需要加载的。

fs模块-文件系统

Node.js提供的核心模块;操作文件的模块;

fs模块不是全局的,不能直接使用。因此需要导入才能使用。

读取文件

语法:fs.readFile(path[, options], callback)

1
2
3
4
5
6
7
8
9
10
11
fs.readFile("data.txt", "utf8",function(err, data){
console.log(err);
console.log(data);
});
/*
参数1: 文件的名字
参数2: 编码,如果设置了,返回一个字符串,如果没有设置,会返回一个buffer对象
参数3: 读取文件的回调函数
参数1:错误对象,如果读取失败,err会包含错误信息,如果读取成功,err是null
参数2:读取成功后的数据(是一个Buffer对象),如果读取失败,data是undefined;
*/

写文件

语法:fs.writeFile(file, data[, options], callback)

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
参数1:写入的文件名(如果文件不存在,会自动创建)
参数2:写入的文件内容(注意:写入的内容会覆盖以前的内容)
参数3:写文件后的回调函数
*/
fs.writeFile("2.txt", "hello world, 我是一个中国人", function(err){
if(err) {
return console.log("写入文件失败", err);
}
console.log("写入文件成功");
});

//txt文件中换行需要拼接 "\n"换行符;

追加文件

语法:fs.appendFile(path, data[, options], callback)

1
2
3
4
5
6
7
8
9
10
11
/*
参数1:追加的文件名(如果文件不存在,会自动创建)
参数2:追加的文件内容(注意:写入的内容会在以前的内容后面追加新的内容)
参数3:追加文件后的回调函数
*/
fs.appendFile("2.txt", "我是追加的内容", function(err){
if(err) {
return console.log("追加文件内容失败");
}
console.log("追加文件内容成功");
})

关于Buffer对象

1
2
3
4
5
6
7
8
9
10
11
12
13
一个二进制数 01010100 0位
1GB = 1024MB
1MB = 1024KB
1KB = 1024B (1024个字节)
1字节 = 8位 (01010101)

1个16进制 = 4 位的二级制 0-9 a-f f: 1111
在中文情况下: 一个中文=3个字节 1个英文 = 1个字节

Buffer对象是Nodejs用于处理二进制数据的。
其实任意的数据在计算机底层都是二进制数据,因为计算机只认识二进制。
所以读取任意的文件,返回的结果都是二进制数据,即Buffer对象;
Buffer对象可以调用toString()方法转换成字符串。

提供了同步和异步操作两种方法;

fs中所有的文件操作,都提供了异步和同步两种方式

异步方式:不会阻塞代码的执行

1
2
3
4
5
6
7
8
9
10
//异步方式
var fs = require("fs");
console.log(111);
fs.readFile("2.txt", "utf8", function(err, data){
if(err) {
return console.log("读取文件失败", err);
}
console.log(data);
});
console.log("222");

同步方式:会阻塞代码的执行

1
2
3
4
5
//同步方式
console.log(111);
var result = fs.readFileSync("2.txt", "utf-8");
console.log(result);
console.log(222);

总结:同步操作使用虽然简单,但是会影响性能,因此尽量使用异步方法,尤其是在工作过程中。

path模块-路径操作

关于路径,在linux系统中,路径分隔符使用的是/,但是在windows系统中,路径使用的\在我们拼写路径的时候会带来很多的麻烦,经常会出现windows下写的代码,在linux操作系统下执行不了,path模块就是为了解决这个问题而存在的。

var path = require("path");模块导入

在读写文件的时候,文件路径可以写相对路径或者绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
data.txt是相对路径,读取当前目录下的data.txt, 相对路径相对的是指向node命令的路径
如果node命令不是在当前目录下执行就会报错, 在当前执行node命令的目录下查找data.txt,找不到
*/
fs.readFile("data.txt", "utf8", function(err, data) {
if(err) {
console.log("读取文件失败", err);
}
console.log(data);
});
/*
相对路径:相对于执行node命令的路径
绝对路径:__dirname: 当前执行的文件所在的的目录,__filename: 当前文件的目录,包含文件名
*/

注意:相对路径参考的是执行node命令的路径;绝对路径是以当前node命令执行的js文件所在目录的路径。

  • 文件中的相对路径,是node命令执行时所在的目录为参考;
  • 在使用相对路径时要注意这一点;
  • 绝对路径是以当前node命令执行的文件所在的目录;
  • 以/开头的也是绝对路径;/1级目录/ 路径以1级目录为参考;

使用:

path.join("1级目录","2级目录","3级目录","文件名+后缀");-拼接路径;

  • window中使用的\,在字符串中使用时需要两个斜杠;有转义的效果;也支持/,
  • Linux和mac中使用/,不支持\,
  • path.join("1级目录","2级目录","3级目录","文件名+后缀")会根据系统不同使用不同的斜杠;
  • 支持dirname读取当前目录,支持../跳到上一级目录;`path.join(dirname,”../其他目录/文件名+后缀”)`

其他方法:

  • path.basename(path[, ext])-返回文件名+后缀
  • path.dirname(path)-返回路径的目录名
  • path.extname(path)-获取路径的扩展名(后缀)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
path.basename(path[, ext])
返回文件的最后一部分

path.dirname(path)
返回路径的目录名

path.extname(path)
获取路径的扩展名

path.isAbsolute(path)
判断目录是否是绝对路径

path.join([...paths])
将所有的path片段拼接成一个规范的路径

path.normalize(path)
规范化路径

path.parse(path)
将一个路径解析成一个path对象

path.format(pathObj)
讲一个path对象解析成一个规范的路径

http模块-创建服务器

var http = require("http");模块导入;

创建服务器的基本步骤

  1. 导入http模块:var http = require("http");
  2. 创建服务器:var server = http.createServer();
  3. 客户端请求事件:server.on("request", function(request,response) {});
    • 给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
    • request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,
    • response是响应对象,可以获取所有与响应相关的信息。
  4. 启动服务器,监听端口:server.listen(9999, function(){"执行给自己看的代码"});
    • 服务器监听的端口范围为:1-65535之间,
    • 推荐使用3000以上的端口,因为3000以下的端口一般留给系统使用
    • 6000,6665,6666,6667,6668,6669特殊端口,也不能使用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//1. http模块是nodejs提供的核心的模块, 需要先导入
var http = require("http");

//2. 创建一个服务器
var server = http.createServer();

//3. 监听用户的请求, 给创建的服务器注册一个事件
//request:每次用户访问服务器的时候,这个事件就会触发
//request: request对象:包含所有和本次请求相关的内容
//response:response对象,包含了所有响应相关的内容
server.on("request", function(request, response){
console.log("哎哟,有人来了");
response.end("hello kitty!!!");
})

//4. 启动服务器, 监听一个端口(一个应用程序想要启动,一定需要占用一个端口) 0-65535
//参数1:监听的端口号
//参数2:服务器启动的回调函数
server.listen(9999, function(){
console.log("服务器成功启动了,请访问:http://localhost:9999");
});

request对象详解

  • request.headers: 所有的请求头信息
  • request.rawHeaders:所有的请求头信息(数组的方式)
  • request.method:请求的方式
  • request.url:请求的地址
  • 注意点:
    • 在发送请求的时候,可能会出现两次请求的情况,这是因为谷歌浏览器会自动增加一个favicon.ico的请求。
    • 小结:request对象中,常用的就是method和url两个参数

response对象详解

response响应对象

  • response.write(data);-响应给客户端的数据;
    • 参数data可以是字符串,也可以是buffer对象;
    • 可以有多个response.write(data);会按顺序自动拼接;
  • response.end([data]);-表示本次响应结束;
    • 必须设置响应结束;否则客户端会默认服务器响应没有结束;
    • 参数data,可以不传递,可以是字符串,也可以是buffer对象;
    • 设置data参数,解析为先调用了response.write(data);在调用了response.end();
  • response.setHeader(“content-type”, “text/html;charset=utf-8”);-设置响应头;让浏览器以utf-8解析;
  • response.statusCode=404;-设置响应http状态码;
  • response.statusMessage=””;-设置响应状态信息;在设置了response.statusCode;会自动生成响应状态信息;一般不用设置;
  • response..writeHead(404, [“响应状态信息”], {“content-type”:’text/html;charset=utf-8’,});-上面三个属性合写;

注意:必须先设置响应头,才能设置响应;也会发生中文会乱码;

request有两个事件

接收客户端发送的post数据时触发;

data事件

  • post数据大小没有限制,数据可能会分成多段发送;
  • chunk事件对象,chunk表示每次接收数据,可以用一个空串,将多次数据拼接起来;

end事件

  • 在所有的post数据接收完触发,此时可以读取到拼接起来的字符串数据;
1
2
3
4
5
6
7
8
9
10
11
12
13
// 接受POST参数
var postData = []

// data事件:用来接受客户端发送过来的POST请求数据
var result = "";
req.on('data', function (chunk) {
result += chunk;
})

// end事件:当POST数据接收完毕时,触发
req.on('end', function () {
cosnole.log(result);
})

mime模块-第三方模块

MIME(Multipurpose Internet Mail Extensions)多用途Internet邮件扩展类型 是一种表示文档性质和格式的标准化方式

浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理文档;因此服务器将正确的MIME类型附加到响应对象的头部是非常重要的

专门用于设置响应头Content-Type的值;可以根据文件的类型(文件的后缀)返回对应的mime类型;

如果没有设置响应头,浏览器回去自动识别文件对应的类型;一般会有问题;

mime.getType(url);

  • 根据url参数返回对应的mime类型;
  • 参数可以是文件的后缀字符串(”css”),也可以是带有路径的文件名,自动识别文件后缀;

mime.getExtension('text/plain');

  • 根据对应的mime类型返回对应的文件类型(文件的后缀);

npm

npm的组成部分:

  • npm网站
  • 注册表(registry)包的仓库;
  • 命令行工具 (CLI)

npm的基本使用

初始化包

npm init;-这个命令用于初始化一个包,创建一个package.json文件,name不能有中文;

npm init -y;-快速的初始化一个包, 不能是中文名;

规范使用npm,都应该初始化包,我们的项目都应该先执行npm init

安装包

npm install 包名; //安装指定的包名的最新版本到项目中

npm install 包名@版本号; //安装指定包的指定版本

npm i 包名; //简写

本地安装和全局安装

本地安装

  • js文件中使用,通过导入加载到文件中;是npm install的默认安装行为;
  • npm install 包名;
  • 本地安装,会把npm包安装到当前项目的node_modules文件中,作为项目的依赖

全局安装

  • 如果想要在命令行中使用命令,则需要通过全局安装;可以在任何目录的命令使用;
  • npm install -g 包名;
  • 全局安装,会把npm包安装到C:\Users\用户名\AppData\Roaming\npm目录下,作为命令行工具使用

卸载包

npm uninstall 包名; //卸载已经安装的包

也可以手动删除;不推荐,package.json文件中的依赖不会删除;

更新包

npm update 包名 更新包

有package.json文件可以直接使用 npm update

它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

强制重新安装包

npm install 包名 --force

有package.json文件可以直接使用 npm install --force

一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f或–force参数。

npm清除缓存

在某些情况下,安装失败或者其他操作的时候需要清除缓存;

npm cache clean --force

package.json文件

package.json文件,包(项目)描述文件,用来管理组织一个包(项目),它是一个纯JSON格式的。

作用:描述当前项目(包)的信息,描述当前包(项目)的依赖项;

  • 作为一个标准的包,必须要有package.json文件进行描述
  • 一个项目的node_modules目录通常都会很大,不用拷贝node_modules目录,可以通过package.json文件使用npm install来安装文件中的依赖包
  • 命令:npm install直接安装项目所有的依赖项(包括开发依赖项)
  • 命令:npm i --production安装项目依赖项(不包括开发依赖)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "03-npm", //描述了包的名字,不能有中文
"version": "1.0.0", //描述了包的的版本信息, x.y.z 如果只是修复bug,需要更新Z位。如果是新增了功能,但是向下兼容,需要更新Y位。如果有大变动,向下不兼容,需要更新X位。
"description": "", //包的描述信息
"main": "index.js", //入口文件(模块化加载规则的时候详细的讲)
"scripts": { //配置一些脚本,在vue的时候会用到,现在体会不到
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], //关键字(方便搜索)
"author": "", //作者的信息
"license": "ISC", //许可证,开源协议
"dependencies": { //重要,项目的依赖, 方便代码的共享 通过 npm install可以直接安装所有的依赖项
"bootstrap": "^3.3.7",
"jquery": "^3.3.1"
},
"devDependencies": { //配置开发依赖项,只是在开发阶段有用,真正运行环境不需要使用,如果需要安装所有的开发依赖项,通过
"webpack": "^3.0.4"
}
}

开发依赖包安装

devDependencies:开发依赖包配置

命令:npm install --save-dev

文档中会有安装方法;

npm下载加速

淘宝镜像:将安装源配置到淘宝镜像地址;不推荐使用;

1
2
3
# 设置淘宝镜像
npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

cnpm:安装cnpm命令,使用淘宝镜像下载;

1
2
命令:npm install cnpm -g
命令:npm install cnpm -g --registry = https://registry.npm.taobao.org

nrm:npm registry manager(npm仓库地址管理工具);专门用于切换安装源地址;

1
2
3
4
5
6
7
8
9
10
# 安装:
npm i -g nrm

#查看仓库地址列表
nrm ls
# 带*表示当前正在使用的地址

# 切换仓库地址
nrm use taobao 切换到淘宝镜像安装源地址;
nrm use npm 切换到npm官网安装源地址;

nodemon模块-自动重启-第三方模块

作用:监视到js文件修改后,自动重启node程序

安装:npm i -g nodemon

使用:nodemon 文件.js 运行node程序

npm发布自己的包

  1. 注册npmjs账号
  2. 创建项目文件夹
  3. 创建package.json文件
  4. 创建index.js实现基本功能
  5. 创建README.md,描述项目
  6. npm publish发布

需要通过 npm login 进行登录

需要使用 nrm use npm 把仓库地址切换回npm

版本号一定要改,才能修改后再次上传;

创建自己的cli程序

  1. 创建要给文件夹
  2. npm init初始化
  3. 在文件夹中创建一个index.js,这个js文件就是使用命令要执行 的文件
  4. package.json中配置bin,在bin中添加一个属性,这个属性就是最终在命令行中使用的命令名称
1
2
3
4
bin: {
//当hcc-cool命令执行,hcc-cool.js就会执行
"hcc-cool":'hcc-cool.js'
}

在JS文件的头部加上
#! node

#! node 表示node中输入命令的时候,执行了bin中命令执行的js文件 hcc-cool ==> node hcc-cool.js
console.log("你的第一个命令行程序已经完成了");

  • 相对路径是以node命令窗口所在路径为参考;
  • 绝对路径以被执行的文件所在的路径为参考;

url模块

用于URL处理与解析;通过url拿到的查询参数都是字符串格式;

url.parse(url) 方法会解析一个url字符串并返回一个URL对象。

  • 如果在url参数后,传递一个布尔类型;会调用querystring来解析URL.query字符串成为一个对象;

querystring模块 - 查询字符串

用于解析与格式化 URL 查询字符串;只在专门处理查询字符串时使用

querystring.parse(str)-解析str字符串成为一个对象。

1
2
3
4
5
//查询字符串 'foo=bar&abc=xyz&abc=123' 被解析成:
{
foo: 'bar',
abc: ['xyz', '123']
}

Node.js 模板引擎

art-template 模板引擎

安装命令:

1
npm install art-template

如果一个模版,需要渲染很多次,那么可以使用complie方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 基于模板路径渲染模板
//参数1:文件的路径,必须是绝对路径
//参数2:数据
//返回值:返回渲染后的内容
var html = template(filename, data)
template(path.join(__dirname, "pages", "index.html"), {name:"大吉大利,今晚吃鸡"});

// 将模板源代码编译成函数
var render = template.compile(source)
render(data)
var render = template.compile('<div>{{ name }}</div>')
var html = render({name: '大吉大利'})

// 将模板源代码编译成函数并立刻执行
template.render(source, data)
var html = template.render('<div>{{ name }}</div>', {name: '大吉大利'})

其他模板引擎

  • ejs
1
npm install ejs
1
2
3
4
5
6
// ejs 示例:
var html = ejs.render('<%= name %>喜欢吃:<%= food %>', {
name: 'rose',
food: '西红柿'
})
console.log(html)
  • underscore
1
npm install underscore
1
2
3
4
// underscore 示例:
var compiled = _.template("hello: <%= name %>")
var ret = compiled({ name: 'moe' })
console.log(ret)
  • jade/pug
1
npm install pug

Node.js模块化

  • 在nodejs中,引用由模块组成,nodejs中采用commonJS模块规范。
  • 一个js文件就是一个模块;
  • 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。

node.js模块分类

  • node.js的核心模块:核心模块只需要引入就可以使用;
1
var fs = require("fs");
  • npm安装的第三方模块:通过npm命令下载的包;下载完成后引入使用;
1
npm install mime
1
var mime = require("mime");
  • 自定义模块.js:自己开发的js文件;通过module.exports导出;
1
2
3
4
5
/*
require("./")引入;文件必须是"./"开头,不在同一级目录使用"../";不使用"./../",就不被识别为自定义模块;
.js后缀可以省略;如果是某个文件夹内的index.js可以直接使用文件夹,自动识别index.js
*/
var a = require("./a.js");

模块的导出

在模块内部提供了module对象,module.exports对外导出的接口;这个属性指向一个空对象;其他模块加载的就是module.exports;

1
2
3
4
5
6
7
8
9
10
11
12
var a = 1;

//module.exports指向的是一个对象,我们给对象增加属性即可。
module.exports.num = 123;
module.exports.age = 18;

//通过module.exports也可以导出一个值,但是多次导出会覆盖
module.exports = '123';
module.exports = "abc";
module.exports = a;
module.exports = {};
module.exports = function () {}

module.exports对外导出的接口;所以当其中一个引用地址改变之后,module对象操作的就不是对外导出的数据了;

复杂对象赋值地址引用

当一个变量a指向一个复杂数据类型时,引用的是复杂数据类型的存储地址;

另一个变量b被a变量赋值(赋值的是引用地址),b也指向这个复杂数据类型的存储地址;

所以两个变量只要有一个赋值被改变的时候,都不在产生关联;不在指向同一个地址;

1
2
3
4
5
6
7
// 复杂对象obj的地址赋值给另外一个变量a后,即使再次修改obj地址,也不会影响a;
var a = {c:1}
var b = a
b = {c:2}
b.c=3;
console.log(b);//{c:3}
console.log(a);//{c:1}

在每个模块内部还提供了一个变量exports;变量exports也指向了module.exports的空对象地址;由于是复杂类型,所以都指向了同一个复杂类型的地址;所以在两个都没有改变指向地址时,添加属性的操作是等价的;如果任意一个改变了地址引用,那么这两个就不在指向同一地址了,使用时一定要注意;

1
2
3
4
5
6
7
8
9
10
11
console.log(module.exports === exports); //true

// 等价操作
module.exports.num = 123
exports.num = 123

// 赋值操作:不要使用
exports = {}
module.exports = {}

console.log(module.exports === exports); //false

注意:极度不推荐使用exports来操作;

模块的导入

  • 通过require(“”)来加载模块;核心模块直接加载使用;
1
var fs =  require("fs");
  • 第三方模块,通过npm命令安装后,在加载;
    • 先在当前目录查找node_modules目录;如果有,则去该目录中找package.json文件;
    • 在package.json文件中查找main属性,找到main对应的路径文件;
    • 如果没有查找到main属性,或者main属性对应的路径没有文件,或者没有package.json文件;
    • 则查找index.js index.node index.json文件,如果还没有查找到,就会向上级目录查找node_modules目录;
    • 如果上级有node_modules目录,则重复之前的过程,如果没有找到node_modules目录;
    • 就会一直向上级查找,一直到根目录,如果还没有就报错:can not find module xxx
1
npm install mime
1
var mime = require("mime");
  • 自定义模块,一定要使用相对路径”./../“;,否则会被识别为第三方模块;
1
2
3
4
5
/*
require("./")引入;文件必须是"./"开头,不在同一级目录使用"../";不使用"./../",就不被识别为自定义模块;
.js后缀可以省略;如果是某个文件夹内的index.js可以直接使用文件夹,自动识别index.js
*/
var a = require("./a.js");

Express框架

基于node平台的一款快速、开放、极简的 web 开发框架;

Express框架的基本使用

安装命令:

1
npm install express

基本使用:

1
2
3
4
5
//创建一个Express应用,并返回,相当于创建了服务器;
var app = express();

//开启服务器;
app.listen(9999,callback);

注册路由的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
//只识别url的页面请求(根据监听类型),可以用?带参数;
app.get("url",callback);
app.post("url",callback);

//只识别url的页面请求(同时支持get和post请求),可以用?带参数;
app.all("url",callback);

/*
只要是path参数开头的地址请求都识别;
参数可以是"/",也可以不传参数,就表示全都识别;
*/
app.use(path,callback);识别以path参数开头的任何请求;

处理静态资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
查找当前服务器开启的目录中的path参数路劲文件夹下的静态资源;
会直接查找path目录下的文件,所以路径不需要再加上path目录了;
*/
express.static(path);//方法来托管静态资源

/*
page表示虚拟目录,将来显示在地址栏中的虚拟目录;需要带斜杠"/page"
显示为http://localhost:9898/page/***.xxx
web表示当前服务器目录中的web文件夹;
实际路径是web文件下的静态资源;
*/
app.use("/", express.static("public"));//显示为http://localhost:9898/***.xxx
app.use("/public", express.static('web'));//显示为http://localhost:9898/public/***.xxx

request的常用属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
//获取请求页面地址的第一个/和?之间的内容
request.path
//http://example.com/users?sort=desc ==> /users

/*
获取get请求的页面地址?后面的参数,得到的直接就是一个处理好的对象;
可以直接使用点语法获取到对象内的某个属性;request.query.k
*/
request.query

//获取post求页面请求的参数;需要中间
req.body

response的常用属性和方法

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
//响应给客户端的数据;自动设置响应头,自动调用end方法;
response.send();

//直接读取文件响应给客户端;根据文件后缀自动设置响应头,自动调用end方法;必须是绝对路径;
response.sendFile();
response.sendFile(path.join(__dirname, 'index.html'));

//设置响应http状态码;
response.status(code);
res.status(403).end();
res.status(400).send('Bad Request');
res.status(404).sendFile('/absolute/path/to/404.png');

/*
设置响应http状态码;
并根据响应的http状态码自动设置响应相关信息;
*/
response.sendStatus(statusCode);

/*
设置响应头,可以是直接的name和value;
也可以是对象,设置多个响应头;
*/
response.set(name, value);

/*
重定向
可以直接响应一个页面地址;可以相对于主机目录,直接传递目录后的页面地址路径;
重定向可以是用于重定向到其他站点的设置好的URL;
*/
response.redirect([status,] path);

Express使用模板引擎

Express安装

1
2
3
npm install express
npm install art-template
npm install express-art-template

给express绑定一个模版引擎

1
2
3
4
//给express设置模版引擎
//参数1: 模版引擎的后缀名,以后的模版文件都应该是html结尾;
//参数2: 使用什么模版引擎
app.engine("html", require('express-art-template'));

通过res.render()渲染模版引擎

1
2
3
4
//参数1; 模版文件的路径,相对路径,会去views目录下查找;也可以是用绝对路径;
//参数2: 数据
res.render("index.html", {name:"zs"});
res.render(path.join(__dirname, "index.html"), {name:"zs"});

关于模版引擎的配置

1
2
3
4
5
//模版文件默认去aa目录下查找  默认值:views
app.set("views", "aa");

//设置模板引擎的默认后缀,渲染模板的时候就可以不用加后缀了
app.set("view engine", "html");

中间件

中间件(Middleware) 是一个函数,它可以访问请求对象request,响应对象response,可以通过next参数将中间件传递到下一个中间件;

中间件的功能包括:

  • 执行任何代码。
  • 修改请求和响应对象。
  • 可以结束整个响应res.send() res.end() res.sendFile()
  • 调用堆栈中的下一个中间件。

定义一个中间件

1
2
3
4
5
6
7
8
//添加一个中间件
//中间件是啥:中间件就是一个函数,中间件可以访问到req和res,可以通过next发送给下一个中间件
app.use(function(req, res, next) {
req.aa = "胡聪聪";
res.bb = "很帅";
//中间件可以通过next传递给下一个中间件
next();
});

body-parser中间件的使用

  • 获取get请求的参数:req.query
  • 获取post请求的参数:req.body,但是需要借助body-parser中间件;

安装:

1
npm install body-parser

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
//1. 导入body-parser中间件
var bodyParser = require('body-parser');
//2.使用body-parser中间件
//body-parser中间可以设置多个解析post请求的类型
//extended:true 表示使用qs库来解析查询字符串
//extended:false 表示使用querystring库来解析字符串
app.use(bodyParser.urlencoded({extended:false}));

//3. 通过req.body获取参数
app.post("/", function(req, res) {
console.log(req.body);
res.send("哈哈");
});

注意:中间件是有执行顺序的

实现自己的body-parser中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
//bodyParser.json(); 返回一个函数(中间件),会处理json数据
app.use(function(req, res, next){
//给req增加一个body属性
var result = "";
req.on('data', function(chunk){
result += chunk;
});

req.on("end", function(){
req.body = querystring.parse(result);
next();
});
});

外置路由的使用

目的:将路由封装到一个独立的路由模块中,有利于代码的封装和模块化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
router.js 文件代码如下:
*/

// 1 加载 express 模块
var express = require('express')

// 2 调用 express.Router() 方法,得到一个路由容器实例对象;
var router = express.Router()

// 3 为 router 添加不同的路由
router.get('/', function (req, res) {
res.send('hello express')
});
router.get('/add', function (req, res) {
});

// 4. 将 router 路由容器导出
module.exports = router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
在 app.js 文件中:
*/
var express = require('express')

//1 加载上面自定义的路由模块
var router = require('./router')

var app = express();

//2. 将自定义路由模块 router 通过 app.use() 方法挂载到 app 实例上
//这样 app 实例程序就拥有了 router 的路由
app.use( router )

app.listen(3000, function () {
console.log('running...')
});

注意:可以定义多个外置路由,通过app.js来导入,注册多个路由挂载在app实例上,不会互相干扰;