vue深入解析之render function code详解

网友投稿 249 2023-04-27


vue深入解析之render function code详解

前言

最近在深入的学习研究vue,其实vue在使用上入门并没有什么太高的门槛,但前端同学们也不该仅仅停留在使用上。以 vue 设计、编码之优秀,足当抽丝剥茧,扒开它的外壳,深入其原理。让我们一起来刺破 vue 的心脏,下面话不多说了,来一起看看详细的介绍吧。

vue核心执行过程图

vue核心的执行过程主要分为这几个阶段:

1) 编译模板,生成可复用的render function code(这是今天要重点解读的),这一步在vue实例的整个生命周期中只会执行一次甚至零次,因为我们可以在打包的时候可以预编译

2) 生成watcher等核心渲染监听,在整个vue实例的生命过程中持续发生着作用,对view和modal进行双向绑定

3) 虚拟dom的diff比较,当watcher监听到data的变更的时候,就会根据注入新的data执行render function code,生成新的虚拟dom,跟老的虚拟dom(第一次执行的时候可能为空)进行diff比对,不同的部分将写入真实的dom

这几个过程都会以源码解析的方式分篇解读,今天我们重点讲解的是第一部分模板编译中输出的render function code

render function code解析

一、模板编译过程分解

// 生成ast语法树

const ast = parse(template.trim(), options)

// 标记静态内容(以免diff的时候需要重复比较)

optimize(ast, options)

// 生成render function code

const code = generate(ast, options)

很遗憾,上诉三步的代码今天都只是点到为止,不需要太努力,就能在网上搜到相关解析的文章,如果实在找不到同时又感兴趣,可以找到vue源码中的下面文件去读:

// 编译入口

src/compiller/index.js

// html解析

src/compiller/parser/html-parser.js

// src/compiller下的其它文件

换一个姿势读源码,今天我们要读的是generate(ast, options)生成的render function code的具体代码

二、写一个 vue demo

之前有提到过,读源码需要先了解整体的设计思想、架构,上面那个执行过程图在此列;另外,搭建一个demo执行环境进行debug单步调试,也是一个重要手段(特别是你对源码的目录结构不是特别清晰的时候)。为了让生成的render function code更为完整,写一个覆盖面尽量广的 demo :

//template

普通属性:{{ message }}

{{msg()}}

{{ct}}

{{ item.text }}

// js

new Vue({

el: '#app',

data: {

message: '以vue的名义',

items: [{

text: '达康书记'

}, {

text: '育良书记'

}]

},

methods: {

bindClick: function() {

this.message = '这就抓同伟去';

},

msg: function() {

return this.message + "这个方法每次都会执行";

}

},

computed: {

ct: function() {

return this.message + "计算属性并不会每次都执行";

}

}

})

render function code 解析

debug 拿到生成的 render function code(当然也可以通过 webpack 的 vue-loader 编译之后的 dist 文件拿到,此处省略1000字)

with(this) {

return _c('div', {

attrs: {

"id": "app"

}

},

[_c('p', [_v("普通属性:" + _s(message))]), _v(" "), _c('p', [_v(_s(msg()))]), _v(" "), _c('p', [_v(_s(ct))]), _v(" "), _c('input', {

directives: [{

name: "model",

rawName: "v-model",

value: (message),

expression: "message"

}],

domProps: {

"value": (message)

},

on: {

"input": function($event) {

if ($event.target.composing) return;

message = $event.target.value

}

}

}), _v(" "), _l((items),

function(item) {

return _c('div', [_v("\n\t\t " + _s(item.text) + "\n\t ")])

}), _v(" "), _c('button', {

on: {

"click": bindClick

}

},

[_v("点我出奇迹抓同伟")])], 2)

}

甭看这段代码有点怪,但是如果告诉你,这段代码,注入 data 执行,生成的就是传得玄乎其玄的虚拟 dom 树,然后再来一本正经的解(cai)读(ce)一下,你又会觉得这段代码其实没有这么晦涩难懂。ok,结合 demo

来看看,模板相关指令都被解析成什么了:

1) {{messge}} 解析成了 _s(message) ,果断认为这个 _s 就是 toString

2) {{msg()}} method 解析成了 _s(msg()) ,可见每一次 render, msg 方法都会被执行一遍(即使最终没有被反应到真实 dom 上),这就是计算属性存在的意义

3) {{ct}} 计算属性依VzOgmDNURl然被解析成了 _s(ct) ,虽然计算属性可以称之为属性,但是形式上毕竟还是方法,是不是觉得比较奇怪?其实 watcher 除了监听组件,还会监听计算属性依赖的属性,一旦属性发生变更,就会执行计算属性方法,并将执行结果赋值给实例作用域下与计算属性方法名相同的属性,这就是直接使用 _s(ct) 而不是 _s(ct()) 就能正确引用计算属性值的原因,计算属性快就快在了这里

4) v-for="item in items" 被解析成了

_l((items),

function(item) {

return _c('div', [_v("\n\t\t " + _s(item.text) + "\n\t ")])

})

可见 _l 跟 for-each 很类似

5) 至于 v-on:click 则解析成了

_c('button', {

on: {

"click": bindClick

}

}

6) 再来看看 _c, c->create->createNode, 假设这个方法就是创建虚拟节点,回头看上述代码,是不是挺有道理,_c 创建根节点,传入的子节点同样需要这个方法创建。找找源码验证下 src/core/vdom/vnode.js,以下中文注释是我加的

constructor (

tag?: string, //标签名

data?: VNodeData, //属性数据,事件监听等

children?: ?Array, //子节点

text?: string,

elm?: Node,

context?: Component,

componentOptions?: VNodeComponentOptions

) {

...

}

至此,也算是自圆其说了

总结

以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:继承类和实现接口(继承类并实现接口)
下一篇:spring boot整合hessian的示例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~