vue的双向绑定式原理

本文内容转载于:
详细版可阅读——剖析Vue原理&实现双向绑定MVVM

简易版文字解答可直接阅读——Vue双向数据绑定原理

一:思路整理

vue的双向绑定式原理的思路总结:

要在vue中实现mvvm的双向绑定,是通过 数据劫持 和 发布-订阅者 功能来实现的。实现步骤:

  1. 实现一个数据监听器 Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

  2. 实现一个指令解析器 Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

  3. 实现一个订阅者Watcher,作为连接Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的回调函数,从而更新视图
    在这里插入图片描述
    Vue 是通过数据劫持的方式来做数据绑定的,其中
    最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的;

    Object.defineProperty(obj,prop,descriptor)方法,其中三个参数:

    1. obj:要在其上定义属性的对象
    2. prop:要定义或修改的属性名称
    3. desciption:将被定义或者修改的属性描述符【通过写入的方法来定义或者修改一个值】
      关于该方法的简单应用:
var obj = {}
Object.defineProperty(obj,'hello',{
   get: function() {
     console.log('调用了get方法获取对象的属性')
     return this
   },
   set: function() {
     console.log('调用了set方法,方法的值是:' + newValue)
   }
})
obj.hello; // 调用了get方法获取对象的属性
obj.hello = 'Hi'; // 调用了set方法,方法的值是:Hi

二:具体实现

1:实现 Observer
在这个函数中我们需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上 getter 和 setter

function observe(data){
    if(!data || typeof data !== 'object') {
       return 
    }
    // 取出所有属性进行递归遍历
    Object.keys(data).forEach(key => {
        defineReactive(data, key, date[key])
    })
}
// 定义可响应式,通过Object.defineProperty来进行数据劫持
function defineReactive(data, key, val) {
    observe(val) // 递归监听子属性
    Object.defineProperty(data, key, {
         enumrable: true // 可枚举
         configurable:false // 不能再define
         get:function() {
             return val
         },
         set:function(newVal) {
            console.log('yeah,监听到了值变化',val ,'变为了', newVal)
           val = newVal  
         }
    })
}

这样可以实现监听每个数据的变化,但是监听到了变化如何通知订阅者。故也需要实现一个消息订阅器,维护一个数组即可,用来收集订阅者(watcher),数据变动触发 notify ,再调用订阅者的 update 方法,可改善如下:

function defineReactive(data, key, val) {
    var dep = new Dep()
    observe(val) // 递归监听子属性
    Object.defineProperty(data, key, {
         ... // 省略
         set:function(newVal) {
           if(val === newVal ) return
           console.log('yeah,监听到了值变化',val ,'变为了', newVal)
           val = newVal  
           dep.notify() // 通知所有订阅者
         }
    })
}
// 实现的消息订阅器[数组形式]
function Dep() {
   this.subs = []
}
Dep.prototype = {
   addSub: function(sub) {
      this.subs.push(sub)
   },
   notify: function() {
      this.subs.forEach(sub => sub.update())
   }
}

在上述的思路整理中,我们已明确订阅者应该就是 Watcher,而 var dep = new Dep()是在defineReactive方法内部定义的,故想通过dep添加订阅者,就必须在闭包内进行操作,所有我们可以在在getter里面进行修改:

function defineReactive(data, key, val) {
         ... // 省略
         get:function() {
             // 需要在闭包中添加watcher,故通过Dep定义一个全局target属性,暂存watcher,添加完再移除
             Dep.target && dep.addSub(Dep.target)
             return val
         }
         ... // 省略
    })
}

// Watcher.js
Watcher.prototype = {
    get: function(key) {
       Dep.target = this;
       this.value = data[key]  // 这里会触发属性的gtter,从而添加订阅者
    }
}

目前已实现了一个Observer ,已具备监听数据和数据变化通知订阅者的功能;
2:实现Compile
compile 主要做的事情就是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加数据的订阅者,一旦数据有变动,收到通知,更新视图
在这里插入图片描述
实现Compile 的代码自己还看不懂…待后期补充
主要思路:

  1. 因为遍历解析的过程有多次操作dom节点,为提高性能和效率,会先将跟节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加回原来的真实dom节点中
  2. compileElement方法将遍历所有节点及其子节点,进行扫描解析编译,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定

