nodejs学习(imooc课程笔记, 主讲人Scott)
课程地址:
1. nodejs创建服务器
var http = require('http'); //加载http模块 //请求进来时, 告诉服务器做什么, 于是给createServer传递一个匿名回调函数. //两个参数, 前面是请求体, 后面是响应体. 请求体用来获取请求相应信息,比如请求ip地址,请求的类型get/post,res用来返回响应 http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/palin'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); //通过listen使服务器在1337端口监听请求 console.log('Server running at https://127.0.0.1:1337/');
2. nodejs安装:
一开始直接使用sudo apt-get install node后, 安装好的node不能启动且node -v也没有输出. node.js的版本号是v0.10.25.但是用npm安装一些包时候却说node版本不够, 没搞明白node和nodejs区别在哪里.
于是卸载掉从nodejs.org上下了新版, 是node v6.10.0. 解压后, 直接在那个目录下可以启动node, 在网上查了使用了一个链接, 分别把node 和 npm设置了全局:
sudo ln -s /media/hao/3b6b3418-c087-4836-a75d-6d7a6023e495/Programs/node-v6.10.0-linux-x64/bin/node /usr/sbin/node
sudo ln -s /media/hao/3b6b3418-c087-4836-a75d-6d7a6023e495/Programs/node-v6.10.0-linux-x64/bin/npm /usr/sbin/npm
可以用了.
关于nodejs和node版本的区别,参考这个帖子吧: nodejs各版本的区别
3. 模块: 从这里开始都是慕课网老师Scott进击NodeJS基础(一)(二)课程的笔记
- 创建模块
teacher.js
- 导出模块
exports.add = function(){ }
- 加载模块
var teacher = require('./teacher.js')
- 使用模块
teacher.add('Scott')
4. nodejs中一些api
- url:
- url.parse
- url.format:
-
url.resolve
- url.parse
-
querystring
var http = require('http') var cheerio = require('cheerio') //npm install cheerio var url = 'http://www.imooc.com/learn/348' function filterChapters(html){ var $ = cheerio.load(html) var chapters = $('.chapter') // [{ // chapterTitle: '', // videos: [ // title:'', // id: '' // ] // }] var courseData = [] chapters.each(function(item){ var chapter = $(this) var chapterTitle = chapter.find('strong').text().trim('\n').split('\n')[0] var videos = chapter.find('.video').children('li') var chapterData = { chapterTitle: chapterTitle, videos: [] } videos.each(function(item){ var video = $(this).find(".J-media-item") var videoTitle = video.text().split(/\s+/).slice(1,-2).join(' ') var id = video.attr('href').split('video/')[1] chapterData.videos.push({ title: videoTitle, id: id }) }) courseData.push(chapterData) }) return courseData } function printCourseInfo(courseData){ courseData.forEach(function(item){ var chapterTitle = item.chapterTitle console.log(chapterTitle + '\n') item.videos.forEach(function(video){ console.log(' [' + video.id + ']' + video.title + '\n') }) }) } http.get(url, function(res) { var html = '' res.on('data', function(data){ html += data }) res.on('end', function(){ var courseData = filterChapters(html) printCourseInfo(courseData) }) }).on('error', function(){ console.log('获取课程数据错误') })
6. 事件监听的一个小例子
var EventEmitter = require('events').EventEmitter var life = new EventEmitter() //addEventListener life.setMaxListeners(6) //设置事件最大监听数 默认为10. 超出这个数目会报警告 life.on('求安慰', function(who){ console.log('给 ' + who + ' 倒水') }) life.on('求安慰', function(who){ console.log('给 ' + who + ' ...1') }) life.on('求安慰', function(who){ console.log('给 ' + who + ' ...2') }) life.on('求安慰', function(who){ console.log('给 ' + who + ' ...3') }) life.on('求安慰', function(who){ console.log('给 ' + who + ' ...4') }) life.on('求安慰', function(who){ console.log('给 ' + who + ' ...5') }) life.on('求安慰', function(who){ console.log('给 ' + who + ' ...6') }) var hasBeenListener = life.emit('求安慰', '杠子') //会返回值, 看是否被监听过 //要移除函数, 不能够使用匿名函数, 只能移除具名函数 console.log(life.listeners('求安慰').length) //查询监听事件数 console.log(EventEmitter.listenerCount(life, '求安慰')) life.removeAllListeners('求安慰') life.emit('求安慰','gangzi')
7. Promise.js imooc
<!DOCTYPE html> <html> <head> <title>Promise animation</title> <style type="text/css"> .ball{ width: 40px; height: 40px; border-radius: 20px; } .ball1 { background: red;} .ball2 { background: yellow;} .ball3 { background: green;} </style> <script type="text/javascript" src="/home/hao/node_modules/bluebird/js/browser/bluebird.js"></script> </head> <body> <div class="ball ball1" style="margin-left: 0;"></div> <div class="ball ball2" style="margin-left: 0;"></div> <div class="ball ball3" style="margin-left: 0;"></div> </body> <script type="text/javascript"> var ball1 = document.querySelector('.ball1') var ball2 = document.querySelector('.ball2') var ball3 = document.querySelector('.ball3') function animate(ball, distance, cb){ setTimeout(function(){ var marginLeft = parseInt(ball.style.marginLeft, 10) if(marginLeft === distance){ cb && cb() } else { if(marginLeft < distance){ marginLeft++ } else { marginLeft-- } ball.style.marginLeft = marginLeft + 'px' animate(ball,distance,cb) } },13); } // animate(ball1, 100, function(){ // animate(ball2, 200, function() { // animate(ball3, 300, function(){ // animate(ball3, 150, function(){ // animate(ball2, 150, function(){ // animate(ball1, 150, function(){ // }) // }) // }) // }) // }) // }) var Promise = window.Promise function promiseAnimate(ball, distance){ return new Promise(function(resolve, reject){ function _animate(){ setTimeout(function(){ var marginLeft = parseInt(ball.style.marginLeft, 10) if(marginLeft === distance){ resolve() } else { if(marginLeft < distance){ marginLeft++ } else { marginLeft-- } ball.style.marginLeft = marginLeft + 'px' _animate() } },13); } _animate() }) } promiseAnimate(ball1, 100) .then(function() { return promiseAnimate(ball2,200) }).then(function(){ return promiseAnimate(ball3,300) }).then(function(){ return promiseAnimate(ball3,150) }).then(function(){ return promiseAnimate(ball2,150) }).then(function(){ return promiseAnimate(ball1,150) }) </script> </html>
- Promise就是对象,有三种状态: 未完成(pending), 已完成(fulfilled), 失败(rejected). 过程不可逆
- Promise A与A+不同点
- A+规范通过术语thenable来区分promise对象
- A+定义onFulfilled/onRejected必须是作为函数来调用, 而且调用过程必须是异步的
- A+严格定义了then方法链式调用时onFulfilled/onRejected的调用顺序
- 使用promise改进之前的爬虫代码:
var http = require('http') var Promise = require('bluebird') var cheerio = require('cheerio') //npm install cheerio var baseUrl = 'http://www.imooc.com/learn/' var url = 'http://www.imooc.com/learn/348' var videoIds = [728,637,197,348,259,75,134] function filterChapters(html){ var $ = cheerio.load(html) var chapters = $('.chapter') var title = $('h2.l').text() var number = $($('span.meta-value.js-learn-num')).text() // courseData = { // title: title, // number: number, // videos: [{ // chapterTitle: '', // videos: [ // title: '', // id: '' // ] // }] // } var courseData = { title: title, videos: [], number:number } chapters.each(function(item){ var chapter = $(this) var chapterTitle = chapter.find('strong').text().trim('\n').split('\n')[0] var videos = chapter.find('.video').children('li') var chapterData = { chapterTitle: chapterTitle, videos: [] } videos.each(function(item){ var video = $(this).find(".J-media-item") var videoTitle = video.text().split(/\s+/).slice(1,-2).join(' ') var id = video.attr('href').split('video/')[1] chapterData.videos.push({ title: videoTitle, id: id }) }) courseData.videos.push(chapterData) }) return courseData } function printCourseInfo(coursesData){ coursesData.forEach(function(courseData){ // console.log(courseData.number + ' 人学过' + courseData.title + '\n') console.log('### ' + courseData.title + '\n') courseData.videos.forEach(function(item){ console.log(item.chapterTitle); item.videos.forEach(function(video){ console.log(' [' + video.id + ']' + video.title + '\n') }) }) }) // coursesData.forEach(function(item){ // }) } function getPageAsync(url){ return new Promise(function(resolve,reject){ console.log('正在爬取 ' + url) http.get(url, function(res) { var html = '' res.on('data', function(data){ html += data }) res.on('end', function(){ resolve(html) // var courseData = filterChapters(html) // printCourseInfo(courseData) }) }).on('error', function(e){ reject(e) console.log('获取课程数据错误') }) }) } var fetchCourseArray = [] videoIds.forEach(function(id){ fetchCourseArray.push(getPageAsync(baseUrl + id)) }) Promise .all(fetchCourseArray) .then(function(pages){ var coursesData = [] pages.forEach(function(html){ var courses = filterChapters(html) coursesData.push(courses) }) coursesData.sort(function(a,b){ return a.number < b.number }) printCourseInfo(coursesData) })
8. Buffer API
使用Buffer来读写图片的一个例子:
1 var fs = require('fs') 2 3 fs.readFile('logo.png', function(err, origin_buffer){ 4 console.log(Buffer.isBuffer(origin_buffer)) 5 fs.writeFile('logo_buffer.png', origin_buffer, function(err){ 6 if(err) console.log(err) 7 }) 8 9 // var base64Image = new Buffer(origin_buffer).toString('base64') 10 var base64Image = origin_buffer.toString('base64') 11 12 console.log(base64Image) 13 14 var decodedImage = new Buffer(base64Image, 'base64') 15 16 console.log(Buffer.compare(origin_buffer, decodedImage)) 17 18 fs.writeFile('logo_decodes.png', decodedImage, function(err){ 19 if(err) console.log(err) 20 }) 21 })
9. Stream API
//拷贝文件 var fs = require('fs') var source = fs.readFileSync('../buffer/logo.png') fs.writeFileSync('stream_copy_logo.png', source)
//拷贝图片 var fs = require('fs') var readStream = fs.createReadStream('stream_copy_logo.js') var n = 0 readStream .on('data', function(chunk) { n++ console.log('data emits') console.log(Buffer.isBuffer(chunk)) //true // console.log(chunk.toString('utf8')) //要读的文件的内容 readStream.pause() console.log('data pause') setTimeout(function(){ console.log('data pause end') readStream.resume() }, 3000) }) .on('readable', function(){ console.log('data readable') }) .on('end', function() { console.log(n) console.log('data ends') }) .on('close', function() { console.log('data close') }) .on('error', function(e){ console.log('data read error' + e) })
//拷贝视频 var fs = require('fs') var readStream = fs.createReadStream('1.mp4') var writeStream = fs.createWriteStream('1-stream.mp4') readStream.on('data', function(chunk){ //如果缓存区数据还在写, 那么暂停读数据 if( writeStream.write(chunk) === false){ console.log('still cached') readStream.pause() } }) readStream.on('end', function(){ writeStream.end() }) writeStream.on('drain', function() { console.log('data drains') readStream.resume() })
10. 流与pipe
网络请求与pipe:
1 var http = require('http') 2 var fs = require('fs') 3 var request = require('request') 4 5 http 6 .createServer(function(req, res) { 7 //=====================常规做法 8 // fs.readFile('./buffer/logo.png', function(err, data){ 9 // if(err){ 10 // res.end('file not exist!') 11 // } else { 12 // res.writeHeader(200, {'Context-Type' : 'text/html'}) 13 // res.end(data) 14 // } 15 // }) 16 17 //===============使用pipe方法读取本地图片 18 // fs.createReadStream('../buffer/logo.png').pipe(res) 19 20 //从网络上获取一张图片 在不保存的前提下再返回给浏览器 21 request('http://www.imooc.com/static/img/index/logo.png?t=1.1').pipe(res) 22 //pipe会自动监听data和end事件. 可自动控制流量压力 23 }) 24 .listen(8090)
使用pipe重构上面的copy视频的代码:
1 var fs = require('fs') 2 fs.createReadStream('1.mp4').pipe(fs.createWriteStream('1-pipe.mp4'))
总结:
- 可读流是为了读取外部数据, 并把数据缓存到内部的buffer数组
- 可写流是为了消费数据, 从可读流里获取数据然后对得到的chunk数据库进行处理.
1 var Readable = require('stream').Readable 2 var Writable = require('stream').Writable 3 4 var readStream = new Readable() 5 var writeStream = new Writable() 6 7 readStream.push('I ') 8 readStream.push('Love ') 9 readStream.push('Imooc\n ') 10 readStream.push(null) 11 12 writeStream._write = function(chunk, encode, cb) { 13 console.log(chunk.toString()) 14 cb() 15 } 16 17 readStream.pipe(writeStream)
一个自己定制的读写转换流的实现:
1 var stream = require('stream') 2 var util = require('util') 3 4 function ReadStream(){ 5 stream.Readable.call(this) 6 7 } 8 9 util.inherits(ReadStream, stream.Readable) 10 11 ReadStream.prototype._read = function(){ 12 this.push('I ') 13 this.push('Love ') 14 this.push('Imooc\n ') 15 this.push(null) 16 } 17 18 function WriteStream(){ 19 stream.Writable.call(this) 20 this._cached = new Buffer('') 21 } 22 23 util.inherits(WriteStream, stream.Writable) 24 25 WriteStream.prototype._write = function(chunk, encode, cb){ 26 console.log(chunk.toString()) 27 cb() 28 } 29 30 function TransformStream(){ 31 stream.Transform.call(this) 32 } 33 34 util.inherits(TransformStream, stream.Transform) 35 36 TransformStream.prototype._transform = function(chunk, encode, cb){ 37 this.push(chunk) 38 cb() 39 } 40 41 TransformStream.prototype._flush = function(cb){ 42 this.push('Oh Yeah!') 43 cb() 44 } 45 46 var rs = new ReadStream() 47 var ws = new WriteStream() 48 var ts = new TransformStream() 49 50 rs.pipe(ts).pipe(ws)