乐趣区

关于vue.js:Vue-data-中随意更改一个属性视图都会被更新吗

面试官:看过 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 {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
};

},
created() {

console.log(this.b);
this.b = 'aaa';

},
template: ‘<div>Hello World{{a}}{{b}}</div>’,
});
复制代码

在 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 /)
复制代码

利用 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, {
enumerable: true,
configurable: true,
get: function 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;

},
set: function 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();

},
});
复制代码

执行 $mount 进行视图挂载

if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
复制代码

$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);
};

退出移动版