vue双向绑定原理

vue双向绑定原理

object.defineProperty() —vue2

这个API是实现双向绑定的核心,通过它对data的属性进行重写,主要是重新数据的get、set方法。
调用get方法时,绑定对应的依赖(dep)和监听器(watcher);更新数据时,调用set方式时,会通知依赖中相关的监听进行值修改。

不足之处:

  • 不能检测到对象属性的增加和删除,即新增属性不是响应式的
    解决方法: vm.$set

  • 不能通过索引操作数组

proxy —vue3

proxy可以理解成在目标对象之前架设了一层"拦截",外界对该对象的访问都需要通过这层拦截,所以就可以利用这层拦截对外界访问进行过滤和改写。

proxy的优点:

  • 可以用来处理一些非核心的逻辑,从而让对象关注于核心逻辑,达到关注点分离,降低对象复杂度等目的

  • 可以劫持整个对象,并返回一个新对象(弥补了vue2中的不足1)

  • proxy有13中劫持操作

  • get、set设置时不能设置writable和value,交叉设置或同时存在,会报错

  • 通过defineProperty设置的属性,默认不能删除,不能遍历,当然你可以通过设置进行更改

  • get、set是函数,可以在里面进行各种操作

实现思路的关键步骤:

  1. 实现数据监听器observer,用Object.defineProperty()重写数据的get、set,值更新就在set中通知订阅者更新数据

  2. 实现模板编译Compile,深度遍历dom树,对每个元素节点的指令模板进行替换数据以及订阅数据。

  3. 实现Watch用于连接Observer和Compile,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。

  4. mvvm入口函数,整合以上三者

流程图

在这里插入图片描述

以下为手动实现简易vue的代码:

<body>
  <div id="app">
    {{msg}}
    <h1>{{msg}}</h1>
    <div v-html="htmltxt" class='abc' @click='fn'></div>
    <div v-text='htmltxt' class='123'></div>
    
  </div>
  <script src="./qiyuVue.js"></script>
  <script>
    let app = new Vue({
      el: "#app",
      data: {
        msg: "offer",
        htmltxt: '<img src="https://www.baidu.com/img/flexible/logo/pc/result.png">'
      },
      methods: {
        fn(e){
          console.log(e)
        }
      }
    })
  </script>
</body>

qiyuvue.js文件

class Vue{
  constructor(options){
    let vm = this
    this.$options = options
    this.$data = this.$options.data
    // 将data中的属性通过代理绑定到实例上
    Object.keys(this.$data).forEach((key)=>{
      this._proxy(key)
    })
    // 劫持data中的属性,当值发生改变的时候,会重新更新依赖的节点
    new Observer(this.$data, vm)
    this.$compile = new Compile(options.el, vm)
  }

  _proxy(key){
    let vm = this
    Object.defineProperty(vm, key, {
      enumerable: true,
      get: ()=>vm.$data[key],
      set: (value)=> vm.$data[key] = value
    })
  }
}

// 创建编译类
class Compile{
  constructor(el, vm){
    this.$vm = vm;
    // 根据选择器查找出需要解析编译的元素
    this.$el = document.querySelector(el);
    if(this.$el){
      // 1、将元素节点取出
      this.$fragment = this.createFragmentObj(this.$el)
      console.log(this.$fragment)
      // 2、生成相应的dom对象
      this.createVdom(this.$fragment)
      // 3、将生成的dom插入到页面中
      this.$el.appendChild(this.$fragment)
    }
  }

  createFragmentObj(el){
    // 创建虚拟节点对象,节点对象包含节点的属性和方法
    let fragment = document.createDocumentFragment();
    let child;
    while(child = el.firstChild){
      fragment.appendChild(child)
    }
    console.log(fragment)
    return fragment;
  }

  createVdom(fragment){
    let childNodes = fragment.childNodes;
    Array.prototype.slice.call(childNodes).forEach(childNode =>{
      // 1、取出文本节点
      let text = childNode.textContent;
      // 2、匹配大括号
      let reg = /\{\{(.*)\}\}/;
      // 3、根据节点类型进行编译,3代表节点类型是文本,1代表节点类型是元素
      if(childNode.nodeType === 3 && reg.test(text)){
        this.compileText(childNode, RegExp.$1)
      }else if(childNode.nodeType === 1 && !childNode.hasChildNodes()){
        // childNode.hasChildNodes()判断是否有子节点,没有的话编译其innerHrml,有的话递归生成vdom
        this.compileInnerHTML(childNode)
      }else if(childNode.nodeType === 1 && childNode.hasChildNodes()){
        this.createVdom(childNode)
      }
    })
  }

  compileText(node,temp){
    this.updateDomVal(node, temp, "textContent", this.$vm[temp]);
  }

