一,commonJS
W3C:BOM,DOM
浏览器端JS:W3C + ECMAscript
Node.js:commonJS + ECMAscript
commonJS: FS,TCP,stream,Buffer。。。。。
二, 模块系统
语法:require();module.export;
实现步骤:
1,路径分析;
2,文件定位(require带上扩展名会加快速度,不带扩展名会按照.js/.json/node次序补足扩展名,一一尝试);
3,编译执行。(语言本身的核心模块可忽略1,2)(定位文件后会新建模块对象进行编译,就是把模块的信息制作成一个对象而已。并进行缓存)
性能:缓存编译后的对象,优先缓存加载
几种模块:
1.核心模块:http,fs,path(优先级仅次于缓存)
2.相对路径和绝对路径模块
3.文件模块
三,异步I/O
操作系统底层:阻塞I/O与非阻塞I/O(非阻塞通过轮询监控状态)
非阻塞I/O的主要轮询技术(从旧到新):
1.read:最原始,低性能,一直轮询检控I/O状态
2.select:改进read,轮询文件描述符的时间状态;限制:采用1024长度的数组储存状态,最多1024个文件操作符
3.poll:改进select,采用链表储存状态
4.epoll:通过事件唤醒,目前效率最高
现实中的异步I/O:
线程池通过主线程和其他IO线程,模拟异步I/O
Node中的异步I/O
事件循环,观察者,请求对象,IO线程池四者共同构成了Node异步IO
Node高效的原因正是因为 基于事件驱动的非阻塞I/O模型
javascript是单线程的,但是Node的I/O是可以并行多线程的
Node中与IO无关的异步API
首先计时器并不是精确的,通过插入定时器观察者的红黑树,每次tick执行检查是否过时,过时则形成事件;
setTimeout()
setInterval()
process.nextTick() 相比setTimeout轻量,因为不用动用红黑树 每次tick全部执行完毕
setImmediate() 如果有多个,每次tick执行一个
process.nextTick()优先级高于setImmediate() ,因为观察者优先级不同
四,异步编程
难点:
1.异常处理:
try/catch 处理异步函数,只会捕获立即返回的第一阶段,callback等无法捕获。
Node规定:异常会作为回调函数的第一个实参传回,为空则没有异常
2.函数嵌套的回调地狱
3.阻塞代码的实现
4.多线程编程
5.异步转同步
解决方案:
事件发布/订阅模式
没有DOM中的事件冒泡和捕获,没有preventDefault(),stopPropagation(),stopImmediatePropagation()等;
有addListener/on(),once(),removeListener(),removeAllListeners(),emit()等事件监听模式的方法;
(�发布订阅模式有点像vue的总线机制)
可以一对多,多对一,即一个/多个emit(),多个/一个on();
注意:
一个事件超过10个侦听器Node会发出一条警告 ,调用emitter.setMaxListerners(0)可以关闭,主要为了防止内存泄露和cpu占用过多;
1.利用事件队列解决雪崩问题
当访问量巨大,同一条SQL语句同时进行大量查询,会影响整体性能,解决:
1,加状态锁(单单加状态锁,只有第一个请求有效,后续就完全无效了)
2,通过once()讲请求引入事件队列,这样执行一次SQL后,所有请求的监听器会被移除,但是回调会被压入事件队列,。查询之后数据被所有回调共同使用,提高性能。
2.多异步之间的协作方案
当存在多个事件对应一个侦听器的时候,可能出现并行问题,但是我们希望是串行的。
原生实现或使用EventProxy(非Node原生)
Promise/Deferred
即get().success().fail()链式写法模式代替回调
promise.then()用于外部暴露给开发者,defer用于内部维护:未完成,已完成,失败三种状态,并触发事件(内部原理还是事件发布订阅模式)
流程控制(略)
尾触发和next,async,step,wind
异步并发控制
并发量过大,底层达上限
解决:bagpipe,async(方式都类似 控制上限)
五,v8内存控制
1,垃圾回收机制
尽管物理内存可能很大,但是v8只能使用部分内存(64位约为1.4G,32位约为0.7G),原因是内存过大垃圾回收机制耗时过长 严重影响前后端体验
process.memoryUsage()查看内存
打开内存限制:启动时增加
node --max-old-space-size=1700 test.js // 单位位MB,老生代内存空间
node --max-new-space-size=1024 test.js // 单位位KB,新生代内存空间
两者想加,为总内存
新生代又二分为两个semispace,From和To,进行垃圾回收时,检测From中的存活对象,复制到To,原From被释放,然后From和To的角色兑换;多次之后仍然存在的对象,移动到老生代内存(晋升);
垃圾回收时,应用程序会暂停,即"全停顿";(老生代清理全停顿事件过长,有增量标记,延迟清理,增量式整理等方式优化时间)
查看垃圾回收日志:(启动Node时增加 --trance_gc参数)
node --trance_gc
2,高效使用内存
无法立即收回的内存有闭包和全局变量,故小心使用。
略查看内存,堆外内存,内存泄露,大内存应用
六,理解buffer对象
1.结构
内建模块使用C++提高性能,核心模块使用JS。内存占用上,buffer属于堆外内存。且buffer被放在了全局对象上,无需require可以直接引入
2.buffer对象
类数组,每个元素为16进制的两位数。中文字占3个元素,英文和半角标点占一个元素,、;
可以访问length属性,可以通过下标访问元素,和赋值元素;
var buf = new Buffer(100);
console.log(buf.length) // 100
console.log(buf[10]) // 0~255的随机数
// 赋值操作(超出0~255,或者赋值小数,会处理后返回)
buf[10] = 100;
console.log(buf[10]) // 100
buf[20] = -100;
console.log(buf[20]) // 156 小于0,逐次加256,直到符合
buf[21] = 300;
console.log(buf[21]) // 44 大于0,逐次减256,直到符合
buf[22] = 3.1415;
console.log(buf[22]) // 3 小数会舍弃小数部分
3.buffer内存分配
小块内存可以手动操作申请和,大内存无需操作,C++自动处理
4.Buffer转换
1),字符串转buffer:
new Buffer(str, [encoding(编码方式)]);
encoding无参数默认utf-8,
可以多次写入,不同编码类型的字符串转码值:buf.write(str, [offset], [length], [encoding])
2),buffer转字符串:
buf.toString([encoding], [start], [end])
如果这个buffer由多段不同编码写入,需要分部分转码
3),不支持类型:
Buffer.isEncoding(xx),判断是否支持buffer转码,返回布尔值;
对于不支持的格式,可以引入其他模块辅助处理。
4),乱码:
var fs = require('fs);
var rs = fs.createReadStream('test.md', {highWaterMark: 11});
var data = '';
rs.on("data", function (chunk) {
data += chunk;
// 等价于 data = data.toString() + chunk.toString();
});
rs.on("end", function () {
console.log(data);
});
// 改进
readable.setEncoding('utf-8') // 可以正常输出中文
这样读取文件,英文不会有问题,中文会产生乱码
原因:文件可读流的每次读取的buffer长度限制为11
改进:上面setEncoding,使文件流传递的使编码后的字符串,限制(只支持utf-8, base64, ucs-2/utf-16le这三种)(不能从根本上解决问题)
正确拼接buffer:
5.buffer与性能
将字符串转化为buffer后传输,速度可提升一倍
highWaterMark值越大,读取文件速度越快
六,网络编程
1.TCP服务
2.HTTP服务
3.websocket服务
4.网络服务与安全
SSL协议,Node相关模块:crypto,tls,https
七,构建web应用
1.基础功能
请求方法,url解析,url查询字符串解析(query),cookie解析,表单数据,文件上传处理……等等
var app = function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('hello world\n');
}
http.createServer(app).listen(1337);
// url解析
req.method === 'POST' || 'GET' || 'PUT' || 'DELETE';
// url解析
var url = require('url');
url.parse(req.url)
// url查询字符串解析(query) ?foo=bar&baz=val
var url = require('url');
var querystring = require('querystring');
var query = url.parse(req.url, true).query || querystring.parse(url.parse(req.url).query);
// 结果 如果query的键出现多次,结果会是一个数组 ?foo=1&foo=2
query = { foo: 'bar', baz: 'val' }; { foo: [1, 2] }
// cookie解析 req.headers.cookie 格式key=value;key2=val2;
var parseCookie = function (cookie) {
var cookies = {};
if (!cookie) {
return cookies;
}
var list = cookie.split(';');
for (var i = 0; i < list.length; i++) {
var pair = list[i].split('=');
cookies[pair[0].trim()] = pair[1];
}
return cookies;
}
// 挂载到req对象上
function (req, res) {
req.cookies = parseCookie(req.headers.cookie);
}
// 返回给用户的cookie在 Set-Cookie中
// name=value是必选,其余是可选;
// path表示这个cookie影响的路径,当前访问路径不匹配时,就不会发送cookie
Set-Cookie: name=value; path=/; .....
Expires和Max-age // cookie过期时间,不设置则关闭浏览器失去cookie Expires是UTC时间,缺点客户端时间与服务器不同,就发生过期偏差,max-age是毫秒
HttpOnly // 告知浏览器不允许通过document.cookie更改cookie值
Secure // 为true只对https有效,表示cookie只能在https链接中传递,http链接不会携带cookie
res.setHeader('Set-Cookie', 'xxx'); // xxx可以通过拼接max-age=abc,path=/形成的数组然后join(';')组成
出于性能:应减少cookie数量与大小,为静态资源换个域名使之不必携带cookie
安全性:前后端都可以修改cookie,易篡改,判别账户信息,用Session(只存在服务端)
session数据与用户一一对应的方式:
1),基于cookie实现映射
将口令放在cookie中,一旦修改即丢失映射,然后实现cookie与session的映射即可(略)
2),通过查询字符串实现对应
session安全:
防止cookie等客户端口令被盗窃或者伪造:方法一私钥加密,
XSS漏洞(跨站脚本攻击):用户输入未转译,输入了JS代码
basic认证(用户账号密码登陆)
在https中或可用
2.数据上传
除了报头中的数据http会解析,携带的数据需要自己解析,通过data事件
// 以数据流接收
var buffers = [];
req.on('data', function (chunk) {
buffers.push(chunk);
});
// 传输完成后转码
req.on('end', function () {
req.rawBody = Buffer.concat(buffers).toString();
});
// 判断数据上传的方式,先定义一个判断请求头的函数
var mime = function (req) {
var str = req.headers['content-type'] || '';
return str.split(';')[0];
}
// 表单数据 报文体数据与query相同,可同样方式解析
var handle = function (req, res) {
if (mime(req) === 'application/x-www-form-urlencoded') {
req.body = querystring.parse(req.rawBody);
}
todo(req, res);
}
// Json文件
var handle = function (req, res) {
if (mime(req) === 'application/json') {
try {
req.body = JSON.parse(req.rawBody);
} catch (e) {
res.writeHead(400);
res.end('Invalid JSON');
return;
}
}
todo(req, res);
}
// XML文件
var xml2js = require('xml2js');
var handle = function (req, res) {
if (mime(req) === 'application/xml') {
// 略
}
}
附件上传,CSRF(跨站请求伪造)略
3.路由解析
路由映射 => MVC => RESTful
4.中间件(洋葱圈)
将从http请求到业务逻辑中间的细节囊括 => 一系列优化 => 中间件+路由
1>异常处理;2>中间件性能(高效与合理路由)
5.页面渲染