当你把一个一般的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并应用 Object.defineProperty 把这些 property 全副转为 getter/setter。

数据劫持

vue2中数据劫持应用了Object.defineProperty来实现
Object.defineProperty(obj, prop, descriptor)

参数

  • obj要定义属性的对象。
  • prop要定义或批改的属性的名称或 Symbol 。
  • descriptor要定义或批改的属性描述符。

返回值

被传递给函数的对象。

模仿vue中的data选项

  // 模仿 Vue 中的 data 选项    let data = {      msg: 'hello'    }    // 模仿 Vue 的实例    let vm = {}    // 数据劫持:当拜访或者设置 vm 中的成员的时候,做一些干涉操作    Object.defineProperty(vm, 'msg', {      // 可枚举(可遍历)      enumerable: true,      // 可配置(能够应用 delete 删除,能够通过 defineProperty 从新定义)      configurable: true,      // 当获取值的时候执行      get () {        console.log('get: ', data.msg)        return data.msg      },      // 当设置值的时候执行      set (newValue) {        console.log('set: ', newValue)        if (newValue === data.msg) {          return        }        data.msg = newValue        // 数据更改,更新 DOM 的值        document.querySelector('#app').textContent = data.msg      }    })    // 测试    vm.msg = 'Hello World'    console.log(vm.msg)


当data对象中有多个属性时,此时须要遍历vm,把每一个属性都转换成vm中的getter和setter

 // 模仿 Vue 中的 data 选项    let data = {      msg: 'hello',      count: 10    }    // 模仿 Vue 的实例    let vm = {}    proxyData(data)    function proxyData(data) {      // 遍历 data 对象的所有属性      Object.keys(data).forEach(key => {        // 把 data 中的属性,转换成 vm 的 setter/setter        Object.defineProperty(vm, key, {          enumerable: true,          configurable: true,          get () {            console.log('get: ', key, data[key])            return data[key]          },          set (newValue) {            console.log('set: ', key, newValue)            if (newValue === data[key]) {              return            }            data[key] = newValue            // 数据更改,更新 DOM 的值            document.querySelector('#app').textContent = data[key]          }        })      })    }    // 测试    vm.msg = 'Hello World'    console.log(vm.msg)


vue3中的数据劫持应用了ES6中的Proxy来实现,他能够代理一整个对象
const p = new Proxy(target, handler)
参数

  • target要应用 Proxy 包装的指标对象(能够是任何类型的对象,包含原生数组,函数,甚至另一个代理)。
  • handler一个通常以函数作为属性的对象,各属性中的函数别离定义了在执行各种操作时代理 p 的行为。即执行代理行为的函数

MDN代码示例

const handler = {    get: function(obj, prop) {        return prop in obj ? obj[prop] : 37;    }};const p = new Proxy({}, handler);p.a = 1;p.b = undefined;console.log(p.a, p.b);      // 1, undefinedconsole.log('c' in p, p.c); // false, 37

测试示例

 <div id="app">    hello  </div>  <script>    // 模仿 Vue 中的 data 选项    let data = {      msg: 'hello',      count: 0    }    // 模仿 Vue 实例    let vm = new Proxy(data, {      // 执行代理行为的函数      // 当拜访 vm 的成员会执行      get (target, key) {        console.log('get, key: ', key, target[key])        return target[key]      },      // 当设置 vm 的成员会执行      set (target, key, newValue) {        console.log('set, key: ', key, newValue)        if (target[key] === newValue) {          return        }        target[key] = newValue        document.querySelector('#app').textContent = target[key]      }    })    // 测试    vm.msg = 'Hello World'    console.log(vm.msg)

模仿VUE中的公布订阅模式
公布/订阅模式

  • 订阅者
  • 发布者
  • 信号核心

咱们假设,存在一个"信号核心",某个工作执行实现,就向信号核心"公布"(publish)一个信
号,其余工作能够向信号核心"订阅"(subscribe)这个信号,从而晓得什么时候本人能够开始执
行。这就叫做"公布/订阅模式"(publish-subscribe pattern)

    // 事件触发器    class EventEmitter {      constructor () {        // { 'click': [fn1, fn2], 'change': [fn] }        this.subs = Object.create(null)      }      // 注册事件      $on (eventType, handler) {        this.subs[eventType] = this.subs[eventType] || []  // 初始化为数组,不便前面的push操作        this.subs[eventType].push(handler)      }      // 触发事件      $emit (eventType) {        if (this.subs[eventType]) {          this.subs[eventType].forEach(handler => {            handler()          })        }      }    }    // 测试    let em = new EventEmitter()    em.$on('click', () => {      console.log('click1')    })    em.$on('click', () => {      console.log('click2')    })    em.$emit('click')

