一、实现双向绑定

  

详细版:

  

  前端MVVM实现双向数据绑定的做法大致有如下三种:

1.发布者-订阅者模式(backbone.js)

思路:使用自定义的data属性在HTML代码中指明绑定。所有绑定起来的JavaScript对象以及DOM元素都将“订阅”一个发布者对象。任何时候如果JavaScript对象或者一个HTML输入字段被侦测到发生了变化,我们将代理事件到发布者-订阅者模式,这会反过来将变化广播并传播到所有绑定的对象和元素。

vueJS 的思路流程:发布者dep发出通知 => 主题对象subs收到通知并推送给订阅者 => 订阅者watcher执行相应操作

2.脏值检查(angular.js)

思路:angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,angular只有在指定的事件触发时进入脏值检测,大致如下:

  • DOM事件,譬如用户输入文本,点击按钮等。( ng-click )

  • XHR响应事件 ( $http )

  • 浏览器Location变更事件 ( $location )

  • Timer事件( $timeout , $interval )

  • 执行 $digest() 或 $apply()

3.数据劫持(Vue.js)

思路: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Object.defineProperty
作用定义:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Object.defineProperty(obj, prop, descriptor)
参数 obj => 要在其上定义属性的对象;
   prop => 要定义或修改的属性的名称;
   descriptor => 将被定义或修改的属性描述符。

  属性描述符 => 数据描述符和存取描述符,两者取一
    数据描述符: 具有值的属性
    存取描述符: 由getter-setter函数对描述的属性

    具有的属性:

    

注:configurable 可配置性相当于属性的总开关,只有为true时才能设置,而且不可逆

              enumerable  是否可枚举,为false时for..in以及Object.keys()将不能枚举出该属性

              writable 是否可写,为false时将不能够修改属性的值

      get 一个给属性提供 getter 的方法

      set 一个给属性提供 setter 的方法

返回值  被传递给函数的对象obj。

示例

  1. var obj = {};
  2. Object.defineProperty(obj, 'hello', {
  3. get: function() {
  4. console.log('get val:'+ val);
  5. return val;
  6.   },
  7.   set: function(newVal) {
  8. val = newVal;
  9. console.log('set val:'+ val);
  10. }
  11. });
  12.  
  13. obj.hello;  // 触发 getter =>get val:undefined
  14. obj.hello='111'; // 触发 setter =>set val:111
  15. obj.hello;  // 触发 getter =>get val:111

 vue 实现双向绑定

实现mvvm的双向绑定的步骤:
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
4、mvvm入口函数,整合以上三者

流程图:

流程解析:

从图中可以看出,当执行 new Vue() 时,Vue 就进入了初始化阶段,一方面Vue 会遍历 data 选项中的属性,并用 Object.defineProperty 将它们转为 getter/setter,实现数据变化监听功能;另一方面,Vue 的指令编译器Compile 对元素节点的指令进行解析,初始化视图,并订阅Watcher 来更新视图, 此时Wather 会将自己添加到消息订阅器中(Dep),初始化完毕。当数据发生变化时,Observer 中的 setter 方法被触发,setter 会立即调用Dep.notify(),Dep 开始遍历所有的订阅者,并调用订阅者的 update 方法,订阅者收到通知后对视图进行相应的更新。因为VUE使用Object.defineProperty方法来做数据绑定,而这个方法又无法通过兼容性处理,所以Vue 不支持 IE8 以及更低版本浏览器。另外,查看vue原代码,发现在vue初始化实例时, 有一个proxy代理方法,它的作用就是遍历data中的属性,把它代理到vm的实例上,这也就是我们可以这样调用属性:vm.a等于vm.data.a。

Observer

利用Obeject.defineProperty()来监听属性变动,将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 settergetter

当给这个对象的某个值赋值,就会触发setter,进而监听到数据变化

监听到变化之后通知订阅者,需要实现一个消息订阅器Dep,通过维护一个数组subs,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法

