常用命令

submodule

git submodule add 仓库url

git diff --cached --submodule

clone后

1
2
git submodule init
git submodule update

clone时自动 拉取git clone --recurse-submodules

git log -p --submodule

subtree

git subtree add --squash --prefix=文件夹名 仓库url 分支名

分割

git subtree split --prefix=文件夹名 -b 分支名

配合remote使用

比较

subtree submodule
相当于拷贝文件甚至commit到当前仓库 相当于只记录了另一个仓库的某个提交的指针
因此 pull容易push难 push容易pull难

参考

https://git-scm.com/book/en/v2/Git-Tools-Submodules

https://git-scm.com/book/en/v1/Git-Tools-Subtree-Merging

本文对应版本 638278b334199f17e052a54a0837c97624940c0c

获得代码

1
2
3
4
git init
git remote add origin https://github.com/vuejs/vue-router.git
git fetch origin 638278b334199f17e052a54a0837c97624940c0c
git reset --hard FETCH_HEAD

前置知识

https://cn.vuejs.org/v2/guide/mixins.html

https://cn.vuejs.org/v2/guide/plugins.html

总览

行数 文件
262 index.js
200 create-matcher.js
353 history/base.js
22 history/errors.js
69 history/abstract.js
80 history/html5.js
157 history/hash.js
190 components/link.js
124 components/view.js
52 install.js
193 create-route-map.js
6 util/misc.js
95 util/query.js
3 util/dom.js
25 util/warn.js
152 util/scroll.js
35 util/params.js
132 util/route.js
64 util/location.js
22 util/state-key.js
18 util/async.js
42 util/push-state.js
74 util/path.js
108 util/resolve-components.js

总共2478行

目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
src/
├── components
│   ├── link.js 控制url显示
│   └── view.js 控制页面渲染
├── create-matcher.js 路由匹配
├── create-route-map.js
├── history
│   ├── abstract.js
│   ├── base.js
│   ├── errors.js
│   ├── hash.js
│   └── html5.js
├── index.js 包含 VueRouter 类
├── install.js 日常mixin
└── util
├── async.js
├── dom.js
├── location.js
├── misc.js
├── params.js
├── path.js
├── push-state.js
├── query.js
├── resolve-components.js
├── route.js
├── scroll.js
├── state-key.js
└── warn.js

回顾那个只有1100行左右的vuex源码,你发现 除了两者都有index.js,其它目录结构大不相同了

准备

首先我们看一段代码,最好先了解它的功能,所以没有用过vue-router的建议先照着tutorial知道它大概的功能

然后看看源码里的demo:

npm installPORT=8085 npm run dev即可在localhost:8085上查看,把上面每个example都看一看

然后就可以看看flow文件夹里的,也不长,’类’的数量也不多,通过阅读flow的文件,你可以对 整个router有个印象

从代码使用上看

  • 实例类router
  • 配置路径 和 对应component
  • 页面<router-view>,<router-link> 以及 a标签等跳转
  • Vue.use(VueRouter) // 注册插件
  • new Vue({router传入实例})

this上会有$route$router

src/

代码阅读

  1. 先看install.js,调用Vue.mixin 注入
  2. 然后index.js, 看完这一部分基本 就大概看到了 主要是靠XXXHistory来 实现 this.history然后方法 不少只是对 this.history的方法转发
  3. 既然知道了主要是XXXHistory来实现,那么 反过来 先按照顺序看util文件夹,伴随着test里的测试用例看完util的代码,把大多代码看懂即可,少部分比较复杂也没有测试用例的大自看看
  4. 然后看history的代码,顺序就errors.js ,base.js,最后 abstract/hash/html5这三个任意顺序
  5. 然后是两个create-*.js,工具人 工具函数
  6. 最后看两个components

下面目录是按字典序排列的,不是按照阅读顺序

components

emmm, noop = ()=>{}

所以 也就会想到说 ,是 函数内 增加一堆if来处理 有值 无值 空值,还是说 强行要求传入符合格式,这两种方式 如何选择

也就是 render函数

可以看到 有从 初始化的options中 读取 linkActiveClass/linkExactActiveClass用什么 或者用默认的router-link-active/router-link-exact-active

这里实现了一个 guardEvent(e) 不会拦截 metaKey,altKey,ctrlKey,shiftKey等等

当拦截时,执行replace或者push

定义变量on配置了click和 传入的event或event数组里所有事件 调用 handler也就是 guardEvent

