在目前的前端面试中,vue的双向数据绑定已经成为了一个非常容易考到的点,即使不能当场写出来,至少也要能说出原理。本篇文章中我将会仿照vue写一个双向数据绑定的实例,名字就叫myVue吧。结合注释,希望能让大家有所收获。

1、原理

Vue的双向数据绑定的原理相信大家也都十分了解了,主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里对原理不做过多描述,主要还是来实现一个实例。为了使代码更加的清晰,这里只会实现最基本的内容,主要实现v-model,v-bind 和v-click三个命令,其他命令也可以自行补充。

添加网上的一张图

2、实现

页面结构很简单,如下

 
  1. <div id="app">
  2. <form>
  3. <input type="text" v-model="number">
  4. <button type="button" v-click="increment">增加</button>
  5. </form>
  6. <h3 v-bind="number"></h3>
  7. </div>

包含:

 
  1. 1. 一个input,使用v-model指令
  2. 2. 一个button,使用v-click指令
  3. 3. 一个h3,使用v-bind指令。

我们最后会通过类似于vue的方式来使用我们的双向数据绑定,结合我们的数据结构添加注释

 
  1. var app = new myVue({
  2. el:'#app',
  3. data: {
  4. number: 0
  5. },
  6. methods: {
  7. increment: function() {
  8. this.number ++;
  9. },
  10. }
  11. })

首先我们需要定义一个myVue构造函数:

 
  1. function myVue(options) {
  2. }

为了初始化这个构造函数,给它添加一 个_init属性

 
  1. function myVue(options) {
  2. this._init(options);
  3. }
  4. myVue.prototype._init = function (options) {
  5. this.$options = options; // options 为上面使用时传入的结构体,包括el,data,methods
  6. this.$el = document.querySelector(options.el); // el是 #app, this.$el是id为app的Element元素
  7. this.$data = options.data; // this.$data = {number: 0}
  8. this.$methods = options.methods; // this.$methods = {increment: function(){}}
  9. }

接下来实现_obverse函数,对data进行处理,重写data的set和get函数

并改造_init函数

 
  1. myVue.prototype._obverse = function (obj) { // obj = {number: 0}
  2. var value;
  3. for (key in obj) { //遍历obj对象
  4. if (obj.hasOwnProperty(key)) {
  5. value = obj[key];
  6. if (typeof value === 'object') { //如果值还是对象,则遍历处理
  7. this._obverse(value);
  8. }
  9. Object.defineProperty(this.$data, key, { //关键
  10. enumerable: true,
  11. configurable: true,
  12. get: function () {
  13. console.log(`获取${value}`);
  14. return value;
  15. },
  16. set: function (newVal) {
  17. console.log(`更新${newVal}`);
  18. if (value !== newVal) {
  19. value = newVal;
  20. }
  21. }
  22. })
  23. }
  24. }
  25. }
  26. myVue.prototype._init = function (options) {
  27. this.$options = options;
  28. this.$el = document.querySelector(options.el);
  29. this.$data = options.data;
  30. this.$methods = options.methods;
  31. this._obverse(this.$data);
  32. }

接下来我们写一个指令类Watcher,用来绑定更新函数,实现对DOM元素的更新

 
  1. function Watcher(name, el, vm, exp, attr) {
  2. this.name = name; //指令名称,例如文本节点,该值设为"text"
  3. this.el = el; //指令对应的DOM元素
  4. this.vm = vm; //指令所属myVue实例
  5. this.exp = exp; //指令对应的值,本例如"number"
  6. this.attr = attr; //绑定的属性值,本例为"innerHTML"
  7. this.update();
  8. }
  9. Watcher.prototype.update = function () {
  10. this.el[this.attr] = this.vm.$data[this.exp]; //比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新。
  11. }

更新_init函数以及_obverse函数

 
  1. myVue.prototype._init = function (options) {
  2. //...
  3. this._binding = {}; //_binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
  4. //...
  5. }
  6. myVue.prototype._obverse = function (obj) {
  7. //...
  8. if (obj.hasOwnProperty(key)) {
  9. this._binding[key] = { // 按照前面的数据,_binding = {number: _directives: []}
  10. _directives: []
  11. };
  12. //...
  13. var binding = this._binding[key];
  14. Object.defineProperty(this.$data, key, {
  15. //...
  16. set: function (newVal) {
  17. console.log(`更新${newVal}`);
  18. if (value !== newVal) {
  19. value = newVal;
  20. binding._directives.forEach(function (item) { // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新
  21. item.update();
  22. })
  23. }
  24. }
  25. })
  26. }
  27. }
  28. }