模仿VUE中的观察者模式
观察者(订阅者) -- Watcher

  • update():当事件产生时,具体要做的事件

指标(发布者) -- Dep

  • subs 数组:存储所有的观察者
  • addSub():增加观察者
  • notify():当事件产生,调用所有观察者的 update() 办法

没有事件核心
代码模式实现

    // 发布者-指标    class Dep {      constructor () {        // 记录所有的订阅者        this.subs = []      }      // 增加订阅者      addSub (sub) {        if (sub && sub.update) {          this.subs.push(sub)        }      }      // 公布告诉      notify () {        this.subs.forEach(sub => {          sub.update() // 调用订阅者办法        })      }    }    // 订阅者-观察者    class Watcher {      update () {        console.log('update')      }    }    // 测试    let dep = new Dep()    let watcher = new Watcher()    dep.addSub(watcher)    dep.notify()
  • 观察者模式是由具体指标调度,比方当事件触发,Dep 就会去调用观察者的办法,所以观察者模式的订阅者与发布者之间是存在依赖的。
  • 公布/订阅模式由对立调度核心调用,因而发布者和订阅者不须要晓得对方的存在。

    Vue 响应式原理模仿

  • Vue.把 data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter
  • Observer.可能对数据对象的所有属性进行监听,如有变动可拿到最新值并告诉 Dep
  • Compiler.解析每个元素中的指令/插值表达式,并替换成相应的数据
  • Dep.增加观察者(watcher),当数据变动告诉所有观察者
  • Watcher.数据变动更新视图

Vue类