  updateDomVal(node, exp, domType, domValue){
    // 此处创建监听器,数据发生改变的时候,就会触发监听器发生修改
    new Watcher(this.$vm, node, exp, (newVal, oldVal)=>{
      node[domType] = newVal;
    })
    node[domType] = domValue;
  }

  compileInnerHTML(node){
    Object.keys(node.attributes).forEach(index => {
      let nodeAttrName = node.attributes[index]['nodeName'];
      let exp = node.attributes[index]['nodeValue']
      let value;
      switch(nodeAttrName){
        case 'v-html':
          value = this.$vm[exp]
          this.updateDomVal(node, exp, 'innerHTML', value)
          break;
        case 'v-text':
          value = this.$vm[exp]
          this.updateDomVal(node, exp, 'textContent', value)
          break;
        case '@click':
          node.addEventListener('click', (event)=>{
            let eventMethod = this.$vm.$options.methods[exp]
            eventMethod.bind(this.$vm)(event)
          })
      }
    })
  }
  
}


// 创建依赖类, 捕获每一个监听点的变化
class Dep{
  constructor(){
    this.subList = []
  }
  // 建立依赖给Dep和watcher
  depend(){
    // Dep.target是监听watcher
    Dep.target.addDep(this)    
  }
  // 添加监听点watch到sublist
  addSub(sub){
    this.subList.push(sub)
  }
  // 通知所有的watcher值发生改变
  notify(newVal){
    this.subList.forEach(sub => {
      sub.update(newVal)
    })
  }
}
Dep.target = null;

// 创建监听类
let uid=0;
class Watcher{
  constructor(vm, node, exp, callback){
    this.uid = uid++;
    this.$vm = vm;
    // 每个watch监听的节点
    this.node = node;
    // 每个监听点关联的data的属性的表达式
    this.exp = exp;
    this.callback = callback;
    // 每个监听点的依赖列表
    this.depList = {};
    // 设置初始值
    this.value = this.getVal()
  }
  // 添加依赖
  addDep(dep){
    if(!this.depList.hasOwnProperty(dep.uid)){
      dep.addSub(this)
    }
  }

  update(newVal){
    this.callback.call(this.$vm, newVal, this.value)
  }

  getVal(){
    // 获取当前的watch指向dep的依赖,方便数据在劫持get函数里建立依赖关系
    Dep.target = this;
    // 获取值,触发get函数,获取节点的位置
    let val = this.$vm[this.exp]
    // 获取完之后对Dep.target进行复原
    Dep.target = null;
    return val;
  }
}

// 建立观察类
class Observer{
  constructor(data, vm){
    this.data = data;
    this.$vm = vm;
    this.walk();
  }
  walk(){
    Object.keys(this.data).forEach(key => {
      this.defineReactive(key, this.data[key])
    })
  }

  defineReactive(key, val){
    // 每个属性都要生成dep依赖对象
    let dep = new Dep();
    // 重新定义data属性,以便添加get和set方法
    Object.defineProperty(this.data, key, {
      enumerable: true,
      get: ()=>{
        if(Dep.target){
          dep.depend()
        }
        return val;
      },
      set: (newVal)=>{
        if(val !== newVal){
          dep.notify(newVal)
          val = newVal
        }
      }
    })
  }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue双向绑定原理是通过数据劫持和发布订阅模式相结合的方式来实现的。在Vue,当用户操作View时,ViewModel会感知到变化并通知Model进行相应的改变;反之,当Model发生改变时,ViewModel也能感知到变化并使View作出相应的更新。双向绑定的核心是使用了Object.defineProperty()方法来实现。 在Vue的初始化过程,会对data数据进行劫持监听,这个过程由监听器Observe来完成。监听器会监听所有属性,当属性发生变化时,会通知订阅者Watcher来判断是否需要更新。由于订阅者Watcher可能有多个,所以需要一个消息订阅器Dep来统一管理这些订阅者。同时,还需要一个指令解析器Compile,用来扫描和解析每个节点的相关指令,将其初始化为一个订阅者Watcher,并替换模板数据或绑定相应的函数。 当订阅者Watcher接收到属性的变化通知时,会执行对应的更新函数,从而更新视图。整个过程,监听器Observer负责劫持并监听所有属性,订阅者Watcher负责接收属性的变化通知并执行相应的函数,消息订阅器Dep负责收集订阅者并通知Watcher触发更新,指令解析器Compile负责扫描和解析节点的指令并初始化相应的订阅者。 综上所述,Vue双向绑定原理是通过数据劫持+发布订阅模式相结合的方式来实现的,通过监听器、订阅者、消息订阅器和指令解析器等组件的协作,实现了数据和视图之间的双向绑定。 #### 引用[.reference_title] - *1* *2* [vue双向绑定原理](https://blog.csdn.net/qq_41645323/article/details/123324680)[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^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *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^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值