什么是Node

Node是一个基于Chrome V8引擎的JavaScript代码运行环境。

注意:需要运行时,在某目录按下shift+鼠标右键即可打开该目录的power shell.

Node.js模块化开发

JavaScript在使用时存在两大问题,文件依赖和命名冲突。Node.js规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到。模块内部可以使用exports对象进行成员导出, 使用require方法导入其他模块。

  • 导出
1
2
3
4
5
6
7
8
9
  // a.js
// 在模块内部定义变量
let version = 1.0;
// 在模块内部定义方法
const sayHi = name => `您好, ${name}`;
// 向模块外部导出数据
exports.version = version;
exports.sayHi = sayHi;
// ()=>{}表示一个函数
  • 导入
1
2
3
4
5
6
7
 // b.js
// 在b.js模块中导入模块a
let a = require('./b.js');
// 输出b模块中的version变量
console.log(a.version);
// 调用b模块中的sayHi方法 并输出其返回值
console.log(a.sayHi('黑马讲师'));
  • 导出的另外一种方式
1
2
3
module.exports.version = version;
module.exports.sayHi = sayHi;
//exports是module.exports的别名(地址引用关系),导出对象最终以module.exports为准

系统模块

  • 系统模块fs文件操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fs = require('fs');
//读取文件内容
fs.reaFile('文件路径/文件名称'[,'文件编码'], callback);
//写入文件操作
fs.writeFile('文件路径/文件名称', '数据', callback);
//样例
const content = '<h3>正在使用fs.writeFile写入文件内容</h3>';
fs.writeFile('../index.html', content, err => {
if (err != null) {
console.log(err);
return;
}
console.log('文件写入成功');
});
  • 系统模块path 路径操作

    为什么要进行路径拼接?不同操作系统的路径分隔符不统一。

    大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录。使用__dirname获取当前文件所在的绝对路径。

1
2
3
4
5
6
7
8
9
//路径拼接语法
path.join('路径', '路径', ...)
//样例
// 导入path模块
const path = require('path');
// 路径拼接
let finialPath = path.join('itcast', 'a', 'b', 'c.css');
// 输出结果 itcast\a\b\c.css
console.log(finialPath);

第三方模块安装

  • 安装方法
1
2
下载:npm install 模块名称   (有末尾-g表示全局作用)
卸载:npm unintall package 模块名称
  • nodemon

    在Node.js中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐,该模块就可解决这一问题。

  • nrm

    npm默认的下载地址在国外,国内下载速度慢,nrm使用国内cdn下载。

    1
    2
    3
    使用npm install nrm –g 下载它
    查询可用下载地址列表 nrm ls
    切换npm下载地址 nrm use 下载地址名称
  • Gulp

    功能:项目上线,HTML、CSS、JS文件压缩合并,语法转换(es6、less …)…

    使用

    • 使用npm install gulp下载gulp库文件
    • 在项目根目录下建立gulpfile.js文件
    • 重构项目的文件夹结构 src目录放置源代码文件 dist目录放置构建后文件
    • 在gulpfile.js文件中编写任务
    • 在命令行工具中执行gulp任务

    方法

    • lgulp.src():获取任务要处理的文件
    • lgulp.dest():输出文件
    • lgulp.task():建立gulp任务
    • lgulp.watch():监控文件的变化

    样例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const gulp = require('gulp');
    // 使用gulp.task()方法建立任务
    gulp.task('first', done => {
    // 获取要处理的文件
    gulp.src('./src/css/base.css')
    // 将处理后的文件输出到dist目录,.pipe为处理函数
    .pipe(gulp.dest('./dist/css'));
    done();
    });

    需要安装插件才可运行上方文件: npm install gulp-cli -g

    运行:gulp first

Gulp使用

插件

  • gulp-htmlmin :html文件压缩
  • gulp-csso :压缩css
  • gulp-babel :JavaScript语法转化
  • gulp-less: less语法转化
  • gulp-uglify :压缩混淆JavaScript
  • gulp-file-include 公共文件包含
  • browsersync 浏览器实时同步