流程图:

完整代码:

  1. function Observer(data) {
  2. this.data = data;
  3. this.walk(data);
  4. }
  5.  
  6. Observer.prototype = {
  7. walk: function(data) {
  8. var me = this;
  9. Object.keys(data).forEach(function(key) {
  10. me.convert(key, data[key]);
  11. });
  12. },
  13. convert: function(key, val) {
  14. this.defineReactive(this.data, key, val);
  15. },
  16.  
  17. defineReactive: function(data, key, val) {
  18. var dep = new Dep();
  19. var childObj = observe(val);
  20.  
  21. Object.defineProperty(data, key, {
  22. enumerable: true, // 可枚举
  23. configurable: false, // 不能再define
  24. get: function() {
  25. // 添加订阅者watcher到主题对象Dep
  26. if (Dep.target) {
  27. dep.depend();
  28. }
  29. return val;
  30. },
  31. set: function(newVal) {
  32. if (newVal === val) {
  33. return;
  34. }
  35. val = newVal;
  36. // 新的值是object的话,进行监听
  37. childObj = observe(newVal);
  38. // 通知订阅者
  39. dep.notify();
  40. }
  41. });
  42. }
  43. };
  44.  
  45. function observe(value, vm) {
  46. if (!value || typeof value !== 'object') {
  47. return;
  48. }
  49.  
  50. return new Observer(value);
  51. };
  52.  
  53. var uid = 0;
  54.  
  55. function Dep() {
  56. this.id = uid++;
  57. this.subs = [];
  58. }
  59.  
  60. Dep.prototype = {
  61. addSub: function(sub) {
  62. this.subs.push(sub);
  63. },
  64.  
  65. depend: function() {
  66. Dep.target.addDep(this);
  67. },
  68.  
  69. removeSub: function(sub) {
  70. var index = this.subs.indexOf(sub);
  71. if (index != -1) {
  72. this.subs.splice(index, 1);
  73. }
  74. },
  75.  
  76. notify: function() {
  77. this.subs.forEach(function(sub) {
  78. sub.update();
  79. });
  80. }
  81. };
  82.  
  83. Dep.target = null;

 

Watcher 

Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、定义一个update()方法
3、在Observe中,待属性变动触发dep.notice()发出通知,调用watcher实例自身的update()方法,并触发Compile中绑定的回调

