Vue响应式原理与模拟实现

360 阅读2分钟

Vue响应式实现流程

  • 模板被解析成render函数,绑定数据依赖
  • 设置data中数据的getter和setter特性。属性变化,重新触发render

Vue的模板解析

Vue的模板就是HTML加上内置的模板语法,如v-ifv-bind等等

  • 模板最终会转换成JS代码(render函数)
  • 模板具有逻辑,如v-ifv-for,必须通过JS实现

模板

<input id="item" v-model="content"/>

解析

// 只列出了主要内容
with(this){
    return _c{
        'input',
        {
            // 属性名
            attrs:{'id':'item'},
            // 指令
            directives:[
                {
                    name:'model',
                    rawName:'v-model',
                    // 对应的数据
                    value:(content)
                }
            ],
            // 绑定的事件
            on:{
                "input" function($event){
                    title = $event.target.value
                }
            }
        }
        
    }
}
  • with(this)指的是该Vue实例
  • _c (createElement)就是render函数,用来创建虚拟DOM

在模板转化为JS代码的阶段,如果使用了v-model,就会在生成的虚拟DOM里面自动监听input事件。如果元素的内容被修改,那么就会触发input事件,该事件会修改Vue组件实例中对应的数据的值。这样一来,页面->数据这个方向的数据绑定就实现了。

Object.defineProperty

给属性设置getter和setter

var obj = {};
// 设置一个中间变量,避免递归
var _content = '0';
Object.defineProperty(obj,'content',{
    get(){
        console.log('get success')
        return _content;
    },
    set(value){
        console.log('set success')
        _content = value;
    }
})

// obj.content
// get success
// 0
// obj.content = 1
// get success
// 1
// obj.content
// get success
// 1

所以Vue数据->页面的数据绑定就是在属性的setter里面调用render函数。这样每次数据更新,都会重新渲染页面

模拟实现

<div>
    <input id="item"/>
</div>

var item = document.getElementById('item')

var obj = {};
var _content = 0;
Object.defineProperty(obj,'content',{
    get(){
        return _content;
    },
    set(value){
        _content = value;
        // 数据变化,重新渲染页面数据
        item.value = _content;
    }
})

//渲染初始值
item.value = obj.content

//页面内容变化,修改绑定数据
item.addEventListener('input',(event)=>{
     obj.content = event.target.value
})

注:由于Object.defineProperty的性能问题,Vue2.x并没有实现对数组和对象属性变化的检查。Vue3.x已经使用Proxy来进行双向绑定,具体可参考
vue3.0 尝鲜 -- 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索