Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
yzsunlei committed Nov 1, 2020
1 parent 5ce45f1 commit 6ec97a3
Show file tree
Hide file tree
Showing 40 changed files with 735 additions and 64 deletions.
51 changes: 26 additions & 25 deletions 1.6.Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
## 关于VNode
* 在浏览器中我们可以通过js来操作DOM,但是这样的操作性能很差,因为真正的DOM元素非常庞大,于是Virtual Dom应运而生。
可以这样理解,Virtual Dom就是在js中模拟DOM对象树来优化DOM操作的一种技术或思路。
## 关于Vue中的VNode
在浏览器中我们可以通过js来操作DOM,但是这样的操作性能很差,因为真正的DOM元素非常庞大,于是Virtual Dom应运而生。可以这样理解,Virtual Dom就是在js中模拟DOM对象树来优化DOM操作的一种技术或思路。

下面先来看看Vue中的Virtual Dom,源码位置在`src/core/vdom/vnode.js`

* 下面先来看看Vue中的Virtual Dom,源码位置在`src/core/vdom/vnode.js`
```vuejs
export default class VNode {
tag: string | void;
Expand Down Expand Up @@ -40,11 +40,11 @@ export default class VNode {
//...
}
```
* 从上面源码可以看出,Virtual Dom相对浏览器中的真实Dom要简单得多。其实Vue中的Virtual Dom就是把真实Dom中一些常用的属性类型,
再加上Vue里面的一些配置项组成的。

* Vue中的Virtual Dom不仅对真实浏览器Dom进行简化外,还对不同场景进行了Virtual Dom的分类。下面是EmptyVNode、TextVNode、
CloneVNode的创建,源码位置在`src/core/vdom/vnode.js`
从上面源码可以看出,Virtual Dom相对浏览器中的真实Dom要简单得多。其实Vue中的Virtual Dom就是把真实Dom中一些常用的属性类型,再加上Vue里面的一些配置项组成的。

Vue中的Virtual Dom不仅对真实浏览器Dom进行简化外,还对不同场景进行了Virtual Dom的分类。下面是EmptyVNode、TextVNode、CloneVNode的创建,源码位置在`src/core/vdom/vnode.js`

```vuejs
// EmptyVNode
export const createEmptyVNode = (text: string = '') => {
Expand Down Expand Up @@ -83,37 +83,38 @@ export function cloneVNode (vnode: VNode): VNode {
return cloned
}
```
* 关于Virtual Dom的分类,除了上面三种外, 还有ElementVnode,ComponentVnode等。

* 关于VNode的创建这里就不多说,在"Vue模板编译-AST生成Render字符串"那一节内容中我们就说了生成Render字符串中_c就是
createElement创建VNode,还不清楚的可以回看一下。
关于Virtual Dom的分类,除了上面三种外, 还有ElementVnode,ComponentVnode等。

关于VNode的创建这里就不多说,在"Vue模板编译-AST生成Render字符串"那一节内容中我们就说了生成Render字符串中_c就是createElement创建VNode,还不清楚的可以回头再看一下。

## Vue中的DOM diff原理
比较两棵DOM树的差异是 `Virtual DOM` 最核心的部分,这也是所谓的 `Virtual DOM` 的diff算法。 它的作用就是根据两个虚拟对象创建出描述改变内容的补丁,将这个补丁用来更新DOM。

两个Dom树的 diff 算法是一个时间复杂度为 O(n^3) 的问题,但实际上在前端当中,很少会跨越层级地移动DOM元素。即Virtual DOM 只会对同一个层级的元素进行对比,这样算法复杂度就可以达到 O(n)。


## DOM diff原理
* 比较两棵DOM树的差异是 `Virtual DOM` 最核心的部分,这也是所谓的 `Virtual DOM` 的 diff 算法。 它的作用就是根据
两个虚拟对象创建出描述改变内容的补丁,将这个补丁用来更新DOM。

* 两个Dom树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题,但实际上在前端当中,很少会跨越层级地移动DOM元素。
即Virtual DOM 只会对同一个层级的元素进行对比,这样算法复杂度就可以达到 O(n)。