这里通过 this.$scopedSlots.default({ 拿去默认slot

然后对它渲染, 这里又可以看到 如果有多个default slot,在production时会报warn

然后 props的tag默认是’a’标签,

如果是 tag=='a'那么 绑上onattrs

如果不是tag=='a'// find the first <a> child and apply listener and href

const a = findAnchor(this.$slots.default) 递归 找第一个 achild

如果没找到data.on = on;

最后 调用 h(this.tag, data包含了on class 等等, this.$slots.default)

其中提供 的两个辅助函数 guardEventfindAnchor分别时 用来 拦截默认 a标签事件和 深搜找到首个a标签的

view.js

也就是 渲染html上的 RouterView 或者说<router-view>

functional:true

意味着 没有响应式数据,没有this上下文

https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6

接受一个props,

1
2
3
4
name: {
type: String,
default: 'default'
}

可以看到接受的render 也和 link的不一样,link的是 render(h)

1
render (_, { props, children, parent, data }) {

给devtools用的

1
2
// used by devtools to display a router-view badge
data.routerView = true

因为没有this,没有接受 h ,这里是通过 h = parent.$createElement

此外 $route,_routerViewCache也是从 parent拿的

通过 比较 parent == parent._routerRoot 来看parent是否为根节点,并递归向上找根节点

如果 某个祖先上有parent.$vnode.data.routerView 那么 深度计数++

如果 某个祖先上有parent.$vnode.data.keepAliveparent._inactive 那么 inactive标识为 true

data.routerViewDepth = 深度计数

如果非活跃

1
2
3
if (inactive) {
return h(cache[name], data, children)
}

通过 $route.matched[深度] 当前的路由的matched数组

1
2
3
4
5
6
const matched = route.matched[depth]
// render empty node if no matched route
if (!matched) {
cache[name] = null
return h()
}

更新cache指针

const component = cache[name] = matched.components[name]

也就是 我们在src/install.js中看到的 registerRouteInstance

1
2
3
4
5
6
7
8
9
10
11
12
// attach instance registration hook
// this will be called in the instance's injected lifecycle hooks
data.registerRouteInstance = (vm, val) => {
// val could be undefined for unregistration
const current = matched.instances[name]
if (
(val && current !== vm) ||
(!val && current === vm)
) {
matched.instances[name] = val
}
}

注册 prepatch 和 init的钩子,这个一个应该是 vue相关的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// also register instance in prepatch hook
// in case the same component instance is reused across different routes
;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {
matched.instances[name] = vnode.componentInstance
}

// register instance in init hook
// in case kept-alive component be actived when routes changed
data.hook.init = (vnode) => {
if (vnode.data.keepAlive &&
vnode.componentInstance &&
vnode.componentInstance !== matched.instances[name]
) {
matched.instances[name] = vnode.componentInstance
}
}

只搜到了 https://github.com/vuejs/vue/issues/8657

最后是 传递matched.props[name]的内容

具体代码就是 把在data.props中,不在 components.props中的 key以及对应的值搬运到 data.attrs里

最后 return h(component, data, children)

层级router-view实现原理

index.js -> createMatcher-> createRoute -> .matched = record ? formatMatch(record) : []

其中record是 RouteRecord类型,formatMatch通过 递归 parent 来把 它变 数组

其中parent的来源 是create-route-mapaddRouteRecord递归计算的

然后 在 解析 元素时 可以通过当前 $route.matched[depth]直接获得实例

create-matcher.js

1
2
3
4
5
6
export function createMatcher (
routes: Array<RouteConfig>,
router: VueRouter
): Matcher {
const { pathList, pathMap, nameMap } = createRouteMap(routes)
}
1
2
3
4
5
function match (
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {

匹配name(nameMap找) -> 匹配 location.path(pathList + pathMap) -> create 一个新的Route

这里用list 也可以看出 这里期望的个数应该要小(?),否则效率O(n)大了性能会较差?(不过O(n*操作)似乎1000个也不会很久? 反正这也不是频繁操作?所以不用在意?

1
2
3
4
function redirect (
record: RouteRecord,
location: Location
): Route {

同样是redirect(record.redirect)中的 name匹配 path匹配

1
2
3
4
5
function alias (
record: RouteRecord,
location: Location,
matchAs: string
): Route {

通过匹配 fillParams(matchAs, location.params,...) 到path 同样返回Route

1
2
3
4
5
function _createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route {

再调用 真的createRoute 或者 调上面的 redirect/alias 只要 record.redirect/matchAs存在, 也就是 可能产生无限循环?的了

create-route-map.js

1
2
3
4
5
6
7
8
9
10
export function createRouteMap (
routes: Array<RouteConfig>,
oldPathList?: Array<string>,
oldPathMap?: Dictionary<RouteRecord>,
oldNameMap?: Dictionary<RouteRecord>
): {
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>
} {

还会自动把 '*' wildcard routes 放到最后

1
2
3
4
5
6
7
8
function addRouteRecord (
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>,
route: RouteConfig,
parent?: RouteRecord,
matchAs?: string
) {

为什么 其他地方 都是直接 用true/false,这边 route.caseSensitive要判断typeof === 'boolean'

递归对.children节点调用 addRouteRecord

这两个函数 主要是封装 对 传入的 pathList,pathMap,nameMap 这些 进行 添加

因为处理了没有传值的情况,所以也可以用于初始化

history

abstract.js

正如其名,所有浏览器上地址栏,url,scroll都没有了,取代的 是 实现了 stack:Array<Route>index:number

base.js

是整个源码中最大的单个文件了, hash/html5/abstract 这三个 都是基于base实现的,反过来想在 index.js中我们有看到根据模式不同 把this.history 赋予了不同的值 也就会想要这三个有共同的基类

对外只提供一个History类

这些 加号 是干嘛的,我看flow的文档只看到 说 readonly,是我没有找到正确的位置吗

1
2
3
4
5
6
// implemented by sub-classes
+go: (n: number) => void
+push: (loc: RawLocation) => void
+replace: (loc: RawLocation) => void
+ensureURL: (push?: boolean) => void
+getCurrentLocation: () => string

初始化

1
2
3
4
5
6
7
8
9
10
11
constructor (router: Router, base: ?string) {
this.router = router
this.base = normalizeBase(base)
// start with a route object that stands for "nowhere"
this.current = START
this.pending = null
this.ready = false
this.readyCbs = []
this.readyErrorCbs = []
this.errorCbs = []
}

listen(cb) 简单粗暴 直接 this.cb=cb

1
onReady (cb: Function, errorCb: ?Function) {}

如果当前ready同步执行cb,否则readyCbs数组push进cb,错误回调push进readyErrorCbs

1
onError (errorCb: Function) {`

push进errorCbs

1
2
3
4
5
transitionTo (
location: RawLocation,
onComplete?: Function,
onAbort?: Function
) {}

调用 confirmTransition

成功执行this.updateRoute(route) & onComplete(route) & this.ensureURL()调用所有 readyCbs Once

失败onAbort(err) 所有readyErrorCbs

1
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {}

如果目标和当前Route一样, 触发 NavigationDuplicated(route),调用errorCbs再 调用onAbort(err)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { updated, deactivated, activated } = resolveQueue(
this.current.matched,
route.matched
)

const queue: Array<?NavigationGuard> = [].concat(
// in-component leave guards
extractLeaveGuards(deactivated),
// global before hooks
this.router.beforeHooks,
// in-component update hooks
extractUpdateHooks(updated),
// in-config enter guards
activated.map(m => m.beforeEnter),
// async components
resolveAsyncComponents(activated)
)

设计了一个iterator函数,用runQueue对上面queue进行执行

真是艹了 就非要重复使用变量名吗?? 这里queue

上面的每个如果都成功了,那么执行

1
2
extractEnterGuards(activated, postEnterCbs, ()=> this.current === route)
this.router.resolveHooks

这里 各种调用,也就意味着可能 不同步 吗? 反正这边是用 pending在 做 跳转前和跳转后的route一样保证

如果上面再成功则 回调onComplete 和 异步回调所有 postEnterCbs

1
updateRoute (route: Route) {}

更新this.current 回调this.cb,调用所有afterHooks

其它自产自用函数

1
function normalizeBase (base: ?string): string {}

// 这里是不是漏处理了file://开头的 对应之前有个bugfix类型

传了base值就前面加个/,否则有<base> tag就取 其中base的部分,否则就空

1
2
3
4
5
6
7
8
function resolveQueue (
current: Array<RouteRecord>,
next: Array<RouteRecord>
): {
updated: Array<RouteRecord>,
activated: Array<RouteRecord>,
deactivated: Array<RouteRecord>
} {}

找第一个current和next中不一样的,下标idx,返回

1
2
3
4
5
{
updated: next.slice(0, idx),
activated: next.slice(idx),
deactivated: current.slice(idx)
}
1
2
3
4
5
6
7
8
9
10
11
function extractGuards (
records: Array<RouteRecord>,
name: string,
bind: Function,
reverse?: boolean // 控制是返回的数组的顺序
): Array<?Function> {}

function extractGuard (
def: Object | Function,
key: string
): NavigationGuard | Array<NavigationGuard> {}

把records中所有 _Vue.extend(具体元素).options[name]处理,如果是数组 对每一个 bind,如果非数组单个bind,最后flatten

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}

function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
}

function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
if (instance) {
return function boundRouteGuard () {
return guard.apply(instance, arguments) // 这里把参数`带`过去
}
}
}
1
2
3
function extractEnterGuards (
// 和上面同理绑定的是 'beforeRouteEnter' 和 'bindEnterGuard'
)
1
2
3
4
5
6
7
function bindEnterGuard (
guard: NavigationGuard,
match: RouteRecord,
key: string,
cbs: Array<Function>,
isValid: () => boolean
): NavigationGuard {}

增加回调调用poll

1
2
3
4
5
6
function poll (
cb: any, // somehow flow cannot infer this is a function
instances: Object,
key: string,
isValid: () => boolean
) {}

instances[key]._isBeingDestroyed为false时 调用cb(instances[key])

这里 用isValid()也就是上面传入的this.current === route+ setTimeout(16ms)来判断要不要调用 poll

这是为了页面跳转了 但是 instances没有实例化完成 所以不停异步尝试?

errors.js

一个错误类 NavigationDuplicated

//support IE9 emmm

hash.js

和 html5,index 一样 都是继承于上面的 History

1
// check history fallback deeplinking
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export function getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
let href = window.location.href
const index = href.indexOf('#')
// empty path
if (index < 0) return ''

href = href.slice(index + 1)
// decode the hash but not the search or hash
// as search(query) is already decoded
// https://github.com/vuejs/vue-router/issues/2708
const searchIndex = href.indexOf('?')
if (searchIndex < 0) {
const hashIndex = href.indexOf('#')
if (hashIndex > -1) {
href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex)
} else href = decodeURI(href)
} else {
if (searchIndex > -1) {
href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex)
}
}

return href
}

setupListeners()调用 setupScroll()

监听popstate或者hashchange 触发 transitionTo()

push和replace也都是 调用 transitionTo() 回调的时候 push/replace Hash()然然后handleScroll, 和onComplete(route)

html5.js

和 hash不同的是, 在constructor里直接 初始化了 ,比如setupScroll()和 增加popstate事件触发,而不是让index.js调用

在 这里默认 pushState和replaceState都是可以用的

这里我在想 因为兼容而写的代码 值得吗,留多久,多久抛弃呢?

index.js

import引入,不用细看,总之是引入依赖的

VueRouter类实现

想说 这种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static install: () => void;
static version: string;

app: any;
apps: Array<any>;
ready: boolean;
readyCbs: Array<Function>;
options: RouterOptions;
mode: string;
history: HashHistory | HTML5History | AbstractHistory;
matcher: Matcher;
fallback: boolean;
beforeHooks: Array<?NavigationGuard>;
resolveHooks: Array<?NavigationGuard>;
afterHooks: Array<?AfterNavigationHook>;

写法对阅读来说真香,这是flow还是ts 来着

  • this.matcher = createMatcher(options.routes || [], this) // 用户传入的routes

三种 mode

‘hash’(默认 只识别井号后面的路径前面的忽略? window.location.hash),’history’,’abstract’(服务端node)

分别调用

  • this.history = new HTML5History(this, options.base)
  • this.history = new HashHistory(this, options.base, this.fallback)
  • this.history = new AbstractHistory(this, options.base)

实例对外提供的方法 整理如下

  • match ( raw: RawLocation, current?: Route, redirectedFrom?: Location): Route
    转发了一下`this.matcher.match(…)

  • get currentRoute (): ?Route 取的this.history.current的内容

  • init (app: any /* Vue component instance */) TODO 暂时不知道这个怎么用

  • beforeEach (fn: Function): Function

  • beforeResolve (fn: Function): Function

  • afterEach (fn: Function): Function
    这三个 都是i把函数注册通过registerHook注册到对应的XXXHooks数组中,返回的是从数组中移除他们的函数

  • onReady (cb: Function, errorCb?: Function)

  • onError (errorCb: Function)
    转发了this.history上对应的方法

  • push (location: RawLocation, onComplete?: Function, onAbort?: Function)

  • replace (location: RawLocation, onComplete?: Function, onAbort?: Function)
    !onComplete 且 !onAbort 且 Promise可用时,返回promise,否则同步执行,都是调用this.history.push/replace(...)

  • go (n: number)

  • back ()

  • forward ()
    this.history.go(数值)的转发

  • getMatchedComponents (to?: RawLocation | Route): Array
    通过对to解析成一个Route,获取其matched中的所有components

  • resolve (to: RawLocation,current?: Route,append?: boolean): { location: Location, route: Route, href: string, normalizedTo: Location, resolved: Route }
    这里 实际只有三个返回, 其中 location和normalizedTo一样,resolved 和route一样, 多最后两个 只是为了 向后兼容

  • addRoutes (routes: Array)

1
2
3
4
this.matcher.addRoutes(routes)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}

然后是两个工具函数

registerHook(list,fn), 注册一个函数fn到list, 返回它的移除函数,概念就是以c++角度看作函数指针可以搜,同时问题就是, 没有防止重复,也就是同一个函数可以 加到list两次,而移除 每次只会移除一个,所以整体还是基于函数指针,并没有完全的实现 返回移除自己的函数。不过只要正确调用就不会出问题

createHref 完整路径拼接

最后是 向Vue里注入的 window.Vue.use(VueRouter)

install.js

防重install

1
2
3
4
beforeCreate(){
注入和调用 registerInstance(this,this)
延伸到vm.$options._parentVnode.data.registerRouteInstance(this,this)
}

mixin 渲染顺序 vue的组件 前序深搜, 子组件从父组件拿

属性定义 $router(实例),$route(当前状态),并且通过Vue.util.defineReactive(this, '_route', this._router.history.current) 来保证改变时相应式触发

component定义 RouterViewRouterLink

1
2
3
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created

util

这一块的使用可以看test/unit/specs里的用例

async.js

export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {

对数组中逐个调用 函数fn(queue[index],回调),在上一个回调后调用下一个函数,最后触发cb()

用的 自定义step箭头函数 依次step(index)

dom.js

export const inBrowser = typeof window !== 'undefined'

location.js

1
2
3
4
5
6
export function normalizeLocation (
raw: RawLocation,
current: ?Route,
append: ?boolean,
router: ?VueRouter
): Location {}

_normalized来标记处理过

其它字段 path,query:resolveQuery(...),hash,name,params

misc.js

1
2
3
4
5
6
export function extend (a, b) {
for (const key in b) {
a[key] = b[key]
}
return a
}

params.js

regexpCompileCache 缓存 编译后的正则

使用path-to-regexp来完成 路径正则匹配

https://www.npmjs.com/package/path-to-regexp

1
2
3
4
5
export function fillParams (
path: string,
params: ?Object,
routeMsg: string
): string {}

path.js

比如这个文件感觉 看测试 比看代码更能理解函数功能

1
2
3
4
5
export function resolvePath (
relative: string,
base: string,
append?: boolean
): string {}

路径解析咯 甚至还”解析”了 ...

1
2
3
4
5
export function parsePath (path: string): {
path: string; // 去掉 query和 hash的部分
query: string; // 问号以后
hash: string; // 井号及以后
}

cleanPath(path: string):string ,把路径里的连续两个斜杠变为一个斜杠

push-state.js

  • 常量布尔 supportsPushState, 可以从这个源代码 看到特殊判断不支持的ua,剩余的 通过window.history是否有 pushState方法进行判断
  • function pushState (url?: string, replace?: boolean) { 依赖于 window.history .pushState/replaceState这俩个那个方法,通过replace参数决定调用哪个,如果挂掉(..) 则改为调用window.location.replace/assign(url)的方法
  • function replaceState (url?: string) { 调用封装的pushState

这里可能挂掉的注释是

1
2
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls

query.js

1
2
3
4
5
export function resolveQuery (
query: ?string,
extraQuery: Dictionary<string> = {},
_parseQuery: ?Function
): Dictionary<string> {}

基本就是,把url里的请求参数query 和 字典里的extraQuery,转化为 Dictionary

如果有相同的让extraQuery覆盖query里的 这里明明是可以用misc.jsextend 为何没用=。= 是有什么考虑么

然后默认内部实现了如上所述的parseQuery函数,你也可以自己实现一个传进去,从测试样例上看,是没有测这个_parseQuery

1
export function stringifyQuery (obj: Dictionary<string>): string {}

转化为url上的请求参数格式 记得encode

此外就是上面 使用了一些 url的编码解码 函数

resolve-components.js

1
2
3
4
export function flatMapComponents (
matched: Array<RouteRecord>,
fn: Function
): Array<?Function> {}

对matched的每个元素m,中m.components的每个key 调用fn(m.components[key],m.instances[key],m,key)

1
export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {}

返回一个闭包函数 (to,from,next)=>{}

使用了上面的flatMapComponents

通过内部 两个变量hasAsyncpending来控制 next()

这里没有使用tofrom只有 next()matched

没有很懂的是 这里def = matched某个 的components[key],就是其某个_Vue.extend()

但是这里resolve,reject都是once

下面为什么 既有res = def(resolve,reject)又有 res.then(resolve, reject)

emmmmmmmmm所以这里正向功能 是配置所有 components[key] 之后调用 next()?

然后似乎为了兼容不同语法,写了比较神奇的

1
export function flatten (arr: Array<any>): Array<any> {}

emmm名为flatten,实际 只是 Array.prototype.concat.apply([],arr),所以最多flatten一层

比如[1,[2,[3,[4]]]] -> [1,2,[3,[4]]]

route.js

1
2
3
4
5
6
export function createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: ?Location,
router?: VueRouter
): Route {}

返回的是一个Object.freeze(Route)

1
2
3
4
// the starting route that represents the initial state
export const START = createRoute(null, {
path: '/'
})
1
export function isSameRoute (a: Route, b: ?Route): boolean {}

如果b=== START,那么 return a===START 所以不会再生成START?

对 a和b的path,name,hash,query,params 这些 需要比较的进行深比较

1
export function isIncludedRoute (current: Route, target: Route): boolean {}

目标path是当前path的前缀,目标hash为空或者和当前hash相等,目标query的key在当前key中都出现过 =.=这个规则 好迷啊

这里 有单独实现cloneisObjectEqual两个方法,都实现了深度处理,但是没有实现可能出现的循环引用。不过因为都是 Route中的 “可控制的”参数,认为是不会出现循环引用的。

scroll.js

1
export function setupScroll () {}

看得出自闭的感受了 这里三个注释 Fix #balabala

监听popstate => export function saveScrollPosition () { positionStore[当前 状态key] = 保存x,y偏移

1
2
3
4
5
6
export function handleScroll (
router: Router,
to: Route,
from: Route,
isPop: boolean
) {}

// wait until re-render finishes before scrolling

router.app.$nextTick()里执行 调用router.options.scrollBehavior.call(router,to,from,isPop?position:null) 来判断是否需要滚动,如果需要
则调用 scrollToPosition(...)

state-key.js

上面文件会用到 gen/get/set StateKey,然后 值是直接取用的 Time.now().toFixed(3)

至于Time 可能取 window.performance或者Date

warn.js

assert/warn/isError/isExtendedError

如果grep代码 发现 都是说 process.env.NODE_ENV !== 'production'

那么问题来了 为什么 不写成 只在 函数内,外部直接调用

或者说

为什么不写成,传递参数增加一个env?

再或者

详细命名出一个函数 warnNonPro

总结

Onhashchange 的触发来源

  • 修改浏览器地址 增加改变#hash
  • 修改location.href / location.hash
  • 点击锚点链接
  • 浏览器前进后退变化

hash

routeLink负责

  • 阻止默认行为 如click e.preventDefault()
  • 设置 location.hash

routeView负责

  • window.addEventListener(‘hashchange’,e=>{具体工作 比如页面渲染});

history(用的h5 api)

pushState 不触发页面刷新,只改变history对象,是同源策略保护限制的

popstate 页面组件刷新

routeLink负责

  • 阻止默认行为 如click e.preventDefault()
  • polyfill补丁,支持低版本浏览器。新版本的才有history.pushState()
  • window.history.pushState(对象,link,link);
    routeView负责
  • window.addEventListener(‘popstate’,e=>{具体工作 比如页面渲染}); 由浏览器前进后退按钮 触发,或者history方法触发

不支持pushState会降级到hash模式

examples

对照 源码里的 example再来回顾实现

在源码的examples/文件夹里

通过npm run dev来启动

basic

mode: 'history',
base: __dirname,

grep -r "base" src/ 可以回顾一下 base相关的实现

    <router-link tag="li" to="/bar" :event="['mousedown', 'touchstart']">
      <a>/bar</a>
    </router-link>
    

这一部分 回顾 link的实现 , tag不为a 则会 递归在this.$slots.default找第一个a元素,然后 绑上事件, 然后 event 是对应 在找到的标签a上的所有 on事件改为 阻止默认事件 调用
router.push/replace,

所以这个/bar鼠标点击下 就会触发, 页面上另一个/bar 要释放才会触发

那么,跟觉源码的逻辑 这样 做后会触发两次 push ,两次 transitionTo,两次 confirmTransition,然后 通过 base.js中的isSameRoute中判读是否是同一个Route,如果是 则调用ensureURL,最后调用 abort,

而 abort中 如果是 NavigationDuplicated的 错误 则不会 warn,会调用 回调函数(如果传递了),

当然 如果你已经在一个路径下 那么你点击 一个指向当前的路由 也会 走上面的逻辑(不是两次),在isSameRoute后就不会再走动

其中的to 是通过router.resolve(this.to,current,this.append)解析出的目标地址

navigateAndIncrement () {

实现了直接去调用$router.push方法 ,你可以通过浏览器的返回看到push的效果

    <router-link to="/foo" v-slot="props">
      <li :class="[props.isActive && 'active', props.isExactActive && 'exact-active']">
        <a :href="props.href" @click="props.navigate">{{ props.route.path }} (with v-slot).</a>
      </li>
    </router-link>

这一段 会对应link中的 scopedSlot, 注意到 它并没有 传递activeClassexactActiveClass而是 自己组件里 动态计算class,然后这里

这里也是把 源码中传递的所有参数都用到了

1
2
3
4
5
6
7
this.$scopedSlots.default({
href,
route,
navigate: handler,
isActive: classes[activeClass],
isExactActive: classes[exactActiveClass]
})

html最下面

  <pre id="query-t">{{ $route.query.t }}</pre>
  <pre id="hash">{{ $route.hash }}</pre>

都是 过程中计算出来的当前 Route上的参数 query

同时可以发现 当 路由改变时 所有 RouterLink 的render函数 都被重新调用

hash-mode

和basic vimdiff一下

首先 最主要的变化是 mode:'hash'

此外这里加入了 /xxx/:yyy这样的匹配

然后在router-link部分 就只使用了最基本的写法

mode也就是 直接文件history/hash.js

然后 冒号路径 同样是 link.js中render里的 router.resolve, 源码反过去搜的话是 index.js: resolve(to...) -> location.js: normalizeLocation(raw...), -> index.js:match(raw...) -> create-matcher.js:match

这里 有点问题! 虽然说index.js::matchcreate-matcher.js里都是有返回Route的,但是,在 match的一些情况下 传入的raw被更改了 比如加上了params,因为 在 normalizeLocation里 返回的location == raw,就有了 后面 match中修改改location时修改了 raw,

也就是 说 在link中 这个返回的location可能是带上 params 也可能没有,所以这是不可靠的=.=

每当这时 就会怀念 c++中的 const引用参数

然后 当点击时 是触发hash.js::push -> bash.js::transitionTo -> router.match 匹配出Route -> base.js updateRoute 跟心 current = route

其中处理冒号格式的是靠 src/util/params.js 中使用’path-to-regexp’,在 match函数中使用

这样也就是 把 计算出的params 之类的 丢到了$route

nested-routes

  1. route表中 有 name' 这样写to就可以 不用写详细路径

实现就是靠 create-matcher.jsnameMap来实现名字-> url/:xxx/yy 以及 对应的正则

emmmmm 经过调试 都在 createRouteMap中把 nameMap做好了,在 下面 record=nameMap[name]始终 都有值

路径也在create-matcher.js中的locatoin.path = fillParams合并

例如

fillParams('/parent/qux/:quxId/quux',{quxId: "1", zapId: 2})

我们可以尝试添加 fillParams /parent/qux/:quxId/quux {quxId: "1", zapId: 2}

你会发现 点击会跳转到根(如果没有 quxId) 会报warn [vue-router] missing param for named route "quux": Expected "quxId" to be defined

在url上是根但是 在 router-view上 还是 按照 name的层次渲染的

这里一个问题就是 说 name 不能重复 会报错 [vue-router] Duplicate named routes definition: { name: "quuy", path: "/parent/qux/:quxId/quuy" } 但如果不管的话, name根据不重复建立 只会保存第一个

第二个就是说 即使从xxx/:quxId 直接跳 name:'quux'也是会回到主页, 因为虽然原来 quxId有值,但树形解析 上并没有quxId的值

  1. 是嵌套的 路由表 和 嵌套的 router-view

路由表嵌套 上面1.已经说了,然后 router-view 嵌套 靠的就是上面 源码阅读中讲的 matched 的计算,见view.js

即每一层router-view渲染是通过depth 去$route.matched[depth]取值

named-routes

基本就是 router配置的时候 带name,然后 router-link 的to时 配置 name,也可以配置 params

没啥新的东西

上面nested-routes都展示了

named-view

router-view 上加上了 name

路由里配置

  components: {
    default: Baz,
    a: Bar,
    b: Foo
  }

那么在 取的时候 根据props的name 去matched.components[name]中获得

和vue实例生命周期顺序

打开codesandbox.iomain.js替换为下面,记得引入vue-router依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import Vue from "vue";
import VueRouter from "vue-router";
Vue.config.productionTip = false;

Vue.use(VueRouter);

const Foo = {
template:
'<div><h1>foo</h1><router-link to="/bar">Go to Bar</router-link></div>',

beforeRouteEnter(to, from, next) {
console.log("foo inner beforeRouteEnter");
next();
},
beforeRouteUpdate(to, from, next) {
console.log("foo inner beforeRouteUpdate");
next();
},
beforeRouteLeave(to, from, next) {
console.log("foo inner beforeRouteLeave");
next();
},

beforeCreated() {
console.log("foo beforeCreated");
},
created() {
console.log("foo created");
},
beforeMount() {
console.log("foo beforeMount");
},
mounted() {
console.log("foo mounted");
},
beforeDestroy() {
console.log("foo beforeDestroy");
},
destroyed() {
console.log("foo destroyed");
}
};
const Bar = {
template:
'<div><h1>bar</h1><router-link to="/foo">Go to foo</router-link></div>'
};

const routes = [
{
path: "/foo",
component: Foo,
beforeEnter: (to, from, next) => {
console.log("Foo beforeEnter");
next();
}
},
{ path: "/bar", component: Bar }
];

const router = new VueRouter({
routes
});

router.beforeEach((to, from, next) => {
console.log("global beforeEach");
next();
});

router.afterEach((to, from) => {
console.log("global afterEach");
});

new Vue({
template: '<div id="app"><router-view /></div>',
router
}).$mount("#app");

查看console

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
global beforeEach 
Foo beforeEnter
foo inner beforeRouteEnter
global afterEach
foo created
foo beforeMount
foo mounted





foo inner beforeRouteLeave
global beforeEach
global afterEach
foo beforeDestroy
foo destroyed

验证了我们上面阅读的源码,router-view 来管理了组件的渲染,虽然有的函数从代码视角写在组件内部,但实际上是vuer-router确定要渲染组件以后,才会调用vue提供的产生VNode的方法,因此,给router用的是由router来用,也就自然早于vue本身的生命周期了

个人其它收获

  1. vuex,vue-router的example 都是 用express写的
  2. vuex,vue-router的文档都是vuepress生成的
  3. vuex的开发测试 目测没有flow,vue-router的测试有用到flow,两者似乎都用到了tsc
  4. dev,测试,release 全部脚本化了
  5. 之前有不少地方建议用typeof == ‘undefined’来比较undefined,这里源码写的依然是 !== undefined来比较,感觉这些就算可有可无的建议吧(吗)
  6. 另外就是 之前有想过说 代码里尽量避免字符串 作为逻辑运算,用enum或者常量,或者常量意义的变量代替,这里看源码依然后很多case 字符串,或者字符串直接比较的。
  7. 又多了一点源码阅读经验,因为 很多源码现在都已经有自动测试了,所以在直接看 util /helper之类的 代码时,先看对应的测试代码可以 快速知道这个代码是干啥用的,再阅读代码就会更容易理解
  8. 比如query里明明是可以用misc.jsextend 为何没用=。= 是有什么考虑么
  9. Object.freeze,没有深入研究,但拉去属性可以看到writable都变成false
  10. 这里为了兼容多种语法,采用的是在具体的函数里接受+一堆if来处理,而不是提供单一标准参数让调用者控制参数。
  11. this.$scopedSlots.default({没有查到,但是 看源码中的ts 是 $scopedSlots[slot名字]=(props:any)=>ScopedSlotChildren;)
  12. 看多了源码 你会发现很多process.env.NODE_ENV !== 'production' 时会报warn,同时 也知道了如果你要负责构建打包,看似 一个字符串’production’其实是会参与逻辑
  13. functional 组件
  14. 里面实现的函数有一些潜在side-effect的 比如 match,可能会修改到raw,这种时候就会怀念C++的const引用

参考

hashchange 事件 mozilia文档

popstate

flow

base tag

TODO

看一下 mixin文档了,不然有些方法看得不太理解

  • install, beforeCreate, $options registerRouteInstance?,defineProperty

https://vuejs.org/v2/api/#optionMergeStrategies

Vue.util.defineReactive(this, ‘_route’, this._router.history.current)

pwa

vue-ssr

ivew

单元测试

nuxtjs

https://npmdoc.github.io/node-npmdoc-vue/build..beta..travis-ci.org/apidoc.html#apidoc.element.vue.util.defineReactive

19-08-26 -> 19-

https://www.runoob.com/design-pattern/design-pattern-tutorial.html

relation

创建型模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

模式名称 简单概括
工厂模式(Factory Pattern) 从new变为从工厂拿具体类
抽象工厂模式(Abstract Factory Pattern) 其它工厂的工厂
单例模式(Singleton Pattern) 某作用域唯一的,存在0或1个
建造者模式(Builder Pattern) 分离不变(基本元素)和易变模块(组合基本元素的方法)
原型模式(Prototype Pattern) 重写clone方法 控制重复对象克隆的代价

工厂模式(Factory Pattern)

复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

factory pattern

主程序 new 工厂

通过工厂方法获得有某些接口实现的实例

调用实例的接口方法

如上图,对于main来说左边框中除了 interface Shape以外 是未知的,通过调用Shape Factory来创建具体实现了Shape的实例

抽象工厂模式(Abstract Factory Pattern)

其他工厂的工厂

Abstract Factory Pattern

讲就是,通过其它工厂的工厂来创建工厂,再通过创建出的工厂来调用具体的对象创建

单例模式(Singleton Pattern)

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

singleton pattern

实现方式

是否 Lazy 初始化:是

是否多线程安全:否

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton instance;
private Singleton (){}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

是否 Lazy 初始化:是

是否多线程安全:是

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步

1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() { // 相对来说 保证了多线程安全
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

是否 Lazy 初始化:否

是否多线程安全:是

1
2
3
4
5
6
7
public class Singleton {  
private static Singleton instance = new Singleton(); // 默认初始化单例实例 但可能不会用到
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:是

是否多线程安全:是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {  
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) { // 因为最多0次或1此创建 而如果使用大多数都是 != null
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

登记式/静态内部类

利用了 classloader 机制来保证初始化 instance 时只有一个线程

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE; // 只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance
}
}

JDK 版本:JDK1.5 起

是否 Lazy 初始化:否

是否多线程安全:是

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

不能通过 reflection attack 来调用私有构造方法。

1
2
3
4
5
public enum Singleton {  
INSTANCE;
public void whateverMethod() {
}
}

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

建造者模式(Builder Pattern)

分离 常变和不变的

builder pattern

如上,具体的食物 等是不变的,而 套餐组合是变化的=.=我暂时没有想到代码中的用例

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

原型模式(Prototype Pattern)

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些”易变类”拥有稳定的接口。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

Prototype Pattern

结构型模式

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

模式名称 简单概括
适配器模式(Adapter Pattern) 解决 接口不兼容 如wine
桥接模式(Bridge Pattern) 抽象实体解耦
过滤器模式(Filter、Criteria Pattern) 抽象过滤方法 实现不同过滤器
组合模式(Composite Pattern) 树形结构
装饰器模式(Decorator Pattern) 不使用子类 包一层 增加方法
外观模式(Facade Pattern) 复杂化内部 简化对外接口
享元模式(Flyweight Pattern) 复用 大量细粒度对象
代理模式(Proxy Pattern) 中间商 抽象/管理cache等 os里常见

适配器模式(Adapter Pattern)

不兼容借口之间的桥梁

1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。

这个自己有遇到过

  1. 之前改写的oiTerminal,目的是同时兼容不同的oj 的页面访问请求 和解析
  2. 之前也有在wukong项目里看过 网路访问做的 adapter

有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

Adapter Pattern

桥接模式(Bridge Pattern)

用抽象解耦实现化

对于两个独立变化的维度,使用桥接模式再适合不过了。

Bridge

过滤器模式(Filter、Criteria Pattern)

Filter

接口是 meetCriteria

不同的过滤器不同的实现方式

组合模式(Composite Pattern)

整体部分模式,树形模式?

Composite Pattern

装饰器模式(Decorator Pattern)

现有类的一个包装

动态的给一个对象添加额外的职责

比子类更灵活 比如rust的macro的 https://cromarmot.github.io/Blog/19-08-15-rust/#Macros

Decorator Pattern

外观模式(Facade Pattern)

不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。

Facade Pattern

享元模式(Flyweight Pattern)

抽离出大量细力度对象复用

HashMap ,如下图的circleMap

Flyweight Pattern

代理模式(Proxy Pattern)

按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

写过操作系统lab代码的应该是 再熟悉不过了

Proxy Pattern

#行为型模式

这些设计模式特别关注对象之间的通信。

责任链模式(Chain of Responsibility Pattern)

1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

我能想到的如nodejs中express的中间件?

自己能处理则处理否则传递给链上的下一个处理函数

Chain

链上所有的类都需要实现抽象类,如上面的AbstractLogger

命令模式(Command Pattern)

关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口

command

如上 Stock是实体用的类

Order的具体实现BuyStock 和 SellStock是要对一个具体Stock操作的方法

Broker是执行者

解释器模式(Interpreter Pattern)

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

如之前写过的sparql解析器

Interpreter Pattern

迭代器模式(Iterator Pattern)

聚合对象内部遍历方法,但是不暴露具体内部实现

比如rust已经说做到用iter能和 for index做当相同效率的迭代器了

C++里常见的stl里各种迭代器

// 就我对迭代器使用理解来说,这里提出的缺点是个什么鬼

iterator

中介者模式(Mediator Pattern)

何时使用:多个类相互耦合,形成了网状结构。

如何解决:将上述网状结构分离为星型结构。

2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

Mediator

备忘录模式(Memento Pattern)

存档,返回上一步,数据库事务管理

备忘录模式使用三个类 Memento、Originator 和 CareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。

MementoPatternDemo,我们的演示类使用 CareTaker 和 Originator 对象来显示对象的状态恢复。

Memento

这个东西 感觉没有什么真实实践经验

以前做过一个很简单版本管理,用的是二进制备份和恢复

也做过基于数据库的备份和恢复,但实际还是靠的数据库

所以在实践中 如果对于一个 大的内容,感觉直接备份整体 消耗是巨大的。看vim的undo tree或者 git的版本管理,这些工程实践过的东西,都是交互式但都是记录的差异

那么问题又是,如何记录差异,如果一个系统的操作是具有连带操作的,那么所有连带影响都应该被记录,而在恢复时不应该触发任何连带反应,从而设计上 感觉就这里所举的例子远远不够

观察者模式(Observer Pattern)

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

Observer

一个是 vue的 watch以及computed设计

另一个是RxJS里的 Observerable的设计

这种描述式的表述法直接代码阅读体验是会比命令式的好一些

比如 挖掘Vue的声明式的交互能力 https://www.bilibili.com/video/av37345007?from=search&seid=18134472664073504003

状态模式(State Pattern)

内部有状态,不同的状态下 不同的方法有不同的行为

使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。

State Pattern

对于if 比较多且复杂的时候会考虑

然后作为简单的练习有 很多算法上的,比如AC自动机等等

空对象模式(Null Object Pattern)

一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。

Null object

个人感觉可以看作是一种 数据的兜底行为,这个感觉要看具体是希望 抛错出去还是说用兜底行为保护,看具体业务希望

策略模式(Strategy Pattern)

Strategy

多个封装起来可以“替换”的策略类

比如复杂的决策功能而不是简单的state

模板模式(Template Pattern)

template

这样看 之前的 基类模式写的oiTerminal 应该是属于模板模式的

访问者模式(Visitor Pattern)

visitor

需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中。

也就是 某一些方法 不应该直接放在 类和子类中时

优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。

缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

结构内很少改变 但需要在对象结构上定义新的操作

J2EE 模式

这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。

MVC 模式(MVC Pattern)

mvc

Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。

View(视图) - 视图代表模型包含的数据的可视化。

Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

mvc pattern

业务代表模式(Business Delegate Pattern)

客户端(Client) - 表示层代码可以是 JSP、servlet 或 UI java 代码。

业务代表(Business Delegate) - 一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。

查询服务(LookUp Service) - 查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。

业务服务(Business Service) - 业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。

Business delegate

我个人里的理解是 比如 有前端和服务端的交互,那么服务端会开发一层扁平的 接口给前端使用,前端直接调用的这一层就是

隐藏了内部实现

组合实体模式(Composite Entity Pattern)

Composite

组合实体模式(Composite Entity Pattern)用在 EJB 持久化机制中。一个组合实体是一个 EJB 实体 bean,代表了对象的图解。当更新一个组合实体时,内部依赖对象 beans 会自动更新,因为它们是由 EJB 实体 bean 管理的。以下是组合实体 bean 的参与者。

  • 组合实体(Composite Entity) - 它是主要的实体 bean。它可以是粗粒的,或者可以包含一个粗粒度对象,用于持续生命周期。

  • 粗粒度对象(Coarse-Grained Object) - 该对象包含依赖对象。它有自己的生命周期,也能管理依赖对象的生命周期。

  • 依赖对象(Dependent Object) - 依赖对象是一个持续生命周期依赖于粗粒度对象的对象。

  • 策略(Strategies) - 策略表示如何实现组合实体。

没有理解到 具体的目的和解决的问题,查wikipedia有说, 消除实体之间关系 减少实体bean提高可管理性。

数据访问对象模式(Data Access Object Pattern)

数据访问对象模式(Data Access Object Pattern)或 DAO 模式用于把低级的数据访问 API 或操作从高级的业务服务中分离出来。以下是数据访问对象模式的参与者。

数据访问对象接口(Data Access Object Interface) - 该接口定义了在一个模型对象上要执行的标准操作。

数据访问对象实体类(Data Access Object concrete class) - 该类实现了上述的接口。该类负责从数据源获取数据,数据源可以是数据库,也可以是 xml,或者是其他的存储机制。

模型对象/数值对象(Model Object/Value Object) - 该对象是简单的 POJO,包含了 get/set 方法来存储通过使用 DAO 类检索到的数据。

Data Access Object

原来DAO的中文全称是这个

DAO在我的印象的java中是连接 数据库 和 java代码的

前端控制器模式(Front Controller Pattern)

前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。

前端控制器(Front Controller) - 处理应用程序所有类型请求的单个处理程序,应用程序可以是基于 web 的应用程序,也可以是基于桌面的应用程序。

调度器(Dispatcher) - 前端控制器可能使用一个调度器对象来调度请求到相应的具体处理程序。

视图(View) - 视图是为请求而创建的对象。

Front Controller

这个不是很理解 这样划分出的模式

我感觉上面没有讲到单独的dispatcher模式 ,但是有提到 业务代表模式

然后就这个图上看,我感觉 像是 业务代表模式和dispatcher模式的融合

拦截过滤器模式(Intercepting Filter Pattern)

拦截过滤器模式(Intercepting Filter Pattern)用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。

过滤器(Filter) - 过滤器在请求处理程序执行请求之前或之后,执行某些任务。

过滤器链(Filter Chain) - 过滤器链带有多个过滤器,并在 Target 上按照定义的顺序执行这些过滤器。

Target - Target 对象是请求处理程序。

过滤管理器(Filter Manager) - 过滤管理器管理过滤器和过滤器链。

客户端(Client) - Client 是向 Target 对象发送请求的对象。

intercepting filter

为什么看下面的实现代码 感觉是让每一个过滤管理器 都执行一边操作

而 过滤器链更像是一系列操作函数的数组。

执行过程是让数组中的一系列操作都 对输入数据执行一遍

服务定位器模式(Service Locator Pattern)

服务定位器模式(Service Locator Pattern)用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。以下是这种设计模式的实体。

  • 服务(Service) - 实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。

  • Context / 初始的 Context - JNDI Context 带有对要查找的服务的引用。

  • 服务定位器(Service Locator) - 服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。

  • 缓存(Cache) - 缓存存储服务的引用,以便复用它们。

  • 客户端(Client) - Client 是通过 ServiceLocator 调用服务的对象

Service Locator

感觉算是上面 模式的合体了

传输对象模式(Transfer Object Pattern)

Transfer Object

传输对象模式(Transfer Object Pattern)用于从客户端向服务器一次性传递带有多个属性的数据。传输对象也被称为数值对象。传输对象是一个具有 getter/setter 方法的简单的 POJO 类,它是可序列化的,所以它可以通过网络传输。它没有任何的行为。服务器端的业务类通常从数据库读取数据,然后填充 POJO,并把它发送到客户端或按值传递它。对于客户端,传输对象是只读的。客户端可以创建自己的传输对象,并把它传递给服务器,以便一次性更新数据库中的数值。以下是这种设计模式的实体。

  • 业务对象(Business Object) - 为传输对象填充数据的业务服务。
  • 传输对象(Transfer Object) - 简单的 POJO,只有设置/获取属性的方法。
  • 客户端(Client) - 客户端可以发送请求或者发送传输对象到业务对象。

我们将创建一个作为业务对象的 StudentBO 和作为传输对象的 StudentVO,它们都代表了我们的实体。

19-07-12 -> 19-08-15

Hell world

开始 & 安装

curl https://sh.rustup.rs -sSf | sh

网络真的是僵硬,下载下一年, 我用远端服务器就很快下了 真是艹了,我在远端 都跟着教程看了两章+尝试demo了,本地的build下载还没下好

国内的话 配置~/.cargo/config如下会稍微快一些,吐槽

1
2
3
4
5
6
7
[registry]
index = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index/"
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.sjtug]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index/"

更新 上面不能用的话看这里 https://lug.ustc.edu.cn/wiki/mirrors/help/rust-crates

IDE/vim配置

我是用的vim + spf13-vim上加自己配置的 ~/.vimrc.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function CompileByFileType()
if &filetype == "cpp"
"!clang++ -o "%<" "%" -std=gnu++17 -O2 -g -Wall -Wcomma
!clang++ -o "%<" "%" -std=gnu++17 -g -Wall -Wcomma
"!g++ -o "%<" "%" -std=gnu++14 -O2 -g -Wall
elseif &filetype == "c"
!gcc -o "%<" "%" -O2 -g
elseif &filetype == "python"
!python3 "%"
elseif &filetype == "go"
!go run "%"
+ elseif &filetype == "rust"
+ !cargo run
else
echo "UNKNOWN FILETYPE : &filetype"
endif
endfunction

nnoremap <F9> :call CompileByFileType() <CR>

其它的话 据说Clion + rust插件 不错

入门命令

rustup docs --book

66666 题外话,我觉得现代的游戏 除了游戏本身还需要 背景,目标玩家定位,目标游戏类型定位,以及自身完备性和营销。自身完备性比如观战,历史记录等,去年出的 刀牌 从6万峰值到现在上线就是世界前100,我认为其中缺少的就是完备性。而rust 带一个 这个文档66666

有了教程,那我yyp一点 教程里的总结吧

  • 单个rs:rustc main.rs
  • build project using cargo build or cargo check.
  • build + run a project cargo run.
  • Instead of saving the result of the build in the same directory as our code, Cargo stores it in the target/debug directory.
  • cargo build –release

依赖相关

如果玩过npm可以感觉到和package.json 和对应的lock相似

首先Cargo.toml 中加入需要依赖的库和版本描述,如rand = "0.3.14",会匹配能匹配的某个版本

然后cargo build

Cargo.lock会保存首次build的各个库的版本

如果希望升级cargo update

基本语法+写一个猜测数游戏

猜数

依赖

閱讀全文 »

Why

作为被factorio洗脑的人 看到 自动化 自动化 自动化 就会激动

Steps

首先你需要 java 8(jre/jdk),docker

这两个我之前都安装过了

然后下载jenkins: https://jenkins.io/zh/download/

运行命令java -jar jenkins.war --httpPort=8080

打开网页http://localhost:8080

输入终端里展示的密码 –> 下一步 –> 等待安装git等一系列工具 –> 创建管理员用户`–> 实例配置(我发现有不少网页开发工具都有意无意的避开了8080端口 这里我直接默认) –>开始使用

我这边最后一步以后白屏了,但是从Network看似乎没有资源卡着,于是我把服务先杀掉再启动一遍,可以进入jenkins了23333

Hell world

单击 New Item

閱讀全文 »

开始 & 安装

文档

https://code.visualstudio.com/api/get-started/your-first-extension

https://github.com/microsoft/vscode-extension-samples

install & Run

1
2
npm install -g yo generator-code
yo code

选择你希望的选项

code ./新建目录

F5启动

Ctrl+Shift+P输入Hello world回车

当你修改了代码后,在新打开的窗口Ctrl+Shift+P再输入Reload Window就重新加载了代码

解剖

package.json

  • 注册 onCommand Activation Event:onCommand:extension.helloWorld ,so the extension becomes activated when user runs the Hello World command.

  • 使用contributes.commands Contribution Point 来让Hello World命令 在Command Palette 可用,并且绑定到命令idextension.helloWorld

  • 使用commands.registerCommand的VS Code API绑定到已注册的命令ID extension.helloWorld

  • Activation Events: events upon which your extension becomes active.

  • Contribution Points: static declarations that you make in the package.json Extension Manifest to extend VS Code.

  • VS Code API: 代码中API,可以在你的extension代码中调起

其它配置文件

  • launch.json used to configure VS Code Debugging
  • tasks.json for defining VS Code Tasks
  • tsconfig.json consult the TypeScript Handbook

package.json

vsc使用 <publisher>.<name>作为一个插件的unique ID

tree-view

emmmmmm 看了一会代码 发现有文档 https://code.visualstudio.com/api/extension-guides/tree-view

直接看文档吧…

Why

可以在私有环境下使用。如果想用非私有,现成的,可以考虑https://github.comhttps://gitlab.com

手工安装 docker-ce

安装依赖

1
sudo apt install ca-certificates curl openssh-server postfix

安装 gitlab-ce

1
2
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo apt install gitlab-ce

注: 国内有不少镜像源 例如https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/,国内镜像会快很多,可以替换上面生成的/etc/apt/sources.list.d/gitlab_gitlab-ce.list中的路径

sudo gitlab-ctl reconfigure

按理说这里就可以用了

你需要设置root的密码,和新建一个用户以及(设置该用户的密码)

配置

然而 因为我本地apache服务和原来开启的nginx服务,有80冲突和原来nginx服务冲突(?),导致我找问题找了很久很久[我似乎也没看到报错

修改 sudo vim /etc/gitlab/gitlab.rb

中的external_url 为你的domain名称+port,如http://192.168.1.51:8081

最后我停掉两个服务再重启 gitlab就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 未使用了 如果本身还有nginx据说可以配一下
# nginx listen_port 配置

修改

`/etc/gitlab/gitlab.rb`

找到`nginx['listen_port']`修改为

nginx['listen_port'] = 8081

再执行`sudo gitlab-ctl reconfigure`

访问`你的domain:8081`即可
*/

gitlab on docker

1
2
3
4
5
6
sudo docker run --detach --name gitlab \
--hostname gitlab.example.com \
--publish 30080:30080 \
--publish 30022:22 \
--env GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.example.com:30080'; gitlab_rails['gitlab_shell_ssh_port']=30022;" \
gitlab/gitlab-ce:latest

好了 ssh 30022和http 30080都ok,docker真香了

https://developer.ibm.com/code/2017/07/13/step-step-guide-running-gitlab-ce-docker/

https://docs.gitlab.com/omnibus/docker/README.html

问题:仓库通过ssh的git clone报错

httpclone可用

sshclone报错: fatal: protocol error: bad line length character: Welc

gihub上很多说sed -i 's/session\s\+required\s\+pam_loginuid.so/# \0/' /etc/pam.d/sshd 就是注释掉 也没用(也restart 过)

问题排查

网上有很多 pam的 校验关闭都没有用

然后 通过ssh -vvv git@<ip>得到Exit status 254,对应也是没有搜到可行方案,但感觉距离问题近了些

查看/var/log/auth.log发现

1
2
3
4
5
6
Aug 20 14:24:03 RBEST systemd: pam_unix(systemd-user:session): session opened for user git by (uid=0)
Aug 20 14:24:03 RBEST systemd-logind[941]: New session 170 of user git.
Aug 20 14:24:03 RBEST sshd[16070]: error: PAM: pam_open_session(): Module is unknown
Aug 20 14:24:03 RBEST sshd[16148]: Received disconnect from 192.168.1.51 port 48618:11: disconnected by user
Aug 20 14:24:03 RBEST sshd[16148]: Disconnected from user git 192.168.1.51 port 48618
Aug 20 14:24:03 RBEST systemd-logind[941]: Removed session 170.

这个error也搜不到对应的

鸽了! 用 docker来搞

docker的方法测试

ssh -vvv git@gitlab.example.com -p 30022的exit code 是0 所以感觉还是 本地的ssh相关的东西配置有问题

修复(重装试试)

上面我得到的信息是

  1. ssh git@应该是成功登录了 但是没有和gitlab联系上
  2. docker里看 只用简单配置 就应该能用
  3. 我尝试新开了个空白虚拟机,装一遍gitlab是能用的

于是按照https://askubuntu.com/a/824723/653605把 gitlab完全删除 再重装就好了?喵喵喵,虽然现在能用了,可是之前究竟是什么问题。

TODO email

https://docs.gitlab.com/omnibus/settings/nginx.html

权限管理

权限管理 https://docs.gitlab.com/ee/administration/#user-settings-and-permissions

用root权限用户登陆

<你的地址>/admin/application_settings

可见和可访问性

的Restricted visibility levelsx限制为 内部和私有

关闭了为所有项目默认 runner和默认CI/CD

HTTPS

/etc/gitlab/gitlab.rb 设置

external_url 'https://gitlab.xxx.com'

以及

1
2
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['你的email']

gitlab-ctl reconfigure就可以自动生成证书并且 启用https了

内存占用过大

1
2
unicorn['worker_timeout'] = 60
unicorn['worker_processes'] = 2

也有其它git server :https://gogs.io/

参考

https://about.gitlab.com/

https://hub.docker.com/r/gitlab/gitlab-ce/

https://gitlab.com/gitlab-org/gitlab-foss/-/issues/25840

https://docs.gitlab.com/omnibus/settings/unicorn.html

知识依赖

  1. 基本的js语法
  2. 基本的nodejs用法
  3. Promise用法

准备工作

我想用nodejs直接跑rxjs,然而它并不支持es6的import,尝试了--experimental-modules依然有报错

方案1 在线编辑器

https://rxjs-dev.firebaseapp.com/guide/observable 在上面代码点击右上角的Edit in StackBlitx

方案2 nodejs + rxjs + ES6

https://github.com/standard-things/esm

安装:npm install esm rxjs

带上esm运行

node -r esm index.js

方案3 webpack + rxjs + ES6

创建文件夹 并初始化

1
2
mkdir rxjsdemo && cd rxjsdemo
npx webpack-cli init

这样选择

1
2
3
4
5
? Will your application have multiple bundles? No
? Which will be your application entry point? index
? In which folder do you want to store your generated bundles? dist
? Will you use one of the below JS solutions? ES6
? Will you use one of the below CSS solutions? No

然后运行

1
npm install --save-dev rxjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Subject } from 'rxjs';
import { interval } from 'rxjs';

const subject = new Subject();

subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});

subject.next(1);
subject.next(2);

const observable = interval(1000);
const subscription = observable.subscribe(x => console.log(x));

运行npm start在打开的网页查看console是否正常输出, 忽略红字,能输出如下即可

1
2
3
4
5
6
7
observerA: 1
observerB: 1
observerA: 2
observerB: 2
0
1
...
閱讀全文 »

参考

https://webpack.js.org/concepts/

警告!虽然有.com的中文文档,但是存在没有更新文档的问题,不是webpack4版本的文档!!例如CommonsChunkPlugin已经被弃用,CleanWebpackPlugin的引入和使用方法不同版本不同

核心概念

入口entry

对应配置中的entry属性

webpack.config.js

1
2
3
module.exports = {
entry: './path/to/my/entry/file.js'
};

常见 分离应用程序和三方库入口

1
2
3
4
5
6
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
}

根据经验:每个 HTML 文档只使用一个入口起点。多页面应用

1
2
3
4
5
6
7
const config = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};

注意 以上都可以用CommonsChunkPlugin来优化公用代码

输出output

在哪里输出所建立的bundles以及命名

閱讀全文 »
0%