-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
674 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
.history | ||
.vscode | ||
node_modules | ||
yarn-error.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
#### 浏览器相关知识点总结 | ||
|
||
> 1、浏览器进程 | ||
* 主进程:只有一个主要是负责各个进程之间的调控 | ||
* gpu 进程:只要是用于 3d 渲染 | ||
* 后台修复进程 | ||
* tab 进程(浏览器渲染进程):开多个 tab 的时候相互之间不会受到影响。控制各个 tab 自己的渲染,脚本执行,事件处理等等事情。 | ||
* 插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建 | ||
|
||
> 2、多线程的浏览器渲染进程 | ||
* GUI 渲染线程 | ||
* JS 引擎线程(单线程) | ||
* 事件触发线程 | ||
* 定时器线程 | ||
* 网络请求线程 | ||
|
||
> 3、网络请求都是单独的线程 | ||
每次网络请求时都需要开辟单独的线程进行,譬如如果 URL 解析到 http 协议,就会新建一个网络线程去处理资源下载 | ||
|
||
因此浏览器会根据解析出得协议,开辟一个网络线程,前往请求资源(这里,暂时理解为是浏览器内核开辟的,如有错误,后续修复) | ||
|
||
> 4、tcp/ip 并发限制 | ||
浏览器在同一域名下的并发 tcp 链接限制一般是 2 到 10 个。 | ||
|
||
> 5、performance | ||
```js | ||
// 计算加载时间 | ||
function getPerformanceTiming() { | ||
var performance = window.performance | ||
|
||
if (!performance) { | ||
// 当前浏览器不支持 | ||
console.log('你的浏览器不支持 performance 接口') | ||
return | ||
} | ||
|
||
var t = performance.timing | ||
var times = {} | ||
|
||
//【重要】页面加载完成的时间 | ||
//【原因】这几乎代表了用户等待页面可用的时间 | ||
times.loadPage = t.loadEventEnd - t.navigationStart | ||
|
||
//【重要】解析 DOM 树结构的时间 | ||
//【原因】反省下你的 DOM 树嵌套是不是太多了! | ||
times.domReady = t.domComplete - t.responseEnd | ||
|
||
//【重要】重定向的时间 | ||
//【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com | ||
times.redirect = t.redirectEnd - t.redirectStart | ||
|
||
//【重要】DNS 查询时间 | ||
//【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长? | ||
// 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364) | ||
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart | ||
|
||
//【重要】读取页面第一个字节的时间 | ||
//【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么? | ||
// TTFB 即 Time To First Byte 的意思 | ||
// 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte | ||
times.ttfb = t.responseStart - t.navigationStart | ||
|
||
//【重要】内容加载完成的时间 | ||
//【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么? | ||
times.request = t.responseEnd - t.requestStart | ||
|
||
//【重要】执行 onload 回调函数的时间 | ||
//【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么? | ||
times.loadEvent = t.loadEventEnd - t.loadEventStart | ||
|
||
// DNS 缓存时间 | ||
times.appcache = t.domainLookupStart - t.fetchStart | ||
|
||
// 卸载页面的时间 | ||
times.unloadEvent = t.unloadEventEnd - t.unloadEventStart | ||
|
||
// TCP 建立连接完成握手的时间 | ||
times.connect = t.connectEnd - t.connectStart | ||
|
||
return times | ||
} | ||
``` | ||
|
||
> 6、性能优化 | ||
* DNS 查询 ============ | ||
|
||
1、控制域名数量,推荐是两个。 | ||
|
||
2、使用缓存 Last-Modified,If-Modified-Since,ETag,If-None-Match,Expires,Cache-Control。 | ||
|
||
3、使用 CDN,提高缓存命中率。 | ||
|
||
4、服务根据需要设置合理的 TTL。 | ||
|
||
5、DNS 的预解析。 | ||
|
||
* 建立连接================= | ||
|
||
1、合并请求,减少请求次数。 | ||
|
||
2、持久连接 keep-alive,避免重新建立连接。 | ||
|
||
3、避免重定向。 | ||
|
||
* 发送请求 =================== | ||
|
||
1、请求数据最小化。 | ||
|
||
2、避免重定向。 | ||
|
||
* 接收数据================ | ||
|
||
1、使用缓存 Last-Modified,If-Modified-Since,ETag,If-None-Match,Expires,Cache-Control | ||
|
||
2、降低传输数据量,避免冗余的数据,适当的压缩后再进行传输。 | ||
|
||
3、传输过程中的压缩。 | ||
|
||
4、使用 CDN,缩短传输链路。 | ||
|
||
* 解析 DOM 树 =================== | ||
|
||
1、简化 DOM 结构,嵌套不要太深。 | ||
|
||
2、css 放头部,js 放底部(?有争议~) | ||
|
||
3、延迟或者异步加载资源。 | ||
|
||
4、按照规范书写 html 文档,浏览器会有很多容错机制,但是这些会带来一些额外的消耗。所以没有必要的情况下,请严格按照 html 规范来。 | ||
|
||
5、同步加载的 js 除了会阻塞页面的解析,如果其中有修改 DOM 结构的相关代码,还会导致 DOM 重新解析,非常划不来。 | ||
|
||
* DOMContentLoaded 事件耗时 | ||
|
||
1、避免多余操作,延迟加载或者按需加载。 | ||
|
||
2、精简代码。 | ||
|
||
3、提高执行效率。 | ||
|
||
|
||
|
||
优化方向 | ||
|
||
优化手段 | ||
|
||
请求数量 | ||
|
||
合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域 | ||
|
||
请求带宽 | ||
|
||
开启GZip,精简JavaScript,移除重复脚本,图像优化 | ||
|
||
缓存利用 | ||
|
||
使用CDN,使用外部JavaScript和CSS,添加Expires头,减少DNS查找,配置ETag,使AjaX可缓存 | ||
|
||
页面结构 | ||
|
||
将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 | ||
|
||
代码校验 | ||
|
||
避免CSS表达式,避免重定向 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
#### 关于 koa2 的源码学习总结 | ||
|
||
通过一个简单的例子来看看 koa2 的源码 | ||
**示例** | ||
|
||
原生 http 请求方式 | ||
|
||
```js | ||
const http = require('http') | ||
|
||
const server = http.createServer((req, res) => { | ||
res.statusCode = 200 | ||
res.setHeader('Content-Type', 'text/plain') | ||
res.end('Hello World\n') | ||
}) | ||
|
||
server.listen(3000) | ||
``` | ||
|
||
koa2 使用方式 | ||
|
||
```js | ||
const Koa = require('koa') | ||
const app = new Koa() | ||
|
||
app.use(ctx => { | ||
ctx.body = 'Hello Koa' | ||
}) | ||
|
||
app.listen(3000) | ||
``` | ||
|
||
> 1、构造函数 `application.js` | ||
这个文件里面主要是在做`new` 一个`koa`实例的初始化工作,以及收集中间件等操作。关键操作就是下面这几步。 | ||
|
||
```js | ||
this.middleware = [] // 用于存储中间件的数组 | ||
this.context = Object.create(context) // 创建上下文 | ||
this.request = Object.create(request) // 创建request | ||
this.response = Object.create(response) // 创建response | ||
``` | ||
|
||
> 2、注册中间件 | ||
```js | ||
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; | ||
} | ||
``` | ||
|
||
初始化构造函数完成后,如果我们有很多中间件的话,那么这个时候就是调用 use 来注册中间件了。上面就是注册中间件的方法。这个方法里面主要就是判断,传进来的是不是一个函数,如果是一个迭代器函数,那么会调用 convert 方法,将迭代器函数转化成为普通的函数,当然还会提示你不要用迭代器了。最后就是将传进来的函数存在构造函数里面声明的 middleware 数组中。 | ||
|
||
> 3、调用 listen 方法 | ||
```js | ||
listen(...args) { | ||
debug('listen'); | ||
const server = http.createServer(this.callback()); | ||
return server.listen(...args); | ||
} | ||
``` | ||
|
||
在构造函数里面初始化完成后,按照我们的使用方式就是注册中间件,然后在最后的时候调用 listen 方法。这个方法很简单,就是以向原生 http.createServer 传入一个函数的形式来创建自身的一个实例。listen 方法就做了这么简单的一个事。 | ||
|
||
> 4、在看 this.callback() | ||
看到这里我们就明白我们实际上最关心的就是这个 this.callback 函数。其实这个也是 koa2 的核心所在。 | ||
|
||
```js | ||
callback() { | ||
const fn = compose(this.middleware); | ||
// 这里是调用Emitter类里面的方法 | ||
if (!this.listeners('error').length) this.on('error', this.onerror); | ||
// 包装函数,将ctx和中间件和并函数传给内部 | ||
const handleRequest = (req, res) => { | ||
// 基于req和req封装出我们使用的ctx对象。 | ||
const ctx = this.createContext(req, res); | ||
return this.handleRequest(ctx, fn); | ||
}; | ||
|
||
return handleRequest; | ||
} | ||
``` | ||
|
||
this.callback 执行的结果是一个函数,这个函数的主要作用就是根据 req 获取请求信息,然后向 res 中写入返回内容。具体做法就是在一开始的时候合并中间件返回一个函数。然后基于 res 和 req 封装出我们平时使用的 ctx 对象。接着就是调用 koa 自己的 handleRequest 方法,将合并好的中间件函数和刚生成的 ctx 对象传入。 | ||
|
||
> 5、创建 ctx---createContext | ||
前面已经说过了 ctx 这个对象就是基于 res 和 res 封装来的,接下来就看看是怎么封装的。 | ||
|
||
```js | ||
createContext(req, res) { | ||
const context = Object.create(this.context); | ||
const request = context.request = Object.create(this.request); | ||
const response = context.response = Object.create(this.response); | ||
context.app = request.app = response.app = this; | ||
context.req = request.req = response.req = req; | ||
context.res = request.res = response.res = res; | ||
request.ctx = response.ctx = context; | ||
request.response = response; | ||
response.request = request; | ||
context.originalUrl = request.originalUrl = req.url; | ||
context.cookies = new Cookies(req, res, { | ||
keys: this.keys, | ||
secure: request.secure | ||
}); | ||
request.ip = request.ips[0] || req.socket.remoteAddress || ''; | ||
context.accept = request.accept = accepts(req); | ||
context.state = {}; | ||
return context; | ||
} | ||
``` | ||
|
||
上面的主要操作就是创建了三个对象 context,request,response | ||
|
||
> 6、关于 handleRequest 函数 | ||
上面我们知道传进 handleRequest 方法的参数就是经过封装的 ctx 和合并后的中间件函数。并且将他们的原型指定为我们 app 中对应的对象,然后将原生的 req 和 res 赋值给相应的属性,就完成了。 | ||
|
||
```js | ||
handleRequest(ctx, fnMiddleware) { | ||
const res = ctx.res; | ||
res.statusCode = 404; | ||
const onerror = err => ctx.onerror(err); | ||
// response 辅助函数 | ||
const handleResponse = () => respond(ctx); | ||
// onFinished 是确保一个流在关闭、完成和报错时都会执行相应的回调函数 | ||
onFinished(res, onerror); | ||
return fnMiddleware(ctx).then(handleResponse).catch(onerror); | ||
} | ||
``` | ||
|
||
这里做的主要是将封装的 ctx 传给合并后的中间件函数 fnMiddleware,中间件函数返回的是一个 promise。resolve 的话就调用 handleResponse,reject 的话就调用 onerror。handleResponse 里面主要做的操作就是通过 ctx 中的信息向 res 中写入信息。 | ||
|
||
> 7、respond 的分析 | ||
respond 方法就是一个辅助方法,主要作用就是根据 ctx 中的相关信息向 res 中写入信息。 | ||
|
||
```js | ||
function respond(ctx) { | ||
// allow bypassing koa | ||
if (false === ctx.respond) return | ||
|
||
const res = ctx.res | ||
if (!ctx.writable) return | ||
|
||
let body = ctx.body | ||
const code = ctx.status | ||
|
||
// ignore body | ||
if (statuses.empty[code]) { | ||
// strip headers | ||
ctx.body = null | ||
return res.end() | ||
} | ||
|
||
if ('HEAD' == ctx.method) { | ||
if (!res.headersSent && isJSON(body)) { | ||
ctx.length = Buffer.byteLength(JSON.stringify(body)) | ||
} | ||
return res.end() | ||
} | ||
|
||
// status body | ||
if (null == body) { | ||
body = ctx.message || String(code) | ||
if (!res.headersSent) { | ||
ctx.type = 'text' | ||
ctx.length = Buffer.byteLength(body) | ||
} | ||
return res.end(body) | ||
} | ||
|
||
// responses | ||
if (Buffer.isBuffer(body)) return res.end(body) | ||
if ('string' == typeof body) return res.end(body) | ||
if (body instanceof Stream) return body.pipe(res) | ||
|
||
// body: json | ||
body = JSON.stringify(body) | ||
if (!res.headersSent) { | ||
ctx.length = Buffer.byteLength(body) | ||
} | ||
res.end(body) | ||
} | ||
``` |
Oops, something went wrong.