vue数据初始化initState的实例详解
数据初始化
Vue实例在建立的时候会运行一系列的初始化操作,而在这些初始化操作里面,和数据绑定关联最大的是initState。
首先,来看一下他的代码:
functioninitState(vm){
vm._watchers=[];
varopts=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。
functioninitComputed(vm,computed){
varwatchers=vm._computedWatchers=Object.create(null);
for(varkeyincomputed){
varuserDef=computed[key];
vargetter=typeofuserDef==='function'?userDef:userDef.get;
//创建一个计算属性watcher
watchers[key]=newWatcher(
vm,
getter||noop,
noop,
computedWatcherOptions
);
if(!(keyinvm)){
//如果定义的计算属性不在组件实例上,对属性进行数据劫持
//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的介绍。
functioninitWatch(vm,watch){
//遍历watch,为每一个属性创建侦听器
for(varkeyinwatch){
varhandler=watch[key];
//如果属性值是一个数组,则遍历数组,为属性创建多个侦听器
//createWatcher函数中封装了vm.$watch,会在vm.$watch中创建侦听器
if(Array.isArray(handler)){
for(vari=0;i
computed
computed本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问computed属性,才会计算新的值
下面将围绕这一句话来做解释。
上面代码中提到过,当计算属性中的数据存在与data和props中时,会被警告,也就是这种做法是错误的。所以一般的,我们都会直接在计算属性中声明数据。还是那个代码片段中,如果定义的计算属性不在组件实例上,会运行defineComputed函数对数据进行数据劫持。下面我们来看下defineComputed函数中做了什么。
functiondefineComputed(target,key,userDef){
//是不是服务端渲染
varshouldCache=!isServerRendering();
//如果我们把计算属性的值写成一个函数,这时函数默认为计算属性的get
if(typeofuserDef==='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,
先来看一段代码
functioncreateComputedGetter(key){
returnfunctioncomputedGetter(){
varwatcher=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的值
returnwatcher.value
}
}
}
//对于计算属性,当取值计算属性时,发现计算属性的watcher的dirty是true
//说明数据不是最新的了,需要重新计算,这里就是重新计算计算属性的值。
Watcher.prototype.evaluate=functionevaluate(){
this.value=this.get();
this.dirty=false;
};
//当一个依赖改变的时候,通知它update
Watcher.prototype.update=functionupdate(){
//三种watcher,只有计算属性watcher的lazy设置了true,表示启用惰性求值
if(this.lazy){
this.dirty=true;
}elseif(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的实例详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,请注明出处,谢谢!