1. 认识Node.js
1.1 Node.js是什么
- Node.js是JavaScript 运行时
- 通俗易懂的讲,Node.js是JavaScript的运行平台
- Node.js既不是语言,也不是框架,它是一个平台
- 浏览器中的JavaScript
- EcmaScript
- BOM
- DOM
- Node.js中javaScript
- 没有BOM,DOM
- EcmaScript
- 在Node中这个JavaScript执行环境为JavaScript提供了一些服务器级别的API
- 文件的读写
- 网络服务的构建
- 网络通信
- http服务器
- 构建与Chrome的V8引擎之上
- 代码只是具有特定格式的字符串
- 引擎可以认识它,帮你解析和执行
- Google Chrome的V8引擎是目前公认的解析执行JavaScript代码最快的
- Node.js的作者把Google Chrome中的V8引擎移植出来,开发了一个独立的JavaScript运行时环境
- Node.js uses an envent-driven,non-blocking I/O mode that makes it lightweight and efficent.
- envent-driven 事件驱动
- non-blocking I/O mode 非阻塞I/O模型(异步)
- ightweight and efficent. 轻量和高效
- Node.js package ecosystem,npm,is the larget scosystem of open sourcr libraries in the world
- npm 是世界上最大的开源生态系统
- 绝大多数JavaScript相关的包都存放在npm上,这样做的目的是为了让开发人员更方便的去下载使用
- 例如:npm install jquery
1.2 Node.js能做什么
- web服务器后台
- 命令行工具
- npm(node)
- git(C语言)
- hexo(node)
- …
- 对于前端工程师来讲,接触最多的是它的命令行工具
- webpack
- gulp
- npm
1.3 安装Node环境
- 查看Node环境的版本号
- 下载:https://nodejs.org/en/
- 安装:
- 傻瓜式安装,一路
next
- 安装过再次安装会升级
- 傻瓜式安装,一路
- 确认Node环境是否安装成功
- 查看node的版本号:
node --version
- 或者
node -v
- 查看node的版本号:
- 配置环境变量
1.4 解析执行JavaScript
- 创建编写
javaScript
脚本文件 - 打开终端,定位脚本文件所在目录
- 输入
node 文件名
执行对应文件
注意:文件名不要用node.js
来命名,也就是说除了node
这个名字随便起,最好不要用中文。
2. 文件的读写(fs文件操作模块)
2.1 读取文件readFile
//浏览器中的JavaScript是没有文件操作能力的
//但是Node中的JavaScript具有文件操作能力
//fs是file-system的简写,就是文件系统的意思
//在Node中如果想要进行文件的操作就必须引用fs这个核心模块
//在fs这个和兴模块中,就提供了人所有文件操作相关的API
//例如 fs.readFile就是用来读取文件的
// 1.使用fs核心模块
const fs = require('fs')
// 2.读取文件
fs.readFile('./data/a.txt', (err,data) => {
if (err) {
console.log('文件读取失败')
}
else {
console.log(data.toString())
}
})
2.2 写入文件writeFile
// 1.使用fs核心模块
const fs = require('fs')
// 2.将数据写入文件
fs.writeFile('./data/a.txt', '我是文件写入的信息', (err,data) => {
if (err) {
console.log('文件写入失败')
}
else {
console.log(data.toString())
}
})
2.3 读取目录 readdir
// readdir(‘读取文件的文件目录路径’,function(err,files){})读取目录
const fs = require('fs')
fs.readdir('D:/Movie/www', function (err, files) {
if (err) {
return console.log('目录不存在')
}
console.log(files)
})
//得到一个数组例如 ['a.txt','apple','img']
3. HTTP服务(http网络服务构建模块)
3.1 最简单的http服务
- 你可以使用 Node 非常轻松的构建一个 Web 服务器
- 在 Node 中专门提供了一个核心模块:http
- http 这个模块的职责就是帮你创建编写服务器的
// 1. 加载 http 核心模块
const http = require('http')
// 2. 使用 http.createServer() 方法创建一个 Web 服务器
// 返回一个 Server 实例
const server = http.createServer()
// 3. 服务器要干嘛?
// 提供服务:对 数据的服务
// 发请求
// 接收请求
// 处理请求
// 给个反馈(发送响应)
// 注册 request 请求事件
// 当客户端请求过来,就会自动触发服务器的 request 请求事件,然后执行第二个参数:回调处理函数
server.on('request', (request,response) => {
console.log('收到客户端的请求了')
})
// 4. 绑定端口号,启动服务器
server.listen(3000, () => {
console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问')
})
// ##################简写
const http = require('http')
http
.createServer((req, res) => { // 创建Web服务
})
.listen('3000', () => { // 绑定端口,启动服务
console.log('Server is Running…');
})
3.2 发送响应
const http = require('http')
const server = http.createServer()
// request 请求事件处理函数,需要接收两个参数:
// Request 请求对象
// 请求对象可以用来获取客户端的一些请求信息,例如请求路径
// Response 响应对象
// 响应对象可以用来给客户端发送响应消息
server.on('request', function (request, response) {
// http://127.0.0.1:3000/ /
// http://127.0.0.1:3000/a /a
// http://127.0.0.1:3000/foo/b /foo/b
console.log('收到客户端的请求了,请求路径是:' + request.url)
// response 对象有一个方法:write 可以用来给客户端发送响应数据
// write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待
response.write('hello')
response.write(' nodejs')
// 告诉客户端,我的话说完了,你可以呈递给用户了
response.end()
})
server.listen(3000, function () {
console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问')
})
3.3 根据不同请求路径,返回不同数据
const http = require('http')
// 1. 创建 Server
const server = http.createServer()
// 2. 监听 request 请求事件,设置请求处理函数
server.on('request', function (req, res) {
console.log('收到请求了,请求路径是:' + req.url)
console.log('请求我的客户端的地址是:', req.socket.remoteAddress, req.socket.remotePort)
// res.write('hello')
// res.write(' world')
// res.end()
// 上面的方式比较麻烦,推荐使用更简单的方式,直接 end 的同时发送响应数据
// res.end('hello nodejs')
// 根据不同的请求路径发送不同的响应结果
// 1. 获取请求路径
// req.url 获取到的是端口号之后的那一部分路径
// 也就是说所有的 url 都是以 / 开头的
// 2. 判断路径处理响应
let url = req.url
if (url === '/') {
res.end('index page')
} else if (url === '/login') {
res.end('login page')
} else if (url === '/products') {
let products = [{
name: '苹果 X',
price: 8888
},
{
name: '菠萝 X',
price: 5000
},
{
name: '小辣椒 X',
price: 1999
}
]
// 响应内容只能是二进制数据或者字符串 数字 对象 数组 布尔值
res.end(JSON.stringify(products))
} else {
res.end('404 Not Found.')
}
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
console.log('服务器启动成功,可以访问了。。。')
})
3.4 IP地址和端口号
- ip地址用来定位计算机
- 端口号用来定位具体的应用程序
- 所有需要联网通信的应用程序都会占用一个端口号
const http = require('http')
const server = http.createServer()
// 2. 监听 request 请求事件,设置请求处理函数
server.on('request', function (req, res) {
console.log('收到请求了,请求路径是:' + req.url)
console.log('请求我的客户端的地址是:', req.socket.remoteAddress, req.socket.remotePort)
res.end('hello nodejs')
})
server.listen(5000, function () {
console.log('服务器启动成功,可以访问了。。。')
})
3.5 Content-Type (响应内容类型)
解决node中乱码问题 HTTP Content-type对照表
- 在服务端默认发送的数据,其实是 utf8 编码的内容,但是浏览器不知道你是 utf8 编码的内容
- 浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码去解析, 中文操作系统默认是 gbk
- 解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的
- 在 http 协议中,Content-Type 就是用来告知对方我给你发送的数据内容是什么类型
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('hello 世界')
- Content-Type
- 服务器最好把每次响应的数据是什么内容类型都告诉客户端,而且要正确的告诉
- 不同的资源对应的 Content-Type 是不一样,具体参照:http://tool.oschina.net/commons
- 对于文本类型的数据,最好都加上编码,目的是为了防止中文解析乱码问题
- 通过网络发送文件
- 发送的并不是文件,本质上来讲发送是文件的内容
- 当浏览器收到服务器响应内容之后,就会根据你的 Content-Type 进行对应的解析处理
// 1. 结合 fs 发送文件中的数据
// 2. Content-Type
// http://tool.oschina.net/commons
// 不同的资源对应的 Content-Type 是不一样的
// 图片不需要指定编码
// 一般只为字符数据才指定编码
const http = require('http')
const fs = require('fs')
const server = http.createServer()
server.on('request', function (req, res) {
// / index.html
let url = req.url
if (url === '/') {
// 肯定不这么干
// res.end('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><h1>首页</h1></body>/html>')
// 我们要发送的还是在文件中的内容
fs.readFile('./resource/index.html', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data)
}
})
} else if (url === '/xiaoming') {
// url:统一资源定位符
// 一个 url 最终其实是要对应到一个资源的
fs.readFile('./resource/ab2.jpg', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
// 图片就不需要指定编码了,因为我们常说的编码一般指的是:字符编码
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
})
server.listen(3000, function () {
console.log('Server is running...')
})
4. Node中的模块系统
- 核心模块
- fs 文件操作模块
- http 网络服务构建模块
- os 操作系统信息模块
- path 路径处理模块
- url 路径操作模块
// 用来获取机器信息的
const os = require('os')
// 用来操作路径的
const path = require('path')
// 获取当前机器的 CPU 信息
console.log(os.cpus())
// memory 内存
console.log(os.totalmem())
// 获取一个路径中的扩展名部分
// extname extension name
console.log(path.extname('c:/a/b/c/d/hello.txt'))
- 第三方模块
- 第三方模块(模板引擎)
- 必须通过
npm
来下载才可以使用
- 自己编写的模块
- 自己创建的文件
4.1 什么是模块化
- 文件作用域(模块是独立的,在不同的文件使用必须要重新引用)【在node中没有全局作用域,它是文件模块作用域】
- 通信规则
- 加载require
- 导出exports
4.2 CommonJS模块规范
在Node中的JavaScript还有个重要的概念,模块系统
模块作用域
使用require方法来加载模块
-
使用exports接口对象来导出模块中的成员
加载
require
语法:
let 自定义变量名 = require('模块')
作用:
- 执行被加载模块中的代码
- 得到被加载模块中的
exports
导出接口对象
导出
exports
- Node中是模块作用域,默认文件中所有的成员只在当前模块有效
- 对于希望可以被其它模块访问到的成员,我们需要把这些公开的成员挂载到
exports
接口对象上就可以了
导出多个成员(必须在exports接口对象中)
exports.a = 123 exports.b = function () { console.log('bbb') } exports.c = { foo: "bar" } exports.d = 'hello'
导出单个成员
module.exports = 'hello'
以下情况会覆盖
module.exports = 'hello' // 后者会覆盖前者 module.exports = function add (x,y) { return x + y }
也可以通过以下方法来导出多个成员:
module.expots = { foo: 'hello', add: function (x,y) { return x + y } }
4.3 模块原理
exports
和module.exports
指向同一个引用
console.log(exports === module.exports) // true
exports.foo = 'bar'
// 等价于
module.exports.foo = 'bar'
当
exports = 'bar'
重新赋值之后,exports != module.exports
因最终return的是module.exports,无论exports中的成员是什么都没用真正去使用的时候:
导出单个成员:exports.xxx = xxx
导出多个成员:module.exports = {}
jQuery中的each 和 原生JavaScript方法forEach的区别:
提供源头:
原生js是es5提供的(不兼容IE8)
jQuery的each是jQuery第三方库提供的(如果要使用需要用2以下的版本也就是1.版本),它的each方法主要用来遍历jQuery实例对象(伪数组),同时也可以做低版本forEach的替代品,jQuery的实例对象不能使用forEach方法,如果想要使用必须转为数组([].slice.call(jQuery实例对象))才能使用模块中导出多个成员和导出单个成员
301和302的区别:
301永久重定向,浏览器会记住
302临时重定向exports和module.exports的区别:
每个模块中都有一个module对象
module对象中有一个exports对象
我们可以把需要导出的成员都挂载到module.exports接口对象中
也就是module.exports.xxx = xxx
的方式
但是每次写太多了就很麻烦,所以Node为了简化代码,就在每一个模块中都提供了一个成员叫exports
exports === module.exports
结果为true,所以完全可以exports.xxx = xxx
当一个模块需要导出单个成员的时候必须使用module.exports = xxx
的方式,=,使用exports = xxx
不管用,因为每个模块最终return的是module.exports,而exports只是module.exports的一个引用,所以exports
即使重新赋值,也不会影响module.exports
。
有一种赋值方式比较特殊:exports = module.exports
这个用来新建立引用关系的。-
当你采用了无分号的代码风格的时候,只需要注意以下情况:
// 当一行代码是以: // ( // [ // ` // 开头的时候,则在前面补上一个分号用以避免一些语法解析错误。 // 所以你会发现在一些第三方的代码中能看到一上来就以一个 ; 开头。 // 结论: // 无论你的代码是否有分号,都建议如果一行代码是以 (、[、` 开头的,则最好都在其前面补上一个分号。 // 有些人也喜欢玩儿一些花哨的东西,例如可以使用 ! ~ 等。
4.4 require的加载规则
- 核心模块
- 模块名
- 第三方模块
- 模块名
- 用户自己写的
- 路径
加载规则
- 优先从缓存加载
- 判断模块标识符
- 核心模块
- 自己写的模块(路径形式的模块)
- 第三方模块(node-modules)
- 第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
- npm
- 开发人员可以把写好的框架库发布到npm上
- 使用者通过npm命令来下载
- 使用方式:
npm install 下载的包名 const 名称 = require('下载的包名')
- node_modules/express/package.json main
- 如果package.json或者main不成立,则查找被选择项:index.js
- 如果以上条件都不满足,则继续进入上一级目录中的node_modules按照上面的规则依次查找,直到当前文件所属此盘根目录都找不到最后报错
// 如果非路径形式的标识
// 路径形式的标识:
// ./ 当前目录 不可省略
// ../ 上一级目录 不可省略
// /xxx也就是D:/xxx
// 带有绝对路径几乎不用(D:/a/foo.js)
// 首位表示的是当前文件模块所属磁盘根目录
// require('./a');
// 核心模块
// 核心模块本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
require('fs');
// 第三方模块
// 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过require('包名')来加载才可以使用
// 第三方包的名字不可能和核心模块的名字是一样的
// 既不是核心模块,也不是路径形式的模块
// 先找到当前文所述目录的node_modules
// 然后找node_modules/art-template目录
// node_modules/art-template/package.json
// node_modules/art-template/package.json中的main属性
// main属性记录了art-template的入口模块
// 然后加载使用这个第三方包
// 实际上最终加载的还是文件
// 如果package.json不存在或者mian指定的入口模块不存在
// 则node会自动找该目录下的index.js
// 也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
//
// 如果条件都不满足则会进入上一级目录进行查找
// 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
const template = require('art-template');
4.5 在node中使用模板引擎art-template
在 Node 中使用art-template
模板引擎
模板引起最早就是诞生于服务器领域,后来才发展到了前端。
art-template 不仅可以在浏览器使用,也可以在 node 中使用
-
安装:
- npm install art-template
- 该命令在哪执行就会把包下载到哪里。默认会下载到 node_modules 目录中
- node_modules 不要改,也不支持改。
-
使用:
- 在需要使用的文件模块中加载 art-template
- 只需要使用 require 方法加载就可以了:require('art-template')
- 参数中的 art-template 就是你下载的包的名字
- 也就是说你 install 的名字是什么,则你 require 中的就是什么
查文档,使用模板引擎的 API
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<p>大家好,我叫:{{ name }}</p>
<p>我今年 {{ age }} 岁了</p>
<h1>我来自 {{ province }}</h1>
<p>我喜欢:{{each hobbies}} {{ $value }} {{/each}}</p>
<script>
let foo = '{{ title }}'
</script>
</body>
</html>
使用
const template = require('art-template')
const fs = require('fs')
// 这里不是浏览器
// template('script 标签 id', {对象})
// let tplStr = `
// <!DOCTYPE html>
// <html lang="en">
// <head>
// <meta charset="UTF-8">
// <title>Document</title>
// </head>
// <body>
// <p>大家好,我叫:{{ name }}</p>
// <p>我今年 {{ age }} 岁了</p>
// <h1>我来自 {{ province }}</h1>
// <p>我喜欢:{{each hobbies}} {{ $value }} {{/each}}</p>
// </body>
// </html>
// `
fs.readFile('./tpl.html', function (err, data) {
if (err) {
return console.log('读取文件失败了')
}
// 默认读取到的 data 是二进制数据
// 而模板引擎的 render 方法需要接收的是字符串
// 所以我们在这里需要把 data 二进制数据转为 字符串 才可以给模板引擎使用
let ret = template.render(data.toString(), {
name: 'Jack',
age: 18,
province: '北京市',
hobbies: [
'写代码',
'唱歌',
'打游戏'
],
title: '个人信息'
})
console.log(ret)
})
http案例加入art-template
const http = require('http')
const fs = require('fs')
const template = require('art-template')
const server = http.createServer()
let wwwDir = 'D:/Movie/www'
server.on('request', function (req, res) {
let url = req.url
fs.readFile('./template-apache.html', function (err, data) {
if (err) {
return res.end('404 Not Found.')
}
// 1. 如何得到 wwwDir 目录列表中的文件名和目录名
// fs.readdir
// 2. 如何将得到的文件名和目录名替换到 template.html 中
// 2.1 在 template.html 中需要替换的位置预留一个特殊的标记(就像以前使用模板引擎的标记一样)
// 2.2 根据 files 生成需要的 HTML 内容
// 只要你做了这两件事儿,那这个问题就解决了
fs.readdir(wwwDir, function (err, files) {
if (err) {
return res.end('Can not find www dir.')
}
// 这里只需要使用模板引擎解析替换 data 中的模板字符串就可以了
// 数据就是 files
// 然后去你的 template.html 文件中编写你的模板语法就可以了
let htmlStr = template.render(data.toString(), {
title: '哈哈',
files: files
})
// 3. 发送解析替换过后的响应数据
res.end(htmlStr)
})
})
})
server.listen(3000, function () {
console.log('running...')
})
4.6 客户端渲染和移动端渲染
- 服务端渲染
- 在服务端使用模板引擎
- 模板引擎最早诞生于服务端,后来才发展到了前端
- 服务端渲染和客户端渲染的区别
- 客户端渲染不利于 SEO 搜索引擎优化
- 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
- 所以你会发现真正的网站既不是纯异步也不是纯服务端渲染出来的
- 而是两者结合来做的
- 例如京东的商品列表就采用的是服务端渲染,目的了为了 SEO 搜索引擎优化
- 而它的商品评论列表为了用户体验,而且也不需要 SEO 优化,所以采用是客户端渲染