一、Node.js实现读、写文件
1. 读文件
浏览器中的 JavaScript 是没有文件操作的能力的,但是 Node 中的 JavaScript 具有文件操作的能力。fs 是 file-system 的简写,就是文件系统的意思。在 Node 中如果想要进行文件操作,就必须引入 fs 这个核心模块。 在 fs 这个核心模块中,就提供了所有的文件操作相关的 API。 例如:fs.readFile 就是用来读取文件的
// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')
/* 2. 读取文件
第一个参数就是要读取的文件路径
第二个参数是一个回调函数
成功
data 数据
error null
失败
data undefined没有数据
error 错误对象
*/
fs.readFile('./data/a.txt', function (error, data) {
/* <Buffer 68 65 6c 6c 6f 20 6e 6f 64 65 6a 73 0d 0a>
文件中存储的其实都是二进制数据 0 1
这里为什么看到的不是 0 和 1 呢?原因是二进制转为 16 进制了
但是无论是二进制01还是16进制,人类都不认识
所以我们可以通过 toString 方法把其转为我们能认识的字符
*/
// 在这里就可以通过判断 error 来确认是否有错误发生
if (error) {
console.log('读取文件失败了')
} else {
// console.log(data)
console.log(data.toString())
}
})
【提示】
./ 当前目录,不可省略
../ 上一级目录,不可省略
/ 在这里表示的是当前文件模块所属磁盘根路径
.js 后缀名可以省略
2. 写文件
var fs = require('fs')
/* 第一个参数:文件路径
第二个参数:文件内容
第三个参数:回调函数
error
成功:
文件写入成功
error 是 null
失败:
文件写入失败
error 就是错误对象
*/
fs.writeFile('./data/你好.md', '大家好,给大家介绍一下,我是Node.js', function (error) {
if (error) {
console.log('写入失败')
} else {
console.log('写入成功了')
}
})
二、HTTP服务
你可以使用 Node 非常轻松的构建一个 Web 服务器。在 Node 中专门提供了一个核心模块:http。 http 这个模块的职责就是帮你创建编写服务器的
案例一
// 1. 加载 http 核心模块
var http = require('http')
// 2. 使用 http.createServer() 方法创建一个 Web 服务器,返回一个 Server 实例
var server = http.createServer()
// 3. 服务器要干嘛?
// 提供服务:对 数据的服务
// 发请求
// 接收请求
// 处理请求
// 给个反馈(发送响应)
// 注册 request 请求事件
// 当客户端请求过来,就会自动触发服务器的 request 请求事件,然后执行第二个参数:回调处理函数
server.on('request', function () {
console.log('收到客户端的请求了')
})
// 4. 绑定端口号,启动服务器
server.listen(3000, function () {
console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问')
})
案例二
var http = require('http')
var server = http.createServer()
/* request 请求事件处理函数,需要接收两个参数:
Request 请求对象
请求对象可以用来获取客户端的一些请求信息,例如请求路径
Response 响应对象
响应对象可以用来给客户端发送响应消息
*/
server.on('request', function (request, response) {
// 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/ 来进行访问')
})
案例三
var http = require('http')
// 1. 创建 Server
var server = http.createServer()
// 2. 监听 request 请求事件,设置请求处理函数
server.on('request', function (req, res) {
console.log('收到请求了,请求路径是:' + req.url)
console.log('请求我的客户端的地址是:', req.socket.remoteAddress, req.socket.remotePort)
// 根据不同的请求路径发送不同的响应结果
var url = req.url
if (url === '/') {
res.end('index page')
} else if (url === '/login') {
res.end('login page')
} else if (url === '/products') {
var products = [{
name: '苹果 X',
price: 8888
},
{
name: '菠萝 X',
price: 5000
},
{
name: '小辣椒 X',
price: 1999
}
]
// 响应内容只能是二进制数据或者字符串
res.writeHead(200,{'Content-Type': 'text/html; charset=utf-8'});
res.end(JSON.stringify(products))
} else {
res.end('404 Not Found.')
}
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
console.log('服务器启动成功,可以访问了。。。')
})
【提示】JSON.stringify(products) 把json结构的products转换成字符串,JSON.parse(products)反之。
案例四(重点掌握)
在服务端默认发送的数据,其实是 utf8 编码的内容,但是浏览器不知道你是 utf8 编码的内容,浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码去解析,中文操作系统默认是 gbk,解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的,在 http 协议中,Content-Type 就是用来告知对方我给你发送的数据内容是什么类型。
var http = require('http')
var server = http.createServer()
server.on('request', function (req, res) {
var url = req.url
if (url === '/plain') {
// text/plain 就是普通文本
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('hello 世界')
} else if (url === '/html') {
// 如果你发送的是 html 格式的字符串,则也要告诉浏览器我给你发送是 text/html 格式的内容
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('<p>hello html <a href="">点我</a></p>')
}
})
server.listen(3000, function () {
console.log('Server is running...')
})
案例五、响应图片资源
// 不同的资源对应的 Content-Type 是不一样的, 图片不需要指定编码
// 一般只为字符数据才指定编码
var http = require('http')
var fs = require('fs')
var server = http.createServer()
server.on('request', function (req, res) {
// / index.html
var url = req.url
if (url === '/') {
// 我们要发送的还是在文件中的内容
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...')
})
扩展:解决代码修改就要重启服务器的问题
三、js模块系统
模块之间如何通信?
【注意】在 Node 没有全局作用域,它是文件模块作用域 。模块是独立,不能因为 a 加载过 fs 了 b 就不需要,这是错误的理解 。正确的做法应该是:a 需要 fs 则 a 就加载 fs ,b 需要 fs 则 b 就加载 fs
案例一、同一个目录下:a.js 、b.js 、c.js
(1)a.js
// require 是一个方法,它的作用就是用来加载模块的
//有时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要是为了使用里面的某个成员
var foo = 'aaa'
console.log('a start')
function add(x, y) {
return x + y
}
// 推荐:可以省略后缀名
require('./b')
console.log('a end')
console.log('foo 的值是:', foo)
(2)b.js
console.log('b start')
// console.log(add(10, 20))
var foo = 'bbb'
require('./c.js')
console.log('b end')
(3)c.js
console.log('ccc')
案例二:同一个目录下:a.js 、b.js
require方法除了加载文件模块并执行里面的代码,还可以拿到被加载文件模块导出的接口对象。
在每个文件模块中都提供了一个对象:exports。exports 默认是一个空对象。为了实现模块间的通信,你要做的就是把所有需要被外部访问的成员挂载到这个 exports 对象中
(1)a.js
var bExports = require('./b')
var fs = require('fs')
console.log(bExports.foo)
console.log(bExports.add(10, 30))
console.log(bExports.age)
bExports.readFile('./a.js')
fs.readFile('./a.js', function (err, data) {
if (err) {
console.log('读取文件失败')
} else {
console.log(data.toString())
}
})
(2)b.js
var foo = 'bbb'
//默认exports是空对象
// console.log(exports)
exports.foo = 'hello'
function add(x, y) {
return x - y
}
exports.add = function (x, y) {
return x + y
}
var age = 18
exports.age = age
exports.readFile = function (path, callback) {
console.log('文件路径:', path)
}
【提示】挂载在exports上的对象(方法或是属性),与本文件中定义的 同名对象是两个不同的对象。
扩展:模块原理
在 Node 中,每个模块内部都有一个自己的 module 对象,该 module 对象中,有一个成员叫:exports 也是一个对象 。 也就是说如果你需要对外导出成员,只需要把导出的成员挂载到 module.exports 中。
我们发现,每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式很麻烦,点儿的太多了 。 所以,Node 为了简化你的操作,专门提供了一个变量:exports 等于 module.exports
真正去使用的时候:
(1)导出多个成员:exports.xxx = xxx (使用多个exports)
(2)导出多个成员也可以:module.exports = {}
(3)导出单个成员:module.exports.成员=
如果你实在分不清楚 exports 和 module.exports,你可以选择忘记 exports。而只使用 module.exports 也没问题。
模块分类:
1. 核心模块
核心模块的本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了.
require('fs')
require('http')
2. 第三方模块
凡是第三方模块都必须通过 npm 来下载,使用的时候就可以通过 require('包名') 的方式来进行加载才可以使用,不可能有任何一个第三方包和核心模块的名字是一样的.
3. 自定义的模块
模块加载的机制:
优先从缓存加载
核心模块
路径形式的文件模块
第三方模块(以art-template为第三模块为例)
先找到当前文件所处目录中的 node_modules 目录
node_modules/art-template
node_modules/art-template/package.json 文件
node_modules/art-template/package.json 文件中的 main 属性
main 属性中就记录了 art-template 的入口模块
然后加载使用这个第三方包
实际上最终加载的还是文件
如果 package.json 文件不存在或者 main 指定的入口模块是也没有
则 node 会自动找该目录下的 index.js
也就是说 index.js 会作为一个默认备选项
如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找
如果上一级还没有,则继续往上上一级查找
。。。
如果直到当前磁盘根目录还找不到,最后报错:
can not find module xxx
【注意】我们一个项目有且只有一个 node_modules,放在项目根目录中,这样的话项目中所有的子目录中的代码都可以加载到第三方包 ,不会出现有多个 node_modules。