vue数据初始化initState的实例详解


Posted in Javascript onApril 11, 2019

数据初始化

Vue 实例在建立的时候会运行一系列的初始化操作,而在这些初始化操作里面,和数据绑定关联最大的是 initState。

首先,来看一下他的代码:

function initState(vm) {
 vm._watchers = [];
 var opts = vm.$options;
 if(opts.props) {
  initProps(vm, opts.props); //初始化props
 }
 if(opts.methods) {
  initMethods(vm, opts.methods); //初始化methods
 }
 if(opts.data) {
  initData(vm); //初始化data
 } else {
  observe(vm._data = {}, true /* asRootData */ );
 }
 if(opts.computed) {
  initComputed(vm, opts.computed); //初始化computed
 }
 if(opts.watch && opts.watch !== nativeWatch) {
  initWatch(vm, opts.watch); //初始化watch
 }
}

在这么多的数据的初始化中,props、methods和data是比较简单的(所以我就不详细介绍了☺),而computed 和 watch则相对较难,逻辑较复杂,所以我下面主要讲下computed 和 watch(以下代码部分为简化后的)。

initState里面主要是对vue实例中的 props, methods, data, computed 和 watch 数据进行初始化。

在初始化props的时候(initProps),会遍历props中的每个属性,然后进行类型验证,数据监测等(提供为props属性赋值就抛出警告的钩子函数)。

在初始化methods的时候(initMethods),主要是监测methods中的方法名是否合法。

在初始化data的时候(initData),会运行 observe 函数深度遍历数据中的每一个属性,进行数据劫持。

在初始化computed的时候(initComputed),会监测数据是否已经存在data或props上,如果存在则抛出警告,否则调用defineComputed函数,监听数据,为组件中的属性绑定getter及setter。如果computed中属性的值是一个函数,则默认为属性的getter函数。此外属性的值还可以是一个对象,他只有三个有效字段set、get和cache,分别表示属性的setter、getter和是否启用缓存,其中get是必须的,cache默认为true。

function initComputed(vm, computed) {
 var watchers = vm._computedWatchers = Object.create(null);

 for(var key in computed) {
  var userDef = computed[key];
  var getter = typeof userDef === 'function' ? userDef : userDef.get;

  //创建一个计算属性 watcher
  watchers[key] = new Watcher(
   vm,
   getter || noop,
   noop,
   computedWatcherOptions
  );

  if(!(key in vm)) {
   //如果定义的计算属性不在组件实例上,对属性进行数据劫持
   //defineComputed 很重要,下面我们再说
   defineComputed(vm, key, userDef);
  } else {
   //如果定义的计算属性在data和props有,抛出警告
  }
 }
}

在初始化watch的时候(initWatch),会调用vm.$watch函数为watch中的属性绑定setter回调(如果组件中没有该属性则不能成功监听,属性必须存在于props、data或computed中)。如果watch中属性的值是一个函数,则默认为属性的setter回调函数,如果属性的值是一个数组,则遍历数组中的内容,分别为属性绑定回调,此外属性的值还可以是一个对象,此时,对象中的handler字段代表setter回调函数,immediate代表是否立即先去执行里面的handler方法,deep代表是否深度监听。

vm.$watch函数会直接使用Watcher构建观察者对象。watch中属性的值作为watcher.cb存在,在观察者update的时候,在watcher.run函数中执行。想了解这一过程可以看我上一篇的 vue响应式系统--observe、watcher、dep中关于Watcher的介绍。

function initWatch(vm, watch) {
 //遍历watch,为每一个属性创建侦听器
 for(var key in watch) {
  var handler = watch[key];
  //如果属性值是一个数组,则遍历数组,为属性创建多个侦听器
  //createWatcher函数中封装了vm.$watch,会在vm.$watch中创建侦听器
  if(Array.isArray(handler)) {
   for(var i = 0; i < handler.length; i++) {
    createWatcher(vm, key, handler[i]);
   }
  } else {
   //为属性创建侦听器
   createWatcher(vm, key, handler);
  }
 }
}

function createWatcher(vm, expOrFn, handler, options) {
 //如果属性值是一个对象,则取对象的handler属性作为回调
 if(isPlainObject(handler)) {
  options = handler;
  handler = handler.handler;
 }
 //如果属性值是一个字符串,则从组件实例上寻找
 if(typeof handler === 'string') {
  handler = vm[handler];
 }
 //为属性创建侦听器
 return vm.$watch(expOrFn, handler, options)
}

computed

