面试题记录AL
1使用过的koa2中间件
1.什么是koa2中间件?
https://www.cnblogs.com/crbluesky/p/7931946.html
koa对网络请求采用了中间件的形式处理,中间件可以介入请求和相应的处理,是一个轻量级的模块,每个中间负责完成某个特定的功能。中间件的通过next
函数联系,执行next()
后会将控制权交给下一个中间件,如果没有有中间件没有执行next
后将会沿路折返,将控制权交换给前一个中间件。
image
当执行app.listen
方法开启服务器时,实际上是在内部,使用http
模块,启动了http服务器,并将自身的callback
函数传入
二、常用的五个中间件
- 1koa:面向node.js的表达式HTTP中间件框架,使Web应用程序和API更加令人愉快地编写。Koa的中间件堆栈以类似堆栈的方式流动,允许您执行下游操作,然后过滤和处理上游的响应。
var Koa = require('koa');
var app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
- 2koa-router:Router middleware for koa(koa的路由中间件)
const Koa = require('koa');
const Router=require('koa-router');
const app = new Koa();
const router=new Router();
router.get('/',function(ctx,next){
ctx.body='hello hello';
})
app.use(router.routes());
app.listen(3000);
- 3koa-views:Template rendering middleware for koa.(koa的模板渲染中间件)
ctx:为网络处理上下文,next指向下个中间件
const Koa=require('koa');
const views=require('koa-views');
const app=new Koa();
//配置模板解析器
app.use(views(__dirname+'/views',{
map:{
html:'swig'
}
}));
app.use(async function(ctx,next){
await ctx.render('layout');
})
app.listen(3000);
- koa-bodyparser:用来解析body的中间件,比方说你通过post来传递表单,json数据,或者上传文件,在koa中是不容易获取的,通过koa-bodyparser解析之后,在koa中this.body就能直接获取到数据。ps:xml数据没办法通过koa-bodyparser解析,有另一个中间件koa-xml-body。
var Koa = require('koa');
var bodyParser = require('koa-bodyparser');
var app = new Koa();
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}));
app.use(async ctx => {
ctx.body = ctx.request.body;
});
app.listen(3000);
- 5.promise-mysql:Promise-mysql是mysqljs / mysql的一个包装函数,它包含Bluebird承诺的函数调用。通常这会用Bluebird的
.promisifyAll()
方法完成,但是mysqljs / mysql的脚本与Bluebird所期望的不同。
对于重复的代码我们将它封装在一起,方便使用,提高代码的重用性,DbUtil.js
var mysql=require('promise-mysql');
var Promise=require('bluebird');
pool = mysql.createPool({
host: '192.168.22.2',
user: 'root',
password: 'root',
database: 'music',
connectionLimit: 2
});//此处可根据实际情况更改
function getSqlConnection() {
return pool.getConnection().disposer(function(connection) {
pool.releaseConnection(connection);
});
}
async function execSql(sql){
let result=null;
await Promise.using(getSqlConnection(), function(connection) {
return connection.query(sql)
.then(function(rows) {
result=rows;
}).catch(function(error) {
console.log(error);
});
})
return result;
}
module.exports = execSql;
var execSql=require('./DbUtil.js');
function SingerDb(){
}
SingerDb.prototype.querySinger=async function(){
let sql='select * from singer';
return execSql(sql);
}
SingerDb.prototype.querySingers=async function(){
let sql='select id,name from singer';
return execSql(sql);
}
SingerDb.prototype.addSinger=async function(singer){
let sql=`insert into singer (name,englishname,guoji,chushengdi,jobs,picName) values ('${singer.name}','${singer.englishname}','${singer.guoji}','${singer.chushengdi}','${singer.jobs}','${singer.picName}')`;
// execSql(sql).then(function(rows){
// return rows;
// });
return execSql(sql);
}
SingerDb.prototype.delSinger=async function(id){
let sql=`delete from singer where id=${id}`;
return execSql(sql);
}
SingerDb.prototype.querySingerById=async function(id){
let sql=`select * from singer where id=${id}`;
return execSql(sql);
}
SingerDb.prototype.updateSinger=async function(singer){
let sql=`update singer set name='${singer.name}' where id=${singer.id}`;
return execSql(sql);
}
// let singer={
// name:'张靓颖',
// englishname:'amanda',
// guoji:'中国',
// chushengdi:'中国湖南',
// jobs:'歌手'
// }
// new SingerDb().addSinger(singer);
// new SingerDb().querySinger();
module.exports=new SingerDb();
2koa-body原理
https://blog.csdn.net/sinat_17775997/article/details/82898312
想要分析 koa-bodyparser 的原理首先需要知道用法和作用,koa-bodyparser 中间件是将我们的 post 请求和表单提交的查询字符串转换成对象,并挂在 ctx.request.body 上,方便我们在其他中间件或接口处取值,使用前需提前安装。
npm install koa koa-bodyparser
koa-bodyparser 具体用法如下:
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");
const app = new Koa();
// 使用中间件
app.use(bodyParser());
app.use(async (ctx, next) => {
if (ctx.path === "/" && ctx.method === "POST") {
// 使用中间件后 ctx.request.body 属性自动加上了 post 请求的数据
console.log(ctx.request.body);
}
});
app.listen(3000);
根据用法我们可以看出 koa-bodyparser 中间件引入的其实是一个函数,我们把它放在了 use 中执行,根据 Koa 的特点,我们推断出 koa-bodyparser 的函数执行后应该给我们返回了一个 async 函数,下面是我们模拟实现的代码。
const querystring = require("querystring");
module.exports = function bodyParser() {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
// 存储数据的数组
let dataArr = [];
// 接收数据
ctx.req.on("data", data => dataArr.push(data));
// 整合数据并使用 Promise 成功
ctx.req.on("end", () => {
// 获取请求数据的类型 json 或表单
let contentType = ctx.get("Content-Type");
// 获取数据 Buffer 格式
let data = Buffer.concat(dataArr).toString();
if (contentType === "application/x-www-form-urlencoded") {
// 如果是表单提交,则将查询字符串转换成对象赋值给 ctx.request.body
ctx.request.body = querystring.parse(data);
} else if (contentType === "applaction/json") {
// 如果是 json,则将字符串格式的对象转换成对象赋值给 ctx.request.body
ctx.request.body = JSON.parse(data);
}
// 执行成功的回调
resolve();
});
});
// 继续向下执行
await next();
};
};
在上面代码中由几点是需要我们注意的,即 next 的调用以及为什么通过流接收数据、处理数据和将数据挂在 ctx.request.body 要在 Promise 中进行。
首先是 next 的调用,我们知道 Koa 的 next 执行,其实就是在执行下一个中间件的函数,即下一个 use 中的 async 函数,为了保证后面的异步代码执行完毕后再继续执行当前的代码,所以我们需要使用 await 进行等待,其次就是数据从接收到挂在 ctx.request.body 都在 Promise 中执行,是因为在接收数据的操作是异步的,整个处理数据的过程需要等待异步完成后,再把数据挂在 ctx.request.body 上,可以保证我们在下一个 use 的 async 函数中可以在 ctx.request.body 上拿到数据,所以我们使用 await 等待一个 Promise 成功后再执行 next。
3介绍pm2
简介
PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。引用
全局安装
sudo npm install [email protected] -g
用法
- 最简单的启用一个应用:
pm2 start app.js
- 停止:
pm2 stop app_name|app_id
- 删除:
pm2 delete app_name|app_id
- 重启:
pm2 restart app_name|app_id
- 停止所有:
pm2 stop all
- 查看所有的进程:
pm2 list
- 查看所有的进程状态:
pm2 status
- 查看某一个进程的信息:
pm2 describe app_name|app_id
参数说明
-
--watch
:监听应用目录源码的变化,一旦发生变化,自动重启。如果要精确监听、不见听的目录,最好通过配置文件 -
-i --instances
:启用多少个实例,可用于负载均衡。如果-i 0
或者-i max
,则根据当前机器核数确定实例数目,可以弥补node.js缺陷 -
--ignore-watch
:排除监听的目录/文件,可以是特定的文件名,也可以是正则。比如--ignore-watch="test node_modules "some scripts"
-
-n --name
:应用的名称。查看应用信息的时候可以用到 -
-o --output <path>
:标准输出日志文件的路径,有默认路径 -
-e --error <path>
:错误输出日志文件的路径,有默认路径 -
--interpreter <interpreter>
:the interpreter pm2 should use for executing app (bash, python...)。比如你用的coffee script来编写应用
完整参数命令: pm2 start index.js --watch -i 2
配置文件
- 配置文件里的设置项,跟命令行参数基本是一一对应的
- 配置文件的格式可以为json/yaml
- json格式的配置文件,pm2当作普通的js文件来处理,所以可以在里面添加注释或者编写代码,这对于动态调整配置很有好处
- 如果启动的时候指定了配置文件,那么命令行参数会被忽略(个别参数除外,比如--env)
完整参数单个app配置:
{
"name" : "node-app", //启动app名称
"cwd" : "/srv/node-app/current",
"args" : ["--toto=heya coco", "-d", "1"],
"script" : "bin/app.js",
"node_args" : ["--harmony", " --max-stack-size=102400000"],
"log_date_format" : "YYYY-MM-DD HH:mm Z",
"error_file" : "/var/log/node-app/node-app.stderr.log",
"out_file" : "log/node-app.stdout.log",
"pid_file" : "pids/node-geo-api.pid",
"instances" : 6, //or 0 => 'max'
"min_uptime" : "200s", // 200 seconds, defaults to 1000
"max_restarts" : 10, // defaults to 15
"max_memory_restart": "1M", // 1 megabytes, e.g.: "2G", "10M", "100K", 1024 the default unit is byte.
"cron_restart" : "1 0 * * *",
"watch" : false,
"ignore_watch" : ["[\\/\\\\]\\./", "node_modules"],
"merge_logs" : true,
"exec_interpreter" : "node",
"exec_mode" : "fork",
"autorestart" : false, // enable/disable automatic restart when an app crashes or exits
"vizion" : false, // enable/disable vizion features (versioning control)
// Default environment variables that will be injected in any environment and at any start
"env": {
"NODE_ENV": "production",
"AWESOME_SERVICE_API_TOKEN": "xxx"
}
"env_*" : {
"SPECIFIC_ENV" : true
}
}
完整配置文件写法:
{
"apps" : [{
// Application #1
"name" : "worker-app",
"script" : "worker.js",
"args" : ["--toto=heya coco", "-d", "1"],
"watch" : true,
"node_args" : "--harmony",
"merge_logs" : true,
"cwd" : "/this/is/a/path/to/start/script",
"env": {
"NODE_ENV": "development",
"AWESOME_SERVICE_API_TOKEN": "xxx"
},
"env_production" : {
"NODE_ENV": "production"
},
"env_staging" : {
"NODE_ENV" : "staging",
"TEST" : true
}
},{
// Application #2
"name" : "api-app",
"script" : "api.js",
"instances" : 4,
"exec_mode" : "cluster_mode",
"error_file" : "./examples/child-err.log",
"out_file" : "./examples/child-out.log",
"pid_file" : "./examples/child.pid"
}]
}
通过yaml管理多个应用
process.yml:
apps:
- script : app.js
instances: 4
exec_mode: cluster
- script : worker.js
watch : true
env :
NODE_ENV: development
env_production:
NODE_ENV: production
启动:pm2 start process.yml
环境切换
正式开发中分为不同的环境(开发环境、测试环境、生产环境),我们需要根据不同的情景来切换各种环境
pm2通过在配置文件中通过env_xx
来声明不同环境的配置,然后在启动应用时,通过--env
参数指定运行的环境
环境配置定义,在应用中,可以通过process.env.REMOTE_ADDR等来读取配置中生命的变量:
"env": {
"NODE_ENV": "production",
"REMOTE_ADDR": "http://www.example.com/"
},
"env_dev": {
"NODE_ENV": "development",
"REMOTE_ADDR": "http://wdev.example.com/"
},
"env_test": {
"NODE_ENV": "test",
"REMOTE_ADDR": "http://wtest.example.com/"
}
启动指定的环境:pm2 start app.js --env development
负载均衡
pm2 start app.js -i 3 # 开启三个进程
pm2 start app.js -i max # 根据机器CPU核数,开启对应数目的进程
开机自动启动
- 通过pm2 save保存当前进程状态。
- 通过pm2 startup [platform]生成开机自启动的命令。例如:pm2 startup centeros
- 将步骤2生成的命令,粘贴到控制台进行,搞定。
更新
安装最新的:npm install [email protected] -g
然后在内存中更新:pm2 update
参考
- 官方文档1:http://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/
- 官方文档2:http://pm2.keymetrics.io/docs/usage/quick-start/
- PM2实用入门指南:http://www.cnblogs.com/chyingp/p/pm2-documentation.html
- nodejs高大上的部署方式-PM2: http://www.2cto.com/kf/201501/367718.html
4React生命周期
初始化
1、getDefaultProps()
设置默认的props,也可以用dufaultProps设置组件的默认属性.
2、getInitialState()
在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以访问this.props
3、componentWillMount()组建将要挂载
此时组件还未渲染完成,dom还未渲染
组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
4、 render()
react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
5、componentDidMount()
组件渲染完成,此时dom节点已经生成,可以在这里调用ajax请求
组件渲染之后调用,只调用一次。
更新
6、componentWillReceiveProps(nextProps)
在接受父组件改变后的props需要重新渲染组件时需要用到这个函数
组件初始化时不调用,组件接受新的props时调用。
7、shouldComponentUpdate(nextProps, nextState)
setState以后,state发生变化,组件会进入重新渲染的流程,return false可以阻止组件的更新
react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候
8、componentWillUpdata(nextProps, nextState)
组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
9、render()
组件渲染
10、componentDidUpdate()
组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。
卸载
11、componentWillUnmount()
组件将要卸载时调用,一些事件监听和定时器需要在此时清除。
clear你在组建中所有的setTimeout,setInterval
移除所有组建中的监听 removeEventListener
5如何配置React-Router
6路由的动态加载模块
7服务端渲染SSR
8介绍Redux数据流的流程
https://www.jianshu.com/p/e984206553c2
Redux三大原则
1 唯一数据源
2 保持只读状态
3 数据改变只能通过纯函数来执行
1、redux中间件
中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。
常见的中间件:
redux-logger:提供日志输出
redux-thunk:处理异步操作
redux-promise:处理异步操作,actionCreator的返回值是promise
2、redux有什么缺点
1.一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。
2.当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。
3、react性能优化是哪个周期函数?
shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新描绘dom。因为dom的描绘非常消耗性能,如果我们能在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能。
为什么虚拟dom会提高性能?
4、虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
具体实现步骤如下:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
5、diff算法?
把树形结构按照层级分解,只比较同级元素。
给列表结构的每个单元添加唯一的key属性,方便比较。
React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)
合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能。
9常见Http请求头
http请求中的常用头(请求头)的含义:
Accept:告诉服务器,客户端支持的数据类型。
Accept-Charset:告诉服务器,客户端采用的编码。
Accept-Encoding:告诉服务器,客户机支持的数据压缩格式。
Accept-Language:告诉服务器,客户机的语言环境。
Host:客户机通过这个头告诉服务器,想访问的主机名。
If-Modified-Since:客户机通过这个头告诉服务器,资源的缓存时间。
Referer:客户机通过这个头告诉服务器,它是从哪个资源来访问服务器的。(一般用于防盗链)
User-Agent:客户机通过这个头告诉服务器,客户机的软件环境。
Cookie:客户机通过这个头告诉服务器,可以向服务器带数据。
Connection:客户机通过这个头告诉服务器,请求完后是关闭还是保持链接。
Date:客户机通过这个头告诉服务器,客户机当前请求时间。
10移动端适配1px的问题
11介绍flex布局
12他css方式设置垂直居中
13使用过webpack里面哪些plugin和loader
14webpack里面的插件是怎么实现的
15dev-server是怎么跑起来
16怎么实现this对象的深拷贝