IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> vue双向数据绑定原理详解(附源码) -> 正文阅读

[JavaScript知识库]vue双向数据绑定原理详解(附源码)

vue的响应式原理

1.vue内部如何监听数据的改变的?

Object.defineProperty 监听对象属性的改变

Object.keys(obj).forEach(key=>
{
    let value = obj[key]
  obj.defineProperty(obj,key,{
    //当value值被修改的时候,执行set()方法
    set(newValue) {
      //可以监听到key的改变了,谁用?告诉谁
    value=newValue
    },
    //当读取value值的时候,执行get()方法
    get() {
     // 可以获取到key对应的值了
      return value
    }
  })
})

在vue实例中的data属性中保存的数据obj,传递给Observer观察者对象,在Observer中对data中所有的数据进行监听,首先通过Object.keys获取obj的所有key组成的数组,为每一个key创建一个dep对象, 再通过forEach遍历这些key,找到对应的value,再通过Object.defineProperty()监听value的改变

Object.defineProperty() - JavaScript | MDNObject.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

2.当数据发生改变,vue是如何通知哪些人需要改变的

解析HTML代码获取哪里在用这些数据(mastach{{}},v-model),每解析出一个地方使用,就创建一个Watcher实例并添加到对应的dep实例的subscribes数组中,subscribes数组中就保存了所有该数据的订阅者对象,在订阅者类中实现update()的方法,根据data中最新的数据更新视图。

如何实现?

发布者订阅者模式

class Dep {
  constructor(){
    //用来保存哪里使用了这个变量,记录所有订阅者
    this.subscribes = []
  }
  addSub(watch){
    this.subscribes.push(watch)
  }
}
class Watcher{
  constructor(node,name,vm){
    this.node=node
    this.name=name
    this.vm=vm
    update()
  }
  update(){
   //更新界面
  this.node.nodeValue = this.vm[name] //触发name的get()方法,
  }
}

?当compiler解析到{{}},就new一个Watch的实例w1,w1保存了该node节点和使用变量name,并调用update()方法读取data[name]去赋值给node节点的nodeValue,当读取data[name]的时候,触发get()方法,调用dep.addSub()将w1添加到subscribes数组中,通过这种方式,所有使用数据的地方都会对应一个Watch实例,并且添加到subscribes数组中,当数据发送改变的时候,dep对象会调用notify()方法,在notify()方法中,会遍历subscribes数组,调用每一个元素的update方法,是使用到这个数据的所有地方都更新界面,达到响应式的效果

简易版源码:?

class Vue {
    constructor(options){
     // 1.保存数据
      this.$options = options
      this.$data = options.data
      this.$el = options.el

      // 2.将data通过Observer添加到响应式系统中
      new Observer(this.$data)

      // 3.代理this.$data的数据,可以在vue实例上直接添加数据,也可以通过vue实例直接读取数据
      Object.keys(options.data).forEach(key=>{
        this._proxy(key)
      })

      //4.处理el
      new Compiler(this.$el,this)
    }
    //数据代理 读取vue实例数据的时候,实际上是从data中读取,给vue实例赋值的时候实际上给data赋值
    _proxy(key){
      Object.defineProperty(this,key,{
        configurable:true,
        enumerable:true,
        set(value){
          this.$data[key]=value
        },
        get(){
          return this.$data[key]
        }
      })
    }
  }

  class Observer {
    constructor(data){
      this.data = data
      Object.keys(data).forEach(key=>{
        defineReactive(data,key,data[key])
      })
    }
  }

  function defineReactive(data,key,val) {
    const dep = new Dep()
    Object.defineProperty(data,key, {
      configurable: true,
      enumerable: true,
      // 当数据被修改时
      set(newValue) {
        // val保存的是原来的数据 newValue是新的数据
        if (newValue === val) {
          return
        }
        val = newValue
        dep.notify()
      },
      // 当数据被读取时
      get() {
        if(Dep.target){
          dep.addSub(Dep.target)
        }
        return val
      }
    })
  }
//发布者类
  class Dep {
    constructor() {
     this.subscribes =[]
    }
    addSub(watcher){
      this.subscribes.push(watcher)
    }
    notify(){
      this.subscribes.forEach(item=>{
        item.update()
      })
    }
  }
  //订阅者类
  class Watcher {
    constructor(node,name,vm) {
      this.node = node
      this.name = name
      this.vm = vm
      Dep.target = this
      this.update()
      // 这里必须要清空是因为:当数据修改之后,会通过dep.notify重新调用watcher的update,对数据进行读取,会再次调用数据的get()方法
      // 为了避免重复添加watcher,所以调用完update之后就把Dep.target清空
      Dep.target = null
    }
    update(){
      //数据改变之后,更新视图
      this.node.nodeValue = this.vm[this.name] //数据劫持
      // 这里读取了vue实例的data中的数据,会触发对应属性的get()方法
    }
  }

  const reg = /\{\{(.*)\}\}/ //匹配{{}}
  class Compiler {
    constructor(el,vm) {
      this.el = document.querySelector(el)
      this.vm = vm
      this.frag = this._createFragment()
      this.el.appendChild(this.frag)
    }
    _createFragment(){
      const frag = document.createDocumentFragment()
      let child;
      while (child = this.el.firstChild){
        this._compile(child) //child node节点
        frag.appendChild(child)
      }
      return frag
    }
    _compile(node){
      // 标签节点
      if(node.nodeType === 1 ){
        const attrs = node.attributes
        if(attrs.hasOwnProperty('v-model')){
          const name = attr['v-model'].nodeValue
          node.addEventListener('input',e=>{
            this.vm[name] = e.target.value
          })
        }
        // 文本节点
      }
      if (node.type==3){
        console.log((reg.test(node.nodeValue)))
        if(reg.test(node.nodeValue)){
          const name = RegExp.$1.trim() ///\{\{(.*)\}\} $s1是(./)中的内容
          console.log(name)
          //解析HTML 解析到{{}}
          // node:包含{{}}的node节点
          // name:{{name}}data中的变量名
          // vm: vue实例
          new Watcher(node,name,this.vm)
        }
      }
    }
  }

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:53:25  更:2022-03-06 12:56:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:25:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码