总结
大厂面试问深度,小厂面试问广度,如果有同学想进大厂深造一定要有一个方向精通的惊艳到面试官,还要平时遇到问题后思考一下问题的本质,找方法解决是一个方面,看到问题本质是另一个方面。还有大家一定要有目标,我在很久之前就想着以后一定要去大厂,然后默默努力,每天看一些大佬们的文章,总是觉得只有再学深入一点才有机会,所以才有恒心一直学下去。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
简单说下chrome
调试nodejs
,chrome
浏览器打开chrome://inspect
,点击配置**configure…**配置127.0.0.1:端口号
(端口号在Vscode
调试控制台显示了)。
更多可以查看English Debugging Guide
中文调试指南
喜欢看视频的读者也可以看慕课网这个视频node.js调试入门,讲得还是比较详细的。
不过我感觉在chrome
调试nodejs
项目体验不是很好(可能是我方式不对),所以我大部分具体的代码时都放在html
文件script
形式,在chrome
调试了。
先看看 new Koa()
结果app
是什么
看源码我习惯性看它的实例对象结构,一般所有属性和方法都放在实例对象上了,而且会通过原型链查找形式查找最顶端的属性和方法。
用koa/examples/middleware/app.js
文件调试时,先看下执行new Koa()
之后,app
是什么,有个初步印象。
// 文件 koa/examples/middleware/app.js
const Koa = require(‘…/…/lib/application’);
// const Koa = require(‘koa’);
// 这里打个断点
const app = new Koa();
// x-response-time
// 这里打个断点
app.use(async (ctx, next) => {
});
在调试控制台ctrl + 反引号键(一般在
Tab上方的按键)唤起
,输入app
,按enter
键打印app
。会有一张这样的图。
koa 实例对象调试图
VScode
也有一个代码调试神器插件Debug Visualizer
。
安装好后插件后,按ctrl + shift + p
,输入Open a new Debug Visualizer View
,来使用,输入app
,显示是这样的。
koa 实例对象可视化简版
不过目前体验来看,相对还比较鸡肋,只能显示一级,而且只能显示对象,相信以后会更好。更多玩法可以查看它的文档。
我把koa实例对象比较完整的用xmind
画出来了,大概看看就好,有个初步印象。
koa 实例对象
接着,我们可以看下app 实例、context、request、request
的官方文档。
app 实例、context、request、request 官方API文档
- index API | context API | request API | response API
可以真正使用的时候再去仔细看文档。
koa 主流程梳理简化
通过F5启动调试(直接跳到下一个断点处)
、F10单步跳过
、F11单步调试
等,配合重要的地方断点,调试完整体代码,其实比较容易整理出如下主流程的代码。
class Emitter{
// node 内置模块
constructor(){
}
}
class Koa extends Emitter{
constructor(options){
super();
options = options || {};
this.middleware = [];
this.context = {
method: ‘GET’,
url: ‘/url’,
body: undefined,
set: function(key, val){
console.log(‘context.set’, key, val);
},
};
}
use(fn){
this.middleware.push(fn);
return this;
}
listen(){
const fnMiddleware = compose(this.middleware);
const ctx = this.context;
const handleResponse = () => respond(ctx);
const onerror = function(){
console.log(‘onerror’);
};
fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
}
function respond(ctx){
console.log(‘handleResponse’);
console.log(‘response.end’, ctx.body);
}
重点就在listen
函数里的compose
这个函数,接下来我们就详细来欣赏下这个函数。
koa-compose 源码(洋葱模型实现)
通过app.use()
添加了若干函数,但是要把它们串起来执行呀。像上文的gif
图一样。
compose
函数,传入一个数组,返回一个函数。对入参是不是数组和校验数组每一项是不是函数。
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError(‘Middleware stack must be an array!’)
for (const fn of middleware) {
if (typeof fn !== ‘function’) throw new TypeError(‘Middleware must be composed of functions!’)
}
// 传入对象 context 返回Promise
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error(‘next() called multiple times’))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
把简化的代码和koa-compose
代码写在了一个文件中。koa/examples/simpleKoa/koa-compose.js
hs koa/examples/
然后可以打开localhost:8080/simpleKoa,开心的把代码调试起来
不过这样好像还是有点麻烦,我还把这些代码放在codepen
https://codepen.io/lxchuan12/pen/wvarPEb中,直接可以在线调试啦。是不是觉得很贴心_,自己多调试几遍便于消化理解。
你会发现compose
就是类似这样的结构(移除一些判断)。
// 这样就可能更好理解了。
// simpleKoaCompose
const [fn1, fn2, fn3] = this.middleware;
const fnMiddleware = function(context){
return Promise.resolve(
fn1(context, function next(){
return Promise.resolve(
fn2(context, function next(){
return Promise.resolve(
fn3(context, function next(){
return Promise.resolve();
})
)
})
)
})
);
};
fnMiddleware(ctx).then(handleResponse).catch(onerror);
也就是说
koa-compose
返回的是一个Promise
,Promise
中取出第一个函数(app.use
添加的中间件),传入context
和第一个next
函数来执行。
第一个
next
函数里也是返回的是一个Promise
,Promise
中取出第二个函数(app.use
添加的中间件),传入context
和第二个next
函数来执行。
第二个
next
函数里也是返回的是一个Promise
,Promise
中取出第三个函数(app.use
添加的中间件),传入context
和第三个next
函数来执行。
第三个…
以此类推。最后一个中间件中有调用
next
函数,则返回Promise.resolve
。如果没有,则不执行next
函数。这样就把所有中间件串联起来了。这也就是我们常说的洋葱模型。
不得不说非常惊艳,“玩还是大神会玩”。
这种把函数存储下来的方式,在很多源码中都有看到。比如lodash
源码的惰性求值,vuex
也是把action
等函数存储下,最后才去调用。
搞懂了koa-compose
洋葱模型实现的代码,其他代码就不在话下了。
错误处理
中文文档 错误处理
仔细看文档,文档中写了三种捕获错误的方式。
-
ctx.onerror
中间件中的错误捕获 -
app.on('error', (err) => {})
最外层实例事件监听形式 也可以看看例子koajs/examples/errors/app.js 文件 -
app.onerror = (err) => {}
重写onerror
自定义形式 也可以看测试用例 onerror
// application.js 文件
class Application extends Emitter {
// 代码有简化组合
listen(){
const fnMiddleware = compose(this.middleware);
if (!this.listenerCount(‘error’)) this.on(‘error’, this.onerror);
const onerror = err => ctx.onerror(err);
fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
onerror(err) {
// 代码省略
// …
}
}
ctx.onerror
lib/context.js
文件中,有一个函数onerror
,而且有这么一行代码this.app.emit('error', err, this)
。
module.exports = {
onerror(){
// delegate
// app 是在new Koa() 实例
this.app.emit(‘error’, err, this);
}
}
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
err.status = err.statusCode || err.status || 500;
throw err;
}
});
try catch
错误或被fnMiddleware(ctx).then(handleResponse).catch(onerror);
,这里的onerror
是ctx.onerror
而ctx.onerror
函数中又调用了this.app.emit('error', err, this)
,所以在最外围app.on('error',err => {})
可以捕获中间件链中的错误。因为koa
继承自events模块
,所以有’emit’和on
等方法)
koa2 和 koa1 的简单对比
中文文档中描述了 koa2 和 koa1 的区别
koa1
中主要是generator
函数。koa2
中会自动转换generator
函数。
// Koa 将转换
app.use(function *(next) {
const start = Date.now();
yield next;
const ms = Date.now() - start;
console.log(${this.method} ${this.url} - ${ms}ms
);
});
koa-convert 源码
在vscode/launch.json
文件,找到这个program
字段,修改为"program": "${workspaceFolder}/koa/examples/koa-convert/app.js"
。
通过F5启动调试(直接跳到下一个断点处)
、F10单步跳过
、F11单步调试
调试走一遍流程。重要地方断点调试。
app.use
时有一层判断,是否是generator
函数,如果是则用koa-convert
暴露的方法convert
来转换重新赋值,再存入middleware
,后续再使用。
class Koa extends Emitter{
use(fn) {
if (typeof fn !== ‘function’) throw new TypeError(‘middleware must be a function!’);
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ’ +
'See the documentation for examples of how to convert old middleware ’ +
‘https://github.com/koajs/koa/blob/master/docs/migration.md’);
fn = convert(fn);
}
debug(‘use %s’, fn._name || fn.name || ‘-’);
this.middleware.push(fn);
return this;
}
}
koa-convert
源码挺多,核心代码其实是这样的。
function convert(){
return function (ctx, next) {
return co.call(ctx, mw.call(ctx, createGenerator(next)))
}
function * createGenerator (next) {
return yield next()
}
}
最后还是通过co
来转换的。所以接下来看co
的源码。
co 源码
tj大神写的co 仓库
本小节的示例代码都在这个文件夹koa/examples/co-generator
中,hs koa/example
,可以自行打开https://localhost:8080/co-generator
调试查看。
看co
源码前,先看几段简单代码。
// 写一个请求简版请求
function request(ms= 1000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({name: ‘若川’});
}, ms);
});
}
// 获取generator的值
function* generatorFunc(){
const res = yield request();
console.log(res, ‘generatorFunc-res’);
}
generatorFunc(); // 报告,我不会输出你想要的结果的
简单来说co
,就是把generator
自动执行,再返回一个promise
。generator
函数这玩意它不自动执行呀,还要一步步调用next()
,也就是叫它走一步才走一步。
所以有了async、await
函数。
// await 函数 自动执行
async function asyncFunc(){
const res = await request();
console.log(res, ‘asyncFunc-res await 函数 自动执行’);
}
asyncFunc(); // 输出结果
也就是说co
需要做的事情,是让generator
向async、await
函数一样自动执行。
模拟实现简版 co(第一版)
这时,我们来模拟实现第一版的co
。根据generator
的特性,其实容易写出如下代码。
// 获取generator的值
function* generatorFunc(){
const res = yield request();
console.log(res, ‘generatorFunc-res’);
}
function coSimple(gen){
gen = gen();
console.log(gen, ‘gen’);
const ret = gen.next();
const promise = ret.value;
promise.then(res => {
gen.next(res);
});
}
coSimple(generatorFunc);
// 输出了想要的结果
// {name: “若川”}“generatorFunc-res”
模拟实现简版 co(第二版)
但是实际上,不会上面那么简单的。有可能是多个yield
和传参数的情况。传参可以通过这如下两行代码来解决。
const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args);
两个yield
,我大不了重新调用一下promise.then
,搞定。
// 多个yeild,传参情况
function* generatorFunc(suffix = ‘’){
const res = yield request();
console.log(res, ‘generatorFunc-res’ + suffix);
const res2 = yield request();
console.log(res2, ‘generatorFunc-res-2’ + suffix);
}
function coSimple(gen){
const ctx = this;
const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args);
console.log(gen, ‘gen’);
const ret = gen.next();
const promise = ret.value;
promise.then(res => {
const ret = gen.next(res);
const promise = ret.value;
promise.then(res => {
gen.next(res);
});
});
}
coSimple(generatorFunc, ’ 哎呀,我真的是后缀’);
模拟实现简版 co(第三版)
问题是肯定不止两次,无限次的yield
的呢,这时肯定要把重复的封装起来。而且返回是promise
,这就实现了如下版本的代码。
function* generatorFunc(suffix = ‘’){
const res = yield request();
console.log(res, ‘generatorFunc-res’ + suffix);
const res2 = yield request();
console.log(res2, ‘generatorFunc-res-2’ + suffix);
const res3 = yield request();
console.log(res3, ‘generatorFunc-res-3’ + suffix);
const res4 = yield request();
console.log(res4, ‘generatorFunc-res-4’ + suffix);
}
function coSimple(gen){
const ctx = this;
const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args);
console.log(gen, ‘gen’);
return new Promise((resolve, reject) => {
onFulfilled();
function onFulfilled(res){
const ret = gen.next(res);
next(ret);
}
function next(ret) {
const promise = ret.value;
promise && promise.then(onFulfilled);
}
});
}
coSimple(generatorFunc, ’ 哎呀,我真的是后缀’);
但第三版的模拟实现简版co
中,还没有考虑报错和一些参数合法的情况。
最终来看下co
源码
这时来看看co
的源码,报错和错误的情况,错误时调用reject
,是不是就好理解了一些呢。
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
// 把参数传递给gen函数并执行
if (typeof gen === ‘function’) gen = gen.apply(ctx, args);
// 如果不是函数 直接返回
if (!gen || typeof gen.next !== ‘function’) return resolve(gen);
onFulfilled();
/**
- @param {Mixed} res
文末
我一直觉得技术面试不是考试,考前背背题,发给你一张考卷,答完交卷等通知。
首先,技术面试是一个 认识自己 的过程,知道自己和外面世界的差距。
更重要的是,技术面试是一个双向了解的过程,要让对方发现你的闪光点,同时也要 试图去找到对方的闪光点,因为他以后可能就是你的同事或者领导,所以,面试官问你有什么问题的时候,不要说没有了,要去试图了解他的工作内容、了解这个团队的氛围。
前端面试题汇总
JavaScript
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
性能
linux
前端资料汇总