性能

  • 负责接管初始化的参数(选项)
  • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
  • 负责调用 observer 监听 data 中所有属性的变动
  • 负责调用 compiler 解析指令/插值表达式

    class Vue {constructor(options) {  //   1,通过属性保留选项的数据  this.$options = options || {}; // 初始化  this.$data = options.data || {};  this.$el =    typeof options.el === "string"      ? document.querySelector(options.el)      : options.el;  //   2,把data中的成员转换成getter和setter,注入到vue实例中  this._proxyData(this.$data);  //   3,调用observer对象,监听数据变动  //   4,调用compiler对象,解析指令和差值表达式}_proxyData(data) {  //   遍历data中的所有属性  Object.keys(data).forEach((key) => {    //   把data中的属性注入到vue实例中    Object.defineProperty(this, key, {      enumerable: true,      configurable: true,      get() {        return data[key];      },      set(newVal) {        if (newVal === data[key]) {          return;        }        data[key] = newVal;      },    });  });}}

    测试代码

    <!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>myVue</title></head><body>  <div class="app">      <h1>差值表达式</h1>      <h3>{{ msg }}</h3>      <h3>{{ count }}</h3>      <h1>v-text</h1>      <div v-text="msg"></div>      <h1>v-model</h1>      <input type="text" v-model="msg">      <input type="text" v-model="count">  </div>  <script src="./myVue.js"></script>  <script>      let vm = new Vue({          el: "#app",          data: {              msg: "my vue",              count: 1          }      })  </script></body></html>

Observer类

性能

  • 负责把 data 选项中的属性转换成响应式数据
  • data 中的某个属性也是对象,把该属性转换成响应式数据
  • 数据变动发送告诉

    性能初步实现

    class Observer {constructor(data) {  this.walk(data);}// 遍历对象所有属性walk(data) {  // 1,判断data是否是对象  if (!data || typeof data !== "object") {    return;  }  // 2,遍历data对象的所有属性  Object.keys(data).forEach((key) => {    this.defineReactive(data, key, data[key]);  });}// 调动Object.defineProperty把属性转换为getter和setterdefineReactive(obj, key, val) {  let that = this;  // Dep类在Observer类做了两件事,先收集依赖,而后告诉。  let dep = new Dep(); //  负责收集依赖,并发送告诉  this.walk(val); // 解决问题一, data属性是对象类型, 如果val是对象,将val外部属性转换成响应式数据,  Object.defineProperty(obj, key, {    enumerable: true,    configurable: true,    get() {      // 收集依赖      Dep.target && dep.addSub(Dep.target);      return val; // 这里如果是return obj[key]会产生死递归,返回的就是obj自身    },    set(newVal) {      if (newVal === val) {        return;      }      val = newVal;      that.walk(newVal); // 解决问题二, 如果新赋值类型是对象类型,也须要循环解决,转换成响应式数据      // 发送告诉      dep.notify();    },  });}}

    在vue.js中引入observer

    //   3,调用observer对象,监听数据变动  new Observer(this.$data);

    控制台打印vm对象,对应的data对象属性曾经转化为getter和setter

Compiler类

性能(操作DOM)

  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染
  • 当数据变动后从新渲染视图

class Compiler {  constructor(vm) {    this.el = vm.$el;    this.vm = vm;    this.compile(this.el);  }  //   编译模板,解决文本节点和元素节点  compile(el) {    let childNodes = el.childNodes;    Array.from(childNodes).forEach((node) => {      //   对节点类型进行判断      // 解决文本节点      if (this.isTextNode(node)) {        this.compileText(node);      } else if (this.isElementNode(node)) {        // 解决元素节点        this.compileElement(node);      }      // 判断node节点是否有子节点,有子节点判断递归compiler      if (node.childNodes && node.childNodes.length) {        this.compile(node);      }    });  }  //   编译元素节点,解决指令,这里处,理v-text,v-model  compileElement(node) {    console.log(node.attributes);    // 遍历所有的属性节点    Array.from(node.attributes).forEach((attr) => {      // 判断是否是指令      let attrName = attr.name;      if (this.isDirective(attrName)) {        // 解决指令        //v-text ==>text        attrName = attrName.substr(2); // 属性名        let key = attr.value; // 属性值        this.update(node, key, attrName);      }    });  }  update(node, key, attrName) {    let updateFn = this[attrName + "Update"];    // 使this指向compiler对象    updateFn && updateFn.call(this, node, this.vm[key], key);  }  // 解决 v-text 指令  textUpdate(node, value, key) {    node.textContent = value;    // 这里如法炮制,在创立指令的时候创立watcher对象    new Watcher(this.vm, key, (newValue) => {      node.textContent = newValue;    });  }  // 解决v-model指令  modelUpdate(node, value, key) {    node.value = value;    // 这里如法炮制,在创立指令的时候创立watcher对象    new Watcher(this.vm, key, (newValue) => {      node.value = newValue;    });    // 双向绑定,文本框值批改,视图扭转,批改数据    node.addEventListener("input", () => {      this.vm[key] = node.value;    });  }  //   编译文本节点,解决差值表达式  compileText(node) {    // console.dir(node)    let reg = /\{\{(.+?)\}\}/;    let value = node.textContent;    if (reg.test(value)) {      let key = RegExp.$1.trim();    //   把msg中的值取出来,替换差值表达式,赋值给 node.textContent      node.textContent = value.replace(reg, this.vm[key]);      // 在创立实例的时候, 创立watcher对象,当数据扭转更新视图      new Watcher(this.vm, key, (newVal) => {        node.textContent = newVal;      });    }  }  //   判断元素节点是否是指令  isDirective(attrName) {    return attrName.startsWith("v-");  }  //   判断节点是否是文本节点  isTextNode(node) {    return node.nodeType === 3;  }  // 判断节点是否是元素节点  isElementNode(node) {    return node.nodeType === 1;  }}

Vue中的响应式,Vue负责把data中的属性存储到实例,并且调用Observer和Compiler,Observer负责数据劫持,监听数据变动,把data中的数据转换为getter和setter。Compiler负责解析差值表达式和指令,这外面须要应用观察者模式监听数据的变动

创立Dep和Watcher

Dep类

性能

  • 收集依赖,增加观察者(watcher)
  • 告诉所有观察者

    class Dep {constructor() {  // 存储所有的观察者  this.subs = [];}// 增加观察者addSub(sub) {  //   判断是否为观察者对象  if (sub && sub.update) {    this.subs.push(sub);  }}// 发送告诉notify() {  this.subs.forEach((sub) => {    sub.update();  });}}

Watcher类

性能

  • 当数据变动触发依赖, dep 告诉所有的 Watcher 实例更新视图
  • 本身实例化的时候往 dep 对象中增加本人

    class Watcher {constructor(vm, key, cb) {  this.vm = vm;  // data中的属性名称  this.key = key;  // 回调函数负责更新视图  this.cb = cb;  // 把watcher对象记录到Dep类的动态属性target  Dep.target = this;  // 触发get办法,在get办法中会调用addSub  this.oldValue = vm[key];  Dep.target = null; // 避免反复增加}//   当数据发生变化时更新视图update() {  let newValue = this.vm[this.key];  if (this.oldValue === newValue) {    return;  }  this.cb(newValue);}}

总结