《深入浅出node.js》、慕课网

I belong to : Reading


《深入浅出node.js》

适合高并发、IO密集型、事件驱动

服务端

http.createServer(function(ServerRequest, ServerResponse){
    //监听
    ServerRequest.on('data',function(chunk){})
    ServerRequest.on('end',function(){})
    ServerRequest.on('close',function(){})
    //响应
    ServerResponse.writeHead(200,{'Content-Type':'text/plain'})
    ServerResponse.write('hello')
    ServerResponse.end()
}).listen(3000)

客户端

options = {
    host:'',
    hostname
    localAdrress
    socketPath
    auth
    agent
    keepAlive
    keepAliveMsecs
    port:'',
    method:'',
    path:'',
    headers:''
}
var clientRequest = http.request(options,function(clientResponse){
    clientResponse.setEncoding()
    clientResponse.pause()
    clientResponse.resume()
    clientResponse.on('data',function(data){
        console.log(Buffer.isBuffer(chunk));//true
        console.log(typeof chunk)//Object
    })//不断被触发,流
    clientResponse.on('end',function(){})
    clientResponse.on('close',function(){})

})

clientRequest.write(请求体)
clientRequest.end()
clientRequest.on('error',function(e){console.log(e.message)})
clientRequest.abort()
clientRequest.setTimeout()
clientRequest.setNoDelay()

文件加载方式

  1. 按路径加载

    require以'/'开头的,以绝对路径方式加载 require以'./'或'../'开头的,以相对路径加载

  2. 查找node_modules文件夹

    在当前目录下的node_modules中查找=> 父目录node_modules找=> 上一层目录的node_modules找。。。。。 一层一层向上

异步I/O原理

事实上,javascript是单线程的,Node自身其实是多线程的,只是I/O线程使用的CPU较少。 除了用户代码无法并行执行外,所有的I/O(磁盘I/O和网络I/O等)都是可以并行执行的。

非I/O异步的方法

非I/O异步的方法:setTimeout,setInterval,setImmediate,process.nextTick

setTimeout,setInterval

调用setTimeout,setInterval创建的定时器会被插入到定时器观察者内部的一个红黑树里面。每次Tick执行,就从改红合数中迭代去除定时器对象,检查是否超过定时时间。如果超过,就形成一个事件,回调函数立即执行

process.nextTick VS setTimeout(function(){},0)

立即执行一个异步任务可以用process.nextTick