安装及使用方法:https://www.npmjs.com/ (在网站搜索插件即可得到使用方法)

样例

  • gulp-htmlmin
1
2
3
4
5
6
7
8
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
// html文件压缩
gulp.task('htmlmin', () => {
return gulp.src('src/*.html')
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(gulp.dest('dist'));
});
  • first
1
2
3
4
5
6
7
8
const gulp = require('gulp');
gulp.task('first', done => {
// 获取要处理的文件
gulp.src('./src/css/base.css')
// 将处理后的文件输出到dist目录,.pipe为处理函数
.pipe(gulp.dest('./dist/css'));
done();
});
  • 构建任务
1
2
// 同时执行多个任务
gulp.task('default', gulp.series('first', 'htmlmin'));

package.json文件

node_modules文件夹的问题

  • 文件夹以及文件过多过碎,当我们将项目整体拷贝给别人的时候,,传输速度会很慢很慢.

  • 复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一致,否则会导致当前项目运行报错

package.json文件的作用

项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github地址、当前项目依赖了哪些第三方模块等。

使用npm init -y命令生成。

项目依赖

  • 在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖

  • 使用npm install 包名命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中

1
2
// 同时执行多个任务
gulp.task('default', gulp.series('first', 'htmlmin'));

开发依赖

  • 在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖

  • 使用npm install 包名 --save-dev命令将包添加到package.json文件的devDependencies字段中

1
2
3
4
5
{
"devDependencies": {
"gulp": "^3.9.1“
}
}

package-lock.json文件的作用

  • 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题

  • 加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外的工作

模块查找规则

当模块拥有路径但没有后缀时

  • require方法根据模块路径查找模块,如果是完整路径,直接引入模块。
  • 如果模块后缀省略,先找同名JS文件再找同名JS文件夹
  • 如果找到了同名文件夹,找文件夹中的index.js
  • 如果文件夹中没有index.js就会去当前文件夹中的package.json文件中查找main选项中的入口文件
  • 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到

当模块没有路径且没有后缀时

  • Node.js会假设它是系统模块
  • Node.js会去node_modules文件夹中
  • 首先看是否有该名字的JS文件
  • 再看是否有该名字的文件夹
  • 如果是文件夹看里面是否有index.js
  • 如果没有index.js查看该文件夹中的package.json中的main选项确定模块入口文件
  • 否则找不到报错

创建web服务器

1
2
3
4
5
6
7
8
9
// 引入必要模块
const http = require('http');
// 创建网站服务器对象
const app = http.createServer();
app.on('request', (req, res) => {
res.end('<h1>hello,user!</h1>');
})
app.listen(3000);
console.log('服务器启动成功!');

请求报文

  • 请求方式

    • GET 请求数据
    • POST 发送数据
  • 请求地址

    • url
1
2
3
4
5
app.on('request', (req, res) => {
req.headers // 获取请求报文
req.url // 获取请求地址
req.method // 获取请求方法
});
  • http状态码

    • 200 请求成功

    • 404 请求的资源没有被找到

    • 500 服务器端错误

    • 400 客户端请求有语法错误

  • 内容类型

    • text/html

    • text/css

    • application/javascript

    • image/jpeg

    • application/json

1
2
3
4
5
app.on('request', (req, res) => {
// 设置响应报文
res.writeHead(200, {'Content-Type': 'text/html;charset=utf8‘
});
});

GET请求

1
2
3
4
5
app.on('request', (req, res) => {
// 设置响应报文
res.writeHead(200, {'Content-Type': 'text/html;charset=utf8‘
});
});

POST请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.on('request', (req, res) => {
// post参数是根据事件的方式传递
// post数据不是一次性传输完,有一定延迟,须有一个回调函数
// data 当请求参数开始传递时触发data
// end 当请求参数传递完成时触发end
let PostParam = '';
req.on('data', param => {
PostParam += param;
});

req.on('end', () => {
// 转为对象形式
console.log(querystring.parse(PostParam));
});
res.end('ok');
});

