Vue.js - 模拟Vue.js响应式原理(2/2)

2023-05-16

项目仓库:https://gitee.com/big-right-right/vue-responsive/tree/master/L8

一. 类的说明

模块说明

Vue类 :保存传入的选项数据,把选项data中的成员注入vue实例(可用this.msg访问),调用observer对象监听数据变化,调用compiler对象解析指令和差值表达式。

Observer类:把data选项中属性转换成响应式数据(若data某个属性是对象,该对象的属性也具有响应式),数据变化发送通知。

Compiler类:负责编译模板并解析指令和差值表达式,负责页面视图首次渲染,以及数据变化后的重新渲染。

Dep类:观察者模式中的发布者,用于记录所有观察者,当收到数据变化时发送通知给所有观察者。

Watcher类:观察者模式中的观察者,当收到Dep发送的通知时,执行自己的update方法来更新视图。

 

二. 项目代码

1. index.html

<html>
<head>
</head>
<body>
  <h1>Vue响应式原理-分析</h1>

  <div id="app">
    <h2>差值表达式</h2>
    <h3>{{ msg }}</h3>
    <h3>{{ count }}</h3>

    <h2>v-text</h2>
    <div v-text="msg"></div>

    <h2>v-model</h2>
    <input type="text" v-model="msg">
    <input type="text" v-model="count">
  </div>

  <script src="./dep.js"></script>
  <script src="./watcher.js"></script>
  <script src="./compiler.js"></script>
  <script src='./observer.js'></script>
  <script src='./vue.js'></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello vue',
        count: 22,
        person: {name: 'zhangsan'}
      }
    })
    console.log(vm)
    // vm.person = { abc: 'abc' }
    // console.log(vm)
  </script>
</body>
</html>

2. vue.js

/* 实现最小版本的 Vue */
/*
Vue
 $optinos
 $el
 $data
 _proxyData()
*/
class Vue {
  constructor(options) {
    // 1.通过属性保存选项的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2.把data中的成员转换为getter setter, 注入Vue实例
    this._proxyData(this.$data)
    // 3.调用observer对象,监听数据变化
    new Observer(this.$data)
    // 4.调用compiler对象,解析指令和差值表达式
    new Compiler(this)
  }
  
  _proxyData(data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key]
        },
        set(newValue) {
          if(newValue === data[key]) {
            return
          }
          data[key] = newValue
        }
      })
    })
  }
}

3. observer.js

/*
Observer
 把data选项中的属性转换成响应式数据
 若data中的某个属性也是对象,把该属性转换成响应式数据
 数据变化发送通知
*/
class Observer {
  constructor(data) {
    this.walk(data)
  }
  walk(data) {
    // 1. 判断data是否是对象
    if(!data || typeof data !== 'object') {
      return
    }
    // 2. 遍历data对象的所有属性
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }
  defineReactive(obj, key, val) {
    const that = this
    // 负责收集依赖 并发送通知
    let dep = new Dep()

    // 如果data对象的某个属性也是对象 则该子对象的每个属性也需要响应式
    this.walk(obj[key])
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val
      },
      set(newVal) {
        if(newVal === val) {
          return
        }
        val = newVal
        // 如果data对象的某个属性被重新赋值 则该属性需要重新设置响应式
        that.walk(newVal)
        // 发送通知
        dep.notify()
      }
    }) 
  }
}

4. compiler.js