那么如何将view与model进行绑定呢?接下来我们定义一个_compile函数,用来解析我们的指令(v-bind,v-model,v-clickde)等,并在这个过程中对view与model进行绑定。

 
  1. myVue.prototype._init = function (options) {
  2. //...
  3. this._complie(this.$el);
  4. }
  5. myVue.prototype._complie = function (root) { root idappElement元素,也就是我们的根元素
  6. var _this = this;
  7. var nodes = root.children;
  8. for (var i = 0; i < nodes.length; i++) {
  9. var node = nodes[i];
  10. if (node.children.length) { // 对所有元素进行遍历,并进行处理
  11. this._complie(node);
  12. }
  13. if (node.hasAttribute('v-click')) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++
  14. node.onclick = (function () {
  15. var attrVal = nodes[i].getAttribute('v-click');
  16. return _this.$methods[attrVal].bind(_this.$data); //bind是使data的作用域与method函数的作用域保持一致
  17. })();
  18. }
  19. if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件
  20. node.addEventListener('input', (function(key) {
  21. var attrVal = node.getAttribute('v-model');
  22. //_this._binding['number']._directives = [一个Watcher实例]
  23. // 其中Watcher.prototype.update = function () {
  24. // node['vaule'] = _this.$data['number']; 这就将node的值保持与number一致
  25. // }
  26. _this._binding[attrVal]._directives.push(new Watcher(
  27. 'input',
  28. node,
  29. _this,
  30. attrVal,
  31. 'value'
  32. ))
  33. return function() {
  34. _this.$data[attrVal] = nodes[key].value; // 使number 的值与 node的value保持一致,已经实现了双向绑定
  35. }
  36. })(i));
  37. }
  38. if (node.hasAttribute('v-bind')) { // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可
  39. var attrVal = node.getAttribute('v-bind');
  40. _this._binding[attrVal]._directives.push(new Watcher(
  41. 'text',
  42. node,
  43. _this,
  44. attrVal,
  45. 'innerHTML'
  46. ))
  47. }
  48. }
  49. }

至此,我们已经实现了一个简单vue的双向绑定功能,包括v-bind, v-model, v-click三个指令。效果如下图

附上全部代码,不到150行

 
  1. <!DOCTYPE html>
  2. <head>
  3. <title>myVue</title>
  4. </head>
  5. <style>
  6. #app {
  7. text-align: center;
  8. }
  9. </style>
  10. <body>
  11. <div id="app">
  12. <form>
  13. <input type="text" v-model="number">
  14. <button type="button" v-click="increment">增加</button>
  15. </form>
  16. <h3 v-bind="number"></h3>
  17. </div>
  18. </body>
  19. <script>
  20. function myVue(options) {
  21. this._init(options);
  22. }
  23. myVue.prototype._init = function (options) {
  24. this.$options = options;
  25. this.$el = document.querySelector(options.el);
  26. this.$data = options.data;
  27. this.$methods = options.methods;
  28. this._binding = {};
  29. this._obverse(this.$data);
  30. this._complie(this.$el);
  31. }
  32. myVue.prototype._obverse = function (obj) {
  33. var value;
  34. for (key in obj) {
  35. if (obj.hasOwnProperty(key)) {
  36. this._binding[key] = {
  37. _directives: []
  38. };
  39. value = obj[key];
  40. if (typeof value === 'object') {
  41. this._obverse(value);
  42. }
  43. var binding = this._binding[key];
  44. Object.defineProperty(this.$data, key, {
  45. enumerable: true,
  46. configurable: true,
  47. get: function () {
  48. console.log(`获取${value}`);
  49. return value;
  50. },
  51. set: function (newVal) {
  52. console.log(`更新${newVal}`);
  53. if (value !== newVal) {
  54. value = newVal;
  55. binding._directives.forEach(function (item) {
  56. item.update();
  57. })
  58. }
  59. }
  60. })
  61. }
  62. }
  63. }
  64. myVue.prototype._complie = function (root) {
  65. var _this = this;
  66. var nodes = root.children;
  67. for (var i = 0; i < nodes.length; i++) {
  68. var node = nodes[i];
  69. if (node.children.length) {
  70. this._complie(node);
  71. }
  72. if (node.hasAttribute('v-click')) {
  73. node.onclick = (function () {
  74. var attrVal = nodes[i].getAttribute('v-click');
  75. return _this.$methods[attrVal].bind(_this.$data);
  76. })();
  77. }
  78. if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
  79. node.addEventListener('input', (function(key) {
  80. var attrVal = node.getAttribute('v-model');
  81. _this._binding[attrVal]._directives.push(new Watcher(
  82. 'input',
  83. node,
  84. _this,
  85. attrVal,
  86. 'value'
  87. ))
  88. return function() {
  89. _this.$data[attrVal] = nodes[key].value;
  90. }
  91. })(i));
  92. }
  93. if (node.hasAttribute('v-bind')) {
  94. var attrVal = node.getAttribute('v-bind');
  95. _this._binding[attrVal]._directives.push(new Watcher(
  96. 'text',
  97. node,
  98. _this,
  99. attrVal,
  100. 'innerHTML'
  101. ))
  102. }
  103. }
  104. }
  105. function Watcher(name, el, vm, exp, attr) {
  106. this.name = name; //指令名称,例如文本节点,该值设为"text"
  107. this.el = el; //指令对应的DOM元素
  108. this.vm = vm; //指令所属myVue实例
  109. this.exp = exp; //指令对应的值,本例如"number"
  110. this.attr = attr; //绑定的属性值,本例为"innerHTML"
  111. this.update();
  112. }
  113. Watcher.prototype.update = function () {
  114. this.el[this.attr] = this.vm.$data[this.exp];
  115. }
  116. window.onload = function() {
  117. var app = new myVue({
  118. el:'#app',
  119. data: {
  120. number: 0
  121. },
  122. methods: {
  123. increment: function() {
  124. this.number ++;
  125. },
  126. }
  127. })
  128. }
  129. </script>

