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 相关文章推荐
Extjs TimeField 显示正常时间格式的代码
Jun 28 Javascript
使用JavaScript的ActiveXObject对象检测应用程序是否安装的方法
Apr 15 Javascript
JavaScript实现N皇后问题算法谜题解答
Dec 29 Javascript
Javascript基础教程之函数对象和属性
Jan 18 Javascript
JavaScript运算符小结
Jun 03 Javascript
jQuery Validate初步体验(一)
Dec 12 Javascript
JavaScript必知必会(六) delete in instanceof
Jun 08 Javascript
微信小程序 地图map详解及简单实例
Jan 10 Javascript
Angular.js中下拉框实现渲染html的方法
Jun 18 Javascript
JS倒计时实例_天时分秒
Aug 22 Javascript
Vue中render函数的使用方法
Jan 31 Javascript
Vue中&quot;This dependency was not found&quot;问题的解决方法
Jun 19 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
Yii查询生成器(Query Builder)用法实例教程
2014/09/04 PHP
linux中cd命令使用详解
2015/01/08 PHP
php筛选不存在的图片资源
2015/04/28 PHP
日常整理PHP中简单的图形处理(经典)
2015/10/26 PHP
php实现映射操作实例详解
2019/10/02 PHP
PHP Swoole异步Redis客户端实现方法示例
2019/10/24 PHP
贴一个在Mozilla中常用的Javascript代码
2007/01/09 Javascript
javascript 限制输入和粘贴(IE,firefox测试通过)
2008/11/14 Javascript
jquery中的sortable排序之后的保存状态的解决方法
2010/01/28 Javascript
基于jQuery的仿flash的广告轮播
2010/11/05 Javascript
js创建数据共享接口——简化框架之间相互传值
2011/10/23 Javascript
使用JS 清空File控件的路径值
2013/07/08 Javascript
javascript 按键事件(兼容各浏览器)
2013/12/20 Javascript
JS版的date函数(和PHP的date函数一样)
2014/05/12 Javascript
JS实现向表格行添加新单元格的方法
2015/03/30 Javascript
jQuery中$this和$(this)的区别介绍(一看就懂)
2015/07/06 Javascript
AngularJS获取json数据的方法详解
2017/05/27 Javascript
JavaScript反弹动画效果的实现代码
2017/07/13 Javascript
浅谈vue引用静态资源需要注意的事项
2018/09/28 Javascript
laravel实现中文和英语互相切换的例子
2019/09/30 Javascript
通过实例了解Render Props回调地狱解决方案
2020/11/04 Javascript
python中使用urllib2伪造HTTP报头的2个方法
2014/07/07 Python
Python深入学习之内存管理
2014/08/31 Python
Python实现从订阅源下载图片的方法
2015/03/11 Python
从Python的源码浅要剖析Python的内存管理
2015/04/16 Python
利用Python脚本在Nginx和uwsgi上部署MoinMoin的教程
2015/05/05 Python
Python使用chardet判断字符编码
2015/05/09 Python
使用python实现接口的方法
2017/07/07 Python
Python3.5模块的定义、导入、优化操作图文详解
2019/04/27 Python
python中怎么表示空值
2020/06/19 Python
python 实现简单的计算器(gui界面)
2020/11/11 Python
基于django和dropzone.js实现上传文件
2020/11/24 Python
Pycharm 设置默认解释器路径和编码格式的操作
2021/02/05 Python
法国一家多品牌成衣精品中/高档商店:Graduate Store
2019/08/28 全球购物
在C中是否有模拟继承等面向对象程序设计特性的好方法
2012/05/22 面试题
无刑事犯罪记录证明
2014/09/18 职场文书