路由

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
// 1.引入系统模块
// 2.创建网站服务器
// 3.为服务器对象添加请求事件
// 4.实现路由功能
//获取请求方式
//获取请求地址
const http = require('http');
const url = require('url');
const app = http.createServer();
app.on('request', (req, res) => {
// 获取请求地址
const method = req.method.toLowerCase();
const pathname = url.parse(req.url).pathname;
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
});
if (method == 'get') {
if (pathname == '/' || pathname == '/index') {
res.end('欢迎来到首页');
} else if (pathname == '/list') {
res.end('欢迎来到list');
} else {
res.end('未找到网页');
}
} else if (method == 'post') {
res.end('post');
}
});

静态网站

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
const fs = require('fs');
const http = require('http');
const url = require('url');
const path = require('path');
const mime = require('mime');
const app = http.createServer();
app.on('request', (req, res) => {
// 获取请求地址
const method = req.method.toLowerCase();
let pathname = url.parse(req.url).pathname;
pathname = (pathname == '/') ? '/default.html' : pathname;
// 获取真实路径
let realPath = path.join(__dirname, 'public' + pathname);
// 获取文件类型
let type = mime.getType(realPath);
// 读取文件
fs.readFile(realPath, (error, result) => {
// 读取失败
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
});
res.end('文件读取失败!');
return;
}
res.writeHead(200, {
'content-type': type
})
res.end(result);
});
});

app.listen(3000);
console.log('服务器启动成功!');

异步编程

  • 同步API:只有当前API执行完成后,才能继续执行下一个API
  • 异步API:当前API的执行不会阻塞后续代码的执行
  • 同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的

Promise

  • Promise出现的目的是解决Node.js异步编程中回调地狱的问题。
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
const fs = require('fs');

function f1() {
return new Promise((resolve, result) => {
fs.readFile('./1.txt', 'utf8', (err, data) => {
resolve(data);
});
});
}

function f2() {
return new Promise((resolve, result) => {
fs.readFile('./2.txt', 'utf8', (err, data) => {
resolve(data);
});
});
}

function f3() {
return new Promise((resolve, result) => {
fs.readFile('./3.txt', 'utf8', (err, data) => {
resolve(data);
});
});
}
f1().then((result) => {
console.log(result);
return f2();
})
.then((result) => {
console.log(result);
return f3();
})
.then((result) => {
console.log(result);
})

异步函数

async

  • 异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。
  • 普通函数定义前加async关键字 普通函数变成异步函数
  • 异步函数默认返回promise对象
  • 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
  • 在异步函数内部使用throw关键字抛出程序异常
  • 调用异步函数再链式调用then方法获取异步函数执行结果
  • 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
1
2
3
4
5
6
7
8
9
10
11
async function fn() {
throw '错误!';
// throw执行时抛出异常,return不再执行
return 123;
}
fn().then(function(data) {
console.log(data);
}).catch(function(err) {
// catch捕捉异常,即throw
console.log(err);
})

await

  • await关键字只能出现在异步函数中

  • await promise await后面只能写promise对象 写其他类型的API是不不可以的

  • await关键字可是暂停异步函数向下执行 直到promise返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function f1() {
return 'f1';
}
async function f2() {
return 'f2';
}
async function f3() {
return 'f3';
}
async function run() {
// f1执行完毕才依次向下执行
let r1 = await f1();
let r2 = await f2();
let r3 = await f3();
console.log(r1);
console.log(r2);
console.log(r3);
}
run();

改造readFile()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fs = require('fs');
// 改造现有异步函数api,让其返回promise对象
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);

async function run() {
let r1 = await readFile('./1.txt', 'utf8');
let r2 = await readFile('./2.txt', 'utf8');
let r3 = await readFile('./3.txt', 'utf8');
console.log(r1);
console.log(r2);
console.log(r3);
}
run();

全局对象global

Node中全局对象有以下方法,可在任何地方使用global可忽略。

  • console.log() 控制台中输出
  • setTimeout() 设置超时定时器
  • clearTimeout() 清除超时定时器
  • setInterval() 设置间歇定时器
  • clearInterval() 清除间歇定时器