完整代码:

  1. function Watcher(vm, expOrFn, cb) {
  2. this.cb = cb; // 回调函数
  3. this.vm = vm; // this 调用对象
  4. this.expOrFn = expOrFn; // watch的对象的key
  5. this.depIds = {};
  6. console.log(typeof expOrFn, expOrFn);
  7. if (typeof expOrFn === 'function') { // function
  8. this.getter = expOrFn;
  9. } else { // express
  10. // this.getter 等于 this.parseGetter 的return返回的匿名函数
  11. this.getter = this.parseGetter(expOrFn);
  12. }
  13. // 调用get方法,从而触发getter
  14. // this.get() ==> this.getter.call(this.vm, this.vm) ==> this.parseGetter(expOrFn)
  15. // this.value = parseGetter中return匿名函数的返回值
  16. this.value = this.get();
  17. }
  18.  
  19. Watcher.prototype = {
  20. update: function() {
  21. this.run(); // 属性值变化收到通知,每次data属性值变化触发dep.notify()
  22. },
  23. run: function() {
  24. var value = this.get(); // 取到最新值
  25. var oldVal = this.value;
  26. if (value !== oldVal) { // 新值与旧值比较
  27. this.value = value;
  28. this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调,更新视图
  29. }
  30. },
  31. addDep: function(dep) {
  32. // 1. 每次调用run()的时候会触发相应属性的getter
  33. // getter里面会触发dep.depend(),继而触发这里的addDep
  34. // 2. 假如相应属性的dep.id已经在当前watcher的depIds里,说明不是一个新的属性,仅仅是改变了其值而已
  35. // 则不需要将当前watcher添加到该属性的dep里
  36. // 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里
  37. // 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性
  38. // 则需要将当前watcher(child.name)加入到新的 child.name 的dep里
  39. // 因为此时 child.name 是个新值,之前的 setter、dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中
  40. // 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了
  41. // 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep
  42. // 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update
  43. // 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter
  44. // 触发了addDep(), 在整个forEach过程,当前wacher都会加入到每个父级过程属性的dep
  45. // 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前watcher
  46. if (!this.depIds.hasOwnProperty(dep.id)) {
  47. dep.addSub(this);
  48. this.depIds[dep.id] = dep;
  49. }
  50. },
  51. get: function() {
  52. Dep.target = this; // 将当前订阅者指向自己
  53. console.log(Dep.target);
  54. var value = this.getter.call(this.vm, this.vm); // 触发getter,添加自己到属性订阅器中
  55. Dep.target = null; // 添加完毕,重置
  56. console.log(Dep.target);
  57. return value;
  58. },
  59.  
  60. parseGetter: function(exp) {
  61. if (/[^\w.$]/.test(exp)) return;
  62.  
  63. var exps = exp.split('.');
  64. //  this.getter.call(this.vm, this.vm)的第二个this.vm 传入 obj
  65. return function(obj) {
  66. for (var i = 0, len = exps.length; i < len; i++) {
  67. if (!obj) return;
  68. obj = obj[exps[i]];
  69. }
  70. return obj;
  71. }
  72. }
  73. };

实例化Watcher的时候,调用get()方法,通过Dep.target = watcher实例 标记订阅者是当前watcher实例,强行触发属性定义的getter方法,getter方法执行的时候,就会在属性的订阅器dep添加当前watcher实例,从而在属性值有变化的时候,watcher实例就能收到更新通知

Compile

Compile 指令实现,解析指令,模版渲染,更新视图,并将每个指令对应的节点绑定更新函数new Updater(),添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

因为遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将跟节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中;监听数据、绑定更新函数的处理是在compileUtil.bind()这个方法中,通过new Watcher()添加回调来接收数据变化的通知

流程图:

