说说vue的双向绑定

49 阅读3分钟

一:什么是双向绑定?

先说单向绑定,就是把model绑定到view上面,当我们用js更新model的时候,view会自动更新。

双向数据绑定就是在单向数据绑定的基础上,用户更新了view,model数据也会被更新,这就是双向数据绑定。

二:双向绑定的原理

vue是数据双向绑定的框架,也就是我们常说的MVVM模型。

  • 数据层(model):应用的数据及业务逻辑
  • 视图层(view):应用的展示效果,各类UI组件
  • 业务逻辑层(viewModel):框架封装的核心,负责将数据与视图关联起来

ViewModel的职责:数据变化后更新视图,视图变化后更新数据。

主要由两部分组成:

  • 监听器(Observer):对所有的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模版替换数据,以及绑定相应的更新函数。

三:实现双向绑定

vue双向绑定的流程:

1,new Vue()首先执行初始化,对data执行响应化处理,这个过程在Observe中

2,同时对模版执行编译,找到其中动态绑定的数据,从data中获取,并且初始化视图,这个过程在compile中

3,同时定义一个更新函数和Watcher,将来数据变化时watcher会调用更新函数。

4,由于data的某个key在一个视图中可能出现多次,所有每个key都需要一个管家dep来管理多个watcher。

5,将来data一旦发生变化,首先找到他的管家dep,然后通知所有的watcher执行更新函数。

image.png

四:实现

先来一个构造函数,对data进行响应化处理

依赖收集 视图中会用到data的某个key,这称为依赖。同一个key可能出现多次,每次都需要收集出来用一个watcher来维护它们,此过程称为依赖收集。

多个watcher需要一个dep来管理,需要更新时由dep统一通知。

实现思路:

1,defineReactive为每一个key创建一个Dep实例

2,初始化视图的时候读取某个key,例如name1,则创建一个watcher1

3,由于name1触发了getter方法,便将watcher1添加到name1对应的dep中

4,当name1更新,setter触发时,便可通过对应的dep通知其管理所有watcher更新

代码实例:

class Vue {
    constructor(options){
        this.$options = options;
        this.$data = options.data;
        // 对data进行响应式处理
        observe(this.$data);
        // 代理data到vm上
        proxy(this)
        // 执行编译
        new Compile(options.el, this)
    }
}

function observe(obj) {
    if(typeof obj !== "object" || obj == null){
        return
    }
    new Observer(obj)
}

class Observer{
    constructor(value){
        this.value = value;
        this.walk(value);
    }
    walk(obj){
        Object.keys(obj).forEach((key) => {
            defineReactive(obj, key, obj[key])
        })
    }
}
class Compile{
    constructor(el, vm){
        this.$vm = vm
        this.$el = document.querySelector(el) // 获取dom
        if(this.$el){
            this.compile(this.$el)
        }
    }
    compile(el){
        const childNodes = el.childNodes
        Array.from(childNodes).forEach((node) => { // 遍历子元素
            if(this.isElement(node)){ // 判断是否为节点
                console.log('编译元素'+node.nodeName)
            }else if(this.isInterpolation(node)){
                console.log("编译插值⽂本" + node.textContent);  // 判断是否为插值文本 {{}}
            }
            if (node.childNodes && node.childNodes.length > 0) {  // 判断是否有子元素
                this.compile(node);  // 对子元素进行递归遍历
            }
        })
    }
    isElement(node){
        return node.nodeType === 1
    }
    isInterpolation(node){
        return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent);
    }
}
// 负责更新视图
class Watcher{
    constructor(vm, key, updater){
        this.vm = vm;
        this.key = key;
        this.updaterFun =  updater

        // 创建实例时,把当前实例制定到dep.target静态属性上
        Dep.target = this
        // 读一下key,触发get
        vm[key]
        // 置空
        Dep.target = null
    }
    // 未来执行dom更新函数,由dep调用
    update(){
        this.updaterFun.call(this.vm, this.vm[this.key])
    }
}
// 声明dep
class Dep {
    constructor(){
        this.deps = []
    }
    addDep(dep){
        this.deps.push(dep)
    }
    notify(){
        this.deps.forEach((dep)=>{
            dep.update()
        })
    }
}
// 创建watcher时触发getter
class Watcher {
    constructor(vm, key, updateFn){
        Dep.target = this
        this.vm[this.key]
        Dep.target = null
    }
}
// 依赖收集,创建Dep实例
function defineReactive(obj, key, val){
    this.observe(val)
    const dep = new Dep()
    Object.defineProperty(obj, key, {
        get(){
            Dep.target && dep.addDep(Dep.target); // Dep.target也就是Watcher实例
            return val
        },
        set(newVal){
            if(newVal === val) return;
            dep.notify(); // 通知dep执行更新方法
        }
    })
}

参考网站:mp.weixin.qq.com/s/TV4mwb4Jm…