vue双向数据绑定原理图(简易)

双向数据绑定的概念,相信大家都耳熟能详,简单来说,数据变化更新视图,视图变化更新数据。为了实现这一效果,在 Vue 中,采用了 数据劫持结合发布订阅者模式 的方式来实现。

通过 Object.defineProperty() 实现数据劫持,监听数据的变化。

通过 发布者Dep() 订阅者Watcher 实现发布订阅者模式,达到视图与数据之间相互更新的解耦。

关于如何实现一个简单的数据双向绑定,网上有很多例子,我就不列举了。我这里将我理解的双向绑定原理图画了一下:

然后,我分模块贴下代码(代码不是我写的,我也是找了别人的学习)

observer()

function observer(data) {
  if(!data || typeof data !== 'object') {
    return;
  }
  Object.keys(data).forEach(key => {
    var dep = new Dep();
    var value = data[key];

    observer(value);
    Object.defineProperty(data, key, {
      configurable: true,
      enumerable: true,
      get() {
        if(Dep.target) {
          dep.addSub(Dep.target)
        }
        return value;
      },
      set(newValue) {
        if (value === newValue) {
          return;
        }
        value = newValue;
        dep.notify();
      },
    })
  })
}

Dep()

function Dep() {
  this.subs = [];
}
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  notify: function() {
    this.subs.forEach(sub => {
      sub.update();
    });
  }
}
Dep.target = null;

Watcher

function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get();
}
Watcher.prototype = {
  get: function() {
    Dep.target = this;
    var value = this.vm._data;
    this.exp.split('.').forEach(key => {
      value = value[key]
    })
    Dep.target = null;
    return value;
  },
  update: function() {
    this.run();
  },
  run: function() {
    var value = this.vm._data;
    this.exp.split('.').forEach(key => {
      value = value[key]
    })
    if (value !== this.value) {
      this.value = value;
      this.cb.call(this.vm, value);
    }
  }
}

Compile

function Compile(el, vm) {
  this.el = document.querySelector(el);
  this.vm = vm;
  this.init();
}
Compile.prototype = {
  init: function() {
    this.fragment = this.node2Fragment(this.el);
    this.compile(this.fragment);
    this.el.appendChild(this.fragment);
  },
  node2Fragment: function(el) {
    var fragment = document.createDocumentFragment();
    var child = el.firstChild;
    while(child) {
      fragment.appendChild(child);
      child = el.firstChild;
    }
    return fragment;
  },
  compile: function(el) {
    var childNodes = el.childNodes;
    var that = this;
    Array.prototype.slice.call(childNodes).forEach(node => {
      // if(that.isElementNode(node)) {
      //   that.compileElement(node)
      // }
      if (that.isTextNode(node)) {
        that.compileText(node)
      }
      if(node.childNodes && node.childNodes.length) {
        that.compile(node)
      }
    })
  },

  compileElement: function(node) {
    var attributes = node.attributes;
    var that = this;

    Array.prototype.forEach.call(attributes, function(attr) {
      if(that.isDirective(attr)) {
        if(that.isModelDirective) {
          that.compileModel()
        }
        if(that.isHtmlDirective) {
          that.compileHtml()
        }
        if(that.isEventDirective) {
          that.compileEvnet()
        }
      }

      // if(that.isShortEventDirective(attr)) {
      //   that.compileEvnet()
      // }
    });
  },

  compileText: function(node) {
    var that = this;
    var reg = /\{\{(.*)\}\}/;
    if(reg.test(node.textContent)) {
      var exp = reg.exec(node.textContent)[1].trim()
      var val = that.vm._data;
      exp.split('.').forEach(key => {
        val = val[key]
      })
      that.updateText(node, val);
      new Watcher(that.vm, exp, function(value) {
        that.updateText(node, value)
      })
    }
  },

  compileModel: function() {},
  compileHtml: function() {},
  compileEvnet: function() {},
  
  updateText: function(node, value) {
    node.textContent = value
  },

  isDirective: function(attr) {
    return attr.indexof('v-') === 0;
  },
  isEventDirective: function(attr) {
    return attr.indexof('on:') === 0;
  },
  isShortEventDirective: function(attr) {
    return attr.indexof('@') === 0;
  },
  isHtmlDirective: function(dir) {
    return dir.indexof('html') === 0;
  },
  isModelDirective: function(dir) {
    return dir.indexof('model') === 0;
  },

  isElementNode: function(node) {
    return node.nodeType === 1;
  },
  isTextNode: function(node) {
    return node.nodeType === 3;
  }
}