完整代码:

  1. function Compile(el, vm) {
  2. this.$vm = vm;
  3. this.$el = this.isElementNode(el) ? el : document.querySelector(el);
  4.  
  5. if (this.$el) {
  6. this.$fragment = this.node2Fragment(this.$el);
  7. this.init();
  8. this.$el.appendChild(this.$fragment);
  9. }
  10. }
  11.  
  12. Compile.prototype = {
  13. // dom节点 转化为 Fragment文档碎片
  14. node2Fragment: function(el) {
  15. var fragment = document.createDocumentFragment(),
  16. child;
  17.  
  18. // 将原生节点拷贝到fragment
  19. while (child = el.firstChild) {
  20. fragment.appendChild(child);
  21. }
  22.  
  23. return fragment;
  24. },
  25.  
  26. init: function() {
  27. this.compileElement(this.$fragment);
  28. },
  29.  
  30. compileElement: function(el) {
  31. var childNodes = el.childNodes,
  32. me = this;
  33.  
  34. [].slice.call(childNodes).forEach(function(node) {
  35. var text = node.textContent; // 文本内容
  36. var reg = /\{\{(.*)\}\}/; // 匹配{{}}花括号
  37.  
  38. if (me.isElementNode(node)) { //节点类型为元素
  39. me.compile(node);
  40. } else if (me.isTextNode(node) && reg.test(text)) { //节点类型为text
  41. me.compileText(node, RegExp.$1);
  42. }
  43.  
  44. if (node.childNodes && node.childNodes.length) {
  45. me.compileElement(node);
  46. }
  47. });
  48. },
  49.  
  50. // 编译 解析 元素节点
  51. compile: function(node) {
  52. var nodeAttrs = node.attributes,
  53. me = this;
  54.  
  55. [].slice.call(nodeAttrs).forEach(function(attr) {
  56. var attrName = attr.name;
  57. if (me.isDirective(attrName)) {
  58. var exp = attr.value; // 属性值
  59. var dir = attrName.substring(2); // v-on:
  60. if (me.isEventDirective(dir)) { // 事件指令 on
  61. compileUtil.eventHandler(node, me.$vm, exp, dir);
  62. } else { // 普通指令
  63. compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
  64. }
  65.  
  66. node.removeAttribute(attrName);
  67. }
  68. });
  69. },
  70. // 编译 解析 文本节点
  71. compileText: function(node, exp) {
  72. compileUtil.text(node, this.$vm, exp);
  73. },
  74.  
  75. isDirective: function(attr) {
  76. return attr.indexOf('v-') == 0;
  77. },
  78.  
  79. isEventDirective: function(dir) {
  80. return dir.indexOf('on') === 0;
  81. },
  82.  
  83. isElementNode: function(node) {
  84. return node.nodeType == 1;
  85. },
  86.  
  87. isTextNode: function(node) {
  88. return node.nodeType == 3;
  89. }
  90. };
  91.  
  92. // 指令处理集合
  93. var compileUtil = {
  94. text: function(node, vm, exp) {
  95. this.bind(node, vm, exp, 'text');
  96. },
  97.  
  98. html: function(node, vm, exp) {
  99. this.bind(node, vm, exp, 'html');
  100. },
  101.  
  102. model: function(node, vm, exp) {
  103. this.bind(node, vm, exp, 'model');
  104.  
  105. var me = this,
  106. val = this._getVMVal(vm, exp);
  107. node.addEventListener('input', function(e) {
  108. var newValue = e.target.value;
  109. if (val === newValue) {
  110. return;
  111. }
  112.  
  113. me._setVMVal(vm, exp, newValue);
  114. val = newValue;
  115. });
  116. },
  117.  
  118. class: function(node, vm, exp) {
  119. this.bind(node, vm, exp, 'class');
  120. },
  121.  
  122. bind: function(node, vm, exp, dir) {
  123. var updaterFn = updater[dir + 'Updater'];
  124. // 初始化 渲染视图
  125. updaterFn && updaterFn(node, this._getVMVal(vm, exp));
  126. // 实例化订阅者,此操作会在对应的属性消息订阅器中添加了该订阅者watcher
  127. new Watcher(vm, exp, function(value, oldValue) {
  128. // 监测到数据变化,更新视图
  129. updaterFn && updaterFn(node, value, oldValue);
  130. });
  131. },
  132.  
  133. // 事件处理
  134. eventHandler: function(node, vm, exp, dir) {
  135. var eventType = dir.split(':')[1],
  136. fn = vm.$options.methods && vm.$options.methods[exp];
  137.  
  138. if (eventType && fn) {
  139. node.addEventListener(eventType, fn.bind(vm), false);
  140. }
  141. },
  142.  
  143. _getVMVal: function(vm, exp) {
  144. var val = vm;
  145. exp = exp.split('.');
  146. exp.forEach(function(k) {
  147. val = val[k];
  148. });
  149. return val;
  150. },
  151.  
  152. _setVMVal: function(vm, exp, value) {
  153. var val = vm;
  154. exp = exp.split('.');
  155. exp.forEach(function(k, i) {
  156. // 非最后一个key,更新val的值
  157. if (i < exp.length - 1) {
  158. val = val[k];
  159. } else {
  160. val[k] = value;
  161. }
  162. });
  163. }
  164. };
  165.  
  166. // 更新函数集合
  167. var updater = {
  168. textUpdater: function(node, value) {
  169. node.textContent = typeof value == 'undefined' ? '' : value;
  170. },
  171.  
  172. htmlUpdater: function(node, value) {
  173. node.innerHTML = typeof value == 'undefined' ? '' : value;
  174. },
  175.  
  176. classUpdater: function(node, value, oldValue) {
  177. var className = node.className;
  178. className = className.replace(oldValue, '').replace(/\s$/, '');
  179.  
  180. var space = className && String(value) ? ' ' : '';
  181.  
  182. node.className = className + space + value;
  183. },
  184.  
  185. modelUpdater: function(node, value, oldValue) {
  186. node.value = typeof value == 'undefined' ? '' : value;
  187. }
  188. };

