Node.js 基础篇

1.课程介绍与开发环境搭建

  • 主要包括

    • nodejs 基础知识
    • web 服务器
    • 异步 同步 阻塞 非阻塞
  • 课程基础

    • javascript 基础
    • html 基础
    • 命令行基础
  • Node.js 介绍

    • Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境
    • Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效
    • Node.js 的包管理器 npm,是全球最大的开源库生态系统
    • javascript 是脚本语言,需要解析器才能执行,浏览器就充当了解析器
    • 在Chrome中,解析器就是 V8 引擎,将 javascript 转换成 机器码
    • V8 引擎是开源的,由 C++ 语言编写,性能高
    • Node.js 高性能,事件驱动,非阻塞,生态圈很好
  • Node.js 安装
    • 官网 下载安装即可,很小不到20M!
    • 验证是否成功,命令行输入 node -v 显示版本号如 v8.11.4
    • 按提示升级 npm,Update available 5.6.0 → 6.4.1, npm i -g npm
    • macOS 安装完提示如下
1
This package has installed:
2
    • Node.js v8.11.4 to /usr/local/bin/node
3
    • npm v5.6.0 to /usr/local/bin/npm
4
Make sure that /usr/local/bin is in your $PATH.
  • Node.js 用途

    • javascript 运行环境
    • 操作文件(grunt gulp webpack)
    • 操作数据库
    • 写后端 api
    • 命令行工具
    • web 开发
    • 聊天室
  • JavaScript 语句后应该加分号么?

    • 知乎讨论
    • 代码风格而已,没有定论
    • 少分号更易读,不累
    • 必须加分号情况很少见:一行开头是括号(或者方括号[的时候加上分号就可以了,其他时候都不要
    • 如果下一行的行首是( [ / + -之一的话,js不会自动在上一行句尾加上分号

2.全局对象

  • 全局对象

    • 不用导入,直接使用的对象
    • 官方文档
    • Buffer 类,用于处理二进制数据
    • console,用于打印 stdout 和 stderr
    • global, 全局的命名空间对象
    • process,进程对象
    • setTimeout(callback, delay[, …args])
    • setInterval(callback, delay[, …args])
    • setImmediate(callback[, …args])
    • clearTimeout(timeoutObject)
    • clearInterval(intervalObject)
    • clearImmediate(immediateObject)
  • 以下变量虽然看起来像全局变量,但实际上不是

    • 全局变量在所有模块中均可使用
    • 以下对象作用域只在模块内,详见 module文档
    • __dirname
    • __filename
    • exports
    • module
    • require()
  • 运行 .js 脚本文件

    • node app 或者 node app.js
  • 实践代码

1
console.log('hello world');
2
3
setTimeout(function () {
4
    console.log("3 seconds have passed 2");
5
}, 3000);
6
7
// 箭头函数,es6的写法
8
setTimeout(() => {
9
    console.log("3 seconds have passed 1");
10
}, 3000);
11
12
// 每间隔2秒不断执行
13
setInterval(function () {
14
    console.log("2 seconds have passed");
15
}, 2000);
16
17
18
var time = 0
19
var timer = setInterval(function () {
20
    time += 2;
21
    console.log(time + " seconds have passed");
22
    if (time > 6) {
23
        clearInterval(timer);
24
        console.log("clearInterval")
25
    }
26
}, 2000)
27
28
// 输出当前目录 和 带绝对路径的文件名
29
console.log(__dirname)
30
console.log(__filename)
31
32
console.log('end')
33
console.dir(global)

3.回调函数

1
function sayHi() {
2
    console.log('Hi')
3
}
4
5
sayHi() // 调用函数
6
7
// 将匿名函数赋给变量
8
var sayBye = function (name) {
9
    console.log(name + ' Bye')
10
}
11
12
sayBye()
13
14
// 第一个参数是函数
15
function callFunction(fun, name) {
16
    fun(name)
17
}
18
19
callFunction(sayBye, 'able')
20
// 或者
21
callFunction(function (name) {
22
    console.log(name + ' Bye')
23
}, 'able')

4.模块

  • module 对象
    • 每个文件都被视为独立的模块
    • 每个模块中,module 指向表示当前模块的对象的引用
    • module 实际上不是全局的,而是每个模块本地的
    • module.exports 导出模块内的对象,方便其他对象引用
    • require() 引入模块
    • 当 Node.js 直接运行一个文件时,require.main 会被设为它的 module
    • 可以通过 require.main === module 来判断一个文件是否被直接运行
    • module 提供了一个 filename 属性(通常等同于 __filename)
    • 可以通过检查 require.main.filename 来获取当前应用程序的入口点
1
// counter.js
2
var counter = function (arr) {
3
    return "There are " + arr.length + " elements in array"
4
}
5
6
var adder = function (a, b) {
7
    return `the sum of the 2 numbers is ${a+b}`
8
}
9
10
var pi = 3.14
11
12
// 只有一个时可以这样导入
13
// module.exports = counter
14
15
/*
16
module.exports.counter = counter
17
module.exports.adder = adder
18
module.exports.pi = pi
19
*/
20
21
module.exports = {
22
    counter: counter,
23
    adder: adder,
24
    pi: pi,
25
}
26
/* 对象可以简写
27
module.exports = {
28
    counter,
29
    adder,
30
    pi,
31
}
32
*/
33
34
//p4.js
35
var stuff = require('./count')
36
37
console.log(stuff.counter(['ruby', 'nodejs', 'react']))
38
console.log(stuff.adder(3, 2))
39
console.log(stuff.pi)

5.事件 events

  • 多数 Node.js 核心 API 都采用异步事件驱动架构

  • 所有能触发事件的对象都是 EventEmitter 类的实例

  • 事件名称通常是驼峰式的字符串

  • 实践代码

1
var events = require('events')
2
var util = require('util')
3
4
// 事件 对象
5
var myEmitter = new events.EventEmitter()
6
7
// 绑定 事件名称 和 回调函数
8
myEmitter.on('someEvent', function (message) {
9
    console.log(message)
10
})
11
12
// 触发实践,使用事件名称
13
myEmitter.emit('someEvent', 'The event was emitted')
14
15
// 创建对象
16
var Person = function (name) {
17
    this.name = name
18
}
19
20
// 继承,让Person类具有事件对象的特性,绑定和触发事件
21
util.inherits(Person, events.EventEmitter)
22
// 新建对象
23
var xiaoming = new Person('xiaoming')
24
var lili = new Person('lili')
25
26
var person = [xiaoming, lili]
27
28
// 循环person数组,绑定事件
29
person.forEach(function (person) {
30
    person.on('speak', function (message) {
31
        console.log(person.name + ' said: ' + message)
32
    })
33
})
34
35
// 触发事件
36
xiaoming.emit('speak', 'hi')
37
lili.emit('speak', 'I want a curry')

6.读写文件(同步和异步)

1
var fs = require('fs')
2
3
// 同步读写文件,顺序执行,如果读取时间很长,会阻塞进程
4
var readMe = fs.readFileSync('readMe.txt', 'utf8')
5
fs.writeFileSync('writeMe.txt', readMe)
6
7
console.log(readMe)
8
console.log('finished sync')
9
10
// 异步读写文件
11
// 异步事件,Nodejs 维护一个事件队列,注册事件,完成后执行主线程
12
// 当主线程空闲时,取出执行事件,从线程池中发起线程执行事件, 当事件执行完成后通知主线程。这就是异步高效的原因。
13
14
var readMe = fs.readFile('readMe.txt', 'utf8', function (err, data) {
15
    fs.writeFile('writeMe.txt', data, function () {
16
        console.log('writeMe has finished')
17
    })
18
})
19
20
console.log('end')

7.创建和删除目录

1
var fs = require('fs')
2
3
// 异步删除文件
4
fs.unlink('writeMe.txt', function () {
5
    console.log('delete writeMe.txt file')
6
})
7
8
// 同步创建和删除目录
9
fs.mkdirSync('stuff')
10
fs.rmdirSync('stuff')
11
12
// 异步
13
fs.mkdir('stuff', function () {
14
    fs.readFile('readMe.txt', 'utf8', function (err, data) {
15
        fs.writeFile('./stuff/writeMe.txt', data, function () {
16
            console.log('copy successfully')
17
        })
18
    })
19
})

8.流和管道

  • 流(stream)

    • 处理流式数据的抽象接口
    • stream 模块提供了一些基础的 API,用于构建实现了流接口的对象
    • 流可以是可读的、可写的、或是可读写的,所有的流都是 EventEmitter 的实例
    • 流处理数据通过缓存可以提高性能
  • 管道

    • 使用管道,代码量更少
    • myReadStream.pipe(myWriteStream)
1
var fs = require('fs')
2
3
var myReadStream = fs.createReadStream(__dirname + '/readMe.txt')
4
myReadStream.setEncoding('utf8')
5
var myWriteStream = fs.createWriteStream(__dirname + '/writeMe.txt')
6
var data = ''
7
8
myReadStream.on('data', function (chunk) {
9
    console.log('new chunk received')
10
    // console.log(chunk)
11
    myWriteStream.write(chunk)
12
})
13
14
myReadStream.on('end', function () {
15
    console.log(data)
16
})
17
18
var writeData = 'hello world'
19
myWriteStream.write(writeData)
20
myWriteStream.end()
21
myWriteStream.on('finish', function () {
22
    console.log('finished')
23
})
24
25
// 使用管道,代码量更少
26
myReadStream.pipe(myWriteStream)

9.web 服务器 part1 介绍

1
var http = require('http')
2
3
var server = http.createServer(function (req, res) {
4
    console.log('request received')
5
    res.writeHead(200, { 'Content-Type': 'text/plain' })
6
    // res.write('Hello from out application')
7
    // res.end()
8
    // 或
9
    res.end('Hello from out application')
10
})
11
12
server.listen(3000, '127.0.0.1')
13
console.log('server started on http://127.0.0.1:3000')

10.web 服务器 part2 响应JSON

  • 响应JSON
1
var myObj = {
2
        name: 'able',
3
        job: 'programmer',
4
        age: 27
5
    }
6
    res.end(JSON.stringify(myObj))
  • JSON 对象
    • 字符串必须使用双引号表示,不能使用单引号
    • 对象的键名必须放在双引号里面
    • 数组或对象最后一个成员的后面,不能加逗号
    • JSON对象是 JavaScript 的原生对象,用来处理 JSON 格式数据
    • JSON.stringify方法用于将一个值转为 JSON 字符串
    • JSON.parse方法用于将 JSON 字符串转换成对应的值

11.web 服务器 part3 响应HTML页面

1
var http = require('http')
2
var fs = require('fs')
3
4
var onRequest = function (req, res) {
5
    console.log('request received')
6
    res.writeHead(200, { 'Content-Type': 'text/html' })
7
    // res.writeHead(200, { 'Content-Type': 'text/plain' })
8
    var myReadStream = fs.createReadStream(__dirname + '/index.html', 'utf8')
9
    myReadStream.pipe(res)
10
}
11
12
var server = http.createServer(onRequest)
13
server.listen(3000)
14
console.log('server started on http://127.0.0.1:3000')

12.web 服务器 part4 用模块化思想组织代码

  • 代码封装成模块,方便统一管理和调用
1
// server.js
2
var http = require('http')
3
var fs = require('fs')
4
5
function startServer() {
6
    var onRequest = function (req, res) {
7
        console.log('request received')
8
        res.writeHead(200, { 'Content-Type': 'text/html' })
9
10
        var myReadStream = fs.createReadStream(__dirname + '/index.html', 'utf8')
11
        myReadStream.pipe(res)
12
    }
13
    var server = http.createServer(onRequest)
14
    server.listen(3000)
15
    console.log('server started on http://127.0.0.1:3000')
16
}
17
18
module.exports.startServer = startServer
19
20
// 调用
21
var server = require('./server')
22
23
server.startServer()

13.web 服务器 part5 路由

  • console.dir(xx) 查看对象的所有属性和方法
  • req.url 请求中包含url等属性
1
function startServer() {
2
    var onRequest = function (req, res) {
3
        console.log('request received ' + req.url)
4
5
        if (req.url === '/' || req.url === '/home') {
6
            res.writeHead(200, { 'Content-Type': 'text/html' })
7
            fs.createReadStream(__dirname + '/index.html', 'utf8').pipe(res)
8
        } else if (req.url === '/review') {
9
            res.writeHead(200, { 'Content-Type': 'text/html' })
10
            fs.createReadStream(__dirname + '/review.html', 'utf8').pipe(res)
11
        } else if (req.url === '/api/v1/records') {
12
            res.writeHead(200, { 'Content-Type': 'application/json' })
13
            var jsonObj = {
14
                name: 'able'
15
            }
16
            res.end(JSON.stringify(jsonObj))
17
        } else {
18
            res.writeHead(200, { 'Content-Type': 'text/html' })
19
            fs.createReadStream(__dirname + '/404.html', 'utf8').pipe(res)
20
        }
21
    }
22
    var server = http.createServer(onRequest)
23
    server.listen(3000)
24
    console.log('server started on http://127.0.0.1:3000')
25
}

14.web 服务器 part6 重构路由代码

  • 将路由、处理函数和主程序分离,单独存放
  • 分工明确,各司其职,方便管理

15.web 服务器 part7 使用 GET或 POST 请求 发送数据

  • querystring - 查询字符串
    • var querystring = require('querystring')
    • querystring.parse(data) 把一个 URL 查询字符串 str 解析成一个键值对的集合
1
// 接收请求数据,然后处理,查看request 类型
2
var data = ""
3
req.on("error", function (err) {
4
    console.error(err)
5
}).on("data", function (chunk) {
6
    data += chunk
7
}).on("end", function () {
8
    if (req.mothod === "POST") {
9
        if (data.length > 1e6) {
10
            req.connection.destroy() // 如果数据很大,就断开
11
        }
12
        route(handle, pathname, res, querystring.parse(data))
13
    } else {
14
        var params = url.parse(req.url, true).query
15
        route(handle, pathname, res, params)
16
    }
17
})
18
// 或者
19
// var data = []
20
// data.push(chunk)
21
// data = Buffer.concat(data).toString()

16.包管理器 npm

1
# Or alias it in .bashrc or .zshrc
2
echo '\n#alias for npm\nalias npm="npm --registry=https://registry.npm.taobao.org \
3
  --cache=$HOME/.npm/.cache/npm \
4
  --disturl=https://npm.taobao.org/dist \
5
  --userconfig=$HOME/.npmrc"' >> ~/.zshrc && source ~/.zshrc
  • yarn 也是包管理器,更快下载速度

17.package.json 文件

  • 记录项目中使用的包名,发布时不用包内容了,只要名称就行
  • npm init 提问式初始化项目信息,生成package.json文件,-y 全部默认
  • npm install --save xxx安装的同时,将信息写入package.json
  • npm install --save-dev xxx安装的同时,将信息写入package.json中的dev开发依赖
  • npm view moduleNames 查看node模块的package.json文件夹
  • npm run start 启动包,执行 package.json scripts 中的 start 命令,还有 stop restart test
  • npm install 安装 package.json 中记录的包

18.nodemon监控文件并重启服务

  • nodemon 用来监视应用中的任何文件更改并自动重启服务
  • 非常适合用在开发环境中,方便啊,不用手动操作了
  • 全局安装 npm install -g nodemon
  • 本地安装 npm install --save-dev nodemon
  • 启动应用 nodemon [your node app]
  • 获取修改 package.json 中的启动脚本,添加nodemon app.js, 用 npm start 直接启动,方便