世界很大, 多踏出一步, 就进步一步。✊!!欢迎技术交流。
nuohui / fe-note Goto Github PK
View Code? Open in Web Editor NEW我的前端之路
我的前端之路
Function.prototype.bind = function (ctx) {
const _otherParam = Array.prototype.slice.call(arguments, 1)
const _this = this
return function customBind () {
const _params = Array.prototype.slice.call(arguments)
_this.apply(ctx, [].concat(_otherParam, _params))
}
}
个人觉得学习源码需要带着目的去看, 才能达到效果, 但是公司又没有上Node, 没有实践怎么办呢?最近发现通过调试Koa2源码也是个不错的法子。
- 安装node
- 安装vscode
- 学习如何在vscode下调试
关于Node下调试推荐阅读下:《Node.js 调试指南》。
我们这里只需要学习如何在vscode下调试即可。
具体就不详情说了, 见链接, 有问题我们可以讨论。
// 安装koa2, nodemon等后, 来个入门的hello world
const Koa = require('koa')
const app = new Koa()
const port = 3000
app.use(async (ctx, next) => {
await next()
ctx.response.status = 200
ctx.response.body = 'hello world'
})
app.listen(port, () => {
console.log(`server is running on the port: ${port}`)
})
是的没错,通过上述这个入门代码也能学习到Koa2的源码知识。
首先观察上面用到了的一些API。
new Koa()
app.use()
app.listen()
我们现在开始进入node_modules目录下找到Koa。
通过package.json得知Koa的入口文件为
"main": "lib/application.js"
// lib目录下模块
- application.js 实例
- context.js 上下文对象
- request.js 请求对象
- response.js 响应对象
现在我们当然从入口文件application.js开始。我们的实例代码第一行是new koa(); 我们肯定是有一个类,这里是开始的突破点。
// 构造函数继承node的EventEmitter类
// http://nodejs.cn/api/events.html
module.exports = class Application extends Emitter {
...
}
然后我们去打三个断点, 分别如下:
之所以在这三个地方打断点是对应前面提到的执行了Koa2的三个api.通过这三个断点我们一步步去了解Koa2内部是怎么执行的。
最开始肯定是执行constructor();部分注释见上述截图。
this.middleware = []; 这个是用来存放通过app.use()注册的中间件的。
重点接下来看app.use()。
很明显fn指的就是:
async (ctx, next) => {
await next()
ctx.response.status = 200
ctx.response.body = 'hello world'
}
在use(fn)方法中主要做了以下事情:
1. 错误校验, fn必须是函数, 否则给出错误提示
2. fn不推荐使用生成器函数, v2版本Koa2会进行转化, 但是v3就会不支持生成器函数了, 这里主要是对koa1的向下兼容。
3. 存储注册的中间件
3. return this. 支持链式调用
这个时候你可以看懂this大概有这些属性:
Application {
_events:Object {}
_eventsCount:0
_maxListeners:undefined
context:Object {}
env:"development"
middleware:Array(1) []
proxy:false
request:Object {}
response:Object {}
subdomainOffset:2
Symbol(util.inspect.custom):inspect() { … }
__proto__:EventEmitter
}
然后进入listen(), 这里有一段this.callback(), 我们需要去这个方法下打断点看执行了什么。
// 其实就是http.createServer(app.callback()).listen(...)的语法糖
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
// callback()做了以下几件事:
1. 通过compose合并中间件
2. 为应用注册error事件的监听器
3. 返回一个请求处理函数handleRequest
接下来我们看看this.createContext()和this.handleRequest(),分别打断点看代码。
note: 提一个小问题, node应该经常会发生端口占用问题。
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
// 错误处理
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
// 通过第三方库on-finished监听http response,当请求结束时执行回调,这里传入的回调是context.onerror(err),即当错误发生时才执行。
onFinished(res, onerror);
// 即将所有中间件执行(传入请求上下文对象ctx),之后执行响应处理函数(respond(ctx)),当抛出异常时同样使用onerror(err)处理。
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
对respond打断点
/**
* Response helper.
* 在所有中间件执行完之后执行
*/
function respond(ctx) {
// allow bypassing koa
// 通过设置ctx.respond = false来跳过这个函数,但不推荐这样子
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();
}
// 当请求方法为HEAD时,判断响应头是否发送以及响应主体是否为JSON格式,若满足则设置响应Content-Length:
if ('HEAD' == ctx.method) {
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}
// status body
// 当返回的状态码表示有响应主体,但响应主体为空时,将响应主体设置为响应信息或状态码。并当响应头未发送时设置Content-Type与Content-Length:
if (null == body) {
if (ctx.req.httpVersionMajor >= 2) {
body = String(code);
} else {
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);
}
错误处理
onerror(err) {
// 当err不为Error类型时抛出异常。
if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
// 当 err.status 是 404 或 err.expose 是 true 时默认错误处理程序也不会输出错误
if (404 == err.status || err.expose) return;
// 默认情况下,将所有错误输出到 stderr,除非 app.silent 为 true
if (this.silent) return;
const msg = err.stack || err.toString();
console.error();
console.error(msg.replace(/^/gm, ' '));
console.error();
}
Vue.js是单向数据流, 不是双向绑定, 双向绑定知识语法糖(.sync)
.sync修饰符实际上还是通过事件emit包装的语法糖(缩写模式)。
// PersonInfo.vue
<template>
<div>
<select
:value="phoneInfo.areaCode"
placeholder="区号"
@change="handleAreaCodeChange"
>
<option value="+86">+86</option>
<option value="+60">+60</option>
</select>
<input
:value="phoneInfo.phone"
type="number"
placeholder="手机号"
@input="handlePhoneChange"
/>
<input
:value="zipCode"
type="number"
placeholder="邮编"
@input="handleZipCodeChange"
/>
</div>
</template>
<script>
export default {
name: "PersonalInfo",
model: {
prop: "phoneInfo", // 默认 value
event: "change" // 默认 input
},
props: {
phoneInfo: Object,
zipCode: String
},
methods: {
handleAreaCodeChange(e) {
this.$emit("change", {
...this.phoneInfo,
areaCode: e.target.value
});
},
handlePhoneChange(e) {
this.$emit("change", {
...this.phoneInfo,
phone: e.target.value
});
},
handleZipCodeChange(e) {
this.$emit("update:zipCode", e.target.value);
}
}
};
</script>
// App.vue
<template>
<div>
<PersonalInfo v-model="phoneInfo" :zip-code.sync="zipCode" />
<PersonalInfo
:phone-info="phoneInfo"
:zip-code="zipCode"
@change="val => (phoneInfo = val)"
@update:zipCode="val => (zipCode = val)"
/>
phoneInfo: {{ phoneInfo }}
<br />
zipCode: {{ zipCode }}
</div>
</template>
<script>
import PersonalInfo from "./page/PersonaInfo.vue";
export default {
components: {
PersonalInfo
},
data() {
return {
phoneInfo: {
areaCode: "+86",
phone: ""
},
zipCode: ""
};
}
};
</script>
<style lang="scss" scoped>
html, body{
height: 100%;
}
body{
width: 100%;
}
</style>
v-model="phoneInfo"
实际上等同于
:phone-info="phoneInfo"
@change="val => (phoneInfo = val)"
另外在自组件可以自定义model
model: {
prop: "phoneInfo", // 默认 value
event: "change" // 默认 input
}
:zip-code.sync="zipCode"
实际上等同于
:zip-code="zipCode"
@update:zipCode="val => (zipCode = val)"
文中写到“索引是可以劫持get/set的, 但是如果监听索引的话, 如果你push一个元素进来, 那个元素的索引就没有被劫持, 那么就不会是响应式的”,那么如果push的时候也做了劫持处理呢?
function bubbleSort(list) {
if (Array.isArray(list)) {
if (list.length <= 1) return list;
// 外层循环控制总的遍历轮数,理论上等于数组长度
for(let i = 0; i < list.length; i++) {
// j才是真正对比数据的轮,- i是因为需要排除之前每轮对比出来的数
let flag = false // 标示是否有数据发生交换
for(let j = 0; j < list.length - 1 - i; j++) {
if (list[j + 1] < list[j]) {
const temp = list[j];
list[j] = list[j + 1];
list[j + 1] = temp;
flag = true;
}
}
// 没有数据发生交换说明已排序完成
if (!flag) {
break
}
}
}
return list;
}
比较两棵DOM树的差异是Virtual DOM算法最核心的部分.简单的说就是新旧虚拟dom 的比较,如果有差异就以新的为准,然后再插入的真实的dom中,重新渲染。借网络一张图片说明:
比较只会在同层级进行, 不会跨层级比较。
比较后会出现四种情况:
结果: 在经过Diff之后, BC节点互相换了位置。并没有节点的删除与新建。
删除CEF节点再新建CEF节点。
删除CEF新建GEF节点。
B1/B2是更新了, E/F是先删除后新建
仅仅B2节点更新了
有key值只需要插入一个B4。
B2更新为B4, B3更新为B2, 插入B4。
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
npm config set sharp_dist_base_url https://npm.taobao.org/mirrors/sharp-libvips/
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
npm config set puppeteer_download_host https://npm.taobao.org/mirrors/
npm config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/
npm config set sentrycli_cdnurl https://npm.taobao.org/mirrors/sentry-cli/
npm config set sqlite3_binary_site https://npm.taobao.org/mirrors/sqlite3/
npm config set python_mirror https://npm.taobao.org/mirrors/python/
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { def } from '../util/index'
// 数组原型
const arrayProto = Array.prototype
// 数组代理原型对象
export const arrayMethods = Object.create(arrayProto)
// 变异数组方法:执行后会改变原始数组的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method 缓存原始的数组原型上的方法
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 返回的value还是通过数组原型方法本书执行的结果
const result = original.apply(this, args)
const ob = this.__ob__
let inserted // 存储调用执行变异数组方法导致数组本身值改变的数组,主要指的是原始数组增加的那部分(需要重新Observer)
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 观察新增加的数组元素
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
// 如果支持__proto__属性: 直接把原型志向代理原型对象
protoAugment(value, arrayMethods)
} else {
// 不支持就在数组实例上定义同名的变异方法(且不可枚举)进行拦截
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
// target: 需要被Observe的对象
// src: 数组代理原型对象
// keys: const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
// keys: 数组代理原型对象上的几个编译方法名
// const methodsToPatch = [
// 'push',
// 'pop',
// 'shift',
// 'unshift',
// 'splice',
// 'sort',
// 'reverse'
// ]
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.