MVVM

MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,Compile解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

完整代码:

  1. function MVVM(options) {
  2. this.$options = options || {};
  3. var data = this._data = this.$options.data;
  4. var me = this;
  5.  
  6. // 数据代理
  7. // 实现 vm.xxx -> vm._data.xxx
  8. Object.keys(data).forEach(function(key) {
  9. me._proxyData(key);
  10. });
  11.  
  12. // 初始化computed
  13. this._initComputed();
  14.  
  15. // 调用observe,监测数据是否变化
  16. observe(data, this);
  17.  
  18. // 编译解析指令模板
  19. this.$compile = new Compile(options.el || document.body, this)
  20. }
  21.  
  22. MVVM.prototype = {
  23. $watch: function(key, cb, options) {
  24. new Watcher(this, key, cb);
  25. },
  26.  
  27. _proxyData: function(key, setter, getter) {
  28. var me = this;
  29. setter = setter ||
  30. Object.defineProperty(me, key, {
  31. configurable: false,
  32. enumerable: true,
  33. get: function proxyGetter() {
  34. return me._data[key];
  35. },
  36. set: function proxySetter(newVal) {
  37. me._data[key] = newVal;
  38. }
  39. });
  40. },
  41.  
  42. _initComputed: function() {
  43. var me = this;
  44. var computed = this.$options.computed;
  45. if (typeof computed === 'object') {
  46. Object.keys(computed).forEach(function(key) {
  47. Object.defineProperty(me, key, {
  48. get: typeof computed[key] === 'function' ?
  49. computed[key] : computed[key].get,
  50. set: function() {}
  51. });
  52. });
  53. }
  54. }
  55. };

