update关联一个视图的时候特别慢_实现一个简单的Vue.js

原文转自 https://const_white.gitee.io/gitee-blog/blog/vue/mini-vue/

Vue响应式原理

图片引自 孟思行 - 图解 Vue 响应式原理

3f4f326b397b6949aa11420ebf3495b3.png

乞丐版 mini-vue

实现mini-vue之前,先看看官网的描述。在Vue官网,深入响应式原理中,是这样说明的:

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

1c674562671f01d5fc5fb5c01308e097.png

起步

技术原因,这里不做Virtual DOMrender部分,而选择直接操作DOM

简单来说,mini vue在创建Vue实例时

  1. Vue类负责把data中的属性注入到Vue实例,并调用Observer类和Compiler类。
  2. Observer类负责数据劫持,把每一个data转换成gettersetter。其核心原理是通过Object.defineProperty实现。
  3. Compiler类负责解析指令和插值表达式(更新视图的方法)。
  4. Dep类负责收集依赖、添加观察者模式。通知data对应的所有观察者Watcher来更新视图。在Observer类把每一个data转换成gettersetter时,会创建一个Dep实例,用来负责收集依赖并发送通知。在每一个data中在getter中收集依赖。在setter中通知依赖,既通知所有Watcher实例新视图。
  5. Watcher类负责数据更新后,使关联视图重新渲染。
b75cc9df49c56495826e2c9f2270c095.png

实现代码都添加了详细的注释,无毒无害,可放心查看

Vue类

class Vue {
  constructor(options) {
    // 1. 保存 options的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2. 为方便调用(vm.msg),把 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
        }
      })
    })
  }
}

Observer类

class Observer {
  constructor(data) {
    this.walk(data)
  }
  // 遍历 data($data)中的属性,把属性转换成响应式数据
  walk(data) {
    if (!data || typeof data !== 'object') {
      return
    }
    Object.keys(data).forEach((key) => {
      this.defineReactive(data, key, data[key])
    })
  }
  // 定义响应式数据
  defineReactive(obj, key, value) {
    const that = this
    // 负责收集依赖并发送通知
    let dep = new Dep()
    // 利用递归使深层(内部)属性转换成响应式数据
    this.walk(value)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      set(newValue) {
        if (value === newValue) {
          return
        }
        value = newValue
        // 如果新设置的值为对象,也转换成响应式数据
        that.walk(newValue)
        // 发送通知
        dep.notify()
      }
    })
  }
}

Compiler类

class Compiler {
  constructor(vm) {
    this.vm = vm
    this.el = vm.$el
    this.compiler(this.el)
  }

  // 编译模板,处理文本节点和元素节点
  compiler(el) {
    const childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 处理文本节点
      if (this.isTextNode(node)) {
        this.compilerText(node)
      } else if (this.isElementNode(node)) {
        // 处理元素节点
        this.compilerElement(node)
      }

      // 判断 node节点是否有子节点。如果有,递归调用 compile
      if (node.childNodes.length) {
        this.compiler(node)
      }
    })
  }

  // 编译元素节点,处理指令
  compilerElement(node) {
    // 遍历所有属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否 v-开头指令
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // 为了更优雅的处理不同方法,减去指令中的 v-
        attrName = attrName.substr(2)
        const key = attr.value
        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

    // 创建 Watcher对象,当数据改变时更新视图
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }

  // 处理 v-model指令
  modelUpdater(node, value, key) {
    node.value = value

    // 创建 Watcher对象,当数据改变时更新视图
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })

    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  // 编译文本节点,处理插值表达式
  compilerText(node) {
    const reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if (reg.test(value)) {
      // 只考虑一层的对象,如 data.msg = 'hello world',不考虑嵌套的对象。且假设只有一个插值表达式。
      const key = RegExp.$1.trim()
      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
  }
}

Dep类

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

Watcher类

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() {
    const newValue = this.vm[this.key]
    // 数据没有发生变化直接返回
    if (this.oldValue === newValue) {
      return
    }
    // 更新视图
    this.cb(newValue)
  }

}

完整版思维导图

786396f446bf8bb129f6f419ae065030.png

对于数组的监听

这里直接把数组的每一项都添加上了gettersetter,所以vm.items[1] = 'x'也是响应式的。

Vue中为什么没这样做呢?参考 为什么vue没有提供对数组属性的监听

