【vue】手写vue2的双向绑定原理

14 篇文章 0 订阅
4 篇文章 0 订阅
本文深入探讨了Vue2的双向数据绑定实现,通过实例代码展示了如何使用Object.defineProperty、数据劫持Observe、模板编译Compile以及Watcher类来实现这一核心特性。详细解释了数据响应化、文本和输入元素的绑定以及依赖收集和更新的过程。
摘要由CSDN通过智能技术生成

手写vue2 的双向绑定原理

知识点概要

  • Object.definePorperty
  • Object.keys
  • 数组的reduce 方法
  • es6 clss 类

直接上代码

html部分

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <h1>{{name}}</h1>
    <h2>{{info.userName}}</h2>
    <h2>{{info.password}}</h2>
    <input type="text" name="" id="" v-model="info.userName">
    <input type="password" name="" id="" v-model="info.password">

  </div>
  <script src="./my_vue.js"></script>
  <script>
    const vm = new myVue({
      el:'#app',
      data:{
        name:'晤西迪西',
        info:{
          userName:'justkang',
          password:'123456'
        }
      }
    })

    console.log(vm)
  </script>
</body>
</html>

js部分

// 实现简单版的vue 只有数据劫持双向绑定功能

// 定义一个myVue构造函数
class myVue {
  constructor(options) {
    this.$data = options.data
    // 调用数据劫持的方法 把this.$data 传入进去 把data中的 数据添加getter和setter
    Observe(this.$data)

    // 属性代理 把this.$data 的属性代理到this上
    Object.keys(this.$data).forEach(key=>{
      Object.defineProperty(this,key,{
        enumerable:true,
        configurable:true,
        get(){
          return this.$data[key]
        },
        set(newValue) {
          this.$data[key] = newValue
        }
      })
    })

    // 调用模版编译
    Compile(options.el,this)
  }
}

// 数据劫持方法
function Observe (data) {
  // 递归的结束条件
  if(!data || typeof data != 'object')  return
  const dep = new Dep()

  // 拿到data对象的每一个key
  Object.keys(data).forEach(key=>{
    // 当前key的值
    let value = data[key]
    // 把vuale这个值在进行递归 把里面的key也添加getter和setter
    Observe (value)
    // 为每个key添加getter和setter
    Object.defineProperty(data,key,{
      enumerable:true,
      configurable:true,
      get(){
        console.log(`有人获取了${key}的值`)
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      set(newValue){
        value = newValue
        // 为新赋值的value 添加getter和setter
        Observe(value)
        dep.notify()
      }
    })
  })
}

// 模版编译方法
function Compile(el,vm) {
  // 获取el对应的dom区域
  vm.$el = document.querySelector(el)
  // 创建文档碎片 提高dom操作性能
  const fragment = document.createDocumentFragment()
  while(childNode = vm.$el.firstChild) {
    fragment.appendChild(childNode)
  }

  // 模版编译
  Replace(fragment)

  vm.$el.appendChild(fragment)


  // Replace 函数
  function Replace(node) {
    const regMustache = /\{\{\s*(\S+)\s*\}\}/
    // 如果节点是文本
    if(node.nodeType == 3) {
      // 获取文本
      const text = node.textContent
      // 对文本内容进行正则匹配和提取
      const execResult = regMustache.exec(text)
      if(execResult) {
        const value = execResult[1].split('.').reduce((newData,key)=>newData[key],vm)
        // console.log(value)
        node.textContent = text.replace(regMustache,value)
        new Watcher(vm,execResult[1],(newValue)=>{
          node.textContent = text.replace(regMustache,newValue)
        })
      }
      return 
    }
    // 如果节点是input 
    if(node.nodeType == 1 && node.tagName == 'INPUT') {
      // 获取当前元素的所有属性
      const attrs = Array.from(node.attributes)
      const findResult = attrs.find((x)=>x.name === 'v-model')
      if(findResult) {
        const expStr = findResult.value
        const value = expStr.split('.').reduce((data,key)=>data[key],vm)
        node.value = value
        new Watcher(vm,expStr,(newValue)=>{
          node.value = newValue
        })
        node.addEventListener('input',(e)=>{
          const keyArr = expStr.split('.')
          const obj = keyArr.slice(0,keyArr.length-1).reduce((data,k)=>data[k],vm)
          console.log(obj[keyArr[keyArr.length-1]])
          obj[keyArr[keyArr.length-1]] = e.target.value
          
        })
      }
      
    }
    node.childNodes.forEach(child=>{
      Replace(child)
    })
  }
}

// 定义一个Dep 类 / 收集watcher订阅者的类
class Dep {
  constructor(){
    this.subs = []
  }
  // 向subs添加wathcer方法
  addSub(watcher) {
    this.subs.push(watcher)
  }
  // 负责通知每个watcher方法
  notify(){
    this.subs.forEach(item=>item.updata())
  }
}

// 定义一个watcher类
class Watcher {
  // cb 回调函数记录者watcher如何更新自己内容
  // 但是只知道更新自己不行,需要知道更新的数据,因此还要把vm传进来
  // 除此之外 还要是知道在vm中哪个是我需要的数据 key
  constructor(vm,key,cb) {
    this.vm = vm
    this.key = key
    this.cb = cb
    Dep.target = this
    key.split('.').reduce((data,key)=>data[key],vm)
    Dep.target = null
  }

  // updata方法 让发布者能通知我们进行更新
  updata() {
    const value = this.key.split('.').reduce((data,key)=>data[key],vm)
    this.cb(value)
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值