## Patch 补丁更新
## Vue中的Patch 补丁更新


## VNode生命周期
## Vue中的VNode生命周期


## 总结一下
* 最后,在这里总结一下Vue中Virtual Dom执行过程:
最后,在这里总结一下Vue中Virtual Dom执行过程:

> 1.用JS对象模拟DOM树,即调用createElement生成VNode
> 2.比较两棵虚拟DOM树的差异
> 2.比较两棵虚拟DOM树的差异
> 3.把差异应用到真正的DOM树上

## 相关资料
* [https://juejin.im/post/5c8e5e4951882545c109ae9c](https://juejin.im/post/5c8e5e4951882545c109ae9c)
* [https://segmentfault.com/a/1190000008291645](https://segmentfault.com/a/1190000008291645)
* [https://github.com/livoras/blog/issues/13](https://github.com/livoras/blog/issues/13)
* [https://github.com/liutao/vue2.0-source/blob/master/vdom%E2%80%94%E2%80%94VNode.md](https://github.com/liutao/vue2.0-source/blob/master/vdom%E2%80%94%E2%80%94VNode.md)
## 相关
- [https://juejin.im/post/5c8e5e4951882545c109ae9c](https://juejin.im/post/5c8e5e4951882545c109ae9c)
- [https://segmentfault.com/a/1190000008291645](https://segmentfault.com/a/1190000008291645)
- [https://github.com/livoras/blog/issues/13](https://github.com/livoras/blog/issues/13)
- [https://github.com/liutao/vue2.0-source/blob/master/vdom%E2%80%94%E2%80%94VNode.md](https://github.com/liutao/vue2.0-source/blob/master/vdom%E2%80%94%E2%80%94VNode.md)
168 changes: 168 additions & 0 deletions 1.Vue/Vue源码学习前必备知识.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
## Vue源码学习前置知识
* 以下是我整理的Vue源码学习前置知识:Flow、DefineProperty、Vnode、类继承、递归、代理。可以针对这些点先好好理解一下。

## Flow

```vuejs
// 摘自源码中src/core/config.js
export type Config = {
// user
optionMergeStrategies: { [key: string]: Function };
silent: boolean;
productionTip: boolean;
performance: boolean;
devtools: boolean;
errorHandler: ?(err: Error, vm: Component, info: string) => void;
warnHandler: ?(msg: string, vm: Component, trace: string) => void;
ignoredElements: Array<string | RegExp>;
keyCodes: { [key: string]: number | Array<number> };
// platform
isReservedTag: (x?: string) => boolean;
isReservedAttr: (x?: string) => boolean;
parsePlatformTagName: (x: string) => string;
isUnknownElement: (x?: string) => boolean;
getTagNamespace: (x?: string) => string | void;
mustUseProp: (tag: string, type: ?string, name: string) => boolean;
// private
async: boolean;
// legacy
_lifecycleHooks: Array<string>;
};
```

## DefineProperty

```vuejs
// 摘自源码中src/core/instance/state.js
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
// 摘自源码中src/core/observer/index.js
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
```

## Vnode

```vuejs
// 摘自源码中src/core/vdom/vnode.js
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// 省略
}
```

## 类继承

```vuejs
// 摘自源码中src/core/global-api/extend.js
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
// 省略
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// 省略
}
```

## 递归

```vuejs
// 摘自源码中src/core/vdom/create-element.js
function applyNS (vnode, ns, force) {
vnode.ns = ns
if (vnode.tag === 'foreignObject') {
// use default namespace inside foreignObject
ns = undefined
force = true
}
if (isDef(vnode.children)) {
for (let i = 0, l = vnode.children.length; i < l; i++) {
const child = vnode.children[i]
if (isDef(child.tag) && (
isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) {
applyNS(child, ns, force)
}
}
}
}
```

## 代理

```vuejs
// 摘自源码中src/core/instance/proxy.js
initProxy = function initProxy (vm) {
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
```

## 相关
* [https://juejin.im/post/5b4ad441f265da0f7d4eeb7a](https://juejin.im/post/5b4ad441f265da0f7d4eeb7a)
* [https://www.jianshu.com/p/c914ccd498e7](https://www.jianshu.com/p/c914ccd498e7)
* [https://blog.csdn.net/qq_24122593/article/details/53197288](https://blog.csdn.net/qq_24122593/article/details/53197288)
2 changes: 1 addition & 1 deletion 1.Vue/src/compiler/codegen/events.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* @flow */

const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*(?:[\w$]+)?\s*\(/
const fnInvokeRE = /\([^)]*?\);*$/
const fnInvokeRE = /\([^)]*?\);*$/
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/

// KeyboardEvent.keyCode aliases
Expand Down
5 changes: 5 additions & 0 deletions 1.Vue/src/compiler/codegen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,23 @@ export type CodegenResult = {
staticRenderFns: Array<string>
};

// 根据 AST 结构拼接生成 render function 的字符串
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
// 最外层包一个 with(this) 之后返回
render: `with(this){return ${code}}`,
// 这个数组中的函数与 VDOM 中的 diff 算法优化相关
// 那些被标记为 staticRoot 节点的 VNode 就会单独生成 staticRenderFns
staticRenderFns: state.staticRenderFns
}
}

// 根据 AST 的属性调用不同的方法生成字符串返回,拼接字符串
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
Expand Down
15 changes: 15 additions & 0 deletions 1.Vue/src/compiler/error-detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ function checkNode (node: ASTNode, warn: Function) {
const range = node.rawAttrsMap[name]
if (name === 'v-for') {
checkFor(node, `v-for="${value}"`, warn, range)
} else if (name === 'v-slot' || name[0] === '#') {
checkFunctionParameterExpression(value, `${name}="${value}"`, warn, range)
} else if (onRE.test(name)) {
checkEvent(value, `${name}="${value}"`, warn, range)
} else {
Expand Down Expand Up @@ -111,3 +113,16 @@ function checkExpression (exp: string, text: string, warn: Function, range?: Ran
}
}
}

function checkFunctionParameterExpression (exp: string, text: string, warn: Function, range?: Range) {
try {
new Function(exp, '')
} catch (e) {
warn(
`invalid function parameter expression: ${e.message} in\n\n` +
` ${exp}\n\n` +
` Raw expression: ${text.trim()}\n`,
range
)
}
}
3 changes: 3 additions & 0 deletions 1.Vue/src/compiler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 1.parse,模板字符串 转换成 抽象语法树(AST)
const ast = parse(template.trim(), options)
// 2.optimize,对 AST 进行静态节点标记
if (options.optimize !== false) {
optimize(ast, options)
}
// 3.generate,抽象语法树(AST) 生成 render函数代码字符串
const code = generate(ast, options)
return {
ast,
Expand Down
9 changes: 8 additions & 1 deletion 1.Vue/src/compiler/optimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ const genStaticKeysCached = cached(genStaticKeys)
* create fresh nodes for them on each re-render;
* 2. Completely skip them in the patching process.
*/
// 将AST节点进行静态节点标记,即标上static和staticRoot属性
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
// staticKeys 是那些认为不会被更改的ast的属性
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// first pass: mark all non-static nodes.
// 第一步 标记 AST 所有静态节点
markStatic(root)
// second pass: mark static roots.
// 第二步 标记 AST 所有父节点(即子树根节点)
markStaticRoots(root, false)
}

Expand Down Expand Up @@ -69,12 +73,14 @@ function markStatic (node: ASTNode) {

function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
// 用以标记在v-for内的静态节点。这个属性用以告诉renderStatic(_m)对这个节点生成新的key,避免patch error
if (node.static || node.once) {
node.staticInFor = isInFor
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
// 一个节点如果想要成为静态根,它的子节点不能单纯只是静态文本。否则,把它单独提取出来还不如重渲染时总是更新它性能高。
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
Expand Down Expand Up @@ -104,7 +110,8 @@ function isStatic (node: ASTNode): boolean {
if (node.type === 3) { // text
return true
}
return !!(node.pre || (
// 处理特殊标记
return !!(node.pre || ( // v-pre标记的
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
Expand Down
Loading

0 comments on commit 6ec97a3

Please sign in to comment.