首页 > 其他分享 >created中两次数据修改,会触发几次页面更新

created中两次数据修改,会触发几次页面更新

时间:2023-11-17 16:02:35浏览次数:30  
标签:count 触发 created dep vm value 页面

面试题:created 生命周期中两次修改数据,会触发几次页面更新?

一、同步的

先举个简单的同步的例子:

new Vue({
  el: "#app",
  template: `<div>
    <div>{{count}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    };
  },
  created() {
    this.count = 2;
    this.count = 3;
  },
});

在 created 生命周期中,通过 this.count = 2 和 this.count = 3 的方式将 this.count 重新赋值。
这里直接抛出答案:渲染一次。

为什么?
这个与数据的响应式处理有关,先看响应式处理的逻辑:

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 重点:创建一个发布者实例
  const dep = new Dep();

  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        // 重点:进行当前正在计算的渲染Watcher的收集
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      // 重点:当数据发生变化时,发布者实例dep会通知收集到的watcher进行更新
      dep.notify();
    },
  });
}

在数据响应式处理阶段,会实例化一个发布者 dep,并且通过 Object.defineProperty 的方式为当前数据定义 get 和 set 函数。在生成虚拟 vNode 的阶段,会触发 get 函数中会进行当前正在计算的渲染 Watcher 的收集,此时,发布者 dep 的 subs 中会多一个渲染 Watcher 实例。在数据发生变化的时候,会触发 set 函数,通知发布者 dep 中 subs 中的 watcher 进行更新。

至于数据修改会触发几次更新,就与当前发布者 dep 的 subs 中收集了几次渲染 watcher 有关了,再看 watcher 收集和 created 执行之间的顺序:

Vue.prototype._init = function (options) {
  // ...
  initState(vm);
  // ...
  callHook(vm, "created");
  // ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el);
  }
};

知道在 initState(vm)阶段对数据进行响应式处理,但是此时发布者 dep 的 subs 还是空数组。当执行 callHook(vm, 'created')的时候,会执行 this.count = 2 和 this.count = 3 的逻辑,也的确会触发 set 函数中的 dep.notify 通知收集到的 watcher 进行更新。但是,此时 dep 的 subs 是空数组,相当于啥也没做。

只有在 vm.$mount(vm.$options.el)执行过程中,生成虚拟 vNode 的时候才会进行渲染 Watcher 收集,此时,dep 的 subs 才不为空。最终,通过 vm.$mount(vm.$options.el)进行了页面的一次渲染,并未因为 this.count=2 或者 this.count=3 而触发多余的页面更新。

简言之,就是 created 钩子函数内的逻辑的执行是在渲染 watcher 收集之前执行的,所以未引起因为数据变化而导致的页面更新。

二、异步的

同步的场景说完了,再举个异步的例子:

new Vue({
  el: "#app",
  template: `<div>
    <div>{{count}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    };
  },
  created() {
    setTimeout(() => {
      this.count = 2;
    }, 0);
    setTimeout(() => {
      this.count = 3;
    }, 0);
  },
});

在 created 生命周期中,通过异步的方式执行 this.count = 2 和 this.count = 3 的方式将 this.count 重新赋值。
这里直接抛出答案:首次渲染一次,因为数据变化导致的页面更新两次

为什么?
这个就与 eventLoop 事件循环机制有关了,知道 js 是一个单线程执行的语言,当通过 new Vue 实例化的过程中,会执行初始化方法 this._init 方法,开始了 Vue 底层的处理逻辑。当遇到 setTimeout 异步操作时,会将其推入到异步队列中去,等待当前同步任务执行完以后再去异步队列中取出队首元素进行执行。

当前例子中,在 initState(vm)阶段对数据进行响应式处理。当执行 callHook(vm, 'created')的时候,会将 this.count = 2 和 this.count = 3 的逻辑推入到异步队列等待执行。继续执行 vm.$mount(vm.$options.el)的过程中会去生成虚拟 vNode,进而触发 get 函数的渲染 Watcher 收集,此时,dep 的 subs 中就有了一个渲染 watcher。

