💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
>PS:个人笔记,仅供参考,需要深入了解请阅读参考资料。 # 参考资料 [https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc](https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc) # 编译入口 ```js export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) optimize(ast, options) const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }) ``` Vue.js 在不同的平台下都会有编译的过程,因此编译过程中的依赖的配置`baseOptions`会有所不同。 找到编译入口后,它主要执行了以下逻辑: - 解析模板字符串生成 AST ```js const ast = parse(template.trim(), options) ``` - 优化语法树 ```js optimize(ast, options) ``` - 生成代码 ```js const code = generate(ast, options) ``` # parse 编译过程首先就是对模板做解析,生成 AST,它是一种抽象语法树,是对源代码的抽象语法结构的树状表现形式。在很多编译技术中,如 babel 编译 ES6 的代码都会先生成 AST。 ```html <ul :class="bindCls" class="list" v-if="isShow"> <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li> </ul> ``` 经过`parse`过程后,生成的 AST 如下: ```js ast = { 'type': 1, 'tag': 'ul', 'attrsList': [], 'attrsMap': { ':class': 'bindCls', 'class': 'list', 'v-if': 'isShow' }, 'if': 'isShow', 'ifConditions': [{ 'exp': 'isShow', 'block': // ul ast element }], 'parent': undefined, 'plain': false, 'staticClass': 'list', 'classBinding': 'bindCls', 'children': [{ 'type': 1, 'tag': 'li', 'attrsList': [{ 'name': '@click', 'value': 'clickItem(index)' }], 'attrsMap': { '@click': 'clickItem(index)', 'v-for': '(item,index) in data' }, 'parent': // ul ast element 'plain': false, 'events': { 'click': { 'value': 'clickItem(index)' } }, 'hasBindings': true, 'for': 'data', 'alias': 'item', 'iterator1': 'index', 'children': [ 'type': 2, 'expression': '_s(item)+":"+_s(index)' 'text': '{{item}}:{{index}}', 'tokens': [ {'@binding':'item'}, ':', {'@binding':'index'} ] ] }] } ``` 可以看到,生成的 AST 是一个树状结构,每一个节点都是一个`ast element`,除了它自身的一些属性,还维护了它的父子关系,如`parent`指向它的父节点,`children`指向它的所有子节点。 # optimize 当我们的模板`template`经过`parse`过程后,会输出生成 AST 树,那么接下来我们需要对这颗树做优化,为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在`patch`的过程跳过对他们的比对。 `optimize`的过程,就是深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,`optimize`把整个 AST 树中的每一个 AST 元素节点标记了`static`和`staticRoot`,它会影响接下来执行代码生成的过程。 ```js function isStatic (node: ASTNode): boolean { if (node.type === 2) { // expression return false } if (node.type === 3) { // text return true } return !!(node.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 isPlatformReservedTag(node.tag) && // not a component !isDirectChildOfTemplateFor(node) && Object.keys(node).every(isStaticKey) )) } ``` # codegen ```html <ul :class="bindCls" class="list" v-if="isShow"> <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li> </ul> ``` 上面这段代码经过编译,执行`const code = generate(ast, options)`,生成的`render`代码串如下: ```js with(this){ return (isShow) ? _c('ul', { staticClass: "list", class: bindCls }, _l((data), function(item, index) { return _c('li', { on: { "click": function($event) { clickItem(index) } } }, [_v(_s(item) + ":" + _s(index))]) }) ) : _e() } ``` 这里的`_c`函数定义在`src/core/instance/render.js`中。 ```js vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) ``` 而`_l`、`_v`定义在`src/core/instance/render-helpers/index.js`中: ```js export function installRenderHelpers (target: any) { target._o = markOnce target._n = toNumber target._s = toString target._l = renderList target._t = renderSlot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners } ``` 顾名思义,`_c`就是执行`createElement`去创建 VNode,而`_l`对应`renderList`渲染列表;`_v`对应`createTextVNode`创建文本 VNode;`_e`对于`createEmptyVNode`创建空的 VNode。