转载来源:https://segmentfault.com/a/1190000014274840

vue的双向数据绑定实现原理的更多相关文章

  1. vue实现双向数据绑定的原理

    vue实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的. 在MDN上对该方法的说明是:O ...

  2. 转 vue实现双向数据绑定之原理及实现篇

    转自:https://www.cnblogs.com/canfoo/p/6891868.html vue的双向绑定原理及实现 前言 先上个成果图来吸引各位: 代码:                  ...

  3. Vue的双向数据绑定的原理

    Vue数据双向绑定的原理就是采用数据劫持结合发布者-订阅者模式,通过object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监 ...

  4. vue的双向数据绑定实现原理(简单)

    如果有人问你,学vue学到了什么,那双向数据绑定,是必然要说的. 我们都知道,在vue中,使用数据双向绑定我们都知道是v-modle实现的. 实现原理是通过Object.defineProperty的 ...

  5. vue双向数据绑定的原理-object.defineProperty() 用法

    有关双向数据绑定的原理 关于数据双向绑定的理解:利用了 Object.defineProperty() 这个方法重新给对象定义了新属性,在操作新属性分别为为获取属性值(调用get方法)和设置属性值(调 ...

  6. 面试题:你能写一个Vue的双向数据绑定吗?

    在目前的前端面试中,vue的双向数据绑定已经成为了一个非常容易考到的点,即使不能当场写出来,至少也要能说出原理.本篇文章中我将会仿照vue写一个双向数据绑定的实例,名字就叫myVue吧.结合注释,希望 ...

  7. 详解 vue 双向数据绑定的原理,并实现一组双向数据绑定

    1:vue 双向数据绑定的原理: Object.defineProperty是ES5新增的一个API,其作用是给对象的属性增加更多的控制Object.defineProperty(obj, prop, ...

  8. Vue基础01vue的基本示例,vue的双向数据绑定,vue中常见的几种用法,vue相关常见指令

    自学vue框架,每天记录重要的知识点,与大家分享!有不足之处,希望大家指正. 本篇将讲述:vue的基本示例,vue的双向数据绑定,vue中常见的几种用法,vue相关常见指令 前期学习基础,使用vue. ...

  9. 浅析vue的双向数据绑定

    vue 的双向数据绑定是基于es5的 object对象的defineProperty属性,重写data的set和get函数来实现的.1.defineProperty 数据展示 Object.defin ...

随机推荐

  1. [转] golang socket

    server.go package main import ( "net" "fmt" "io/ioutil" "time&quo ...

  2. 直接下载:Windows 10正式版官方原版镜像!

    本文搜集整理微软官方发布的Windows 10正式版镜像下载链接,从RTM原始正式版开始,按照时间倒序排列,即越往上的越新. 注意:以下资源均来自于微软官方原版,ed2k可视为P2P下载链接.下载完成 ...

  3. flash builder 4.7 打开闪退解决办法

    删除文件 /Users/apple/Documents/Adobe Flash Builder 4.7/.metadata/.plugins/org.eclipse.ui.workbench/work ...

  4. 获取本机内网、外网ip(C++)<转>

    基础知识 电脑在局域网内,通过网关/路由器连接到Internet则ip分为内网ip.外网ip.通过ipconfig得到的为局域网ip. 电脑直接拨号连接等,则本机通过ipconfig得到的就是外网ip ...

  5. 解决Mac上安装mysqlclient的错误

       要想使用python操作mysql,那么就需要安装python操作数据库的驱动,由于mysqldb不支持python3,我选择安装mysqlclient, 命令行输入:pip3 install  ...

  6. 装饰者模式——Head First

    一.定义 装饰者模式(Decorator Pattern)动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案. 二.类图 三.星巴兹饮料 //Component public ...

  7. python环境和工具

    1.版本问题 python2.X和python3.X是不兼容,所以选择如果选择了2.X版本,那么为了避免兼容性的问题,在以后使用其他python库或者工具时,也需要选择相对应的版本. 下载地址:htt ...

  8. APP发行渠道

    1,安卓APP发行:google play,原名android store 2,IOS APP: apple store 3,国内各大平台,应用宝,360,小米,华为 ...

  9. 一小段测试atof的代码

    #include <stdio.h> //#include <stdlib.h> double a=0; int main(int argc, char *argv[]) { ...

  10. Linux grep命令使用方法

    Linux系统中grep命令可以根据指定的字符串或者正则表达式对文件内容进行匹配查找.在Linux文件处理和SHELL编程中使用广泛. grep基本语法 用法: grep [选项] "字符串 ...