computed本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值

下面将围绕这一句话来做解释。

上面代码中提到过,当计算属性中的数据存在与data和props中时,会被警告,也就是这种做法是错误的。所以一般的,我们都会直接在计算属性中声明数据。还是那个代码片段中,如果定义的计算属性不在组件实例上,会运行defineComputed函数对数据进行数据劫持。下面我们来看下defineComputed函数中做了什么。

function defineComputed(target, key, userDef) {
 //是不是服务端渲染
 var shouldCache = !isServerRendering();
 //如果我们把计算属性的值写成一个函数,这时函数默认为计算属性的get
 if(typeof userDef === 'function') {
  sharedPropertyDefinition.get = shouldCache ?
   //如果不是服务端渲染,则默认使用缓存,设置get为createComputedGetter创建的缓存函数
   createComputedGetter(key) :
   //否则不使用缓存,直接设置get为userDef这个我们定义的函数
   userDef;
  //设置set为空函数
  sharedPropertyDefinition.set = noop;
 } else {
  //如果我们把计算属性的值写成一个对象,对象中可能包含set、get和cache三个字段
  sharedPropertyDefinition.get = userDef.get ?
   shouldCache && userDef.cache !== false ?
   //如果我们传入了get字段,且不是服务端渲染,且cache不为false,设置get为createComputedGetter创建的缓存函数
   createComputedGetter(key) : 
   //如果我们传入了get字段,但是是服务端渲染或者cache设为了false,设置get为userDef这个我们定义的函数
   userDef.get :
   //如果没有传入get字段,设置get为空函数
   noop;
  //设置set为我们传入的传入set字段或空函数
  sharedPropertyDefinition.set = userDef.set ?
   userDef.set :
   noop;
 }
 //虽然这里可以get、set都可以设置为空函数
 //但是在项目中,get为空函数对数据取值会报错,set为空函数对数据赋值会报错
 //而computed主要作用就是计算取值的,所以get字段是必须的
 //数据劫持
 Object.defineProperty(target, key, sharedPropertyDefinition);
}

在上一篇的 vue响应式系统--observe、watcher、dep 中,我有关于Watcher的介绍中提到,计算属性 watcher实例化的时候,会把options.lazy设置为true,这里是计算属性惰性求值,且可缓存的关键,当然前提是cache不为false。

cache不为false,会调用createComputedGetter函数创建计算属性的getter函数computedGetter,

先来看一段代码

function createComputedGetter(key) {
 return function computedGetter() {
  var watcher = this._computedWatchers && this._computedWatchers[key];
  if(watcher) {
   if(watcher.dirty) {
    //watcher.evaluate中更新watcher的值,并把watcher.dirty设置为false
    //这样等下次依赖更新的时候才会把watcher.dirty设置为true,然后进行取值的时候才会再次运行这个函数
    watcher.evaluate();
   }
   //依赖追踪
   if(Dep.target) {
    watcher.depend();
   }
   //返回watcher的值
   return watcher.value
  }
 }
}
//对于计算属性,当取值计算属性时,发现计算属性的watcher的dirty是true
//说明数据不是最新的了,需要重新计算,这里就是重新计算计算属性的值。
Watcher.prototype.evaluate = function evaluate() {
 this.value = this.get();
 this.dirty = false;
};
//当一个依赖改变的时候,通知它update
Watcher.prototype.update = function update() {
 //三种watcher,只有计算属性 watcher的lazy设置了true,表示启用惰性求值
 if(this.lazy) {
  this.dirty = true;
 } else if(this.sync) {
  //标记为同步计算的直接运行run,三大类型暂无,所以基本会走下面的queueWatcher
  this.run();
 } else {
  //将watcher推入观察者队列中,下一个tick时调用。
  //也就是数据变化不是立即就去更新的,而是异步批量去更新的
  queueWatcher(this);
 }
};

当options.lazy设置为true之后(仅计算属性watcher的options.lazy设置为true),每次依赖更新,都不会主动触发run函数,而是把watcher.dirty设置为true。这样,当对计算属性进行取值时,就会运行computedGetter函数,computedGetter函数中有一个关于watcher.dirty的判断,当watcher.dirty为true时会运行watcher.evaluate进行值的更新,并把watcher.dirty设置为false,这样就完成了惰性求值的过程。后面只要依赖不更新,就不会运行update,就不会把watcher.dirty为true,那么再次取值的时候就不会运行watcher.evaluate进行值的更新,从而达到了缓存的效果。

