极客学院Nodejs学习,还有菜鸟课程
Nodejs资料:https://github.com/nswbmw/N-blog
1.Nodejs解决的问题
1、Node.js 在前端开发中,主要是作为工具,比如打包工具 Webpack / Gulp 等,都是使用 Node.js 编写的
2、Node.js 并不直接解决前端问题,只是作为前端开发的工具
3、作为前端开发中的工具角色,在没有它之前, js / css 的打包,压缩,一般使用各种命令行工具,但无法做到与 js 的深度整合,例如,完全不能实现像 webpack / browserify 一样根据 js 中的 require / import 来打包文件
另外,Node.js 主要是作为一个后端语言。它可以像 PHP / Python 一样编写后端逻辑。但 JavaScript 生来就是以事件驱动的,所以它非常适合处理各种 IO(网络请求、数据库、存储操作等)。
2.npm全局安装和局部安装
可以参考文章:
https://www.cnblogs.com/linziwei/p/7786895.html
https://www.cnblogs.com/PeunZhang/p/5629329.html
https://yq.aliyun.com/articles/36217
附注:局部安装时候,命令行窗口运行位置是在项目文件夹下,node_modules文件夹所在位置。
3.Nodejs回调函数
Nodejs异步编程的直接体现就是回调,异步编程依托于回调来实现,但不能说使用了回调后程序就是异步化了。
回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。
例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
-- 阻塞代码示例
var fs = require("fs");
var data = fs.readFileSync('input.txt');
console.log(data.toString());
console.log("程序执行结束!");
-- 非阻塞代码示例
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执行结束!");
以上两个实例我们了解了阻塞与非阻塞调用的不同。第一个实例在文件读取完后才执行完程序。 第二个实例我们不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。
因此,阻塞是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。
同步是指一个时刻仅有一件事件在执行。
异步指一个时刻可以有多个事件同时执行。
阻塞指事件执行必须连续,一个事件从开始到结束不能有其他的事件插入执行。
非阻塞指事件可以分成小段执行,不要求从开始到结束连续执行。
4.Nodejs事件驱动编程
可以参考文章:
https://segmentfault.com/a/1190000005173218(程序调用顺序可以参考这个)
https://blog.csdn.net/sinat_25127047/article/details/54016931
https://www.cnblogs.com/lua5/archive/2011/02/01/1948760.html
https://blog.csdn.net/charlene0824/article/details/51711154
https://segmentfault.com/a/1190000004322358
同步概念:阻塞式调用,等待执行。
回调概念:回调是一个异步等效的功能。(不直接返回执行结果,而是在函数的参数中传递回调函数,在回调函数中处理返回结果。)
var test = function(name,callback){
callback(name);
}
var callback = function(name){
echo 'I am come from callback';
return name;
}
test('jike',callback);
异步概念:非阻塞,通知获取结果。(非阻塞调用,将异步的代码添加到系统内核中,在系统内核中执行异步代码,主干中通过回调函数获取异步执行结果,异步代码执行过程中不影响主干中代码的运行过程。)
/**
*
* 文件名:answer.js,Node.js测试代码,应用代码说明异步调用思想
*/
function Person(){
this.think = function(callback){ // 定义Person对象的think方法 异步函数
setTimeout(function(){
console.log('thinking~~~!');
},5000);
}
this.answer = function(){ // 定义Person对象的answer方法 同步函数
console.log('I am answering other questions');
}
}
var person = new Person(); // new创建Person对象
person.think(function(){ // 根据person对象调用think方法
console.log('tinking 5 seconds, get the right answer!')
});
person.answer();
# 打印结果
I am answering other questions
thinking~~~!
thinking 5 seconds,get the right answer!
5.Nodejs的EventEmitter
Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:
// 引入events 模块
var events = require('events');
// 创建eventEmitter 对象
var eventEmitter = new events.EventEmitter();
// 绑定事件及事件的处理程序
eventEmitter.on('eventName',eventHandler);
// 通过程序触发事件
eventEmitter.emit('eventName');
实例:
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
// 创建事件处理程序
var connectHandler = function connected() {
console.log('连接成功。');
// 触发 data_received 事件
eventEmitter.emit('data_received');
}
// 绑定 connection 事件处理程序
eventEmitter.on('connection', connectHandler);
// 使用匿名函数绑定 data_received 事件
eventEmitter.on('data_received', function(){
console.log('数据接收成功。');
});
// 触发 connection 事件
eventEmitter.emit('connection');
console.log("程序执行完毕。");
# 输出结果
$ node main.js
连接成功。
数据接收成功。
程序执行完毕。
EventEmitter的每个事件由一个事件名和若干个参数组成,事件是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter支持若干个事件监听器。当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。
//event.js 文件
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener1', arg1, arg2);
});
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent', 'arg1 参数', 'arg2 参数');
# 运行结果
$ node event.js
listener1 arg1 参数 arg2 参数
listener2 arg1 参数 arg2 参数
6.Nodejs Buffer(缓冲区)
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
Buffer与字符编码:
Buffer实例一般用于表示编码字符的序列,比如UTF-8、USC2、Base64、或十六进制编码的数据。通过使用显式地字符编码,就可以再Buffer实例与普通地JavaScript字符串之间进行相互转换。
const buf = Buffer.from('runoob', 'ascii');
// 输出 72756e6f6f62
console.log(buf.toString('hex'));
// 输出 cnVub29i
console.log(buf.toString('base64'));
Node.js 目前支持的字符编码包括:
- ascii - 仅支持 7 位 ASCII 数据。如果设置去掉高位的话,这种编码是非常快的。
- utf8 - 多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8 。
- utf16le - 2 或 4 个字节,小字节序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。
- ucs2 - utf16le 的别名。
- base64 - Base64 编码。
- latin1 - 一种把 Buffer 编码成一字节编码的字符串的方式。
- binary - latin1 的别名。
创建Buffer类:
// 创建一个长度为 10、且用 0 填充的 Buffer。
const buf1 = Buffer.alloc(10);
// 创建一个长度为 10、且用 0x1 填充的 Buffer。
const buf2 = Buffer.alloc(10, 1);
// 创建一个长度为 10、且未初始化的 Buffer。
// 这个方法比调用 Buffer.alloc() 更快,
// 但返回的 Buffer 实例可能包含旧数据,
// 因此需要使用 fill() 或 write() 重写。
const buf3 = Buffer.allocUnsafe(10);
// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。
const buf4 = Buffer.from([1, 2, 3]);
// 创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。
const buf5 = Buffer.from('tést');
// 创建一个包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的 Buffer。
const buf6 = Buffer.from('tést', 'latin1');
写入缓冲区:
buf = Buffer.alloc(256);
len = buf.write("www.runoob.com");
console.log("写入字节数 : "+ len);
从缓冲区读取数据:
buf = Buffer.alloc(26);
for (var i = 0 ; i < 26 ; i++) {
buf[i] = i + 97;
}
console.log( buf.toString('ascii')); // 输出: abcdefghijklmnopqrstuvwxyz
console.log( buf.toString('ascii',0,5)); // 输出: abcde
console.log( buf.toString('utf8',0,5)); // 输出: abcde
console.log( buf.toString(undefined,0,5)); // 使用 'utf8' 编码, 并输出: abcde
将Buffer转换为JSON对象:
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
const json = JSON.stringify(buf);
// 输出: {"type":"Buffer","data":[1,2,3,4,5]}
console.log(json);
const copy = JSON.parse(json, (key, value) => {
return value && value.type === 'Buffer' ?
Buffer.from(value.data) :
value;
});
// 输出: <Buffer 01 02 03 04 05>
console.log(copy);
7.Nodejs Stream(流)
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。
Node.js,Stream 有四种流类型:
- Readable - 可读操作。
- Writable - 可写操作。
- Duplex - 可读可写操作.
- Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
- data - 当有数据可读时触发。
- end - 没有更多的数据可读时触发。
- error - 在接收和写入过程中发生错误时触发。
- finish - 所有数据已被写入到底层系统时触发。
从流中读取数据:
var fs = require("fs");
var data = '';
// 创建可读流
var readerStream = fs.createReadStream('input.txt');
// 设置编码为 utf8。
readerStream.setEncoding('UTF8');
// 处理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {
data += chunk;
});
readerStream.on('end',function(){
console.log(data);
});
readerStream.on('error', function(err){
console.log(err.stack);
});
console.log("程序执行完毕");
写入流:
var fs = require("fs");
var data = '菜鸟教程官网地址:www.runoob.com';
// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('output.txt');
// 使用 utf8 编码写入数据
writerStream.write(data,'UTF8');
// 标记文件末尾
writerStream.end();
// 处理流事件 --> data, end, and error
writerStream.on('finish', function() {
console.log("写入完成。");
});
writerStream.on('error', function(err){
console.log(err.stack);
});
console.log("程序执行完毕");
管道流:
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另一个流中。
var fs = require("fs");
// 创建一个可读流
var readerStream = fs.createReadStream('input.txt');
// 创建一个可写流
var writerStream = fs.createWriteStream('output.txt');
// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream);
console.log("`
序执行完毕");
链式流:
链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。
接下来我们就是用管道和链式来压缩和解压文件。
var fs = require("fs");
var zlib = require('zlib');
// 压缩 input.txt 文件为 input.txt.gz
fs.createReadStream('input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('input.txt.gz'));
console.log("文件压缩完成。");
8.Nodejs 模块系统
创建模块:
var hello = require('./hello');
hello.world();
以上实例中,代码require(‘./hello’)引入了当前目录下的hello.js文件(./为当前目录,nodejs默认后缀为js)。
Nodejs提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即所获取模块的exports对象。
接下来我们就来创建hello.js文件,代码如下:
exports.world = function(){
console.log('Hello World');
}
在以上实例中,hello.js通过exports对象把world作为模块的访问接口,在main.js中通过require(‘./hello’)加载这个模块,然后就可以直接访问hello.js在exports对象的成员函数了。
有时候我们只是想把一个对象封装到模块中,格式如下:
module.exports = function() {
// ...
}
例如:
//hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
这样就可以直接获得这个对象了:
//main.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
模块接口的唯一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。
9.Nodejs 函数
在JavaScript中,一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。
Node.js中函数的使用与Javascript类似,举例来说,你可以这样做:
function say(word) {
console.log(word);
}
function execute(someFunction, value) {
someFunction(value);
}
execute(say, "Hello");
以上代码中,我们把 say 函数作为execute函数的第一个变量进行了传递。这里传递的不是 say 的返回值,而是 say 本身!
这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。
当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。
10.Nodejs 路由
var express = require('express');
var app = express();
// 主页输出 "Hello World"
app.get('/', function (req, res) {
console.log("主页 GET 请求");
res.send('Hello GET');
})
// POST 请求
app.post('/', function (req, res) {
console.log("主页 POST 请求");
res.send('Hello POST');
})
// /del_user 页面响应
app.get('/del_user', function (req, res) {
console.log("/del_user 响应 DELETE 请求");
res.send('删除页面');
})
// /list_user 页面 GET 请求
app.get('/list_user', function (req, res) {
console.log("/list_user GET 请求");
res.send('用户列表页面');
})
// 对页面 abcd, abxcd, ab123cd, 等响应 GET 请求
app.get('/ab*cd', function(req, res) {
console.log("/ab*cd GET 请求");
res.send('正则匹配');
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
11.Nodejs 工具模块
在Nodejs模块库中有很多好用的模块。
比如OS模块,Path模块,Net模块,DNS模块,Domain模块。