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 相关文章推荐
jQuery用unbind方法去掉hover事件及其他方法介绍
Mar 18 Javascript
在JavaScript中访问字符串的子串
Jul 07 Javascript
JavaScript简单获取页面图片原始尺寸的方法
Jun 21 Javascript
angularJS Provider、factory、service详解及实例代码
Sep 21 Javascript
js无提示关闭浏览器窗口的两种方法分析
Nov 06 Javascript
JavaScript ES6中CLASS的使用详解
Nov 22 Javascript
详解vue-router基本使用
Apr 18 Javascript
jQuery Plupload上传插件的使用
Apr 19 jQuery
vue2.0 与 bootstrap datetimepicker的结合使用实例
May 22 Javascript
Layer.js实现表格溢出内容省略号显示,悬停显示全部的方法
Sep 16 Javascript
layui form.render('select', 'test2') 更新渲染的方法
Sep 27 Javascript
Jquery滑动门/tab切换实现方法完整示例
Jun 05 jQuery
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根据IP判断地区名信息的示例代码
2014/03/03 PHP
php类声明和php类使用方法示例分享
2014/03/29 PHP
PHP解决URL中文GBK乱码问题的两种方法
2014/06/03 PHP
PHP内置过滤器FILTER使用实例
2014/06/25 PHP
Javascript与PHP验证用户输入URL地址是否正确
2014/10/09 PHP
微信开发之获取JSAPI TICKET
2017/07/07 PHP
PHP根据树的前序遍历和中序遍历构造树并输出后序遍历的方法
2017/11/10 PHP
JQuery动态给table添加、删除行 改进版
2011/01/19 Javascript
Jquery中getJSON在asp.net中的使用说明
2011/03/10 Javascript
EasyUI中的tree用法介绍
2011/11/01 Javascript
THREE.JS入门教程(3)着色器-下
2013/01/24 Javascript
JS仿百度搜索自动提示框匹配查询功能
2013/11/21 Javascript
如何实现修改密码时密码框显示保存到cookie的密码
2013/12/10 Javascript
js获取判断上传文件后缀名的示例代码
2014/02/19 Javascript
Javascript中获取对象的原型对象的方法小结
2015/02/25 Javascript
jQuery+CSS3实现3D立方体旋转效果
2015/11/10 Javascript
js验证框架实现代码分享
2016/05/18 Javascript
详解vue2路由vue-router配置(懒加载)
2017/04/08 Javascript
vue中的event bus非父子组件通信解析
2017/10/27 Javascript
nodejs基于mssql模块连接sqlserver数据库的简单封装操作示例
2018/01/05 NodeJs
javascript设计模式 ? 命令模式原理与用法实例分析
2020/04/20 Javascript
Vue使用轮询定时发送请求代码
2020/08/10 Javascript
微信小程序使用前置摄像头拍照
2020/10/22 Javascript
使用python 爬虫抓站的一些技巧总结
2018/01/10 Python
python设定并获取socket超时时间的方法
2019/01/12 Python
解决Numpy中sum函数求和结果维度的问题
2019/12/06 Python
使用CSS3滤镜的filter:blur属性制作毛玻璃模糊效果的方法
2016/07/08 HTML / CSS
HTML5 Web Workers之网站也能多线程的实现
2013/04/24 HTML / CSS
点菜员岗位职责范本
2014/02/14 职场文书
主题教育活动总结
2014/05/05 职场文书
新闻发布会活动策划方案
2014/09/15 职场文书
2014年安全工作总结范文
2014/11/13 职场文书
理想国读书笔记
2015/06/25 职场文书
2019最新版劳务派遣管理制度
2019/08/16 职场文书
php7中停止php-fpm服务的方法详解
2021/05/09 PHP
利用python进行数据加载
2021/06/20 Python