vue2双向数据绑定原理

1.数据双绑原理

vue使用MVVM框架,MVVM框架核心是数据双绑。即通过数据劫持和发布订阅模式,使用Object.defineProperty()对数据的属性和子属性进行递归遍历并对其设置getter和setter方法,数据发生改变时,发布消息给订阅者,出发对应的监听回调渲染视图。达到数据更新则视图更新,视图更新则数据更新的效果。

2.数据双绑流程

主要涉及三个核心:Observe、Compile、Watcher。

1:new Vue()时进行初始化,并在Observe中对data数据进行数据劫持(响应式处理)。

2:在Compile中进行模版编译,找到其中动态绑定的数据,从data中获取并初始化视图

3:定义一个dep()函数和watcher,由watcher对数据的变化和更新进行统一处理

4:data中的属性,称为key,一个视图中key可能会出现多次;一个watcher对应一个key;相同key对应的watcher存在一个dep()中。即一个watcher对应一个key,但是一个dep()里面存放多个相同key的watcher

5:data数据发生变化时,首先找到对应的dep(),然后通知watcher执行更新函数

流程图如下: 

vue2双向数据绑定原理_第1张图片

3.要点解析

2.1 Observe 观察者

通过Object.defineProperty()对数据进行数据劫持

class Vue {  
  constructor(options) {  
    this.$options = options;  
    this.$data = options.data;  
        
    // 对data选项做响应式处理  
    observe(this.$data);  
        
    // 代理data到vm上  
    proxy(this);  
        
    // 执行编译  
    new Compile(options.el, this);  
  }  
}  
Object.keys(obj).forEach((key) => {  
      defineReactive(obj, key, obj[key]);  
    });

2.2 Compile 编译模版

对每个元素节点及其子元素节点进行递归遍历,对元素上的节点和文本进行编译,把模板中的变量替换成数据,然后初始化渲染视图,同时把每个指令对应的节点绑定上更新函数,添加订阅者,如果数据变化,收到通知,更新视图

vue2双向数据绑定原理_第2张图片

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);  
  }  
}  
  

2.3收集依赖,Watcher,Dep

 视图中会用到data中某key,称为依赖。同⼀个key可能出现多次,每次都需要收集出来用⼀个Watcher来维护它们,此过程称为依赖收集。多个Watcher需要⼀个Dep来管理,需要更新时由Dep统⼀通知。

vue2双向数据绑定原理_第3张图片

 4.依赖收集实现思路:

  1. defineReactive时为每⼀个key创建⼀个Dep实例 (Observe) 
  2. 初始化视图时读取某个key,例如name1,创建⼀个watcher1(Compile) 
  3. 由于触发name1getter方法,便将watcher1添加到name1对应的Dep中(Observe中调用addDep())
  4. name1更新,setter触发时,便可通过对应Dep通知其管理所有Watcher更新

4.1 Watcher类

Watcher类负责更新视图:创建实例时,把当前实例指定到Dep.targer静态属性上;然后读取key,出发getter,再将Dep.target置空;当执行Dom更新函数时,由dep()调用进行Watcher进行更新

// 负责更新视图  
class Watcher {  
  constructor(vm, key, updater) {  
    this.vm = vm  
    this.key = key  
    this.updaterFn = updater  
  
    // 创建实例时,把当前实例指定到Dep.target静态属性上  
    Dep.target = this  
    // 读一下key,触发get  
    vm[key]  
    // 置空  
    Dep.target = null  
  }  
  
  // 未来执行dom更新函数,由dep调用的  
  update() {  
    this.updaterFn.call(this.vm, this.vm[this.key])  
  }  
}  

4.2声明Dep 

class Dep {  
  constructor() {  
    this.deps = [];  // 依赖管理  
  }  
  addDep(dep) {  
    this.deps.push(dep);  
  }  
  notify() {   
    this.deps.forEach((dep) => dep.update());  
  }  
}  

 4.3创建watcher时触发getter

class Watcher {  
  constructor(vm, key, updateFn) {  
    Dep.target = this;  
    this.vm[this.key];  
    Dep.target = null;  
  }  
}  
  

4.4Observe中创建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执行更新方法  
    },  
  });  
}  

5:总结

Observe: 递归遍历data上的属性和子属性,并通过Object.defineProperty()进行数据劫持;同时创建dep实例,并在Object.defineProperty()的get()上将watcher设置进dep中

Compile: 编译解析模版指令,并将模版中的变量替换成数据,然后初始化渲染视图,并给每个节点指令绑定上更新函数,添加订阅者。当数据变化时,收到通知更新视图

依赖收集: 初始化渲染视图时就会触发属性的getter方法,并调用Dep中的addDep()将值设置进Dep中;而数据中每个属性key对应一个watcher,多个相同key的watcher存放在一个Dep中;数据变化时,会首先找到对应的Dep,通知Dep对应的所有Watcher执行更新函数

参考地址:

面试官:双向数据绑定是什么 | web前端面试 - 面试官系列

你可能感兴趣的:(vue,vue2双向绑定,前端)