数据双向绑定的简单实现 实例

  1. <!DOCTYPE html>
  2. <head></head>
  3. <body>
  4. <div id="app">
  5. <input type="text" id="a" v-model="text">
  6. {{text}}
  7. </div>
  8. <script type="text/javascript">
  9.   function Compile(node, vm) {
  10. if(node) {
  11. this.$frag = this.nodeToFragment(node, vm);
  12. console.log('vm===>', vm);
  13. console.log('$frag=>>>',this.$frag) // #document-fragment
  14. return this.$frag;
  15. }
  16. }
  17. Compile.prototype = {
  18. nodeToFragment: function(node, vm) {
  19. var self = this;
  20. var frag = document.createDocumentFragment();
  21. var child;
  22.  
  23. console.log('node=>>>', node) // #app 节点
  24.  
  25. while(child = node.firstChild) {
  26. console.log('child=>>>', child)
  27. self.compileElement(child, vm);
  28. frag.append(child); // 将所有子节点添加到fragment中
  29. }
  30. return frag;
  31. },
  32. compileElement: function(node, vm) {
  33. var reg = /\{\{(.*)\}\}/;
  34. console.log('reg===>', reg);
  35. console.log('node.nodeType==>>', node.nodeType);
  36.  
  37. //节点类型为元素
  38. if(node.nodeType === 1) {
  39. var attr = node.attributes;
  40. // 解析属性
  41. for(var i = 0; i < attr.length; i++ ) {
  42. if(attr[i].nodeName == 'v-model') {
  43. var name = attr[i].nodeValue; // 获取v-model绑定的data中的属性名 [text]
  44. console.log('name===>',name);
  45. node.addEventListener('input', function(e) {
  46. // 给相应的data属性赋值,进而触发该属性的set方法
  47. vm[name]= e.target.value;
  48. });
  49. node.value = vm[name]; // 将data的值赋给该node
  50. new Watcher(vm, node, name, 'value');
  51. }
  52. };
  53. }
  54. //节点类型为text
  55. if(node.nodeType === 3) {
  56. console.log('node.nodeValue==>>', node.nodeValue);
  57. console.log(reg.test(node.nodeValue))
  58. if(reg.test(node.nodeValue)) {
  59. var name = RegExp.$1; // 获取匹配到的字符串
  60. name = name.trim();
  61. node.nodeValue = vm[name]; // 将data的值赋给该node
  62. new Watcher(vm, node, name, 'nodeValue');
  63. console.log(vm, node, name)
  64. }
  65. }
  66. },
  67. }
  68. function Dep() {
  69. this.subs = [];
  70. }
  71. Dep.prototype = {
  72. addSub: function(sub) {
  73. this.subs.push(sub);
  74. },
  75. notify: function() {
  76. this.subs.forEach(function(sub) {
  77. sub.update();
  78. })
  79. }
  80. }
  81. // node => dom真实节点
  82. // name => data 属性key
  83. //
  84. function Watcher(vm, node, name, type) {
  85. Dep.target = this;
  86. this.name = name;
  87. this.node = node;
  88. this.vm = vm;
  89. this.type = type;
  90. this.update();
  91. Dep.target = null;
  92. }
  93.  
  94. Watcher.prototype = {
  95. update: function() {
  96. this.get();
  97. // console.log('node, type=>', this.node, this.type);
  98. this.node[this.type] = this.value; // 订阅者执行相应操作
  99. },
  100. // 获取data的属性值
  101. get: function() {
  102. this.value = this.vm[this.name]; //触发相应属性的get
  103. // console.log('value, name=>',this.value, this.name)
  104. }
  105. }
  106. function defineReactive (obj, key, val) {
  107. var dep = new Dep();
  108. Object.defineProperty(obj, key, {
  109. get: function() {
  110. console.log(Dep.target)
  111. // <input type="text" id="a" v-model="text"> => Watcher {name: "text", node: input#a, vm: Vue, type: "value"}
  112. // {{text}} => Watcher {name: "text", node: text, vm: Vue, type: "nodeValue"}
  113. //添加订阅者watcher到主题对象Dep
  114. if(Dep.target) {
  115. // JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
  116. dep.addSub(Dep.target);
  117. }
  118. return val;
  119. },
  120. set: function (newVal) {
  121. console.log(newVal === val, val, newVal);
  122. if(newVal === val) return;
  123. val = newVal;
  124. console.log(val);
  125. // 作为发布者发出通知
  126. dep.notify();
  127. }
  128. })
  129. }
  130. function observe(obj, vm) {
  131. Object.keys(obj).forEach(function(key) {
  132. defineReactive(vm, key, obj[key]);
  133. })
  134. }
  135.  
  136.   function Vue(options) {
  137. this.data = options.data;
  138. var data = this.data;
  139. observe(data, this);
  140. var id = options.el;
  141. // console.log(this)
  142. var dom =new Compile(document.getElementById(id),this);
  143.  
  144. // 编译完成后,将dom返回到app中
  145. document.getElementById(id).appendChild(dom);
  146. }
  147. var vm = new Vue({
  148. el: 'app',
  149. data: {
  150. text: 'hello world'
  151. }
  152. });
  153. console.log(vm)
  154. </script>
  155. </body>
  156. </html>

完整代码:https://github.com/136shine/MVVM_ada

参考:https://www.cnblogs.com/libin-1/p/6893712.html

   https://segmentfault.com/a/1190000006599500

http://baijiahao.baidu.com/s?id=1596277899370862119&wfr=spider&for=pc

