1. Node是啥?
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
- node既不是语言也不是框架,而是js的运行平台
- 构建于Chrome的V8引擎之上,解析执行JavaScript
2. Node和浏览器中用到的js有何不同?
浏览器中的js: ECMAscript(基本语法、function、Array...)、DOM、BOM
作用:开发Web页面交互效果、操作DOM、BOM
Node中的js:
- ECMAscript
- 没有DOM、BOM
- node为JavaScript提供了一些服务器级别的API:
- 例如文件的读写
- 网络服务的构建
- 网络通信
- http服务器
3. Node的组成(架构)
- Node.js 标准库,这部分是由 Javascript编写的,即我们使用过程中直接能调用的 API。
- Node bindings,这一层是 Javascript与底层 C/C++ 能够沟通的关键,前者通过 bindings 调用后者,相互交换数据
- 这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。 V8: Node.js 为什么使用的是 Javascript的关键,它为 Javascript提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。 Libuv:为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。 C-ares:提供了异步处理 DNS 相关的能力。 http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。
4. Node的执行机制
- 每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。
- 主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
- 主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
- 主线程不断重复上面的第三步,看似单线程,实则事件循环机制将所有的阻塞操作交给了内部的线程池来处理,而主线程本身就是在不断的往返调度。
5. Node的特点
非阻塞I/O模型(异步I/O)、事件驱动、单线程、跨平台
- 异步、非阻塞
在我的理解中非阻塞和异步实际上是两个不同的概念,同步/异步关注的是消息通信机制,即在调用发出得到结果前是否会直接返回;非阻塞&阻塞强调的是程序等待结果时的状态,即调用结果返回前,线程/程序是否会挂起或处于等待状态。
具体的例子网上有很多,可以自己找找体会一下其中的差异
一个很常见的栗子: ajax请求
//Ajax
$.post('/url', {title: 'Node.js'}, function (data) {
console.log('收到响应');
});
console.log('发送Ajax结束');
收到响应前后续代码已经被执行了,不会等待响应结果且不影响后续执行,这就是典型的异步非阻塞模型
- 事件驱动
事件之间各自独立,只关注事务点、低耦合、轻量级
Node中的实现就是异步回调函数
//创建一个Web服务器
//事件驱动 & 回调函数
//事件之间各自独立,只关注事务点、低耦合、轻量级
var http = require('http');
var querystring = require('querystring');
// 监听服务器的request事件
http.createServer(function (req, res) {
var postData = '';
req.setEncoding('utf8');
// 监听请求的data事件
req.on('data', function (chunk) {
postData += chunk;
});
// 监听请求的end事件
req.on('end', function () {
res.end(postData);
});
}).listen(8080);
console.log('服务器启动完成');
- 单线程
这里的单线程实际上只是主线程,具体的机制可以看下上文Node的运行机制 至于为什么是单线程,因为js这门语言本质是单线程的(当然,worker之类的多线程的实现方式就不多深究了,Node本身也有类似的实现) 相比多线程,单线程存在程序稳定性(一点出错,全盘崩溃),执行效率低等劣势,但不需要考虑线程转换、状态同步等问题。
6. Node的应用场景
-
RESTful API (最理想的应用场景)
-
充分利用事件循环机制,资源占用少,擅长I/O密集型应用
-
Node不适合CPU密集型应用 ? (可以通过C/C++扩展、child_process作为常驻服务进程,没有不适合,只有合理的资源调度和利用才是王道)
总体来说,Node适合运用在高并发、I/O密集、少量业务逻辑的场景。
7. Node的模块系统
额,总之就是node也是站在了commonjs模块规范这个巨人的肩上完成了自身的模块系统的演变 (commonjs规范无非就是引用和模块定义,就不展开讲了)
Node的模块规范就几个重要的点(需要深入了解的可以看看朴灵大佬的《深入浅出Node.js》):
-
优先从缓存加载
-
路径分析和文件定位 (核心模块、路径形式的文件模块、自定义模块)
-
模块编译 (.js 、 .json 、 .node、 ...)
.js : 在编译的过程中,Node对获取的JavaScript文件内容进行了头尾包装。
(function (exports, require, module, __filename, __dirname) {
var math = require('math');
exports.add = function (x, y) {
return x + y;
};
});
.json: JSON.parse() => 赋值给exports
.node: process.dlopen()方法进行加载和执行
核心模块
- http模块: 处理客户端网络请求
- fs模块: 处理文件(上传、返回到浏览器)
- url模块: 处理客户端请求过来的url
- path模块: 处理文件与目录的路径
- querystring模块: 处理客户端通过get/post请求传递过来的参数
- global模块: 全局模块,不需要引用 (_dirname 、 _filename、require() 、exports...) events(触发器)、os(操作系统)、dns(域名服务器)....
8. 常用API及示例
文件操作
//1.使用fs核心模块
var fs = require('fs');
// 2.读取文件 & 异常处理
fs.readFile('./a.txt', 'utf8', function(err, data){
if(err){
console.log('文件读取失败');
}else{
console.log(data);
}
})
var fs = require('fs');
//将数据写入文件
fs.writeFile('./a.txt','我是写入的信息', 'utf8', function(err){
if(err){
console.log('文件写入失败');
}else{
console.log('文件写入成功');
}
})
//判断文件状态
fs.stat('./index.js',(err, data) => {
if(err) {
console.log('文件不存在');
return;
};
console.log(data.isFile()); // 是否为文件
console.log(data.isDirectory()); // 是否为文件夹
console.log(data);
});
fs.unlink(path,callback) 删除文件
fs.mkdir(path[,model],callback) 新建目录 ......
路径处理
const path = require('path');
//规范化路径
console.log(path.normalize('D://test'));
//解析为绝对路径
console.log(path.resolve('./'));
//文件名
console.log(path.extname('D:/test/abc.txt'));
//文件拓展名
console.log(path.basename('D:/test/abc.txt'));
//根据路径返回对象
console.log(path.parse('D:/test/abc.txt'));
创建web服务器
// 1.加载http核心模块
var http = require('http');
// 2.使用http.createServer()创建一个web服务器
var server = http.createServer();
//3.监听request事件
server.on('request', function(){
console.log('收到客户的请求了')
})
// 4.绑定端口号,启动服务
server.listen(3000, function(){
console.log('runing...')
})
如何响应 ? 根据不同请求路径响应不同数据?
var http = require('http');
var fs = require('fs');
http.createServer((req, res) => {
if(req.url !== '/favicon.ico'){
console.log('请求地址:' + req.url)
if (req.url == '/') {
res.writeHead(200, { 'Content-Type': 'text/plain;charset=utf-8' });
res.end('响应数据')
} else if (req.url == '/html') {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });
res.end('<h1>html</h1>')
} else if (req.url == '/json') {
var json = {
code: '0',
status: 'success',
payload: {
list: ['vue','node','javaScript']
}
};
res.end(JSON.stringify(json))
} else if (req.url == '/file') {
fs.readFile('./test.html', 'utf8', function(err,data){
if(err){
res.writeHead(500, { 'Content-Type': 'text/html;charset=utf-8' });
res.end('文件资源请求失败');
}else{
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });
res.end(data);
}
})
} else if (req.url == '/img') {
fs.readFile('./test.jpg', function(err,data){
if(err){
res.writeHead(500, { 'Content-Type': 'text/html;charset=utf-8' });
res.end('文件资源请求失败');
}else{
res.writeHead(200, { 'Content-Type': 'image/jpeg' });
res.end(data);
}
})
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset=utf-8' });
res.end('404')
}
}
}).listen(3000);
Get/Post 请求
var http = require('http');
var url = require('url');
http.createServer(function(req, res){
res.writeHead(200, {'Content-Type': 'text/plain;charset=utf-8'});
//将一个URL字符串转换成对象并返回
console.log(url.parse(req.url));
var params = url.parse(req.url).query;
res.write("Name:" + params.name);
res.write("\n");
res.write("Url:" + params.url);
res.end();
}).listen(3000);
9. Node操作数据库
- node操作mysql (操作其他数据库的可以自行网上找找)
- npm install myql (安装个依赖包先)
还有一些环境的集成软件,把数据库还有数据表备好就可以愉快的蹂躏数据了~
var mysql = require('mysql');
// var http = require('http');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
port: '3306',
database: 'nodetest'
});
connection.connect((err) => {
if (err) {
console.log('[query] - :' + err);
return;
}
console.log('[connection connect] succeed!');
});
var sql = 'SELECT * FROM user';
//查
connection.query(sql, function (err, result) {
if(err){
console.log('[SELECT ERROR] - ',err.message);
return;
}
console.log('--------------------------SELECT----------------------------');
console.log(result);
console.log('------------------------------------------------------------\n\n');
});
// var addSql = 'INSERT INTO user(name,age) VALUES(?,?)';
// var addSqlParams = ['啊哈哈', 24];
// //增
// connection.query(addSql, addSqlParams, function (err, result) {
// if(err){
// console.log('[INSERT ERROR] - ', err.message);
// return;
// }
// console.log('--------------------------INSERT----------------------------');
// console.log(result);
// console.log('-----------------------------------------------------------------\n\n');
// });
connection.end(function (err) {
if (err) {
return;
}
console.log('[connection end] succeed!');
});
// http.createServer((req, res) => {
// console.log('请求地址:' + req.url)
// if (req.url == '/') {
// res.writeHead(200, { 'Content-Type': 'text/plain;charset=utf-8' });
// var sql = 'SELECT * FROM user';
// connection.query(sql, function (err, result) {
// if(err){
// console.log('[SELECT ERROR] - ',err.message);
// return;
// }
// let data = JSON.stringify(result)//将数据转换为json格式
// res.end(data)
// connection.end(function (err) {
// if (err) {
// return;
// }
// console.log('[connection end] succeed!');
// });
// });
// }
// }).listen(3000);