3:实现Watcher

Watcher 订阅者作为Observer 和 Compile 之间的通信桥梁,主要做的事情就是:

  1. 在自身实例化时往属性订阅器(dep)里添加自己
  2. 自身必须有个 update() 方法
  3. 待属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发Compile 中绑定的回调
function Watcher(vm, exp, cb) {
   this.cb = cb
   this.vm = vm;
   this.exp = exp;
   // 此处为了触发属性的getter,从而在dep[消息订阅器数组]添加自己,结合Observer更易理解
   this.value = this.get()
}

Watcher.prototype = {
  update: function() {
      this.run()  // 属性值变化收到通知
  }
  run: function() {
     var value = this.get(); // 取到最新值
     var oldVal = this.value;
     if(value !== oldVal) {
         this.value = value
         // 值有更新时调用Compile中绑定的回调函数,更新视图
         this.cb.call(this.vm, value, oldVal)
     }
  },
  get:function() {
     Dep.target = this; // 将当前订阅者指向自己
     var value = this.vm[exp]; // 触发 getter,添加自己到属性订阅器中
     Dep.target = null // 添加完毕,重置
     return value
  }
}
// 这里再次列出Observer和Dep,方便理解
Object.defineProperty(data, key, {
    get: function() {
        // 由于需要在闭包内添加watcher,所以可以在Dep定义一个全局target属性,暂存watcher, 添加完移除
        Dep.target && dep.addSub(Dep.target);
        return val;
    }
    // ... 省略
});
// set中值有更新->消息订阅器notify()->订阅者update()->执行compile中回调函数
Dep.prototype = {
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update(); // 调用订阅者的update方法,通知变化
        });
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[2\]:首先再vue初始化的时候,就对data数据进行了劫持监听,其中就是监听器 Observe,用来监听所有属性。 若有属性发生变化就需要告诉订阅者Watcher看是否需要更新。 因为订阅者Watcher有多个,所以需要一个消息订阅器 Dep 来专门收集这些订阅者,在监听器Observe和订阅者Watcher之间进行统一管理。 还需要有一个指令解析器 Compile ,对每个节点元素进行扫描解析,将相关的指令(如 v-model,v-on …)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应函数 当订阅者Watcher接收到相应属性的变化通知,就会执行对应的更新函数,从而去更新视图。 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者实现一个订阅者Watcher,每个Watcher都绑定一个更新函数,Watcher可以收到属性的变化通知并执行相应的函数,从而更新视图实现一个消息订阅器 Dep ,主要收集订阅者,当 Observe监听到发生变化,就通知Dep 再去通知Watcher去触发更新。实现一个解析器Compile,可以扫描和解析每个节点的相关指令,若节点存在指令,则Compile初始化这类节点的模板数据(使其显示在视图上),以及初始化相应的订阅者。\[2\] 问题:vue双向绑定原理 回答:vue双向绑定原理是通过数据劫持结合发布者-订阅者模的方来实现的。在vue初始化时,会对data数据进行劫持监听,使用监听器Observe来监听所有属性。当属性发生变化时,会通知订阅者Watcher来判断是否需要更新。为了管理多个订阅者Watcher,使用消息订阅器Dep来收集这些订阅者。同时,还有一个指令解析器Compile,用于扫描和解析每个节点的相关指令,并将其初始化为订阅者Watcher,以及绑定相应的模板数据或函数。当订阅者Watcher接收到属性变化的通知时,会执行相应的更新函数,从而更新视图。这样就实现了数据和视图的双向绑定,数据变化时视图跟着变化,视图变化时数据也随之改变。\[2\]\[3\] #### 引用[.reference_title] - *1* [Vue基础知识总结 6:vue双向绑定原理](https://blog.csdn.net/guorui_java/article/details/120557340)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Vue双向绑定原理](https://blog.csdn.net/weixin_52092151/article/details/119810514)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值