Node从入门到入土

207 阅读8分钟

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);

10. 以上,因为懒0.0,所以很多都没有展开多写(笔记里面复制粘贴:)),大佬们见谅~