Vue


function Vue(options = {}) {
  this.$options = options;
  this.$el = document.getElementById(options.el);
  this._data = options.data;
  let data = this._data;
  this._proxyData(options.data);

  observer(data);
  this.methods = options.methods;
  new Compile(options.el, this)
}
Vue.prototype = {
  _proxyData: function(data) {
    var that = this;
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        configurable: true,
        enumerable: false,
        get() {
          return that._data[key];
        },
        set(newValue) {
          that._data[key] = newValue;
        },
      })
    })
  }
}

测试

<div id="app">
    <div>
      <span>{{ msg }}</span>
      <br>
      <span>{{ f.name }}</span>
  </div>
</div>
var vm = new Vue({
  el: '#app',
  data: {
    msg: '11',
    f: {
      name: 1
    },
    dom: '<strong></strong>'
  },
  methods: {
    clickMe() {
      console.log(123);
    }
  }
})

在 浏览器控制台 输入

vm.msg = 333;
vm.f.name = 'xxxxx'

可以看到数据变化了

做一枚精致的前端er

81 声望
6 粉丝
0 条评论
推荐阅读
webpack 独立打包与缓存
通过 webpack 打包文件,并根据不同环境独立出相应的配置文件,这是我们都会的。但,我们不能满足于能打包出文件就可以了,还应该思考怎么打包出更优的文件。比如,我们都知道浏览器有强缓存与协商缓存,如果能够...

zhangjinpei2阅读 3.4k

Vue中的diff算法
diff算法是一种通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n)。diff算法的在很多场景下都有应用,例如在 vue 虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较更新时...

款冬27阅读 13.4k评论 7

给我实现一个前端的 Excel 导入和导出功能
前言【负责人 A】:现在报表部分基于接口的 Excel 的导入和导出功能有点慢,前端这边能不能实现一下这个功能,然后我们在比对看看效果!【切图仔 B】: 接口这边不能优化一下吗?比如排查下慢的原因什么的。【负...

熊的猫19阅读 2.6k

封面图
vue-property-decorator使用手册
@Component 装饰器可以接收一个对象作为参数,可以在对象中声明 components ,filters,directives等未提供装饰器的选项,也可以声明computed,watch等

似曾相识17阅读 29.1k评论 7

一个开源vue网站博客,nuxt开源网站,前后端分离项目
开媛笔记,基于nuxt ssr首屏服务器端渲染 。用于分享、记录、交流和学习,希望可以帮助到小伙伴们。同时网站在不断更新,创造属于猿(媛)的世界 -$Bao Yalong ..Let's Go! [链接]

jigsaw16阅读 8.4k评论 3

你知道前端水印功能是怎么实现的吗?
前一段时间由于项目需要实现水印功能,于是去了解了相关的内容后,基于 Vue 的实现了一个 v-watermark 指令完成了对应的功能,其实整体内容并不复杂!

熊的猫14阅读 1.7k

封面图
2022 你还不会微前端吗 (上) — 从巨石应用到微应用
微前端系列分为 上/下 两篇,本文为 上篇 主要还是了解微前端的由来、概念、作用等,以及基于已有的微前端框架进行实践,并了解微前端的核心功能所在,而在下篇 2022 你还不会微前端吗 (下) — 揭秘微前端核心原理...

熊的猫14阅读 1.6k

封面图

做一枚精致的前端er

81 声望
6 粉丝
宣传栏