在学习源码之前,我们需要了解这么几个东西的概念。什么是AST、什么是render函数、什么是模板等等。。。
前提概念
AST语法树
Abstract Syntax Tree
。抽象语法树,用js语言来描述html代码。将html代码筛选出我们需要的属性,转成js语言。
<div id="app" style="color: red"> <p class="text">文本</p> </div> // ast语法树 描述html语法 <script> let root = { tag: 'div', type: 1, attrs: [ { name: "id", value: "app" }, { name: "style",value: {color: " red"} } ], parent: null, children: [{ tag: 'p', attrs: [{ name: 'class', value: 'text' }], parent: root, type: 1, children: [{ text: '文本', type: 3 }] }] } </script>
模板template
template
:基于html的字符串模板。优先级高于原始html模板。
渲染函数render
render
:模板的代替,相比较模板更接近编译器。在vue中,如果render存在,template模板字符串将不会挂载到元素中渲染。
html模板、字符串模板、render函数比较
大家可以先看一下vue最初的使用:
html
:
<div id="app"> <ul> <li v-for="item in sex" v-if="item==='男'">{{item}}</li> </ul> </div> <script> let vm = new Vue({ el: '#app', data: { sex: ["男", "男", "女", "男"] }, }) console.log(vm.$options.render); </script>
template
模板:
<div id="app"></div> <script> let vm = new Vue({ el: '#app', template: `<ul> <li v-for="item in sex" v-if="item==='男'">{{item}}</li> </ul>`, data: { sex: ["男", "男", "女", "男"] }, }) console.log(vm.$options.render); </script>
运行浏览器,两者页面上出现的均是男字。但是在template
模板中,在html
中写入其他的东西是无法显示的。说明如果模板存在,那么优先渲染template
中的内容。
而在控制台输出vm.$options.render
,可以看到这么一串东西:
ƒ anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((sex),function(item){return (item==='男')?_c('li',[_v(_s(item))]):_e()}),0)])} }
这一串的东西就是将我们html中的内容转成用js语言描述的模样。当然我们也可以将这一串东西转成vue中的render函数,试一试浏览器运行。
<div id="app">hhh</div> <script> let vm = new Vue({ el: '#app', data: { sex: ["男", "男", "女", "男"] }, render(h) { with (this) { return _c('div', { attrs: { "id": "app" } }, [_c('ul', _l((sex), function (item) { return (item === '男') ? _c('li', [_v(_s(item))]) : _e() }), 0)]) } }, }) </script>
对于render函数里面的_l
、_e
、_v
等这些函数可以参考源码中定义的作用。
target._o = markOnce // v-once target._n = toNumber // 转数字 target._s = toString // 转字符串 JSON.stringify target._l = renderList // 循环 v-for target._t = renderSlot // 插槽 slot target._v = createTextVNode // 文本节点 target._e = createEmptyVNode // 空节点 ...
此外还有一个_c
函数是创建虚拟节点(createElement)。
事实证明:
render
函数可以代替模板,并且render
的优先级高于template
模板,template
模板优先级高于原始的html
。
jsx
相比较render内部一长串的东西,我们可能难于理解。相对应的模板会比较简单一些。 Vue中支持使用jsx就是因为vue中的Babel插件。
let vm = new Vue({ el: '#app', data: { sex: ["男", "男", "女", "男"] }, render() { return <ul> <li v-for="item in sex" v-if="item==='男'">{{ item }}</li> </ul> } })
真实DOM
页面上的每个元素都算一个节点。 例子:
<div id="app" style="color: red"> <p class="text">文本</p> </div> 复制代码
虚拟DOM
简化DOM节点的。通过渲染函数的_c
、_v
等函数实现用对象来描述dom的操作。虚拟DOM将真实DOM简化,包含tag,data,key,children,text等属性。
<div id="app" style="color: red"> <p class="text">文本</p> </div> // 虚拟dom <script> let root = { tag: 'div', data: [{ id: 'app', style: { color: 'red' } }], key:undefined, text:undefined, children:[ { tag: 'p', key:undefined, text:undefined, data:{class:'text'}, children: [] ] } </script>
编译
简要说明一下编译渲染过程:
- 解析
html
字符 将html
字符 =>AST
语法树 - 需要将
AST
语法树生成最终的render
函数
- 拼接字符串
- 增加
with
new Function
- 生成虚拟
dom
- 生成真实
dom
在编译的过程中,我们需要将html
转成AST
语法树再转成render
虚拟dom
。其实普通的编译也可直接从render
转虚拟dom
。
只是在vue的编译过程中,在转成AST时,需要根据模板处理插值、指令,进行一些优化标记,提取出最大静态树(固定不变),把DOM操作次数减到最少。
AST和虚拟DOM(VNode)两者都是js语言,不同:
- 两者出现的时机不同,AST出现在编译过程;虚拟DOM出现在运行时。
- AST就相当于虚拟DOM的前身,经过一些列操作后变成虚拟DOM。
- 可以对比两者结构,虽然相似,但是我们可以明显看到虚拟dom没有attrs属性,虚拟dom将ast中的attrs放到了相应节点中。比如,attr中的style属性,在虚拟dom中放到了相对应的元素data中,转化成了元素节点的style。