### nodejs 从入门到企业web开发中的应用
###第一章 课程内容介绍
1-1导学
课程目标:
从零开始掌握大型互联网公司nodejs实际使用
课程内容:
nodejs原理
nodejs基础API
静态资源服务器
代码本地构建
单元测试
UI测试
headless爬虫
技术栈:
nodejs
HTTP
1-2课程介绍
web开发所需要了解的nodejs
课程大纲:
nodejs介绍
调试&项目初始化
基本API
简单web Server
单元测试&发布
nodejs爬虫示例
###第二章 nodejs是什么,为什么偏爱nodejs
2-1 nodejs是什么
nodejs is a javascript runtime built on Chrome's V8
nodejs uses an event-driven,non-blocking I/O model
非阻塞I/O:
阻塞:I/O时进程休眠等待I/O完成后进行下一步
非阻塞:I/O是函数立即返回,进程不等待I/O完成
事件驱动:
I/O等异步才做结束后的通知
观察者模式
2-2 nodejs究竟好在哪里
前端职责范围变大,统一开发体验
在处理高并发,I/O密集场景性能优势明显
CPU密集VSI/O密集:
CPU密集:压缩,解压,加密,解密
I/O密集:文件操作,网络操作,数据库
web常用场景:
静态资源读取
数据库操作
渲染页面
高并发应对之道:
增加机器数
增加每台机器的CPU数--多核
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位
多线程:启动多个进程,多个进程可以一块执行多个任务
nodejs工作模型:
线程:进程内一个相对独立的,可调度的执行单元,与同属一个进程的线程共享进程的资源
多线程:启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务
nodejs的单线程:
单线程只是针对主进程,I/O操作系统底层多线程调度
单线程并不是单进程
常用场景:
web Server
本地代码构建
实用工具开发
###第三章 环境&调试
3-1 环境&调试--CommonJS1
环境:
CommonJS(nodejs的模块管理规范)
global
process
CommonJS:
每个文件是一个模块,有自己的作用域
在模块内部module变量代表模块本身
module.exports属性代表模块对外接口
require规则:
/表示绝对路径,./表示相对于当前文件的
支持js,json,node扩展名,不写依次尝试
不写路径则认为是build-in模块或者各级node_modules内的第三方模块
require特性:
module被加载的时候执行,加载后缓存
一旦出现某个模块被循环加载,就只输出已经执行的部分,还未执行的部分不会输出
3-2 环境&调试--CommonJS2
见mycode
3-3 环境&调试--CommonJS3
见mycode
3-4 环境&调试--引用系统内置模块&引用第三方模块
见mycode
3-5 环境&调试--module.exports 与 exports 的区别
见mycode
3-6 环境&调试--global变量
global:
CommonJS
Buffer,process,console
timer
3-7 环境&调试--process进程
常用的参数相关:
argv
argv0
execArgv
execPath
const {argv,argv0,execArgv,execPath}=process;
argv.forEach(element => {
console.log(element);
});
//argv的第一个值
console.log(argv0);
执行命令:
node 10_argv.js
执行结果:
C:\Program Files\nodejs\node.exe //node安装路径
//当前执行文件的路径
E:\百度云\nodeJS从入门到实战\Node.js入门到企业Web开发中的应用\mycode\10_argv.js
C:\Program Files\nodejs\node.exe
execArgv
//调用node所产生的特殊参数
console.log(execArgv)
执行命令:
node --inspect 10_argv.js
执行结果:
[ '--inspect' ]
execArgv:调用node命令的路径
环境:
const {env}=process;
console.log(env);
cwd:
//打印当前process的执行路径
console.log(process.cwd());
//执行结果:
E:\百度云\nodeJS从入门到实战\Node.js入门到企业Web开发中的应用\mycode
setImmediate
//同步的执行完再去执行
setImmediate(()=>{
console.log('setImmediate');
});
setTimeout(() => {
console.log('timeout');
}, 0);
//在setImmediate之前执行,慎重使用
process.nextTick(()=>{
console.log('nextTick');
})
执行结果:
nextTick
timeout
setImmediate
3-8 环境&调试--debug1
inspector
vscode
3-9 环境&调试--debug2
###第四章 nodejs基础API
4-1基础API---path
path:和路径有关的一切
normalize :将路径格式化
join :将几个路径拼接,会先格式化
resolve :相对路径解析成绝对路径
basename :文件名
extname :拓展名
dirname:所在路径
parse :
代码:
执行结果:
format
sep:路径的分隔符
delimiter: path的分隔符
win32 posix
执行结果:
path
__dirname,__filename总是返回文件的绝对路径
process.cwd()总是返回执行node命令所在文件夹
./
在require方法中,总是相对当前文件所在文件夹
在其他地方和process.cwd()一样,相对node启动文件夹
4-2 基础API--buffer
buffer:缓冲
buffer用于处理二进制数据流
实例类似于整数数组,大小固定
C++代码在V8堆外分配物理内存
代码:
//长度10 ,默认用0填充
console.log(Buffer.alloc(10));
//长度20
console.log(Buffer.alloc(20));
//长度5,用1填充
console.log(Buffer.alloc(5,1));
//长度5 ,没有进行初始化
console.log(Buffer.allocUnsafe(5,1));
//按指定格式初始化
console.log(Buffer.from([1,2,3]));
//用test的ASCII码初始化,默认用utf-8
console.log(Buffer.from('test'));
//指定用一种编码格式
console.log(Buffer.from('test','base64'));
执行结果;
Buffer.byteLength
Buffer.isBuffer()
Buffer.concat()
代码:
//占多少字节
console.log(Buffer.byteLength('test'));
console.log(Buffer.byteLength('测试'));
//判断是否为buffer
console.log(Buffer.isBuffer({}));
console.log(Buffer.isBuffer(Buffer.from([1,2,3])));
//拼接buffer
const buf1=Buffer.from('this');
const buf2=Buffer.from('is');
const buf3=Buffer.from('a');
const buf4=Buffer.from('test');
const buf5=Buffer.from('!');
const buf=Buffer.concat([buf1,buf2,buf3,buf4,buf5]);
console.log(buf.toString());
执行结果:
4
6
false
true
thisisatest!
buffer实例的属性和方法
//buffer实例的属性和方法
//buffer.length
const buf=Buffer.from('this is a test!');
console.log(buf.length);
const buf2=Buffer.alloc(10);
buf2[0]=2;
console.log(buf2.length);
//buffer.toString()
console.log(buf.toString('base64'));
//buffer.fill()
const buf3=Buffer.allocUnsafe(10);
console.log(buf3);
console.log(buf3.fill(10,2,6));
//buffer.equals()
const buf4=Buffer.from('test');
const buf5=Buffer.from('test');
const buf6=Buffer.from('test!');
console.log(buf4.equals(buf5));
console.log(buf4.equals(buf6));
//buffer.indexOf()
console.log(buf4.indexOf('es'));
console.log(buf4.indexOf('esa'));
执行结果:
15
10
dGhpcyBpcyBhIHRlc3Qh
true
false
1
-1
buffer的中文乱码问题:
//中文乱码问题
const StringDecoder=require('string_decoder').StringDecoder;
const decoder=new StringDecoder('utf8');
const buf=Buffer.from('中文字符串!');
for(let i=0;i
const b=Buffer.allocUnsafe(5);
buf.copy(b,0,i);
console.log(b.toString());
}
for(let i=0;i
const b=Buffer.allocUnsafe(5);
buf.copy(b,0,i);
console.log(decoder.write(b));
}
执行结果:
中�
�字�
��串
!╔
中
文字
符串
!.%�
4-3基础API--events
const EventEmitter=require('events');
class CustomEvent extends EventEmitter{
}
const ce=new CustomEvent();
ce.on('test',()=>{
console.log('this is a test!')
})
setInterval(()=>{
//emit触发事件
ce.emit('test')
},500)
传参的emit
const EventEmitter=require('events');
class CustomEvent extends EventEmitter{}
const ce=new CustomEvent();
//换成once,只调用一次
ce.on('error',(err,time)=>{
console.log(err);
console.log(time);
});
ce.emit('error',new Error('oops!'),Date.now());
执行结果:
Error: oops!
at Object.
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
1535614137791
const EventEmitter=require('events');
class CustomEvent extends EventEmitter{}
function fn1(){
console.log('fn1');
}
function fn2(){
console.log('fn2');
}
const ce=new CustomEvent();
ce.on('test',fn1);
ce.on('test',fn2);
setInterval(()=>{
ce.emit('test');
},500);
setTimeout(()=>{
// ce.removeListener('test',fn2);
// ce.removeListener('test',fn1);
ce.removeAllListeners('test');
},1500);
执行结果:
fn1
fn2
fn1
fn2
4-4 基础API--fs
文件I/O是由简单封装的标准POSIX函数提供.通过require('fs')使用该模块.所有的方法都有异步和同步的形式.
异步方法的最后一个蚕食都是一个回调函数.传给回调函数的参数取决于具体方法,但毁掉函数的第一个参数都会保留给异常.如果操作成功完成,则第一个参数会是null或undefined.
当使用同步方法时,任何异常都会被立即抛出,可以使用try/catch来处理异常,或让异常向上冒泡.
读文件:
const fs=require('fs');
fs.readFile('./readfile.js','utf8',(err,data)=>{
if(err) throw err;
console.log(data);
});
//同步操作,先执行
console.log(fs.readFileSync('event.js','utf8'));
写文件:
const fs=require('fs');
fs.writeFile('./text.txt','this is a test ',{
encoding:'utf8'
},err=>{
if(err) throw err;
console.log('done!')
})
stat:
const fs=require('fs');
fs.stat('./stat.js',(err,stats)=>{
if(err) throw err;
console.log(stats.isFile());
console.log(stats.isDirectory());
console.log(stats);
})
执行结果:
true
false
Stats {
dev: 1446712323,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: undefined,
ino: 1970324837109880,
size: 187,
blocks: undefined,
atimeMs: 1535681732342.411,
mtimeMs: 1535681849574.2173,
ctimeMs: 1535681849574.2173,
birthtimeMs: 1535681732342.411,
atime: 2018-08-31T02:15:32.342Z,
mtime: 2018-08-31T02:17:29.574Z,
ctime: 2018-08-31T02:17:29.574Z,
birthtime: 2018-08-31T02:15:32.342Z }
rename:
fs.rename('./text.txt','text',err=>{
if(err) throw err;
console.log('done!')
})
//unlink
// fs.unlink('/text',err=>{});
//readdir 读取上层目录
fs.readdir('../',(err,files)=>{
if(err) throw err;
console.log(files);
})
mkdir,rmdir,watch
//创建文件夹
// fs.mkdir('test',err=>{});
//删除文件夹
// fs.rmdir('./test',err=>{});
//watch
fs.watch('./',{
//是否循环递归
recursive:true,
},(eventType,filename)=>{
console.log(eventType,filename);
})
readstream
const rs=fs.createReadStream('./stat.js');
//方向,往控制台输出
rs.pipe(process.stdout);
writestream
const ws=fs.createWriteStream('./test.txt');
const tid=setInterval(()=>{
const num=parseInt(Math.random()*10);
console.log(num);
if(num<7){
ws.write(num+'');
}else{
clearInterval(tid);
ws.end();
}
},200);
ws.on('finish',()=>{
console.log('done!')
})
回调地域问题:
//回调地域问题
const promisify=require('util').promisify;
const read=promisify(fs.readFile);
// read('/stat.js').then(data=>{
// console.log(data.toString());
// }).catch(ex =>{
// console.log(ex);
// });
async function test() {
try{
const content=await read('./stat.js');
console.log(content.toString());
}catch(ex){
console.log(ex);
}
}
test();
###第五章 项目初始化
静态资源服务器实战
先进入到项目目录下,再执行git init,再进行克隆
.gitignore规则:
匹配模式前/代表项目根目录
匹配模式最后加/代表是目录
匹配模式前加!代表取反
*代表任意个字符
?匹配任意一个字符
**匹配多及目录
.npmignore:忽略src,test,node_modules
如果没有.npmignore,则自动匹配.gitignore
.editorconfig:定义团队的代码风格
###第六章 静态资源服务器
在app.js中写如下代码:
const http=require('http');
const chalk=require('chalk');
const conf=require('./config/defaultConfig');
const server=http.createServer((req,res)=>{
res.statusCode=200;
res.setHeader('Content-Type','text/html');
res.write('');
res.write('');
res.write('Hello my first server!');
res.write('');
res.write('');
res.end();
});
server.listen(conf.port,conf.hostname,()=>{
const addr=`http://${conf.hostname}:${conf.port}/`;
console.info(`Server started at ${chalk.green(addr)}`);
})
没有chalk模块要先安装,npm i chalk,类型应该是'text/html',代码才会被解读为HTML格式,如果为'text/plain',会解读为文本.
在含有变量的语句中的单引号,应为与波浪线一起的那歌单引号,$才会起作用
全局安装supervisor工具,便于调试,每次修改内容后不必重启服务器
npm i -g supervisor
服务器搭建:
const http=require('http');
const chalk=require('chalk');
const path=require('path');
const fs=require('fs');
const conf=require('./config/defaultConfig');
const server=http.createServer((req,res)=>{
const filePath=path.join(conf.root,req.url);
fs.stat(filePath,(err,stats)=>{
if(err){
res.statusCode=404;
res.setHeader('Content-Type','text/plain');
res.end(`${filePath} is not a directory or file!`);
return
}
if(stats.isFile()){
res.statusCode=200;
res.setHeader('Content-Type','text/plain');
//响应速度慢
// fs.readFile(filePath,(err,data){
// res.end(data);
// })
fs.createReadStream(filePath.pipe(res));
}else if(stats.isDirectory()){
fs.readdir(filePath,(err,files)=>{
res.statusCode=200;
res.setHeader('Content-Type','text/plain');
res.end(files.join(','));
});
}
});
});
server.listen(conf.port,conf.hostname,()=>{
const addr=`http://${conf.hostname}:${conf.port}/`;
console.info(`Server started at ${chalk.green(addr)}`);
})
改造异步调用:
route.js:
const fs=require('fs');
const promisify=require('util').promisify;
const stat=promisify(fs.stat);
const readdir=promisify(fs.readdir);
module.exports=async function (req,res,filePath) {
try{
const stats=await stat(filePath);
if(stats.isFile()){
res.statusCode=200;
res.setHeader('Content-Type','text/plain');
//响应速度慢
// fs.readFile(filePath,(err,data){
// res.end(data);
// })
fs.createReadStream(filePath.pipe(res));
}else if(stats.isDirectory()){
const files=await readdir(filePath);
res.statusCode=200;
res.setHeader('Content-Type','text/plain');
res.end(files.join(','));
}
}catch(ex){
console.error(ex);
res.statusCode=404;
res.setHeader('Content-Type','text/plain');
res.end(`${filePath} is not a directory or file!\n${ex.toString()}`);
}
}
app.js:
const http=require('http');
const chalk=require('chalk');
const path=require('path');
const route=require('./helper/route');
const conf=require('./config/defaultConfig');
const server=http.createServer((req,res)=>{
const filePath=path.join(conf.root,req.url);
route(req,res,filePath);
});
server.listen(conf.port,conf.hostname,()=>{
const addr=`http://${conf.hostname}:${conf.port}/`;
console.info(`Server started at ${chalk.green(addr)}`);
})
模板引擎handlebars:
安装,建立相应的模板:
body{
margin:30px;
}
a{
display: block;
font-size:30px;
}
{{#each files}}
{{/each}}
修改后的route.js:
const fs=require('fs');
const path=require('path');
const Handlebars=require('handlebars');
const promisify=require('util').promisify;
const stat=promisify(fs.stat);
const readdir=promisify(fs.readdir);
const config=require('../config/defaultConfig')
const tplPath=path.join(__dirname,'../template/dir.hbs');
const source=fs.readFileSync(tplPath);
const template=Handlebars.compile(source.toString());
module.exports=async function (req,res,filePath) {
try{
const stats=await stat(filePath);
if(stats.isFile()){
res.statusCode=200;
res.setHeader('Content-Type','text/plain');
//响应速度慢
// fs.readFile(filePath,(err,data){
// res.end(data);
// })
fs.createReadStream(filePath).pipe(res);
}else if(stats.isDirectory()){
const files=await readdir(filePath);
res.statusCode=200;
res.setHeader('Content-Type','text/html');
const dir=path.relative(config.root,filePath);
const data={
title:path.basename(filePath),
dir:dir?`/${dir}`:'',
files
}
res.end(template(data));
}
}catch(ex){
console.error(ex);
res.statusCode=404;
res.setHeader('Content-Type','text/plain');
res.end(`${filePath} is not a directory or file!\n${ex.toString()}`);
}
}
为了使浏览器按文件的后缀名解析文件,在helper文件夹中新增:mime.js:
const path=require('path');
const mimeTypes={
'css':'text/css',
'gif':'image/gif',
'html':'text/html',
'ico':'image/x-icon',
'jpeg':'image/jpeg',
'jpg':'image/jpeg',
'js':'text/javascript',
'json':'application/json',
'pdf':'application/pdf',
'svg':'image/svg+xml',
'swf':'application/x-shockwave-flash',
'tiff':'image/tiff',
'txt':'text/plain',
'wav':'audio/x-wav',
'wma':'audio-x-ma-wma',
'wmv':'video/x-ms-wmv',
'xml':'text/xml'
};
module.exports=(filePath)=>{
let ext=path.extname(filePath).split('.').pop().toLowerCase();
if(!ext){
ext=filePath;
}
return mimeTypes[ext]||mimeTypes['txt'];
}
并在app.js中做相应的修改.
压缩文件
未完...