vue实现梳理

深蓝一人

本文通过一个示例,描述了vue的编译及运行过程。例子:

<div id="app">
    {{ message }}
    <input type="text" v-model="message"/>
    <ul>
        <li v-for="item in list">{{ item }}</li>
    </ul>
    <div @click="onClick">
        点击
    </div>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue!',
            list: [1, 2, 3]
        },
        methods: {
            onClick () {
                this.list.push(2);
            }
        }
    })
</script>

编译

如果是使用vue的compiler+runtime版本,首先在mount之前会有一个编译流程,会通过new Vue时传入的el或者template里面的元素编译为一个render函数。该步骤主要思路是将template视为一个字符串,通过正则表达式不断匹配tag和attr等,parse生成AST树,之后再将优化后转化为render函数。
Vue._init => Vue.$mount => compileToFunctions => compile

图片描述

最终生成的render函数如下:

with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n            "+_s(message)+"\n            "),_c('input',{directives:[{name:"model",rawName:"v-model",value:(message),expression:"message"}],attrs:{"type":"text"},domProps:{"value":(message)},on:{"input":function($event){if($event.target.composing)return;message=$event.target.value}}}),_v(" "),_c('ul',_l((list),function(item){return _c('li',[_v(_s(item))])}),0),_v(" "),_c('div',{on:{"click":onClick}},[_v("\n                点击\n            ")])])}
一般来说,v-model就是:value和@input="function($event){message=$event.target.value}"的语法糖。从编译结果看,input使用v-model时多了一句$event.target.composing的判断,这句判断的主要作用是使用中文输入法还没有确定输入时,不会更新DOM,具体见v-model指令到底做了什么

渲染

根据传入的data和render函数,生成对应的VNode Tree
图片描述

这期间会有一次依赖收集过程,且v-if为false的元素不会出现在生成的VNode Tree中。最后调用patch,因为没有oldVnode,会直接将VNode渲染为真实的DOM

update

当点击元素或者触发输入框改变时,触发派发更新,会再次调用updateComponent并重新生成新的vnode。例如点击元素,增加list长度时
图片描述

再次调用patch时,会对比新旧VNode并将差异应用到真实的DOM上。简单来讲,会对比新旧VNode同级的tag和key是否有相同的,如果不同,则使用removeChild/appendChild/createElement等原生DOM方法将旧的删除新的增加,如果相同,则元素可以复用,但依然会对比新旧VNode的attrs等,并使用原生removeAttribute/setAttribute等触发DOM更新。具体对比方法参考虚拟DOM

更新DOM用到了nextTick,将需要更新的watchers放到一个queue里面,nextTick时触发其更新。因为一个数据更新可以会触发一连串的数据改变,不能每次数据改变都重新修改DOM,而应该一次性的修改,所以这里使用nextTick异步更改DOM,这也是为什么想获取更新后的DOM需要使用nextTick的原因。

事件的实现

对于原生事件,vue使用的原生addEventListener添加事件。对于自定义事件,vue采用的是事件中心的设计模式,调用on时注册事件,调用emit时触发事件。如下

vm._events = Object.create(null)
// 依赖收集
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    // ...
    (vm._events[event] || (vm._events[event] = [])).push(fn)
    // ...
    return vm
  }
// 触发更新
Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    // ...
    let cbs = vm._events[event]
    if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs
        const args = toArray(arguments, 1)
        const info = `event handler for "${event}"`
        for (let i = 0, l = cbs.length; i < l; i++) {
            invokeWithErrorHandling(cbs[i], vm, args, vm, info)
        }
    }
    return vm
}
阅读 692

前端学习
前端学习整理

暂时没有个人简介

1.4k 声望
59 粉丝
0 条评论
你知道吗?

暂时没有个人简介

1.4k 声望
59 粉丝
宣传栏