`,即标题中提到的“最大静态子树”。那么,该模板最终生成的 AST 简化之后是这样的:
535 |
536 | ```js
537 | {
538 | type: 1,
539 | tag: 'div',
540 | attrs: {id: 'app'},
541 | children: [
542 | {
543 | type: 1,
544 | tag: 'div',
545 | attrs: {id: 'static-div'},
546 | children: [
547 | // 省略两个静态的 p 节点
548 | ],
549 | static: true
550 | },
551 | {
552 | // 省略
{{price}} 元
节点
553 | }
554 | ],
555 | static: false
556 | }
557 | ```
558 |
559 | 在知道如何标注出静态节点的最后,还是要再次强调一下标注最大静态子树的用意:根据 MVVM 的交互方式,Model 的数据修改要同时修改 View ,那些 View 中和 Model 没有关系的部分,就没必要随着 Model 的变化而修改了,因此要将其标注为静态节点。最终目的就是要更加更新 View 的效率。
560 |
561 | ### 生成 render 函数
562 |
563 | ```html
564 |
567 | ```
568 |
569 | 以上模板中的`price`完全可以通过`vm.price`获取,这个没问题。然后还需要定义一个函数`_c`,这个函数用于创建一个 node(不管是 elem 还是 vnode),再定义一个函数`_t`,用于创建文本标签。
570 |
571 | ```js
572 | function _c(tag, attrs, children) {
573 | // 省略函数体
574 | }
575 | function _t(text) {
576 | // 省略函数体
577 | }
578 | ```
579 |
580 | 然后,根据模板的内容生成如下字符串,注意字符串中的`_c` `_t`和`this.price`
581 |
582 | ```js
583 | var code = '_c("div", {id: "app"}, [_c("p", {}, [_t(this.price)])])'
584 | ```
585 |
586 | 再然后,就可以通过`var render = new Function(code)`来生成一个函数,最终生成的函数其实就是这样一个格式:
587 |
588 | ```js
589 | render = function () {
590 | _c('div', {id: 'app'}, [
591 | _c('p', {}, [
592 | _t(this.prirce + ' 元')
593 | ])
594 | ])
595 | }
596 | ```
597 |
598 | 看以上代码的函数和最初的模板,是不是很相似?他们的区别在于:模板是 html 格式,但是 html 不会识别`{{price}}`;而函数完全是 JS 代码,`this.price`放在 JS 中完全可以被运行。
599 |
600 | 以上还是以一个最简单的模板为例,模板如果变的复杂,需要注意两方面:
601 |
602 | - 不同的指令,生成 render 函数会不一样。指令越多,render 函数会变得越复杂。
603 | - 如果有静态的节点,将不会在 render 函数中体现,而是在`staticRenderFns`中体现。静态节点只会在初次显示 View 的时候被执行,后续的 Model 变化将不会再触发静态节点的渲染。
604 |
605 | ### 整理流程
606 |
607 | 
608 |
609 | 参考文章一开始给出的流程图,模板渲染就是图中红框标出那部分。简单看来就是 3 部分:
610 |
611 | - 根据模板生成 AST
612 | - 优化 AST 找出其静态,不依赖 Model 改变而改变的部分
613 | - 根据优化后的 AST 生成 render 函数
614 |
615 | ### 和绑定依赖的关系
616 |
617 | 回顾文章一开始介绍响应式的那部分,通过`Object.defineProperty`的设置,在 Model 属性`get`的时候会被绑定依赖。现在看一下 render 函数,在 render 函数执行的时候,肯定会触发`get`,从而绑定依赖。
618 |
619 | 因此,到这里,绑定依赖的逻辑,就首先清楚了。
620 |
621 | ### 接下来
622 |
623 | 该部分最后生成是 render 函数,那么 render 函数何时被执行,以及执行的时候生成了什么,下文将要介绍。
624 |
625 | -------
626 |
627 | ## 虚拟 DOM
628 |
629 | 这部分是围绕着 vdom 来介绍 View 的渲染,包括 View 的渲染和更新、以及响应式如何触发这种更新机制。但是,不会深入讲解 vdom 内部的原理。Vue2 源码中的 vdom 也不是完全自己写的,而是将 [Snabbdom](https://github.com/snabbdom/snabbdom) 这一经典的 vdom 开源库集成进来的。想要深入学习 vdom 可参考:
630 |
631 | - 经典博客 [深度解析 vdom](https://github.com/livoras/blog/issues/13)
632 | - 《Vue.js 权威指南》一书中介绍 vdom 的章节,通过示例和图形的方式介绍的比较清晰
633 | - Snabbdom 的使用和实现,具体资源网上去搜
634 |
635 | ### vdom 的基本使用
636 |
637 | 浏览器中解析 html 会生成 DOM 树,它是由一个一个的 node 节点组成。同理,vdom 也是由一个一个的 vnode 组成。vdom 、 vnode 都是用 JS 对象的方式来模拟真实的 DOM 或者 node 。
638 |
639 | 参考 [Snabbdom](https://github.com/snabbdom/snabbdom) 的样例
640 |
641 | ```js
642 | // 获取容器元素
643 | var container = document.getElementById('container');
644 | // 创造一个 vnode
645 | var vnode = h('div#container.two.classes', {on: {click: someFn}}, [
646 | h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
647 | ' and this is just normal text',
648 | h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')
649 | ]);
650 | // Patch into empty DOM element – this modifies the DOM as a side effect
651 | patch(container, vnode);
652 | ```
653 |
654 | `h`函数可以生成一个 vnode ,然后通过`patch`函数将生成的 vnode 渲染到真实的 node(`container`)中。这其中涉及不到 vdom 的核心算法 —— diff 。继续追加几行代码:
655 |
656 | ```js
657 | var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [
658 | h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
659 | ' and this is still just normal text',
660 | h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')
661 | ]);
662 | // Second `patch` invocation
663 | patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
664 | ```
665 |
666 | 又生成了新的 vnode `newVnode` ,然后`patch(vnode, newVnode)`。这其中就会通过 diff 算法去对比`vnode`和`newVnode`,对比两者的区别,最后渲染真实 DOM 时候,只会将有区别的部分重新渲染。在浏览器中,DOM 的任何操作都是昂贵的,减少 DOM 的变动,就会提高性能。
667 |
668 | ### render 函数生成 vnode
669 |
670 | 以上示例中,vnode 是手动生成的,在 Vue 中 render 函数就会生成 vnode —— render 函数就是刚刚介绍过的。这样一串联,整个流程就很清晰了:
671 |
672 | - 解析模板最终生成 render 函数。
673 | - 初次渲染时,直接执行 render 函数(执行过程中,会触发 Model 属性的`get`从而绑定依赖)。render 函数会生成 vnode ,然后 patch 到真实的 DOM 中,完成 View 的渲染。
674 | - Model 属性发生变化时,触发通知,重新执行 render 函数,生成 newVnode ,然后`patch(vnode, newVnode)`,针对两者进行 diff 算法,最终将有区别的部分重新渲染。
675 | - Model 属性再次发生变化时,又会触发通知 ……
676 |
677 | 另外,还有一个重要信息。如果连续修改多个 Model 属性,那么会连续触发通知、重新渲染 View 吗?—— 肯定不会,**View 的渲染是异步的**。即,Vue 会一次性集合多个 Model 的变更,最后一次性渲染 View ,提高性能。
678 |
679 | 以上描述的触发 render 函数的过程,可以从源码 [./code/src/core/instance/lifecycle.js] 中`mountComponent`中找到。这一行最关键`vm._watcher = new Watcher(vm, updateComponent, noop)`,其中`updateComponent`就会触发执行 render 函数。
680 |
681 | 下面就重点介绍一下`new Watcher`是干嘛用的。
682 |
683 | ### Watcher
684 |
685 | Watch 的定义在 [./code/src/core/observer/watcher.js](./code/src/core/observer/watcher.js) 中,简化后的代码如下
686 |
687 | ```js
688 | import Dep, { pushTarget, popTarget } from './dep'
689 |
690 | export default class Watcher {
691 | constructor (
692 | vm: Component,
693 | expOrFn: string | Function,
694 | cb: Function
695 | ) {
696 | // 传入的函数,赋值给 this.geter
697 | this.getter = expOrFn
698 | // 执行 get() 方法
699 | this.value = this.get()
700 | }
701 |
702 | get () {
703 | // 将 this (即 Wathcer 示例)给全局的 Dep.target
704 | pushTarget(this)
705 | let value
706 | const vm = this.vm
707 | try {
708 | // this.getter 即 new Watcher(...) 传入的第二个参数 expOrFn
709 | // 这一步,即顺便执行了 expOrFn
710 | value = this.getter.call(vm, vm)
711 | } catch (e) {
712 | // ...
713 | } finally {
714 | popTarget()
715 | }
716 | return value
717 | }
718 |
719 | // dep.subs[i].notify() 会执行到这里
720 | update () {
721 | // 异步处理 Watcher
722 | // queueWatcher 异步调用了 run() 方法,因为:如果一次性更新多个属性,无需每个都 update 一遍,异步就解决了这个问题
723 | queueWatcher(this)
724 | }
725 |
726 | run () {
727 | // 执行 get ,即执行 this.getter.call(vm, vm) ,即执行 expOrFn
728 | this.get()
729 | }
730 | }
731 | ```
732 |
733 | 结合调用它的语句`new Watcher(vm, updateComponent, noop)`,将`updateComponent`复制给`this.getter`,然后代码会执行到`get`函数。
734 |
735 | 首先,`pushTarget(this)`将当前的 Wacther 实例赋值给了`Dep.target`。这里要联想到上文介绍响应式的时候的一段代码
736 |
737 | ```js
738 | Object.defineProperty(obj, key, {
739 | get: function reactiveGetter () {
740 | if (Dep.target) {
741 | dep.depend()
742 | }
743 | return val
744 | },
745 | set: function reactiveSetter (newVal) {
746 | val = newVal
747 | dep.notify()
748 | }
749 | })
750 | ```
751 |
752 | 注意看上面代码中的`if (Dep.target) { dep.depend() }`。此前一直说“触发`get`时候绑定依赖”这句话,到现在终于可以有一个结论:绑定的依赖就是这个`new Watcher(...)`。
753 |
754 | 然后,执行了`this.getter.call(vm, vm)`,即将`updateComponent`执行了,触发了初次的渲染。函数的执行将真正触发`get`,然后绑定依赖。过程基本走通了。
755 |
756 | ### 触发通知
757 |
758 | 当 Model 属性修改时会触发到上面代码中的`set`函数,然后执行`dep.notify()`。继续看看这个`notify`函数的内容
759 |
760 | ```js
761 | export default class Dep {
762 | // 省略 N 行
763 |
764 | notify () {
765 | // stabilize the subscriber list first
766 | const subs = this.subs.slice()
767 | for (let i = 0, l = subs.length; i < l; i++) {
768 | // 在 defineReactive 的 setter 中触发
769 | // subs[i] 是一个 Watcher 实例
770 | subs[i].update()
771 | }
772 | }
773 | }
774 | ```
775 |
776 | 这里的`subs`就存储着所有的`Watcher`实例,然后遍历去触发它们的`update`方法。找到上文粘贴出来的`Watcher`的源码,其中`update`这么定义的:
777 |
778 | ```js
779 | // dep.subs[i].notify() 会执行到这里
780 | update () {
781 | // 异步处理 Watcher
782 | // queueWatcher 异步调用了 run() 方法,因为:如果一次性更新多个属性,无需每个都 update 一遍,异步就解决了这个问题
783 | queueWatcher(this)
784 | }
785 |
786 | run () {
787 | // 执行 get ,即执行 this.getter.call(vm, vm) ,即执行 expOrFn
788 | this.get()
789 | }
790 | ```
791 |
792 | 看一下代码的注释也基本就能明白了,`update`会异步调用`run`(为何是异步调用,上文也介绍过了)。然后`run`中执行的`this.get()`函数上文已经介绍过,会触发传进来的`updateComponent`函数,也就触发了 View 的更新。
793 |
794 | ### 整体流程
795 |
796 | 该部分虽然名字是“虚拟 DOM”,但是有一半儿介绍了响应式的内容。这也是没办法,Vue MVVM 的整体流程就是这么走的。因此,该部分要求读者明白两点内容:
797 |
798 | - vdom 的生成和 patch
799 | - 完整的响应式流程
800 |
801 | 阅读至此,先忽略掉一些细节,再看文章最开始的这张图,应该会和一开始看不一样吧?图中所画的流程,肯定都能看懂了。
802 |
803 | 
804 |
805 | -----
806 |
807 | ## 如何阅读简化后的源码
808 |
809 | 下面简单说一下如何阅读我整理出来的只针对 MVVM 的简化后的 Vue2 的源码
810 |
811 | - `code/package.json`中,`scripts`规定了打包的各种命令,只看`dev`就好了
812 | - 通过`dev`打包命令,可以对应到`code/build/config.js`,并对应到`web-full-dev`的配置,最终对应到`./code/src/platforms/web/entry-runtime-with-compiler.js`
813 | - 然后剩下的代码,就是可以通过 JS 模块化规则找到。大的模块分为:`src/compiler`是模板编译相关,`src/core/instance`是 Vue 实例相关,`src/core/observer`是响应式相关,`src/core/vdom`是 vdom 相关。
814 |
815 | vue2 源码结构非常清晰,读者如果自己做框架和工具的话,可以参考这个经典框架的源码。
816 |
817 | -----
818 |
819 | ## 最后
820 |
821 | MVVM 涉及的内容、代码都很多,虽然是快速了解,但是篇幅也很大。外加自己也是现学现卖,难免有些杂乱,读者如有问题或者建议,欢迎给我 [提交 issue](https://github.com/wangfupeng1988/learn-vue2-mvvm/issues) 。
822 |
823 | -------
824 |
825 | ## 参考链接
826 |
827 | - https://segmentfault.com/a/1190000004346467
828 | - http://www.cnblogs.com/libin-1/p/6845669.html
829 | - https://github.com/xiaofuzi/deep-in-vue/blob/master/src/the-super-tiny-vue.js
830 | - https://github.com/KevinHu-1024/kevins-blog/issues/1
831 | - https://github.com/KevinHu-1024/kevins-blog/issues/5
832 | - https://github.com/liutao/vue2.0-source
833 | - http://www.jianshu.com/p/758da47bfdac
834 | - http://www.jianshu.com/p/bef1c1ee5a0e
835 | - http://www.jianshu.com/p/d3a15a1f94a0
836 | - https://github.com/luobotang/simply-vue
837 | - http://zhouweicsu.github.io/blog/2017/03/07/vue-2-0-reactivity/
838 | - https://segmentfault.com/a/1190000007334535
839 | - https://segmentfault.com/a/1190000007484936
840 | - http://www.cnblogs.com/aaronjs/p/7274965.html
841 | - http://www.jackpu.com/-zhang-tu-bang-zhu-ni-xiao-hua-vue2-0de-yuan-ma/
842 | - https://github.com/youngwind/blog/issues
843 | - https://www.zybuluo.com/zhouweicsu/note/729712
844 | - https://github.com/snabbdom/snabbdom
845 | - https://github.com/Matt-Esch/virtual-dom
846 | - https://gmiam.com/post/evo.html
847 | - https://github.com/livoras/blog/issues/13
848 | - https://calendar.perfplanet.com/2013/diff/
849 | - https://github.com/georgebbbb/fakeVue
850 | - http://hcysun.me/2016/04/28/JavaScript%E5%AE%9E%E7%8E%B0MVVM%E4%B9%8B%E6%88%91%E5%B0%B1%E6%98%AF%E6%83%B3%E7%9B%91%E6%B5%8B%E4%B8%80%E4%B8%AA%E6%99%AE%E9%80%9A%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%8F%98%E5%8C%96/
851 | - https://github.com/answershuto/learnVue
852 | - https://github.com/xufei/blog/issues/10
853 | - https://cn.vuejs.org/v2/guide/reactivity.html
854 | - https://item.jd.com/12028224.html
855 |
856 | ## 关于作者
857 |
858 | - 关注作者的博客 - 《[深入理解javascript原型和闭包系列](http://www.cnblogs.com/wangfupeng1988/p/4001284.html)》《[深入理解javascript异步系列](https://github.com/wangfupeng1988/js-async-tutorial)》《[换个思路学习nodejs](https://github.com/wangfupeng1988/node-tutorial)》《[CSS知多少](http://www.cnblogs.com/wangfupeng1988/p/4325007.html)》
859 | - 学习作者的教程 - 《[前端JS高级面试](https://coding.imooc.com/class/190.html)》《[前端JS基础面试题](http://coding.imooc.com/class/115.html)》《[React.js模拟大众点评webapp](http://coding.imooc.com/class/99.html)》《[zepto设计与源码分析](http://www.imooc.com/learn/745)》《[json2.js源码解读](http://study.163.com/course/courseMain.htm?courseId=691008)》
860 |
861 | 如果你感觉有收获,欢迎给我打赏 ———— 以激励我更多输出优质开源内容
862 |
863 | 
--------------------------------------------------------------------------------
/code/ISSUE.md:
--------------------------------------------------------------------------------
1 |
2 | ## ISSUE
3 |
4 | - 生成 ast 时,各个 directive 如何处理,如(v-model v-if v-for 等)
5 | - 声明周期的流程串联
6 | - 渲染时,各个 directive 如何实现的
7 | - 异步渲染是如何实现的
8 | - 静态子树是如何渲染的,又是如何避免 re-render 时候不渲染的
9 |
--------------------------------------------------------------------------------
/code/build/config.js:
--------------------------------------------------------------------------------
1 | const builds = {
2 | // Runtime+compiler development build (Browser)
3 | 'web-full-dev': {
4 | // 入口文件指向 platforms/web/entry-runtime-with-compiler.js
5 | entry: resolve('web/entry-runtime-with-compiler.js'),
6 | dest: resolve('dist/vue.js'),
7 | format: 'umd',
8 | env: 'development',
9 | alias: { he: './entity-decoder' },
10 | banner
11 | }
12 | }
13 |
14 | if (process.env.TARGET) {
15 | module.exports = genConfig(builds[process.env.TARGET])
16 | } else {
17 | exports.getBuild = name => genConfig(builds[name])
18 | exports.getAllBuilds = () => Object.keys(builds).map(name => genConfig(builds[name]))
19 | }
--------------------------------------------------------------------------------
/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue",
3 | "version": "2.4.2",
4 | "description": "Reactive, component-oriented view layer for modern web interfaces.",
5 | "main": "dist/vue.runtime.common.js",
6 | "module": "dist/vue.runtime.esm.js",
7 | "unpkg": "dist/vue.js",
8 | "typings": "types/index.d.ts",
9 | "files": [
10 | "src",
11 | "dist/*.js",
12 | "types/*.d.ts"
13 | ],
14 | "scripts": {
15 | "dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev"
16 | }
17 | }
--------------------------------------------------------------------------------
/code/src/compiler/codegen/index.js:
--------------------------------------------------------------------------------
1 |
2 | export function generate (
3 | ast: ASTElement | void,
4 | options: CompilerOptions
5 | ): CodegenResult {
6 | // 重点关注 state.staticRenderFns ,最大静态子树的渲染函数会放在其中
7 | const state = new CodegenState(options)
8 | // 根据 ast 生成的 render 函数体
9 | // genElement 定义于本文件中
10 | const code = ast ? genElement(ast, state) : '_c("div")'
11 | return {
12 | render: `with(this){return ${code}}`,
13 | staticRenderFns: state.staticRenderFns
14 | }
15 | }
16 |
17 | // 生成 render 函数体的代码
18 | export function genElement (el: ASTElement, state: CodegenState): string {
19 | if (el.staticRoot && !el.staticProcessed) {
20 | return genStatic(el, state)
21 | } else if (el.once && !el.onceProcessed) {
22 | return genOnce(el, state)
23 | } else if (el.for && !el.forProcessed) {
24 | return genFor(el, state)
25 | } else if (el.if && !el.ifProcessed) {
26 | return genIf(el, state)
27 | } else {
28 | // data 是一个 stirng 化 JSON 格式,包含当前节点的所有信息
29 | const data = genData(el, state)
30 | // 返回一个数组的 string 形式,子元素的信息
31 | const children = genChildren(el, state, true)
32 | const code = `_c('${el.tag}'${ // _c 即 createElement
33 | data ? `,${data}` : '' // data
34 | }${
35 | children ? `,${children}` : '' // children
36 | })`
37 | return code
38 | }
39 | }
40 |
41 | // hoist static sub-trees out
42 | function genStatic (el: ASTElement, state: CodegenState): string {
43 | el.staticProcessed = true
44 | // 用上了 state.staticRenderFns
45 | state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
46 | // 返回 '_m(n)' n 即是 staticRenderFns 元素的 index
47 | return `_m(${state.staticRenderFns.length - 1})` // _m 即 renderStatic
48 | }
49 |
50 | // v-once
51 | function genOnce (el: ASTElement, state: CodegenState): string {
52 | el.onceProcessed = true
53 | // 注意 el.onceProcessed = true 之后,执行到 genStatic
54 | // 会在继续执行到 genElement ,此时 onceProcessed = true 就能起到作用
55 | return genStatic(el, state)
56 | }
57 |
58 | // v-for
59 | export function genFor (
60 | el: any,
61 | state: CodegenState
62 | ): string {
63 | const exp = el.for
64 | const alias = el.alias
65 | const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
66 | const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
67 |
68 | el.forProcessed = true // avoid recursion
69 | return `${'_l'}((${exp}),` + /* _l 即 renderList */
70 | `function(${alias}${iterator1}${iterator2}){` +
71 | `return ${genElement(el, state)}` +
72 | '})'
73 | }
74 |
75 | export function genIf (
76 | el: any,
77 | state: CodegenState,
78 | altGen?: Function,
79 | altEmpty?: string
80 | ): string {
81 | el.ifProcessed = true // avoid recursion
82 | // ?????
83 | return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
84 | }
85 |
86 | export function genData (el: ASTElement, state: CodegenState): string {
87 | let data = '{'
88 |
89 | // directives first.
90 | // directives may mutate the el's other properties before they are generated.
91 | const dirs = genDirectives(el, state)
92 | // dirs 格式如 'directives:[{name:"show",rawName:"v-show",value:(show),expression:"show"}, {...}]'
93 | if (dirs) data += dirs + ','
94 |
95 | // key
96 | if (el.key) {
97 | data += `key:${el.key},`
98 | }
99 |
100 | // attributes
101 | if (el.attrs) {
102 | data += `attrs:{${genProps(el.attrs)}},`
103 | }
104 |
105 | // event handlers
106 | if (el.events) {
107 | data += `${genHandlers(el.events, false, state.warn)},`
108 | }
109 |
110 | // component v-model
111 | if (el.model) {
112 | data += `model:{value:${
113 | el.model.value
114 | },callback:${
115 | el.model.callback
116 | },expression:${
117 | el.model.expression
118 | }},`
119 | }
120 |
121 | data = data.replace(/,$/, '') + '}'
122 | // 最终 data 会是一个 stirng 化 JSON 格式,包含当前节点的所有信息
123 | return data
124 | }
125 |
126 | export function genChildren (
127 | el: ASTElement,
128 | state: CodegenState
129 | ): string | void {
130 | const children = el.children
131 | if (children.length) {
132 | // optimize single v-for
133 | if (children.length === 1 && el.for) {
134 | // 再调用 genElement
135 | return genElement(el, state)
136 | }
137 | // 注意:针对 v-for 的情况,不能走正常的递归子节点的处理,需要特殊处理
138 |
139 | // 递归分析子节点
140 | return `[${children.map(c => genNode(c, state)).join(',')}]`
141 | }
142 | }
143 |
144 | function genNode (node: ASTNode, state: CodegenState): string {
145 | if (node.type === 1) {
146 | // 普通节点
147 | return genElement(node, state)
148 | } if (node.type === 3 && node.isComment) {
149 | // 注释
150 | return genComment(node)
151 | } else {
152 | // 文字
153 | return genText(node)
154 | }
155 | }
156 |
157 | export function genComment (comment: ASTText): string {
158 | return `_e(${JSON.stringify(comment.text)})` // _e 即 createEmptyVNode
159 | }
160 |
161 | export function genText (text: ASTText | ASTExpression): string {
162 | return `_v(${text.type === 2 /* _v 即 createTextVNode */
163 | ? text.expression // no need for () because already wrapped in _s()
164 | : JSON.stringify(text.text)
165 | })`
166 | }
167 |
168 | function genDirectives (el: ASTElement, state: CodegenState): string | void {
169 | const dirs = el.directives
170 | if (!dirs) return
171 | let res = 'directives:['
172 | let i, l, dir
173 | for (i = 0, l = dirs.length; i < l; i++) {
174 | dir = dirs[i]
175 | res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
176 | dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
177 | },`
178 | }
179 | // 返回 directives:[{name:"show",rawName:"v-show",value:(show),expression:"show"}, {...}] 格式
180 | return res.slice(0, -1) + ']'
181 | }
--------------------------------------------------------------------------------
/code/src/compiler/create-compiler.js:
--------------------------------------------------------------------------------
1 | import { createCompileToFunctionFn } from './to-function'
2 |
3 | export function createCompilerCreator (baseCompile: Function): Function {
4 | // 传入一个函数参数 baseCompile ,最终返回一个函数
5 | return function createCompiler (baseOptions: CompilerOptions) {
6 |
7 | // 定义 compile 函数
8 | function compile (
9 | template: string,
10 | options?: CompilerOptions
11 | ): CompiledResult {
12 | // baseCompile 是传递过来的函数,最终返回值格式 {ast, render, staticRenderFns}
13 | const compiled = baseCompile(template, finalOptions)
14 | return compiled
15 | }
16 |
17 | // 返回
18 | return {
19 | compile,
20 | compileToFunctions: createCompileToFunctionFn(compile)
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/code/src/compiler/helper.js:
--------------------------------------------------------------------------------
1 | export function addHandler (
2 | el: ASTElement,
3 | name: string,
4 | value: string
5 | ) {
6 | let events
7 | // 事件都存储在 el.events 中
8 | events = el.events || (el.events = {})
9 | // 新事件
10 | const newHandler = { value }
11 | // 看是否已有 name 的其他事件
12 | const handlers = events[name]
13 | if (Array.isArray(handlers)) {
14 | handlers.push(newHandler)
15 | } else if (handlers) {
16 | events[name] = [handlers, newHandler]
17 | } else {
18 | // 存储事件
19 | events[name] = newHandler
20 | }
21 | }
22 |
23 | export function addAttr (el: ASTElement, name: string, value: string) {
24 | (el.attrs || (el.attrs = [])).push({ name, value })
25 | }
26 |
27 | export function addDirective (
28 | el: ASTElement,
29 | name: string,
30 | rawName: string,
31 | value: string
32 | ) {
33 | (el.directives || (el.directives = [])).push({ name, rawName, value })
34 | }
35 |
36 | export function getAndRemoveAttr (el: ASTElement, name: string): ?string {
37 | let val
38 | if ((val = el.attrsMap[name]) != null) {
39 | const list = el.attrsList
40 | for (let i = 0, l = list.length; i < l; i++) {
41 | if (list[i].name === name) {
42 | list.splice(i, 1)
43 | break
44 | }
45 | }
46 | }
47 | return val
48 | }
--------------------------------------------------------------------------------
/code/src/compiler/index.js:
--------------------------------------------------------------------------------
1 | import { createCompilerCreator } from './create-compiler'
2 | import { parse } from './parser/index'
3 | import { optimize } from './optimizer'
4 | import { generate } from './codegen/index'
5 |
6 | // createCompilerCreator 定义于 ./create-compiler
7 | export const createCompiler = createCompilerCreator(
8 | // 传入一个函数作为参数
9 | function baseCompile (
10 | template: string,
11 | options: CompilerOptions
12 | ): CompiledResult {
13 | // parse 定义于 ./parser/index
14 | // 将 html 转换为 ast (抽象语法树)
15 | const ast = parse(template.trim(), options)
16 | // optimize 定义于 ./optimizer
17 | // 标记 ast 中的静态节点,即不包含变量无需改变的节点
18 | optimize(ast, options)
19 | // generate 定义于 ./codegen/index
20 | // 返回 {render: '..render函数体..(字符串)', staticRenderFns: Array
}
21 | const code = generate(ast, options)
22 |
23 | // 返回 CompiledResult 格式
24 | return {
25 | ast,
26 | render: code.render,
27 | staticRenderFns: code.staticRenderFns
28 | }
29 | }
30 | )
--------------------------------------------------------------------------------
/code/src/compiler/optimizer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Goal of the optimizer: walk the generated template AST tree
3 | * and detect sub-trees that are purely static, i.e. parts of
4 | * the DOM that never needs to change.
5 | *
6 | * Once we detect these sub-trees, we can:
7 | *
8 | * 1. Hoist them into constants, so that we no longer need to
9 | * create fresh nodes for them on each re-render;
10 | * 2. Completely skip them in the patching process.
11 | */
12 | export function optimize (root: ?ASTElement) {
13 | if (!root) return
14 | // first pass: mark all non-static nodes.
15 | // 标记所有静态节点
16 | markStatic(root)
17 | // second pass: mark static roots.
18 | // 找出最大的静态子树
19 | markStaticRoots(root)
20 | }
21 |
22 | // 标记静态节点
23 | function markStatic (node: ASTNode) {
24 | // 获取该节点是否是 static
25 | node.static = isStatic(node)
26 | if (node.type === 1) {
27 | for (let i = 0, l = node.children.length; i < l; i++) {
28 | const child = node.children[i]
29 | // 递归子节点
30 | markStatic(child)
31 | if (!child.static) {
32 | // 只要有一个子节点不是 static ,那么当前节点就肯定不是 static
33 | node.static = false
34 | }
35 | }
36 | }
37 | }
38 |
39 | // 找出最大的静态子树
40 | function markStaticRoots (node: ASTNode) {
41 | if (node.type === 1) {
42 | if (node.static && node.children.length && !(
43 | // 下面两行代码如 abc
44 | node.children.length === 1 &&
45 | node.children[0].type === 3
46 | )) {
47 | // 满足以下情况才能设置 staticRoor = true
48 | // 第一,node.static
49 | // 第二,node.children.length
50 | // 第三,不能是 abc
这种只有一个根节点,且根节点 type === 3
51 | node.staticRoot = true
52 |
53 | // 一旦当前节点标记为 staticRoot = true 之后,就退出,不再去深入递归子节点
54 | // 因为该情况下,子节点肯定都是 static 的情况
55 | // 这样就找到了“最大的静态子树”
56 | return
57 | } else {
58 | // 标记当前节点
59 | node.staticRoot = false
60 | }
61 | if (node.children) {
62 | for (let i = 0, l = node.children.length; i < l; i++) {
63 | // 递归子节点
64 | markStaticRoots(node.children[i])
65 | }
66 | }
67 | }
68 | }
69 |
70 | function isStatic (node: ASTNode): boolean {
71 | if (node.type === 2) { // expression
72 | return false
73 | }
74 | if (node.type === 3) { // text
75 | return true
76 | }
77 | return !!(
78 | !node.hasBindings && // no dynamic bindings
79 | !node.if && !node.for && // not v-if or v-for or v-else
80 | )
81 | }
--------------------------------------------------------------------------------
/code/src/compiler/parser/html-parser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Not type-checking this file because it's mostly vendor code.
3 | */
4 |
5 | /*!
6 | * HTML Parser By John Resig (ejohn.org)
7 | * Modified by Juriy "kangax" Zaytsev
8 | * Original code by Erik Arvidsson, Mozilla Public License
9 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
10 | */
11 |
12 | export function parseHTML (html, options) {
13 | // ...
14 | }
--------------------------------------------------------------------------------
/code/src/compiler/parser/index.js:
--------------------------------------------------------------------------------
1 | // 介绍这个之前,可以先用 simplehtmlparser.js 做例子
2 |
3 | import { parseHTML } from './html-parser'
4 | import { parseText } from './text-parser'
5 |
6 | import {
7 | addProp,
8 | addAttr,
9 | baseWarn,
10 | addHandler,
11 | addDirective,
12 | getBindingAttr,
13 | getAndRemoveAttr,
14 | pluckModuleFunction
15 | } from '../helpers'
16 |
17 | export const onRE = /^@|^v-on:/
18 | export const dirRE = /^v-|^@|^:/
19 | export const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/
20 | export const forIteratorRE = /\((\{[^}]*\}|[^,]*),([^,]*)(?:,([^,]*))?\)/
21 |
22 | const argRE = /:(.*)$/
23 | const bindRE = /^:|^v-bind:/
24 | const modifierRE = /\.[^.]+/g
25 |
26 | /**
27 | * Convert HTML string to AST.
28 | */
29 | export function parse (
30 | template: string,
31 | options: CompilerOptions
32 | ): ASTElement | void {
33 | // 节点栈
34 | const stack = []
35 | // 根节点,最终改回返回
36 | let root
37 | // 当前的父节点
38 | let currentParent
39 |
40 | parseHTML(template, {
41 | // node 的开始
42 | start (tag, attrs, unary) {
43 | // unary 是否一元标签,如
44 |
45 | const element = {
46 | type: 1,
47 | tag,
48 | // attrsList 数组形式
49 | attrsList: attrs,
50 | // attrsMap 对象形式,如 {id: 'app', 'v-text': 'xxx'}
51 | attrsMap: makeAttrsMap(attrs),
52 | parent: currentParent,
53 | children: []
54 | }
55 |
56 | // 处理 v-for ,生成 element.for, element.alias
57 | processFor(element)
58 | // 处理 v-if ,生成 element.if, element.else, element.elseif
59 | processIf(element)
60 | // 处理 v-once ,生成 element.once
61 | processOnce(element)
62 | // 处理 key ,生成 element.key
63 | processKey(element)
64 |
65 | // 处理属性
66 | // 第一,处理指令:v-bind v-on 以及其他指令的处理
67 | // 第二,处理普通的 html 属性,如 style class 等
68 | processAttrs(element)
69 |
70 | // tree management
71 | if (!root) {
72 | // 确定根节点
73 | root = element
74 | }
75 | if (currentParent) {
76 | // 当前有根节点
77 | currentParent.children.push(element)
78 | element.parent = currentParent
79 | }
80 | if (!unary) {
81 | // 不是一元标签(
等)
82 | currentParent = element
83 | stack.push(element)
84 | }
85 | },
86 | // node 的结束
87 | end () {
88 | // pop stack
89 | stack.length -= 1
90 | currentParent = stack[stack.length - 1]
91 | },
92 | // 字符
93 | chars (text: string) {
94 | const children = currentParent.children
95 | let expression
96 | // 处理字符
97 | expression = parseText(text) // 如 '_s(price)+" 元"' ,_s 在 core/instance/render.js 中定义
98 | children.push({
99 | type: 2,
100 | expression,
101 | text
102 | })
103 | },
104 | // 注释内容
105 | comment (text: string) {
106 | currentParent.children.push({
107 | type: 3,
108 | text,
109 | isComment: true
110 | })
111 | }
112 | })
113 | return root
114 | }
115 |
116 | function makeAttrsMap (attrs: Array