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个原因里...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

python numpy库安装winerror5_详解idea从git上拉取maven项目详细步骤

刚从Eclipse转Intellij,对于它的各种操作也是一脸懵逼,但觉得使用起来还不错,今天就说一下我用Idea导入git中的Maven项目的详细步骤: 1. 首先打开Intellji Idea,选择check out from Version Control,选择Git(图片上选到gitHub了,抱…

java jcsh执行linux命令,java jcsh执行linux命令

java jcsh执行linux命令[2021-02-03 01:26:29] 简介:php去除nbsp的方法:首先创建一个PHP代码示例文件;然后通过“preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/", " ", strip_tags($val));”方法去除所有nbsp即可。推荐&#xff…

sql md5函数_【学习笔记】常见漏洞:SQL注入的利用与防御

第 21 课 SQL注入的利用与防御课程入口(付费)个人背景李,本科,电子信息工程专业,毕业一年半,有JavaScript的,PHP,Python的语言基础,目前自学网络安全中。SQL注入的利用与防御01 SQL盲注1.1 S…

docker -v 覆盖了容器中的文件_浅谈docker中宿主机和容器之间互相copy文件的两种方式,欢迎补充...

在dokcer的日常使用过程中,我们可能会遇到将宿主机内文件/目录copy到容器内,或者将容器的文件/目录copy到宿主机中,下面我们就来简单的谈一下关于这种情况的两种操作。1、Docker cp命令:用于容器与主机之间的数据copy语法&#xf…

线性代数第九版pdf英文_斯坦福CS229机器学习课程的数学基础(线性代数)翻译完成...

文章转载自公众号 机器学习初学者 , 作者 机器学习初学者Stanford cs229 manchine learning课程,相比于Coursera中的机器学习有更多的数学要求和公式的推导,课程全英文,基础材料部分还没有翻译。这个基础材料主要分为线性代数和概…

c 多文件全局变量_C语言开发单片机为什么大多数都采用全局变量的形式?

点击上方蓝字关注我哦~01前言全局变量简直就是嵌入式系统的戈兰高地。冲突最激烈的双方是1. 做控制的工程师 2. 做非嵌入式的软件工程师。02做控制的工程师特点他们普遍的理解就是“变量都写成全局该有多方便”。我之前面试过一个非常有名的做控制实验室里出来的PhD…

医学图像处理_专刊征稿|医学图像处理中的认知计算

认知科学是20世纪世界科学标志性的新兴研究门类,它作为探究人脑或心智工作机制的前沿性尖端学科,已经引起了全世界科学家们的广泛关注。认知计算代表一种全新的计算模式,它包含信息分析,自然语言处理和机器学习领域的大量技术创新…

python 如何判断一个函数执行完成_Python 函数为什么会默认返回 None?

👆 “Python猫” ,一个值得加星标的公众号Python 有一项默认的做法,很多编程语言都没有——它的所有函数都会有一个返回值,不管你有没有写 return 语句。 本文出自“Python为什么”系列,在正式开始之前,我们…

不在 sudoers 文件中。此事将被报告_快餐包装中检出致癌物质?麦当劳、汉堡王回应!...

薯条汉堡、雪碧可乐已然成为大家的用餐首选之一一周吃了两次以上的人相信也不在少数可最近一则“麦当劳、汉堡王等快餐包装中检出致癌物质”的消息却让许多人吓出了一身冷汗而且迅速登上热搜榜…近日,环保组织的一份报告称,美国当地麦当劳McDonald’s、汉…

lichee linux nfs,SPI Flash 系统编译

在一些低成本应用场景,需要在SPI flash上启动系统,这需要对Uboot和系统镜像做些适配。本文介绍SPI Flash镜像的制作过程。这里 使用 MX25L25645G, 32M SPI flash 作为启动介质,规划分区如下:分区序号分区大小分区作用地址空间及分…