vue实现双向绑定原理

╰半橙微兮° 2022-05-27 04:26 390阅读 0赞

原理:

vue主要是借助对象的访问器属性(Object.defineProperty)劫持数据,并结合订阅者-发布者模式来实现数据双向绑定。

通过Object.defineProperty把data中的各数据属性改为访问器属性,来劫持每个属性的setter、getter;setter劫持到数据变化后,作为发布者 发布通知,订阅者们接到通知后更新数据。

为了进一步说明vue原理,这里我们借助实例来进行讲解。

实例:

有一段html代码(mvvm.html),需要保持 输入框中数据和data中数据同步。需求有了,那我们如何实现呢?看代码:

mvvm.html代码

  1. <div id="app"> <input type="text" id="a" v-model='name' /> { { name}} </div>

利用Object.defineProperty把data中的各数据属性改为访问器属性

  1. //把data中的所有数据属性修改为访问器属性:
  2. function observe(obj,vm){
  3. Object.keys(obj).forEach(function(key){
  4. defineReactive(obj,key,obj[key]);
  5. })
  6. }
  7. //数据属性修改为访问器属性:
  8. function defineReactive(obj,key,val){
  9. var dep = new Dep();//主题对象,存放key属性的所有订阅者
  10. Object.defineProperty(obj,key,{
  11. get: function(){
  12. if(Dep.target){
  13. dep.subs.push(Dep.target);//添加订阅者
  14. }
  15. return val;
  16. },
  17. set: function(newVal){
  18. if(val==newVal)return;
  19. val = newVal;//数据劫持:当给key属性赋值时,会触发set方法
  20. dep.notify();//发布通知
  21. }
  22. })
  23. }

定义主题对象:添加订阅者和发布者方法

  1. function Dep(){
  2. this.subs = [];
  3. }
  4. Dep.prototype = {
  5. addSub: function(sub){ //添加订阅者
  6. this.subs.push(sub);
  7. },
  8. nodify: function(){
  9. //发布通知
  10. this.subs.forEach(function(sub){
  11. sub.update();//订阅者更新数据
  12. })
  13. }
  14. }

订阅者:添加数据更新方法

  1. function Watcher(vm, node, name){
  2. Dep.target = this;
  3. this.name = name;
  4. this.node = node;
  5. this.vm = vm;
  6. this.update();
  7. Dep.target = null;
  8. }
  9. Watcher.prototype = {
  10. update: function(){ //更新数据
  11. this.get();
  12. this.node.nodeValue = this.value;
  13. },
  14. get: function(){ //获取数据
  15. this.value = this.vm.data[this.name];//触发get
  16. }
  17. }

利用documentFragment(文档片段)来处理节点,处理后再把文档片段插入挂载目标(注:操作documentFragment优于直接操作Dom节)

  1. function nodeToFragment(node,vm){
  2. var fragment = document.createDocumentFragment();
  3. var child;
  4. while(child = node.firstChild){
  5. compile(child,vm);//编译数据
  6. fragment.appendChild(child);
  7. }
  8. return fragment;
  9. }

编译数据:解析模板指令,将模板中的变量替换成数据

  1. function compile(node,vm){
  2. var reg = /\{\{(.*)\}\}/;
  3. if(node.nodeType==1){
  4. var attrs = node.attributes;
  5. for(var i=0,ln=attrs.length;i<ln;i++){
  6. if(attrs[i].nodeName=='v-model'){
  7. var name = attrs[i].nodeValue;
  8. node.addEventListener("input",function(event){
  9. //给相应的data赋值,触发该属性的set方法
  10. vm.data[name]=event.target.value;
  11. })
  12. node.value = vm.data[name];//将data的值赋值给节点
  13. node.removeAttribute('v-model');
  14. }
  15. }
  16. }
  17. if(node.nodeType==3){
  18. if(reg.test(node.nodeValue)){
  19. var name = (node.nodeValue).match(reg)[1];
  20. name = name.trim();
  21. new Watcher(vm, node, name);//为节点添加订阅者方法
  22. }
  23. }
  24. }

创建Vue构造方法:

  1. function Vue(options){
  2. this.data = options.data;
  3. var data = this.data;
  4. observe(data,this);
  5. var id = options.el;
  6. var dom = nodeToFragment(document.getElementById(id),this);
  7. document.getElementById(id).appendChild(dom);
  8. }

创建Vue示例:调用Vue,实现 输入框数据和data数据同步

  1. var vm = new Vue({
  2. el: 'app',
  3. data: {
  4. name:'Lucy'
  5. }
  6. })

访问器属性介绍:https://blog.csdn.net/yihanzhi/article/details/79900047
参考文章:https://segmentfault.com/a/1190000006599500

发表评论

表情:
评论列表 (有 0 条评论,390人围观)

还没有评论,来说两句吧...

相关阅读