综上,我们了解到cache不为false的时候,计算属性都是惰性求值且具有缓存性的,而cache默认是true,我们也大多使用这个默认值,所以我们说 computed本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值 。

总结

以上所述是小编给大家介绍的vue数据初始化initState的实例详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

如果你觉得本文对你有帮助,欢迎转载,请注明出处,谢谢!

Javascript 相关文章推荐
一个刚完成的layout(拖动流畅,不受iframe影响)
Aug 17 Javascript
JavaScript 在各个浏览器中执行的耐性
Apr 06 Javascript
innerText和innerHTML 一些问题分析
May 18 Javascript
jquery教程ajax请求json数据示例
Jan 13 Javascript
JavaScript的String字符串对象常用操作总结
May 26 Javascript
简单理解vue中el、template、replace元素
Oct 27 Javascript
javascript基础知识讲解
Jan 11 Javascript
元素全屏的设置与监听实例
Nov 28 Javascript
解决使用Vue.js显示数据的时,页面闪现原始代码的问题
Feb 11 Javascript
jQuery实现DIV响应鼠标滑过由下向上展开效果示例【测试可用】
Apr 26 jQuery
详解nvm管理多版本node踩坑
Jul 26 Javascript
如何在Express4.x中愉快地使用async的方法
Nov 18 Javascript
Vue起步(无cli)的啊教程详解
Apr 11 #Javascript
vue+vuex+json-seiver实现数据展示+分页功能
Apr 11 #Javascript
小白教程|一小时上手最流行的前端框架vue(推荐)
Apr 10 #Javascript
微信小程序 wepy框架与iview-weapp的用法详解
Apr 10 #Javascript
解决JQuery的ajax函数执行失败alert函数弹框一闪而过问题
Apr 10 #jQuery
详解Vue源码学习之双向绑定
Apr 10 #Javascript
node.js 基于cheerio的爬虫工具的实现(需要登录权限的爬虫工具)
Apr 10 #Javascript
You might like
PHP的可变变量名的使用方法分享
2012/02/05 PHP
thinkphp3.x中display方法及show方法的用法实例
2016/05/19 PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
2018/02/07 PHP
PHP获取文件扩展名的常用方法小结【五种方式】
2018/04/27 PHP
在IE下获取object(ActiveX)的Param的代码
2009/09/15 Javascript
关于javascript中的typeof和instanceof介绍
2012/12/04 Javascript
JS获取当前网址、主机地址项目根路径
2013/11/19 Javascript
js点击选择文本的方法
2015/02/09 Javascript
JavaScript操作Cookie详解
2015/02/28 Javascript
jquery动态遍历Json对象的属性和值的方法
2016/07/27 Javascript
jQuery购物网页经典制作案例
2016/08/19 Javascript
JS实现隔行换色的表格排序
2017/03/27 Javascript
React-Native之定时器Timer的实现代码
2017/10/04 Javascript
JavaScript正则表达式函数总结(常用)
2018/02/22 Javascript
Vue路由之JWT身份认证的实现方法
2019/08/26 Javascript
layui 点击重置按钮, select 并没有被重置的解决方法
2019/09/03 Javascript
VUE+elementui面包屑实现动态路由详解
2019/11/04 Javascript
Python 爬虫多线程详解及实例代码
2016/10/08 Python
NumPy 如何生成多维数组的方法
2018/02/05 Python
Django自定义用户认证示例详解
2018/03/14 Python
Django实现表单验证
2018/09/08 Python
对Python3使运行暂停的方法详解
2019/02/18 Python
基于python生成器封装的协程类
2019/03/20 Python
Python3使用TCP编写一个简易的文件下载器功能
2019/05/08 Python
python3实现在二叉树中找出和为某一值的所有路径(推荐)
2019/12/26 Python
python3.7调试的实例方法
2020/07/21 Python
澳大利亚领先的睡衣品牌:Peter Alexander
2016/08/16 全球购物
HashMap和Hashtable的区别
2013/05/18 面试题
医院护士的求职信
2014/01/03 职场文书
党校培训自我鉴定范文
2014/04/10 职场文书
装饰工程师岗位职责
2014/06/08 职场文书
保密工作承诺书
2014/08/29 职场文书
HR必备:销售经理聘用合同范本
2019/08/21 职场文书
营销策划分析:怎么策划才能更好销量产品?
2019/09/04 职场文书
Python中的xlrd模块使用整理
2021/06/15 Python
使用CSS定位HTML元素的实现方法
2022/07/07 HTML / CSS