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
npm install -g xxx全局安装可执行文件,当作命令行工具使用国内源,解决慢的问题
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.jsonnpm install --save-dev xxx安装的同时,将信息写入package.json中的dev开发依赖npm view moduleNames查看node模块的package.json文件夹npm run start启动包,执行 package.json scripts 中的 start 命令,还有 stop restart testnpm 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 直接启动,方便