类似java的jre,在chrome's V8运行环境的runtime,语言在不同的运行环境上面跑就需要对应的runtime。
不能使用js的DOM和BOM,这是DOM结构和浏览器的属性,算是一个符合ECMA标准的web开发。
I/O:input/output
阻塞:I/O时进程休眠等待I/O完成后进行下一步
非阻塞:I/O时函数立即返回,进程不等待I/O完成(I/O操作程序进程继续,等到I/O结束后通知程序相关操作)
I/O操作时间很慢
I/O等异步操作结束后的通知
观察这模式
适合处理高并发、I/O密集场景性能优势明显
CPU密集:压缩、解压、加密、解密
I/O密集:文件操作、网络操作、数据库
- 静态资源读取
- 数据库操作
- 渲染页面
- 增加机器数(负载均衡)
- 增加每台机器的CPU数-多核
计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。(正在进行中的程序)
多进程:启用多个进程,多个进程可以一块执行多个任务。
进程中一个相对独立,可调度的执行单元,与同属一个进程的线程共享进程的资源。
多线程:启动一个进程,在一个进程内启动多个线程,这样多个线程也可以一块执行多个任务
单线程只针对主进程,I/O操作系统底层多线程调度
单线程不是单进程
- web server
- 本地代码构建
- 使用工具开发
(function (exports, require, module, __filename, __dirname) {
// nodejs在执行的时候在外包裹了函数
...
})exports(object):模块的输出(对外提供接口、属性)
require(function):需要依赖别的模块的时候所调用的function
module(object):模块本身
__filename:文件的直接路径(绝对地址)
__dirname:文件所在的文件夹路径(绝对地址)
- 每个文件是一个模块,有自己的作用域(匿名函数)
- 在模块内部module变量代表模块本身
- module.exports属性代表模块对外接口
- /绝对路径,./表示相对于当前文件的路径
- 支持js、json、node拓展名,不写依次尝试
- 不写路径则认为是build-in模块或者各级node_modules内的第三方模块
- 只加载一次,加载后缓存(demo1/04cache.js)
- 一旦一个模块被循环加载,就值输出已经执行的部分,还未执行的部分不会加载(demo1/05_main.js)
系统模块(demo1/06_fs.js)
const fs = require('fs');
const result = fs.readFile('./06_fs.js', (err, data) => {
if (err) {
console.log(err) // 报错信息,找不到文件
} else {
console.log(data.toString()) // 不加toString()会得到一堆二进制的文件
}
});
console.log(result) // undefined,读取文件是异步的,这时候还没读取第三方模块
首先npm下载依赖查找本模块,找不到就去node_modules文件找
const chalk = require('chalk')
console.log(chalk.red('red'))npm root -g 可以知道本电脑所有的全局依赖
exports默认是model.exports,但修改了exports,他就不在默认了
CommonJS
buffer:表示二级制数据处理
process:和进程相关挂载在global
const {argv, argv0, execArgv, execPath, env} = process;
// argv启动的参数 例如 node 08_process.js a
// 输出node所在的文件目录,08_process.js的文件目录,a
argv.forEach(item => {
console.log(item)
});
console.log(argv0) // 输出第一个node所在的文件目录
console.log(execArgv) // 输出在文件名前面的参数
console.log(execPath) // 调用的程序路径
console.log(env) // 当前系统的执行环境
console.log(process.cwd()) // 一个函数,输出当前命令执行所在的文件console: 打印
timer: 计时器
// 一样有setTimeOut(),setInterval()
setImmediate(() => {
})
// 和时间无关 等下一个事件队列,同步任务执行后执行他
process.nextTick(() => {
// 会比setImmediate执行早,setTimeOut在他们之间
// nextTick放在当前事件队列最后,setImmediate下一个事件队列之前
// nextTick出现循环调用会导致后面的异步无法执行,选择需要慎重
})// 暴露到全局
global.prop = 100在node命令和文件中间输入 --inspect-brk
例如 node --inspect-brk 10_debug.js
在chrome浏览器中输入chrome://inspect,点击target
normalize:把不规范的路径处理规范。(demo1/11_normalize.js)
const {normalize} = require('path')
console.log(normalize('/user///local'))join:把传入的路径拼接起来,本身也会调用normalize。(demo1/12_join.js)
const {join} = require('path')
console.log(join('/user', 'local', 'path1'))resolve:把相对路径变成绝对路径。(demo2/13_resolve.js)
const {resolve} = require('path');
console.log(resolve('./'))basename、dirname、extname
const {basename, dirname, extname} = require('path')
const filePath = '/user/local/text.txt'
console.log("basename:", basename(filePath)) // 文件名text.txt
console.log("dirname", dirname(filePath)) // 文件目录/user/local
console.log("extname", extname(filePath)) // 文件拓展名.txtparse、format
const {parse, format} = require('path');
const filePath = '/user/local/text.jpg'
const parseRes = parse(filePath)
console.log(parseRes)
// { root: '/',
// dir: '/user/local',
// base: 'text.jpg',
// ext: '.jpg',
// name: 'text'
// }
const formatRes = format(parseRes)
console.log(formatRes) // /user/local\text.jpg__dirname、__filename总是返回文件的绝对路径
process.cwd()总是返回执行Node命令所在文件夹
buffer用于处理二进制数据流
实例类似整数数组、大小固定
c++代码在v8堆外分配物理内存
console.log(Buffer.alloc(10)) // <Buffer 00 00 00 00 00 00 00 00 00 00>
console.log(Buffer.alloc(20)) // <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
console.log(Buffer.alloc(5, 1)) // <Buffer 01 01 01 01 01>
console.log(Buffer.allocUnsafe(10)) // <Buffer 19 b7 58 98 bc 03 00 00 00 00>
console.log(Buffer.from([1, 2, 3])) // <Buffer 01 02 03>
console.log(Buffer.from('text')) // <Buffer 74 65 78 74> 默认utf8
console.log(Buffer.from('text', 'base64')) // <Buffer b5 ec 6d>
console.log(Buffer.byteLength('text')) // 4
console.log(Buffer.byteLength('文字')) // 6
// 判断是否二进制
console.log(Buffer.isBuffer({})); // false
console.log(Buffer.isBuffer(Buffer.from([1, 2, 3]))) // true
// 拼接buffer
const buf1 = Buffer.from('jiang')
const buf2 = Buffer.from('wen')
const buf3 = Buffer.from('yong')
const name = Buffer.concat([buf1, buf2, buf3])
console.log(name.toString()) // jiangwenyong
// buffer的长度,和申请空间有关
const buf = Buffer.from('jiangwenyong')
console.log(buf.length); // 12
// 字符串转换
console.log(buf.toString('base64'))
// 填充
const bufFill = Buffer.allocUnsafe(10);
console.log(bufFill) // <Buffer 60 13 bf f4 16 02 00 00 07 00>
console.log(bufFill.fill(10, 1, 9)) // Buffer 60 0a 0a 0a 0a 0a 0a 0a 0a 00>
// 比较buffer大小
const bufEq1 = Buffer.from('a');
const bufEq2 = Buffer.from('a');
const bufEq3 = Buffer.from('b');
console.log(bufEq1.equals(bufEq2)) // true
console.log(bufEq1.equals(bufEq3)) // false
// indexOf
const bufIdx = Buffer.from('abcd');
console.log(bufIdx.indexOf('bc')) // 1
console.log(bufIdx.indexOf('bca')) // -1
// copy方法,解决中文乱码
const { StringDecoder } = require('string_decoder')
const decoder = new StringDecoder('utf8')
const bufCopy = Buffer.from('中文乱码!');
for (let i = 0; i < bufCopy.length; i += 4) {
const b = Buffer.allocUnsafe(4);
bufCopy.copy(b, 0, i);
console.log(b.toString())
}
for (let i = 0; i < bufCopy.length; i += 4) {
const b = Buffer.allocUnsafe(4);
bufCopy.copy(b, 0, i);
console.log(decoder.write(b))
}const EventEmitter = require('events')
// 触发一个事件
class CustomEvent extends EventEmitter {}
const ce = new CustomEvent();
ce.on('test', ()=> {
console.log('test')
})
// setInterval(() => {
// ce.emit('test')
// }, 500);
// 传递参数
class CustomError extends EventEmitter {}
const cb = new CustomError();
cb.on('error', (err, timer) => {
console.log(err)
console.log(timer)
})
// cb.emit('error', new Error('oops!'), Date.now());
// once
class CustomOnce extends EventEmitter {}
const cc = new CustomOnce();
cc.once('once', () => {
console.log('once')
})
// setInterval(() => {
// cc.emit('once')
// }, 500);
// remove
class CustomRemove extends EventEmitter {}
function fn1 () {
console.log('fn1')
}
function fn2 () {
console.log('fn2')
}
const cd = new CustomRemove()
cd.on('remove', fn1);
cd.on('remove', fn2);
setInterval(() => {
cd.emit('remove')
}, 500);
setTimeout(() => {
// cd.removeListener('remove', fn1)
// cd.removeListener('remove', fn2)
// 移除全部
cd.removeAllListeners('remove')
}, 1500);const fs = require('fs')
// 读取文件
// 异步
fs.readFile('./19_fs.js', 'utf8', (err, data) => {
if (err) throw err;
console.log('异步', data)
})
// 同步
const data = fs.readFileSync('./19_fs.js')
console.log('同步', data)
// 写入文件
fs.writeFile('./text', 'hello', {
encoding: 'utf8'
}, err => {
if (err) throw err;
console.log('done')
})
// 文件信息(可用于判断文件是否存在)
fs.stat('./19_fs.js', (err, stats) => {
if (err) throw err;
console.log(stats.isFile());
console.log(stats.isDirectory());
console.log(stats)
})
// 修改文件名
fs.rename('./text', 'text.txt', err => {
if (err) throw err;
console.log('done');
})
// 删除文件
fs.unlink('./text.txt', err => {
if (err) {
console.log(err)
}
console.log('done')
})
// 读取文件夹
fs.readdir('./', (err, files) => {
if (err) throw err;
console.log(files)
})
// 创建文件夹
fs.mkdir('test', err => {})
// 删除文件夹
fs.rmdir('test', err => {})
// 检查文件发生变化
fs.watch('./', {
recursive: true
}, (eventType, filename) => {
console.log(eventType, filename)
})(demo1/19_fs.js)
const fs = require('fs');
const rs = fs.createReadStream('./20_stream.js')
rs.pipe(process.stdout) // 从上往下读取
const ws = fs.createWriteStream('./text.txt');
const timer = setInterval(() => {
const num = parseInt(Math.random() * 10);
if (num < 8) {
// 这里写入的必须是字符串
ws.write(num.toString())
} else {
clearInterval(timer)
ws.end();
}
}, 200);
ws.on('finish', () => {
console.log('done')
})(demo1/20_stream.js)
解决A函数中执行B函数回调,B函数中执行C的回调
const fs = require('fs')
const { promisify } = require('util')
const read = promisify(fs.readFile);
// 方法1 promisify
read('./21_promisify.js').then(data => {
console.log(data.toString())
}).catch(ex => {
console.log(ex)
})
// async await
async function test () {
try {
const content = await read('./21_promisify.js')
console.log(content.toString())
} catch (ex) {
console.log(ex)
}
}
test()(demo1/21_promisify.js)
在Headers中RequestHeaders的Accept-Encoding: gzip, deflate是浏览器发给服务器的信息,告诉服务器浏览器支持什么压缩格式。Response Header中content-encoding: 代表服务器发给浏览器的压缩格式。
(myanydoor/src/helper/compress)
range: bytes=[start]-[end]
Accept-Ranges: bytes
Content-Range: bytes start-end/total
(myanydoor/src/helper/range)
Expires(绝对时间), Cache-Control(相对时间): 来判断是否失效的时间
If-Modified-Since / Last-Modified: 服务器每次反问告诉你上次修改的时间
If-None-Match / ETag: 生成一个hash值
(myanydoor/src/helper/cache)
一个简单的测试用例
// math.js
module.exports = {
add: ( ...args ) => {
return args.reduce((prev, curr) => {
return prev + curr;
})
},
mul: ( ...args ) => {
return args.reduce((prev, curr) => {
return prev * curr;
});
}
} // simple.js
const assert = require('assert');
const { add, mul } = require('../src/math');
if (add(2, 3) === 5) {
console.log('add(2, 3) === 5,ok');
} else {
console.log('add(2, 3) !== 5,error');
}使用chai,mocha
npm install chai
npm install mocha
// 在pagejson.js的scripts里面使用命令行
// "test": "mocha ./mocha.js"
// mocha.js
const assert = require('assert');
const { add, mul } = require('../src/math');
describe('#math', () => {
describe('add', () => {
it ('should return 5 when 2 + 3', () => {
assert.equal(add(2, 3), 5);
});
it ('should return -1 when 2 + -3', () => {
assert.equal(add(2, -3), -1);
});
});
describe('mul', () => {
it ('should return 6 when 2 * 3', () => {
assert.equal(mul(2, 3), 6);
});
});
});测试覆盖率
利用istanbul测试测试用例的执行顺序和执行覆盖率
npm install istanbul
// math.js
function min (a, b) {
return b * a;
}
module.exports = {
add: ( ...args ) => {
return args.reduce((prev, curr) => {
return prev + curr;
});
},
mul: ( ...args ) => {
return args.reduce((prev, curr) => {
return prev * curr;
});
},
cover: (a, b) => {
if (a > b) {
return a - b;
} else if (a === b) {
return a + b;
} else {
return min(a, b)
}
}
}
// mocha.js
const assert = require('assert');
const { add, mul, cover } = require('../src/math');
describe('#math', () => {
describe('add', () => {
it ('should return 5 when 2 + 3', () => {
assert.equal(add(2, 3), 5);
});
it ('should return -1 when 2 + -3', () => {
assert.equal(add(2, -3), -1);
});
});
describe('mul', () => {
it ('should return 6 when 2 * 3', () => {
assert.equal(mul(2, 3), 6);
});
});
describe('cover', () => {
it ('should return 6 when cover(2, 3)', () => {
assert.equal(cover(2, 3), 6);
});
it ('should return 1 when cover(3, 2)', () => {
assert.equal(cover(3, 2), 1);
});
it ('should return 1 when cover(2, 2)', () => {
assert.equal(cover(2, 2), 4);
});
});
});持续集成
1、频繁地将代码集成到主干。
2、每次集成都通过自动化构建来验证。
优点:今早发现错误,防止分支大幅偏离主干
性能
使用benchmark做性能测试,ops/sec每秒操作次数,误差率,越大越好。
npm install benchmark
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;
suite.add('RefExp#test', function() {
/o/.test('Hellow World!');
})
.add('String#indexOf', function() {
'Hello World!'.indexOf('o') > -1;
})
.add('String#match', function() {
!!'Hello World'.match(/o/);
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({'async': true})按照一定的规则自动抓取网络信息的程序
1、User-Agent、Referer,验证码。
2、单位时间访问次数、访问量。
3、关键信息图片混淆。
4、异步加载。
const puppeteer = require('puppeteer');
const { screenshot } = require('./config/defult');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
await page.screenshot({
path: `${screenshot}/${Date.now()}.png`
});
await browser.close();
})()puppeteer安装失败
安装puppeteer时因为需要下载chrom的缘故总是安装失败提示,执行npm install puppeteer --ignore-scripts 跳过安装chromuin步骤
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
executablePath: './Chromium'
});
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
await page.screenshot({
path: `${screenshot}/${Date.now()}.png`
});
browser.close();
})();1、安装express
2、通过生成器自动生成目录
3、配置分析
安装一个express-generator生成器
npm install -g express-generatorexpress servernode bing/wwwvar createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var ejs = require('ejs');
var indexRouter = require('./routes/index'); // 加载路由模块
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
// app.set('views', path.join(__dirname, 'views')); // 使用什么view模版
// app.set('view engine', 'jade'); // view模版使用什么引擎
// 使用html
app.set('views', path.join(__dirname, 'views/html'));
app.engine('.html', ejs.__express);
app.set('view engine', 'html');
app.use(logger('dev')); // 使用第三方插件
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); // 静态资源路径
app.use('/', indexRouter); // 通过不同地址,访问不同的模块
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) { // 全局404拦截,next是不是什么都拿不到,拿不到404
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) { // 全局错误拦截
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;具体在/demo/express_demo/server/app.js
tip: 修改内容有点麻烦要重启
如果需要使用html模版需要安装ejs
高可拓展性
分布式存储
低成本
结构灵活
1、下载安装包或者压缩包
2、添加db存储和日志存储文件夹
3、添加服务、配置环境变量、启用Mongo
安装时选用custom
a.在c:\MongoDB(可随意起)下面建一个data文件夹 c:\MongoDB\data。(目前安装的时候会有可视化指定)
b.在c:\MongoDB(可随意起)下面建一个logs文件夹 c:\MongoDB\logs ,在里面建一个文件mongo.log。(目前安装的时候会有可视化指定)
c.在c:\MongoDB(可随意起)下面建一个etc(随意起,放配置文件)文件夹 c:\MongoDB\etc ,在里面建一个文件mongo.conf。(目前安装的时候会有可视化指定)
d.打开mongo.conf文件,修改如下:
数据库路径
dbpath=c:\MongoDB\data\
日志输出文件路径
logpath=c:\MongoDB\logs\mongodb.log
错误日志采用追加模式,配置这个选项后mongodb的日志会追加到现有的日志文件,而不是从新创建一个新文件
logappend=true
启用日志文件,默认启用
journal=true
这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=false
端口号 默认为27017
port=27017
指定存储引擎(默认先不加此引擎,如果报错了,大家在加进去)
storageEngine=mmapv1
启动MongoDB
// 执行看看如果能出现访问端口27017等等,并且通过localhost:27017能出现
// It looks like you are trying to access MongoDB over HTTP on the native driver port.
// 访问成功
// 一定要进入Mongo安装的文件夹的bin目录才可以执行mongod.exe,如果觉得麻烦可以在环境变量上添加Mongo把路劲指向Mongo安装的文件夹的bin目录
mongod --dbpath c:\MongoDB\data
http://www.imooc.com/article/18438
可以下载一个MongoVUE可视化数据库连接
| SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
|---|---|---|
| database | database | 数据库 |
| table | collection | 数据库表/集合 |
| row | document | 数据记录行/文档 |
| column | field | 数据字段/域 |
| index | index | 索引 |
| tablejoins | 表连接,MongoDB不支持 | |
| primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
进入Mongo安装的文件夹的bin目录才能执行下列操作
查看目录下面有什么数据库
show dbs
创建一个集合
// 方法1
db.createCollection("user")
// 方法2
db.user.insert({id:1,name:'jwy'})
查看集合
show collections
删除数据库demo
// 首先进入数据库
use demo
db.dropDatabase()
删除集合user
// 首先进入数据库
use demo
// db代表当前数据库,user代表集合
db.user.drop()
插入集合(注意每个命令里面都是对象)
db.user.insert({userId:1,userName:'jwy',userAge:18,class:{name:'fee',num:12}})
查询集合的内容
db.user.find()
// 格式化
db.user.find().pretty()
// 查询第一条数据
db.user.findOne()
// 查询某条数据
db.user.find({userName: 'jwy'})
db.user.find({'class.name':'fee'})
// 条件查询
db.user.find({userAge:{$gt:20}}) // 年龄大于20
// 条件查询
db.user.find({userAge:{$lt:20}}) // 年龄小于20
// 条件查询
db.user.find({userAge:{$eq:20}}) // 年龄等于20
// 条件查询
db.user.find({userAge:{$gte:20}}) // 年龄大于等于20
更新集合的内容
db.user.update({userName:"jwy"},{$set:{userAge:20}})
// 更新集合内的子内容
db.user.update({userName:"jwy"},{$set:{'class.num':20}})
删除文档
db.user.remove({userName:"jwy"})
demo(新建两个集合)
Goods: {
productId: String,
productName: String,
salePrcie: Number,
checked: String,
productNum: Number,
productImage: String
}
Users: {
userId: String,
userName: String,
userPwd: String,
orderList: Array,
cartList: [
{
productId: String,
productName: String,
salePrcie: Number,
checked: String,
productNum: Number
}
],
addressList: [
{
addressId: String,
userNmae: String,
streetName: String,
postCode: Number,
tel: number,
isDefault: Boolean
}
]
}
// 命令导入
mongoimport -d db_demo -c users --file ...
-d: 数据库
-c: 集合
--file: 导入的文件地址
PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。
npm install pm2 -g
pm2 start app.js --watch -i 2
重启
pm2 restart app.js
停止
停止特定的应用。可以先通过pm2 list获取应用的名字(--name指定的)或者进程id。
pm2 stop app_name|app_id
如果要停止所有应用,可以
pm2 stop all
删除
类似pm2 stop,如下
pm2 stop app_name|app_id
pm2 stop all
查看进程状态
pm2 list
在我项目中命令是
pm2 start demo/express_demo/server/bin/www