/*
Compiler
 负责编译模板,解析指令/差值表达式
 负责页面的首次渲染
 当数据变化后重新渲染视图
*/
class Compiler {
  constructor(vm) {
    this.el = vm.$el
    this.vm = vm
    this.compile(this.el)
  }
  // 编译模板 处理文本节点和元素节点
  compile(el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 处理文本节点
      if(this.isTextNode(node)) {
        this.compileText(node)
      } else if(this.isElementNode(node)) {
        // 处理元素节点
        this.compileElement(node)
      }
      // 判断node节点,是否有子节点,如果有子节点,要递归调用compile
      if(node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }
  // 编译元素节点 处理指令
  compileElement(node) {
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name
      if(this.isDirective(attrName)) { // v-text v-model
        attrName = attrName.substr(2)  // text model
        let key = attr.value           // msg count
        this.update(node, key, attrName)
      }
    })
  }
  update(node, key, attrName) {
    let updateFn = this[attrName + 'Updater']
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }
  // 处理 v-text 指令
  textUpdater (node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // 处理 v-model 指令
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
  // 编译文本节点,处理差值表达式
  compileText(node) {
    let reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if(reg.test(value)) {
      let key = RegExp.$1.trim()
      console.log('key: ', key)
      node.textContent = value.replace(reg, this.vm[key])
    
      // 创建watcher对象,当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 判断元素是否是指令
  isDirective(attrName) {
    return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode(node) {
    return node.nodeType === 3
  }
  // 判断节点是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }
}

5. dep.js

class Dep {
  constructor () {
    // 存储所有的观察者
    this.subs = []
  }
  // 添加观察者
  addSub (sub) {
    if(sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 发起通知
  notify () {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

6. watcher.js

class Watcher {
  constructor (vm, key, cb) {
    this.vm = vm
    // data中的属性名称
    this.key = key
    // 回调函数负责更新视图
    this.cb = cb

    // 把watcher对象记录到Dep类的静态属性target
    Dep.target = this
    // 触发get方法,在get方法中会调用addSub
    this.oldValue = vm[key]
    Dep.target = null
  }
  // 当数据发生变化时更新视图
  update () {
    let newValue = this.vm[this.key]
    if(this.oldValue === newValue) {
      return
    }
    this.cb(newValue)
  }
}

本文 完。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue.js - 模拟Vue.js响应式原理(2/2) 的相关文章

  • 【图像识别与处理】ros下使用realsense d435获取点云

    realsense驱动安装见上篇博文 1 通过源码安装intel RealSense ROS 1 创建catkin工作空间 mkdir p catkin ws src cd catkin ws src 2 将下载的源码复制到 catkin
  • 【图像识别与处理】构建用于垃圾分类的图像分类器

    1 构建图像分类器 训练一个卷积神经网络 xff0c 用fastai库 xff08 建在PyTorch上 xff09 将图像分类为纸板 xff0c 玻璃 xff0c 金属 xff0c 纸张 xff0c 塑料或垃圾 使用了由Gary Thun
  • kitti数据集各个榜单介绍

    kitti数据集网站 下面我们分别介绍下KITTI的几项benchmark Stereo Stereo Evaluation xff08 立体评估 xff09 基于图像的立体视觉和3维重建 xff0c 从一个图像中恢复结构本质上是模糊的 x
  • 解决ubuntu20.04虚拟机无法上网的问题

    64 linux虚拟机无法正常上网 前言 刚建立好的linux虚拟机使用NAT方式可以连接外网 xff0c 系统重启几次 xff0c 系统无法上网 xff0c 这是什么问题导致的呢 xff1f 提示 xff1a 以下是本篇文章正文内容 xf
  • [Linux] 使用vim保存文件时报E45错误

    今天在使用vim为Linux系统设置静态IP时 xff0c 报了E45错误 xff1a 环境说明 系统 xff1a Ubuntu18 04 操作步骤 1 打开到静态IP配置文件 打开到netplan目录 cd etc netplan amp
  • 【C++】类和对象-继承

    目录 一 继承基本方式 1 公 共 继 承 2 保 护 继 承 3 私 有 继 承 二 继承中的对象模型 三 继承中的构造和析构顺序 四 继承中同名成员处理方式 1 成员变量的处理方式 2 成员函数的处理方式 五 继承同名静态成员处理方式
  • Pytorch param.grad.data. 出现 AttributeError: ‘NoneType‘ object has no attribute ‘data‘

    程序中有需要优化的参数未参与前向传播
  • 大白话谈谈ChatGPT:多点人工,多点智能

    对于NLP领域 xff0c 本人也是门外汉 xff0c 就是最近了看到的博文 xff0c 记录自己的一些体会 ChatGPT简介 ChatGPT的全称是 34 Conversational Generative Pre training T
  • GO如何查看变量大小和数据类型

    如何查看一个变量的大小和数据类型 如何查看一个变量的大小和数据类型 paceage main import 34 fmt 34 34 unsafe 34 var n2 int64 61 10 fmt Printf 34 n2的类型 T n2
  • GO语言百分号参数

    常用 参数 v 值的默认格式 T 值得类型的GO语法表示 t 单词true或者false b 表示为二进制 c 该值对应的unicode码值 d 表示十进制 o 表示八进制 f 有小数部分但无指数部分 q 双引号输出
  • java第八节-重复执行

    import java util Scanner public class hello public static void main String args for System out println 34 hello 34 impor
  • java基础第九节-跳转控制语句-数组

    continue用在循环中 xff0c 基于条件控制 xff0c 跳过某次循环体内容的执行 xff0c 继续下一次的执行 break用在循环中 xff0c 基于条件控制 xff0c 终止循环体内容的执行 xff0c 结束当前的整个循环 数组
  • JAVA基础-基本类型转换

    int 和string的相互转换 1 int转换String public static String valuesOf int i 返回int参数的字符串表示形式 xff0c 该方法是String类的方法 1 String转换int pu
  • ubuntu系统-查看系统版本信息

    cat etc issue
  • Ubuntu查看cpu使用情况

    top命令查看cpu等信息 id是 xff1a 空闲 CPU 占用的 CPU 百分比
  • Ubuntu系统查看内存信息

    free命令查看内存信息 h 选项会在数字后面加上适于可读的单位 free h total xff1a 总物理内存大小 used xff1a 内存使用量 free xff1a 剩余可用内存
  • 嘉立创打样的阻抗匹配

    一 适用条件 最好使用4层板以上 xff0c 2层做匹配没啥意义 xff0c 套用大佬的话 主要是中间层和表层的距离近 xff0c 表层和中间层的玻璃纤维厚度是0 2mm xff0c 双层板最少是0 6mm xff0c 这里的差距很大 xf
  • echo 命令总结

    echo命令的功能是在显示器上显示一段文字 xff0c 一般起到一个提示的作用 此外 xff0c 也可以直接在文件中写入要写的内容 也可以用于脚本编程时显示某一个变量的值 xff0c 或者直接输出指定的字符串 echo命令的语法是 xff1
  • Android音频子系统(十三)------audio音频测试工具

    你好 xff01 这里是风筝的博客 xff0c 欢迎和我一起交流 测试音频延时的话 xff0c 一般使用WALT来测试是最为准确的 xff0c 他是借助了外部硬件来捕获音频信号 xff0c 某宝上有卖 xff1a 就是有丢丢小贵 xff0c
  • 一位北邮信通硕士的求职历程,看看 或许有帮助

    序 xff1a 写在前面的话 这篇文章的适用对象为 xff1a 非技术类方向的同学 xff0c 如果你是技术大牛 xff0c 你可以跳过这篇文章了 如果你觉得自己不喜欢技术或者技术不适合你 xff0c 此文或许会给你些有用的东西 简单介绍一

随机推荐

  • [转]STM32 串口传输处理方式 FreeRTOS+队列+DMA+IDLE (二)

    紧接着上一篇文章 xff0c 如何合理处理多个串口接收大量数据 此种方法 xff0c 很厉害 xff0c 很NB xff0c 首先 xff0c 利用DMA 可节省大量CUP资源 其次 xff0c 利用IDLE空闲中断来接收位置个数的数据 最
  • [转]FreeRTOS消息队列、信号量、事件标志组、任务通知

    功能及区别列表 消息队列 xff08 需要传递消息时使用 xff09 在任务与任务间 中断和任务间传递信息 xff0c 可以数据传输 事件标志组 xff08 多个事件同步 xff0c 不需要传递消息时使用 xff09 实现任务与任务间 中断
  • ubuntu 终端打不开解决办法

    由于ubuntu自带的是python3 5 在新安装了python3 6以后 xff0c 开机突然发现无论是点击图标还是使用快捷键终端都无法打开 xff0c 解决办法如下 xff1a xff11 xff0e 按Ctrl 43 Alt 43
  • Jack server already installed in "/***/.jack-server" 异常

    xff08 1 xff09 在新增新用户后 xff0c 进行android编译 xff0c 出现如下异常 xff1a Ensure Jack server is installed and started FAILED bin bash c
  • gstreamer移植qnx(二):交叉编译glib

    一 简介 这里以glib的2 63 0版本 xff0c QNX系统的版本是 xff1a 6 6 这里是为了编译gstreamer的依赖库 xff0c 也就是说最终目标 xff0c 是将gstreamer移植到QNX6 6系统上 我选择的是g
  • repo安装与简单使用

    一 概述 当一个大的项目需要拆分成很多的子项目 xff0c 或者说一个软件系统拆分成多个子系统 每一个子项目或者子系统都对应一个git repository 这种需求在实际项目当中是很常见的 xff0c 有的可能就直接写一个shell脚本来
  • 通过qemu-img命令将raw image转换成VMware虚拟硬盘vmdk

    为了在VMware中跑QNX系统 xff0c 我需要想办法将编译BSP生成的img文件固化到VMware的虚拟硬盘中去 xff0c 之前一直找不到方法 xff0c 到渐渐的只能用很笨的方法几次中专 将生成的img文件通过win32DiskI
  • WSL2 Ubuntu安装Qt(包括QtCreator)

    最近因为需要在Linux下使用qtcreator做一些界面开发的预研和学习 xff0c 主要是因为要交叉编译Qt 但又不想再使用虚拟机了 xff0c 真的太消耗内存了 于是就想着直接使用Windows10 下面的WSL2 怎么安装WSL2这
  • 架构师成长之路工具篇(1):markdown撰写文档

    今天笔者想说的工具就是markdown xff0c 正所谓工欲善其事必先利其器 xff0c 选择高效的工具自然能提升工作效率 笔者使用的markdown工具是 xff1a typora word太重 xff0c 太复杂 xff0c 在写文档
  • Artifact xxxx:Web exploded: Error during artifact deployment. See server log........

    从Git上拉取了一个新项目到idea xff0c 结果一运行就报错 xff0c 错误下图 看大家的解决方法基本都是重新部署Tomcat Maven或者项目 xff0c 还有什么jar包冲突要删除的 xff0c 齐齐试了一遍 xff0c 并没
  • 如何优雅的退出qemu虚拟环境

    在console环境下 xff0c 先 按 ctrl 43 a xff0c 释放之后再按 x 键 既可terminate qemu 注 xff1a 1 a 和 x 均为小写 2 必须先释放ctrl 43 a 之后 再按x键
  • xmake经验总结1:解决c++ future/promise抛出std::system_error的问题

    1 背景 1 1 场景 编译器 xff1a gcc 9 4 运行系统 xff1a Ubuntu 20 04 4 LTS xmake v2 6 7 场景 xff1a 其大致场景是使用c 43 43 的future promise功能 xff0
  • 神经网络实现手写数字识别(MNIST)

    一 缘起 原本想沿着 传统递归算法实现迷宫游戏 gt 遗传算法实现迷宫游戏 gt 神经网络实现迷宫游戏的思路 xff0c 在本篇当中也写如何使用神经网络实现迷宫的 xff0c 但是研究了一下 xff0c 感觉有些麻烦不太好弄 xff0c 所
  • 从高考到吃“软”饭

    上大学之前 xff0c 我是一个连本科和专科都分不清的农村小娃 那时的我天真的以为 xff0c 专科就是教授比较专业的知识 xff0c 而本科就是学得比较广而不深 上大学之后 xff0c 我算是开眼界了 xff0c 各种社团真是百花齐放 对
  • 解决visio对象在word中显示不全的问题

    作为一个软件工程师 xff0c 编写技术文档是常有的事情 xff0c 使用visio绘制各种图形 如 xff0c 流程图 xff0c 结构图 xff0c 框架图 xff0c 状态图等等 也是再正常不过的事情 如果我们在word中撰写文档时
  • git submodule使用以及注意事项

    一 背景 在平时的软件开发过程中常常会有这样的场景 xff0c 自己负责的某个模块会依赖其他模块或者第三方的library 这时你自己的模块是一个独立的代码仓库 xff0c 你想要实现这样一种功能 xff0c 当你从你的模块的代码仓库里把代
  • Webpack5 - 基本使用

    一 webpack有何作用 webpack是一个Javascript应用程序的模块打包器 它可以递归地构建一个应用程序的模块依赖关系图 xff0c 然后将所有模块打包在一起 为什么需要模块打包器 xff1a 现在的应用程序模块文件很多 xf
  • Vue.js - VueRouter的Hash与History模式 / 手写VueRouter

    一 Hash与History模式 Hash模式History模式url地址外观http localhost 8081 abouthttp localhost 8080 about原理基于锚点 xff0c 监听锚点变化时触发的onhashch
  • Vue.js - Vue.js响应式原理(1/2)

    一 数据驱动 数据响应式 xff1a 数据改变 xff0c 则视图改变 xff0c 避免频繁的Dom操作 xff0c 提高运行效率 双向绑定 xff1a 数据改变 xff0c 则视图改变 xff1b 视图改变 xff0c 则数据也随之改变
  • Vue.js - 模拟Vue.js响应式原理(2/2)

    项目仓库 xff1a https gitee com big right right vue responsive tree master L8 一 类的说明 Vue类 xff1a 保存传入的选项数据 xff0c 把选项data中的成员注入