setTimeout(function(){},0)需要动用到红黑树,创建定时器对象和迭代操作等操作,较浪费性能,复杂度为O(lg(n)。

nextTick只会将回调函数放入队列中,在下一轮Tick时取出执行,复杂度为O(1)。

setImmediate VS process.nextTick

process.nextTick优先级大于 setImmediate 。因为时间循环对观察者的检查是有先后顺序的,process.nextTick属于idle观察者,setImmediate属于check观察者。在每一个时间循环检查中,观察者检查顺序:idle---I/O---check

process.nextTick的回调保存在一个数组中。 setImmediate的回调保存在一个链表

process.nextTick在一个事件循环中会将所有回调函数全部执行完。setImmediate每一个时间循环只会执行一个回调。

异常处理

对异步方法进行try/catch操作只可以捕获当次事件循环里面的异常

solution:Node在处理异常上形成一种约定,将异常作为回调函数的第一个实参传回。

需要睡眠阻塞代码调用setTimeout更好,不要用下面的代码.这段代码会持续占用CPU,与真正的sleep相去甚远

var s =new Date();
while(neW Date()-s<100){}

异步编程解决方案

EventProxy 是对 events.EventEmitter 的补充,可以自由订阅组合事件

流程控制库

内存

V8 的垃圾回收会引起Javascript线程暂停执行,回收的内存越多,时间就会被耽搁得越长。所以造成了V8的内存限制。

Buffer

Node在内存的使用上采用的是在C++层面申请内存、在Javascript中分配内存的策略。

宽字节字符串被截断的问题可以用setEncoding()解决。原因在于,在调用setEncoding()的时候,可读流对象在内部设置了一个decoder对象。每次data事件都通过该decoder对象进行Buffer到字符串的解码,然后再进行下一步。decoder来自于string_decoder模块的StringDecoder实例对象,StringDecoder知道编码后会自行决定输出哪些字节,不输出哪些字节,保证字节不会被截断。但是setEncoding不能从根本上解决该问题。

更好的解决方法是把多个小Buffer对象拼接成一个Buffer对象(concat方法),然后通过iconv-lite一类的模块来转码。

TCP

在node中,TCP默认启用Nagle算法,要求缓冲区的数据达到一定数量或一定时间后才将其发出,所以小数据包会被合并,优化网络。 可以调用socket.setNoDelay(true)取消Nagle算法,使得write()可以立即发送数据到网络中。 并不是每次write()都会触发一次data事件。在关闭掉Nagle后,另一端可能会将接收到的多个小数据包合并,然后只触发一次data事件.

其他

事件驱动的实质:主循环+事件触发

为了充分利用浏览器缓存,提高页面的加载速度,在生产环境中常常会向静态文件的文件名添加MD5戳,即使用bundle_[hash].js,而不是bundle.js。 这里的[hash]会在构建时被盖chunk内容的MD5结果替换,以实现内容不变则文件名不变的效果。

幂等 :重复请求多次与请求一次的效果是一样的。

Node模块分为核心模块文件模块

文件模块没有扩展名时,会按照顺序优先加上后缀 .js=>.json=>.ndoe

server = new HttpServer()

server.on('request',function(req,res){})
//上面一句相当于=>
server.listen(3000)

慕课网

  1. 浏览器搜索自身的DNS缓存
  2. 搜索操作系统自身的DNS缓存
  3. 读取本地的HOST文件
  4. 浏览器发起一个DNS系统调用
  5. 宽带运营商服务器查看本身缓存
  6. 运营商服务器发起迭代DNS解析的请求

HTTP模块

什么是回调?
什么是同步/异步?
什么是I/O?
什么是单线程/多线程?
什么是阻塞/非阻塞?
什么是事件?
什么是事件驱动?
什么是基于事件驱动的回调?
什么是事件循环?

上下文

var pet = {
    speak :function(){
        console.log(this ===pet)//true
    }
}

function pet(){
    console.log(this===global)//true
}

function pet(){
    this.word="miaomiao';
    this.speak = function(){
        console.log(this)//cat
    }
}
var cat =new pet();

this通常指向当前函数的拥有者,叫做执行上下文

function Pet(word){
    this.word = word;
    this.speak = function(){
        console.log(this.word);
    }
}
function Dog(word){
    Pet.call(this,word);//Pet的this指向当前的Dog,相当于继承
}
var dog = new Dog('wang');
dog.speak()

cheerio

cheerio可以像jquery一样,解析html代码

var $ = cheerio.load(html);
var chapters = $('.learnchapter');
chapters.each(function(item){
    var c =$(this);
    var chapterTitle = c.find('strong').text();
    var vedio = c.find('.vedio').children('li');
})

events模块

var EventEmitter =require('events').EventEmitter;
var life = new EventEmitter();

life.on(eventname,function(){console.log(1)});
life.on(eventname,function(){console.log(2)});
life.on(eventname,function(){console.log(3)});
life.on(eventname,function(){console.log(4)});
...

var haseventname = life.emit(eventname,args)//返回有没有监听过

life.removeListener(eventname,functionname)//移除
life.removeAllListener(eventname)//批量移除

life.listeners(eventname)//所有监听器

EventEmitter.listenerCount(life,eventname)

默认一个事件不要超过10个监听器,不然内容泄露.可以进行设置

life.setMaxListeners(0)//去掉限制
life.setMaxListeners(11)//最多11个

Buffer暂存

Buffer 二进制数据。因为JS的字符串是utf-8存储的,处理二进制的能力弱。

var buf = new Buffer([1,2.11,3.22,4]);
console.log(buf[1])//2
转换图片为base64
var fs = require('fs');
fs.readFile('logo.png',function(err,origin_buff){
    fs.writeFile('logo_buffer.png'.origin_buff,function(){
        if(err) console.log(err)
    })
    var base64Img=origin_buff.toString('base64')
    var decodedImg = new Buffer(base64Img ,'base64')//拼装成data:image/png,base64,decodedImg才可使用
    console.log(Buffer.compare(origin_buff,decodedImg))//0

})

Stream 边读边写

转换图片为base64
//stream_logo.js
var fs = require('fs');
fs.writeFileSync('stream_logo.png',fs.readFileSync('logo.png'));//大文件会爆仓
var fs = require('fs');
var n = 0;
var readStream = fs.createReadStream('stream_logo.js')
readStream
.on('data',function(chunk){
    n++
    console.log(Buffer.isBuffer(chunk));//true 一次大概64TB
    console.log(chunk.toString('utf8'));//stream_logo.js内容
    readStream.pause();
    setTimeout(function(){
        readStream.resume()
    },3000)
})
.on('readable',function(){})
.on('end',function(){ console.log(n)})
.on('close',function(){})
.on('error',function(){})

复制文件
var fs = require('fs');
var readStream = fs.createReadStream('1.mp4');
var writeStream = fs.createWriteStream('copy.mp4');
readStream.on('data',function(chunk){
    if(writeStream.write(chunk)===false){//数据还在缓存区,读得快写得慢
        readStream.pause();//先暂停
    }
})
writeStream.on('drain',function(chunk){//已经写完了
    readStream.resume();  
})
readStream.on('end',function(chunk){
    writeStream.end()
})
强大的Pipe

Duplex双工 Transform双工,但是不存储数据

http.createServer(function(req,res){
    // fs.readFile('logo.png',function(err,data){
    //     if(err){
    //         res.end('file is not exist!')
    //     }else{
    //         res.writeHead(200,{‘Content-Type':'text/html'});
    //         res.end(data)
    //     }
    // })
    fs.createReadStream('logo.png').pipe(res);//返回给浏览器
    request('https://imoooc/logo.png').pipe(res)//同上 边下载边pipe
})

上面的复制文件代码就可以改成

var fs = require('fs');
fs.createReadStream('1.mp4').pipe(fs.createWriteStream('copy.mp4'))
var Readable = require('stream').Readable
var Writable = require('stream').Writable
var readStream = new Readable()
var writeStream = new Writable()
readStream.push('I')
readStream.push('Love')
readStream.push('You\n')
readStream.push('null')
writeStream._write = function(chunk,encode,cb){
    console.log(chunk.toString())
    cb()
}
readStream.pipe(writeStream)

定制流

funcion ReadStream(){
    stream.Readable.call(this)
}
util.inherits(ReadStream,stream.Readable);//继承原型
ReadStream.prototype._read=function(){
    this.push('I')
    this.push('Love')
    this.push('You\n')
    this.push('null')
}

funcion WritStream(){
    stream.Writable.call(this)
    this._cached=new Buffer('')
}
util.inherits(WritStream,stream.Writable);
WritStream.prototype._write = function(chunk,encode,cb){
    console.log(chunk.toString())
    cb()
}

function TransfromStream(){
    stream.Transform.call(this)
}
util.inherits(TransfromStream,stream.Transfrom);
TransfromStream.prototype._transform= function(chunk,encode,cb){
    this.push(chunk)
    cb()
}
TransfromStream.prototype.flush= function(cb){
    this.push('On Yeah!')
    cb()
}

var rs= new ReadStream()
var ws = new WritStream();
var ts = new TransfromStream()

rs.pipe(ts).pipe(ws)

createReadStream是给你一个ReadableStream,你可以听它的'data',一点一点儿处理文件,用过的部分会被GC(垃圾回收),所以占内存少。

readFile是把整个文件全部读到内存里,这种方式是把文件内容全部读入内存,然后再写入文件。对于小型的文本文件,这没有多大问题。但是对于体积较大的二进制文件,比如音频、视频文件,动辄几个GB大小,如果使用这种方法,很容易使内存“爆仓”。理想的方法应该是读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完成,这里就需要用到流的概念

node不适合的: 极高并发数(电商)、 密集CPU运算(最优化路线)、 高安全高可靠性(银行)、 内存精密控制和释放

express中,req.param()方法是对params\body\query的封装,取值顺序是params->body->query。

非简单请求的CORS(跨域)请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。在你的post请求之前会发送一次OPTIONS请求 跨域资源共享 CORS 详解

服务端重启会清除session.session持久化方法:cookies,redis,MongoDB,硬盘内存 为了弥补HTTP的无状态,就有了cookies和session.之前没有seesion的时候都用的是cookies. 当程序需要给某个客户端请求创建一个session时,服务器会检查请求里面是否包含sessionid,服务器把这个session找出来就行了。没有就创建,返回sessionid给客户端保存. koa-session