33f8559900528f001d9ef33986963fce.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/502821.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接口500什么原因_80%小餐饮店几乎都“活“不过500天,为什么?都在这5个原因里...

餐饮创业分享达人 原创作者:秦.关中我是餐饮创业实战经验分享达人,想要了解和学习更多餐饮实战经验,记得关注我哦!希望我的分享能帮助大家在创业的道路上少走弯路,不踩坑,为你创业成功助上一臂之力。无论是…

linux 生成hash值命令,linux-从给定哈希计算base64编码哈希?

我创建了一个文件,并在其中输入了一些随机字符串.touch tesseract && echo TestTestTestTestTest > tesseract现在,如果我使用openssl来计算base64哈希[sha256],请运行以下命令:cat tesseract | openssl dgst -sha256 | openssl base64 -A回到我身边KHN0…

padding-left在ie8中不生效_陆国应 律师从民法典第209条来看房屋未过户买卖合同是否生效?...

基本案例(案例来源于大律师教你怎样打官司):张某与某公司签订了房屋买卖合同。双方在合同中约定:某公司将三居室一套售于张某,价款30万元,并约定该房屋于2018年2月1日前交付使用。此后,张某先后向某公司支付购房款人民…

linux shadow 时间,Linux Shadow-Password-HOWTO - 7. 将 Shadow Suite 放进来使用(1)

这节描述你需要知道有些程式在安装时就已经有 Shadow Suite。大部分的资讯在操作手册可以找到。7.1 新增、修改和删除使用者Shadow Suite 新增下列指令用来新增、修改和删除使用者。 这也是可以安装 adduser 程式。useradduseradd 使令可用在系统中新增使用者。 你也可以采用此…

电脑反应慢卡怎么解决_电脑键盘失灵怎么解决

键盘是电脑中重要的输入设备之一,但在使用的过程中有时可能会碰到键盘失灵的问题,该怎么办呢?别着急,接下来,小编我就来给大家详细的介绍一下电脑键盘出现怎么办,并将解决键盘失灵的操作方法来分享给你们。…

金山手机控usb调试模式开启工具_话筒坏了无法连麦?一招手机秒变电脑麦克风...

前段时间孩子上网课,沉睡多年的摄像头和麦克风被从箱子底请了出来。当连接妥当后,突然发现麦克风失灵了,几经调试宣布报废!当时正处于疫情高发期,电脑城肯定是关了,网购是来不及了,怎么办&#…

linux下openldap版本查询,用openldap进行linux认证

用openldap进行linux认证(时间:2010-11-25)(OS:LDAP SERVER:Linux version 2.6.18-8.el5xenLDAP:openldap-devel-2.3.27-5LDAP CLIENT:Linux version 2.6.32-71.el6.i686)一、LDAP Server 安装(一)安装配置#rpm -ihv openldap-servers-2.3.27-5.i386.rpm在安装过程中…

idea的setting界面怎么进_电脑怎么备份系统

电脑怎么备份系统?提前备份系统可以帮助下次系统出现问题后,随时还原备份的系统。下面小白提供两种备份系统的方法。一、系统自带备份一般现在的电脑系统都自带备份还原的功能,不管是win10还是win7都需要手动去备份。优点:可以增加…

快手用旺旺瓶子做机器人_用罐头瓶子做醪糟容易做好保存

醪糟是湖北四川一带的著名风味小吃,醪糟汤圆、醪糟鸡蛋酸甜可口,非常美味。这些年,北方人喜欢它的人也逐渐多了起来,我们单位食堂每周三都做醪糟汤圆大枣汤,是同事们最喜爱、期待的一道美食。我告诉她们可以自己做醪糟…

linux下关闭网络服务,Linux 关闭网络管理服务

关于各种类型数据char、int、double、float 所占空间长度的计算&#xff0c;而char类型让我长姿势了#include int main() { using namespace std; //int A&#xff1d;10; //double B&#xff1d;6; cout << ...python网络编程【一】TCP/IP 是标准的协议,它可以使用世界范…

你不出去卖我拿什么养你_玉树枝干“胳膊粗”,至少养了20年,给钱也不卖!...