等首次页面渲染完成以后,会去执行 this.count=2 的逻辑,数据的修改会触发 set 函数中的 dep.notify,此时发布者 dep 的 subs 不为空,会引起页面的更新。同理,this.count=3 会再次引起页面数据的更新。也就是说,首次渲染一次,因为 this.count=2 和 this.count=3 还会导致页面更新两次。

三、附加

如果改变的值和 data 中定义的值一致呢?

new Vue({
  el: "#app",
  template: `<div>
    <div>{{count}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    };
  },
  created() {
    setTimeout(() => {
      this.count = 1;
    }, 0);
  },
});

这个时候,在触发 set 的逻辑中,会当执行到 if (newVal === value || (newVal !== newVal && value !== value)) { return }的逻辑,不会再执行到 dep.notify,这种场景下数据的数据也不会引起页面的再次更新。

总结

从生命周期 created 和页面渲染的先后顺序,Object.defineProperty 触发 get 和 set 函数的机理,以及 eventLoop 事件循环机制入手,去分析 created 中两次数据修改会触发几次页面更新的问题就会清晰很多。

标签:count,触发,created,dep,vm,value,页面
From: https://www.cnblogs.com/wp-leonard/p/17838958.html

相关文章

  • vue中created、watch和computed的执行顺序
    总结关于vue中created和watch的执行顺序相对比较简单,而其中computed是通过Object.defineProperty为当前vm进行定义,再到后续创建vNode阶段才去触发执行其get函数,最终执行到计算属性computed对应的逻辑。官网的生命周期图中,initreactivity是晚于beforeCreate......
  • 直播平台搭建,实现自定义设置登录页面
    直播平台搭建,实现自定义设置登录页面1.在resources中建立static文件夹(默认找这里面的页面)创建login.html <!DOCTYPEhtml><htmlxmlns:th="http://www.thymeleaf.org"><head>  <metacharset="UTF-8">  <title>static中的login</title></hea......
  • javascript postMessage给子页面发消息
    发送消息页面<!DOCTYPEhtml><html><head><title>demo</title><metacharset="utf-8"/><script>varchildwinconstchildname="popup"functionopenChild(){......
  • vue+pdfh5实现将pdf渲染到页面上
    版本:[email protected]+.netCore6.0webapi方法一:通过访问后端获取二进制数据来渲染前端渲染<template><vol-boxref="box":width="width":height="height"><divid="demo"ref="render"></div></vol......
  • 使用js添加按钮,vue页面 el-calendar 添加自定义按钮
    html代码:<divclass="schedule"><divclass="title">今日日程</div><divclass="allSchedule"><el-rowclass="addSchedule"type="flex"align="......
  • htmlunit 模拟登入、点击、获取页面信息
    本文介绍了htmlunit模拟登入、点击、获取页面信息的demopublicstaticStringgetHtml(Stringurl){System.out.println("****************开始执行****************");//模拟一个浏览器@SuppressWarnings("resource")WebClientwebClient......
  • Eclipse安装中文语言包导致部分页面功能和工作区域无法加载或使用的解决办法
    Eclipse安装中文语言包插件(eclipse菜单栏:“Help”—>“InstallNewSoftware”)出现:“Welcome”页面无法加载,“TaskList”“Outline”等工作区无法使用等情况。针对这种情况,需要卸载安装的中文语言包插件。具体步骤为:eclipse菜单栏—>“帮......
  • oracle-触发器
    创建触发器的语法:create[orreplace]triggertri_name [before|after|insteadof]tri_event     ontable_name|view_name|user_name|db_name     [referencing[:old][:new]]     [foreachrow[whentri_condition]]begi......
  • Streamlit 快速构建交互式页面的python库
    基础介绍streamlit是什么Streamlit是一个面向机器学习和数据科学团队的开源应用程序框架,通过它可以用python代码方便快捷的构建交互式前端页面。streamlit特别适合结合大模型快速的构建一些对话式的应用,可以看到一些行业内热门的使用。项目本身也比较成熟,release版本,start数量等都......
  • KingbaseES启用和禁用触发器
    启用触发器您可以使用带有ENABLE选项的ALTERTRIGGER语句启用禁用状态的触发器。要在class表中启用名为class_trigger的触发器(禁用状态),输入以下语句:ALTERTRIGGERclass_triggerENABLE;上述用于启动特定的触发器,如果要启用特定表的所有触发器,请使用带有ENABLE......