vue2.0响应式原理

269 阅读3分钟

一、vue.js响应式原理

在 Vue.js 中,当我们修改数据时,视图随之更新,这就是 Vue.js 的双向数据绑定(也称响应式原理)。

核心:观察者模式和Object.defineProperty() 方法。

在vue中,每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”(“Touch” 过程)过的数据 property 记录为依赖(Collect as Dependency 过程)。之后当依赖项的 setter 触发时,会通知 watcher(Notify 过程),从而使它关联的组件重新渲染(Trigger re-render 过程)——观察者模式。

二、组成部分

1、mvvm是什么

MVVM拆开来即为Model-View-ViewModel,有View,ViewModel,Model三部分组成。View层代表的是视图层(UI)。Model层代表的是数据,ViewModel层连接Model和View。 数据双向绑定:数据影响视图,视图影响数据。

2、组成部分

  • Observer(监听器):将普通数据转换为响应式数据,从而实现响应式对象。
  • Dep(订阅者,依赖收集者):主要通过addSub()方法增加观察者,通过notify()方法通知观察者,调用观察者的update更新相应的视图。
  • Watcher(观察者):观察者的get()方法获取未更新之前的值,当数据更新时,调用观察者的update更新相应的视图。

3、mvvm实现

不同的框架当中,MVVM实现的原理是不同的;在vue中,采用的是数据劫持+发布订阅模式。

  • 2.1、数据劫持observe 通过Object.defineProperty定义所有的属性。即当你把一个普通的JavaScript对象传入 Vue 实例作为 data 选项,Vue将遍历此对象所有的property,并使用Object.defineProperty把这些property全部转为 getter/setter(数据的响应式)。
//处理data
function Observe(data){
    for( let key in data){
        let val=data[key]
        observe(val)
        Object.defineProperty(data,key,{
            enumerable:true,
            get(){
                return val
            },
            set(newVal){
                if(newVal==val) return
                val=newVal
                observe(newVal)
            }
        })
    }

}

//监测data
function observe(data){
 	if (typeof data!='object') return
    return new Observe(data)
}

通过对数据(Model)进行劫持,当数据变动时,数据会触发劫持时绑定的方法,对视图进行更新。

数据代理:

function myVue(options={}){
    this.$options=options
    var data=this._data=this.$options.data
    observe(data)
    //this代理this._data
    for(let key in data){
        Object.defineProperty(this,key,{
            enumerable:true,
            get(){
                return this._data[key]
            },
            set(newVal){
                this._data[key]=newVal
            }
        })
    }
}
  • 2.2、发布订阅模式 先订阅(存储),再发布(执行)。

订阅者主要通过addSub()方法增加观察者,通过notify()方法通知观察者,调用观察者的update更新相应的视图。 通过观察者Watcher的get()方法获取未更新之前的值,给每个属性创建一个发布订阅的功能,当数据更新时,调用观察者的update更新相应的视图。

//被观察者(订阅者)
class Dep{
    constructor(){
        this.subs=[]
    }
    //添加观察者
    addSub(watcher){
        this.subs.push(watcher)
    }
    //通知观察者
    notify(data){
        this.subs.forEach(element => element.update(data));
    }

}

//观察者
class Watcher{
    constructor(vm,key,cb){
        this.vm=vm
        this.key=key
        this.cb=cb
        // 全局唯一,创建实例时,把当前实例指定到Dep.target静态属性上
        Dep.target = this; 
        // 此处通过 this.vm.$data[key] 读取属性值,触发 getter
        this.oldValue = this.vm.$data[key]; // 保存变化的数据作为旧值,后续作判断是否更新
        // 前面 getter 执行完后,执行下面清空
        Dep.target=null
    }
    update(data){
        console.log(`数据发生变化!`);
        let oldValue = this.oldValue;
        let newValue = this.vm.$data[this.key];
        if (oldValue != newValue) {  // 比较新旧值,发生变化才执行回调
            this.cb(newValue, oldValue);
        };
    }
}

  • 2.3、模板编译compile

使用虚拟dom的方法,先把el中的内容通过创建文档碎片的方式移入内存中,再在内存中去查找和替换,(查找页面中的双大括号的文本节点的内容和通过v-model绑定的内容,用data中的数据进行替换。),最终将结果再重新挂载回页面中。

获取template模板——》ast语法树(描述template源代码的树形结构)——》render函数——》虚拟dom——》真实dom

//模板编译
function Compile(el,vm){
    // el表示要替换的范围
    vm.$el=document.querySelector(el)
    let fragment=document.createDocumentFragment();
    // 将el中的内容移入内存中
    while(child=vm.$el.firstChild){
        fragment.appendChild(child)
    }
    //查找替换
    replace(fragment)
    function replace(fragment){
        Array.from(fragment.childNodes).forEach((node)=>{
            console.log("node",node);
            let text=node.textContent
            let reg=/\{\{(.*)\}\}/
            //文本节点
            if(node.nodeType==3&&reg.test(text)){
                console.log("文本节点",RegExp.$1);//this.a.a  this.b
                let arr=RegExp.$1.split('.')// ['a','a'] ['b']
                let val=vm
                arr.forEach((key)=>{
                    val=val[key]
                })
                node.textContent=text.replace(reg,val)
            }
            if(node.childNodes){
                replace(node)
            }
        })
    }
    //将文档碎片重新挂载在页面中
    vm.$el.appendChild(fragment)
}

参考:juejin.cn/post/691627…

mp.weixin.qq.com/s/yCdRsJ09Q…