玉树是很多花友家里常见的栽培花卉&#xff0c;很多地方叫厚脸皮&#xff0c;是很早进入到国内的多肉植物&#xff0c;经常能够几十年的玉树老桩&#xff0c;长得霸气。玉树叶子四季浓绿油亮&#xff0c;叶子犹如一片片碧玉&#xff0c;枝干古朴苍劲&#xff0c;犹如一棵大树&a…

python面试设计模式问题_聊聊 Python 面试最常被问到的几种设计模式(下)

1. 前言上篇文章 写到了 Python 最常用的 2 种设计模式&#xff0c;单例模式和工厂模式本篇文章我们继续聊聊面试中&#xff0c;Python 面试经常被问到的设计模式&#xff0c;即&#xff1a;构建者模式代理模式观察者模式2. 构建者模式构建者模式&#xff0c;是将一个复杂对象的…

vscode安装swift插件_使用 Webpack 优化 VS Code 插件加载性能

Webpack 这一 JS 模块打包神器相信大家都不陌生了。由于 VS Code 插件大部分也都是 JS/TS 代码 依赖库的形式&#xff0c;因此也可以使用 Webpack 打包&#xff0c;优化性能。经过实测&#xff0c;经过 Webpack 打包优化后的插件&#xff0c;其加载耗时可缩短几倍甚至数十倍之…

全国战争linux添加eth0,linux服务器双线路接入配置

linux服务器双线路接入配置现在有两个专线外接进来分别是移动IP&#xff1a;218。204。242。XXX和电信IP&#xff1a;124。172。245。XXX服务器的地址eth0和eth1的地址分别是&#xff1a;移动IP&#xff1a;218。204。242。XXX和电信IP&#xff1a;124。172。245。XXXDEVICEeth…

c++rpg黑框游戏_NO总本色出演断智大师兄,电竞魔音主C人《超级猎杀》一战成名...

谁说没有智力不能玩RPG&#xff1f;近日&#xff0c;一款名为《超级猎杀》的生存RPG地图在诸多暴雪主播的相继挑战下进入了玩家们的眼帘&#xff0c;要说其中哪位的操作堪称"迷幻"之最&#xff0c;或许No总排第二没人敢称第一。这位菜的同时又自诩无敌的"断智法…

linux 命令行模式自动登录,实现linux的自动登录--命令行模式启动

linux的登录主要是由两个文件在控制,/usr/sbin/getty来获得用户名,并进行检查用户名是否存在,然后将用户名传递给/usr/bin/login来获取用户输入密码和检查密码是否正确.所以要实现linux的自动登录,就要改动这两个文件.1.getty实现的主要功能是:1)打开指定的tty;2)提示用户登录(…

centos7 修改为任意网卡名_centos7首次启动需要配置的内容

前言用户需安装centos图形化界面或命令行界面安装centos7命令行界面&#xff0c;我为大家整理好了&#xff0c;请点击以下链接&#xff0c;亲测N次没问题&#xff01;&#xff01;[https://blog.csdn.net/zkzbhh/article/details/78145708](https://blog.csdn.net/zkzbhh/artic…

python将图片存入数据库_使用python把图片存入数据库

一般情况下我们是把图片存储在文件系统中&#xff0c;而只在数据库中存储文件路径的&#xff0c;但是有时候也会有特殊的需求&#xff1a;把图片二进制存入数据库。今天我们采用的是pythonmysql的方式MYSQL 是支持把图片存入数据库的&#xff0c;也相应的有一个专门的字段 BLOB…

linux播放到设备,linux - 将字节流式传输到ALSA播放设备 - 堆栈内存溢出

我在使用libasound将随机字节写入ALSA播放设备时遇到了很多麻烦。 最终&#xff0c;我的目标是能够通过网络路由回放流并让它在远程设备上播放。此问题中提供的代码将WAV文件读入内存并通过snd_pcm_writei将其写入驱动程序并且可以正常工作。 但是&#xff0c;这段代码与我正在…

vue实现一个带搜索功能的列表_(Vue起步)2.模板指令:v-for / v-on / v-model

①公众号&#xff1a;王酱酱记②记录跟着文档学习Vue的一些关键点&#xff0c;持续更新。感兴趣的小白建议关注一下③Vue当中有几个常见的指令&#xff0c;看看是怎么用的&#xff0c;强烈建议你自己在编辑器里打一遍&#xff0c;你就更明白Vue为什么是数据驱动型的一款框架啦&…