vue 之 双向绑定原理的更多相关文章

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

    前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...

  2. vue数据双向绑定原理

    vue的数据双向绑定的小例子: .html <!DOCTYPE html> <html> <head> <meta charset=utf-> < ...

  3. vue的双向绑定原理解析(vue项目重构二)

    现在的前端框架 如果没有个数据的双向/单向绑定,都不好意思说是一个新的框架,至于为什么需要这个功能,从jq或者原生js开始做项目的前端工作者,应该是深有体会. 以下也是个人对vue的双向绑定原理的一些 ...

  4. vue的双向绑定原理浅析与简单实现

    很久之前看过vue的一些原理,对其中的双向绑定原理也有一定程度上的了解,只是最近才在项目上使用vue,这才决定好好了解下vue的实现原理,因此这里对vue的双向绑定原理进行浅析,并做一个简单的实现. ...

  5. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  6. Vue数据双向绑定原理及简单实现

    嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...

  7. Vue之双向绑定原理动手记

    Vue.js的核心功能有两个:一是响应式的数据绑定系统,二是组件系统.本文是通过学习他人的文章,从而理解了双向绑定原理,从而在自己理解的基础上,自己动手实现数据的双向绑定. 目前几种主流的mvc(vm ...

  8. Vue.js双向绑定原理

    Vue.js最核心的功能有两个,一个是响应式的数据绑定系统,另一个是组件系统.本文仅仅探究双向绑定是怎样实现的.先讲涉及的知识点,再用简化的代码实现一个简单的hello world示例. 一.访问器属 ...

  9. 探讨vue的双向绑定原理及实现

    1.vue的实现原理 vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属 ...

  10. 【Vue】vue的双向绑定原理及实现

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,那么vue是如果进行数据劫持的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西. 代码: var ...

随机推荐

  1. VASP计算参考

    1.VASP 结构优化.静态自洽.非自洽计算:https://blog.csdn.net/kyang_823/article/details/59110848 2.VASP贋势:https://blo ...

  2. 前端必须掌握的 docker 技能(2)

    概述 作为一个前端,我觉得必须要学会使用 docker 干下面几件事: 部署前端应用 部署 nginx 给部署的 nginx 加上 https 使用 docker compose 进行部署 给 ngi ...

  3. 原生js去除行内样式

    概述 今天我用js给dom元素设置样式,碰到了一些问题,记下来供以后开发时参考,相信对其他人也有用. 心得 js加上class: $dom.classList.add('some-class'); j ...

  4. tomcat7源码包编译安装

    tomcat/:作用解析jsp程序.先安装jdk容器.1.下载jdk, wget http://download.oracle.com/otn- pub/java/jdk/8u131- b11/d54 ...

  5. 测开之路一百五十三:ajax之load、get、ajax在项目中的体现

    在查询的时候是使用ajax进行请求的 目录结构 personal.models from datetime import datetimefrom flask_sqlalchemy import SQ ...

  6. Java ——多线程编程

    本节重点思维导图 多线程编程

  7. numpy2

    1.通用函数,是一种在ndarray数据中进行逐元素操作的函数.某些函数接受一个或多个标量数值,并产生一个或多个标量结果,通用函数就是对这些函数的封装. 1.常用的一元通用函数有:abs\fabs s ...

  8. 25. Reverse Nodes in k-Group[H]k个一组翻转链表

    题目 Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. ...

  9. linux下 sleep() 与 usleep()

    usleep() 将进程挂起一段时间, 单位是微秒(百万分之一秒): 头文件: unistd.h 语法: void usleep(int micro_seconds); 返回值: 无 内容说明:本函数 ...

  10. [DS+Algo] 010 二叉树的遍历

    二叉树遍历 深度优先 一般用递归 一些名词 遍历方式 英文 先序 Preorder 中序 Inorder 后序 Postorder 广度优先 一般用队列 Python 代码示例 class Node( ...