Vue data 中随意更改一个属性,视图都会被更新吗?

共 1379字,需浏览 3分钟

 ·

2022-01-04 21:18

以下内容来自公众号逆锋起笔,关注每日干货及时送达b6d96e9d2bef7c04cd4a61b51ba67a3b.webp

作者:Rudy24

链接:https://juejin.cn/post/7040733315791323143


  • 面试官:看过 Vue 的源码没?

  • 候选者:看过。

  • 面试官:那你说下 Vue data 中随意更改一个属性,视图都会被更新吗?

  • 候选者:不会。

  • 面试官:why?

  • 候选者:如果该属性没有被用到 template 中,就没有必要去更新视图,频繁这样性能不好。

  • 面试官:那 Vue 中是如何去实现该方案的?

  • 候选者:在实例初始化过程中,利用Object.defineProperty对 data 中的属性进行数据监听,如果在 template 中被使用到的属性,就被 Dep 类收集起来,等到属性被更改时会调用notify更新视图。

  • 面试官:那你怎么知道那些属性是在 template 被用到的呢?

  • 候选者:WTF。。。这个倒不是很清楚,您能解释下吗?

  • 面试官:OK,那我就简单解释下:

先写个简单的 demo,其中 data 中有 4 个属性a,b,c,d,在模板中被利用到的属性只有a,b。看看是不是只有a,b才会调用Dep收集起来呢?

new Vue({
  el'#app',
  data() {
    return {
      a1,
      b2,
      c3,
      d4,
    };
  },
  created() {
    console.log(this.b);
    this.b = 'aaa';
  },
  template'
Hello World{{a}}{{b}}
'
,
});
复制代码
  1. 在Vueinstance/state.js里面,会利用proxy把每个属性都 代理一遍
const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 代理对象的属性
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
复制代码
  1. 利用defineReactive对data中的每个属性进行劫持
observe(data, true /* asRootData */);

// observe
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
  defineReactive(obj, keys[i]);
}

// defineReactive
Object.defineProperty(obj, key, {
  enumerabletrue,
  configurabletrue,
  getfunction reactiveGetter() {
    const value = getter ? getter.call(obj) : val;
    // 重点在这里,后续如果在模板中使用到的属性,都会被执行reactiveGetter函数
    // 被Dep类 收集起来
    if (Dep.target) {
      console.log(`${key} 属性 被Dep类收集了`)
      dep.depend();
      if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    return value;
  },
  setfunction reactiveSetter(newVal{
    const value = getter ? getter.call(obj) : val;
    /* eslint-disable no-self-compare */
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return;
    }
    if (setter) {
      // 这里是处理computed set 函数
      setter.call(obj, newVal);
    } else {
      val = newVal;
    }
    childOb = !shallow && observe(newVal);
    // 如果我们在更改属性时,就会调用notify 异步更新视图
    dep.notify();
  },
});
复制代码
  1. 执行$mount进行视图挂载
if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}
复制代码
  1. $mount 是调用 Vue 原型上的方法, 重点是最后一句 mount.call(this, el, hydrating)
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component 
{
  el = el && query(el);

  const options = this.$options;
  // resolve template/el and convert to render function
  /**
   * 查看render 函数是否存在?如果不存在就解析template模板
   * Vue渲染页面时,有两个方式 1. template,2. render,最终所有的模板类的都需要使用render去渲染
   */

  if (!options.render) {
    let template = options.template;
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template);
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            );
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this);
        }
        return this;
      }
    } else if (el) {
      // 如果模板不存在,就创建一个默认的html模板
      template = getOuterHTML(el);
    }
  }
  // 重写了Vue.prototype.$mount ,最终调用缓存的mount方法完成对$mount的挂载
  return mount.call(this, el, hydrating);
};
复制代码
  1. 这里mount调用了 mountComponent(this, el, hydrating) 方法,而 mountComponent是执行了 _render函数,最终_render是调用render 生成一个vnode
const { render, _parentVnode } = vm.$options;
vnode = render.call(vm._renderProxy, vm.$createElement);
复制代码

9291779dd6f1663e136f3506a6587481.webp


8cacad31f5171516afd9df5b70131587.webp

最后一张图可以看到是render函数在渲染我们demo里面的template模板,最终只有a, b两个属性才会被Dep类收集起来。微信搜索readdot,关注后回复视频教程获取23种精品资料


b4a9ea0afbe65a07fa6f2ef9517d0aea.webpimage.png


如果文中有错误的地方,麻烦各位指出,我会持续改进的。谢谢, 需要调试源码的,这里点击这里,按照 readme操作即可。

逆锋起笔专注于程序员圈子,你不但可以学习到javapython等主流技术干货,还可以第一时间获悉最新技术动态内测资格BAT大佬的经验精品视频教程副业赚钱经验,微信搜索readdot关注!

Vue3 相比于 Vue2 有哪些 “与众不同”?

Vue 开发最佳指南,你都需要学点啥?

记一次 Vue3.0 技术分享会

Vue 实战中的一些小魔法

一名 Vue 程序员总结的 React 基础


点一下,代码无 Bug

823466b09f0a98ed618d2fab4ef046d3.webp
浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