You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functioninitProps(vm: Component,propsOptions: Object){constpropsData=vm.$options.propsData||{}constprops=vm._props={}// cache prop keys so that future props updates can iterate using Array// instead of dynamic object key enumeration.constkeys=vm.$options._propKeys=[]constisRoot=!vm.$parent// root instance props should be convertedif(!isRoot){toggleObserving(false)}for(constkeyinpropsOptions){keys.push(key)constvalue=validateProp(key,propsOptions,propsData,vm)/* istanbul ignore else */if(process.env.NODE_ENV!=='production'){consthyphenatedKey=hyphenate(key)if(isReservedAttribute(hyphenatedKey)||config.isReservedAttr(hyphenatedKey)){warn(`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,vm)}defineReactive(props,key,value,()=>{if(vm.$parent&&!isUpdatingChildComponent){warn(`Avoid mutating a prop directly since the value will be `+`overwritten whenever the parent component re-renders. `+`Instead, use a data or computed property based on the prop's `+`value. Prop being mutated: "${key}"`,vm)}})}else{defineReactive(props,key,value)}// static props are already proxied on the component's prototype// during Vue.extend(). We only need to proxy props defined at// instantiation here.if(!(keyinvm)){proxy(vm,`_props`,key)}}toggleObserving(true)}
functioninitData(vm: Component){letdata=vm.$options.datadata=vm._data=typeofdata==='function'
? getData(data,vm)
: data||{}if(!isPlainObject(data)){data={}process.env.NODE_ENV!=='production'&&warn('data functions should return an object:\n'+'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconstkeys=Object.keys(data)constprops=vm.$options.propsconstmethods=vm.$options.methodsleti=keys.lengthwhile(i--){constkey=keys[i]if(process.env.NODE_ENV!=='production'){if(methods&&hasOwn(methods,key)){warn(`Method "${key}" has already been defined as a data property.`,vm)}}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)}elseif(!isReserved(key)){proxy(vm,`_data`,key)}}// observe dataobserve(data,true/* asRootData */)}
data 的初始化也是做两件事,首先对定义 data 函数返回的对象进行一次遍历,通过 proxy 把每一个值 vm._data.xxx 都代理到 vm.xxx 上;另一个是调用 observe 方法观测整个 data 的变化,把 data 也变成响应式,可以通过 vm._data.xxx 访问到定义 data 返回函数中对应的属性。
/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
export functionobserve(value: any,asRootData: ?boolean): Observer|void{if(!isObject(value)||valueinstanceofVNode){return}letob: Observer|voidif(hasOwn(value,'__ob__')&&value.__ob__instanceofObserver){
ob =value.__ob__}elseif(shouldObserve&&!isServerRendering()&&(Array.isArray(value)||isPlainObject(value))&&Object.isExtensible(value)&&!value._isVue){ob=newObserver(value)}if(asRootData&&ob){ob.vmCount++}returnob}
如果 value 不是一个对象,并且是一个VNode,就直接返回。接着判断 valu e有没有 __ob__ 属性,并且它是一个 Observer 的实例的话,就返回这个 __ob__。
// root instance props should be convertedif(!isRoot){toggleObserving(false)}
注释上说:根的props应该需要观测,所以它的逻辑里,如果不是root就设置为false,那也就走不到 ob = new Observer(value) 这个逻辑了,这样就决定了:非根props是不会执行 new Observer 的,也就不会变成 Observer 的实例,所以这个 shouldObserve 就是控制要不要变成 Observer 实例的。
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */
export classObserver{value: any;
dep: Dep;
vmCount: number;// number of vms that has this object as root $dataconstructor(value: any){this.value=valuethis.dep=newDep()this.vmCount=0def(value,'__ob__',this)if(Array.isArray(value)){constaugment=hasProto
? protoAugment
: copyAugmentaugment(value,arrayMethods,arrayKeys)this.observeArray(value)}else{this.walk(value)}}/** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */walk(obj: Object){constkeys=Object.keys(obj)for(leti=0;i<keys.length;i++){defineReactive(obj,keys[i])}}/** * Observe a list of Array items. */observeArray(items: Array<any>){for(leti=0,l=items.length;i<l;i++){observe(items[i])}}}
它可以理解为定义了一个观察者的类,在每次 new 它的时候,都会有一个 value 值,会实例化一个 dep,会有一个计数的 vmCount 等等,然后会调用 def 函数,这个 def 的定义是这样的:
Vue.js实现响应式原理的核心是利用ES5的
Object.defineProperty
,而 IE8 一下是没有这个东西的,所以这也就是为什么Vue.js不能兼容IE8及以下的原因。Object.defineProperty
Object.defineProperty
会在一个对象上定义一个属性,或者修改一个现有属性,并返回这个对象,它的用法如下:Obj
参数是要定义属性的对象,prop
是定义或修改的属性名称,descriptor
是将被定义或修改的描述符。使用这种方式来操作对象的时候,最关键的就是
get
和set
,get
是给一个属性提供的getter
方法,在访问对象的属性的时候使用,set
是给一个属性提供的setter
方法,在修改对象的属性的时候使用(这块可以看重学JavaScript【对象的结构、创建和继承关系】)。一旦对象有了
getter
和setter
,就可以简单的把该对象理解为 响应式对象,在Vue.js里被定义成响应式对象的对象,有initState
,initProps
和initData
。initState
在Vue初始化的时候有一个
_init
方法,里面有一个initState
:这个方法的作用是初始化了
props
,data
,methods
,computed
,watcher
等,它的定义在src/core/instance/state.js
里:这里重点关注一下
props
和data
。initProps
initProps
的定义也在src/core/instance/state.js
里:props
的初始化过程,主要就是遍历定义的props
配置,在遍历期间调用了一个defineReactive
函数,这个函数就是把传入的props
对象上的key
变成一个响应式的,然后通过vm._props.xxx
就可以访问到定义props
中对应的属性,该方法在下面有分析。在下面还使用了一个proxy
,这个proxy
之前也分析过,这里就可以通过proxy
把vm._props.xxx
的访问代理到vm.xxx
上,下面还会再分析一下它。initData
initData
的定义也在src/core/instance/state.js
里:data
的初始化也是做两件事,首先对定义data
函数返回的对象进行一次遍历,通过proxy
把每一个值vm._data.xxx
都代理到vm.xxx
上;另一个是调用observe
方法观测整个data
的变化,把data
也变成响应式,可以通过vm._data.xxx
访问到定义data
返回函数中对应的属性。不管是
props
还是data
,它们的初始化都是把它们变成一个响应式对象,在这个过程中会走几个函数,下面来具体分析一下。proxy
在new Vue发生了什么事情文章里有分析过它的作用,这里再提一下:
proxy
定义了get
和set
,通过Object.defineProperty
在参数 target(就是vm) 上定义了_data
属性,从而把我们常写的this.xx
代理到this._data.xx
上(也就是代理到实例上,可以理解为vm._data.xx
),这样就可以在data
或者methods
里拿到并且使用xx
。上面是对
data
的,对于props
而言也一样,对vm._props.xxx
的读写就变成了vm.xxx
的读写,而对于vm._props.xxx
我们可以访问到定义在props
中的属性,所以我们就可以通过vm.xxx
访问到定义在props
中的xxx
属性了。observe
observe
的功能就是用来监测数据变化的,它的定义在src/core/observer/index.js
中:如果
value
不是一个对象,并且是一个VNode,就直接返回。接着判断valu
e有没有__ob__
属性,并且它是一个Observer
的实例的话,就返回这个__ob__
。下一个判断有一个
shouldObserve
布尔值,它有一个改变值的方法:这个方法在上面的
initProps
上调用了一次:注释上说:根的props应该需要观测,所以它的逻辑里,如果不是root就设置为false,那也就走不到
ob = new Observer(value)
这个逻辑了,这样就决定了:非根props是不会执行new Observer
的,也就不会变成Observer
的实例,所以这个shouldObserve
就是控制要不要变成Observer
实例的。整体来看的话,
observe
的作用就是给非VNode的对象数据添加一个Observer
,如果已经添加过就直接返回,否则满足一些条件的话,就实例化一个Observer
对象实例。Observer
Observer
的定义是这样的:它可以理解为定义了一个观察者的类,在每次 new 它的时候,都会有一个
value
值,会实例化一个dep
,会有一个计数的vmCount
等等,然后会调用def
函数,这个def
的定义是这样的:也就是封装了一下
Object.defineProperty
。这里调用
def
的目的是,给value
添加一个__ob__
属性,并且这个属性指向了当前实例,目的是第一次定义了它之后,在接下来后面调用observe
的话,进行到hasOwn(value, '__ob__')
判断的时候,可以直接返回当前实例。接着判断
value
是数组的话就执行observeArray
方法(递归数组元素,观察每一个元素),否则就是对象,就执行walk
方法(遍历每一个键,从而观察它的值)。这里再分析一下在
Observer
的constructor
里,为什么它要调用def
把__ob__
指向 this,而不是直接value.__ob__ = this
?因为如果
value
是一个对象,就会走walk
,如果用直接赋值的方式(就是value.__ob__ = this
)的话,那walk
就会遍历这个__ob__
,然后执行defineReactive
,而我们不希望它走这一步(因为没必要,我们也不会手动去修改这个__ob__
),所以使用了def
方法,然后传的最后一个参数enumerable
没传,也就是false,也就是不可枚举,这样就不会遍历__ob__
属性了。defineReactive
最后再来分析一下
defineReactive
是如何把参数obj
变成响应式的,它的定义在src/core/observer/index.js
中:通过
Object.getOwnPropertyDescriptor
拿到属性的定义,如果该属性的configurable
是false,就什么都不做。然后尝试拿到该属性的原生get
和set
,如果没有get
,有set
,并且传入了2个参数(其实就是通过walk调用的话),就直接拿默认值。接着如果对象的值是一个对象的话,就递归调用observe
,然后把该对象重写get
和set
,get
主要做的就是依赖收集,set
主要做的就是派发更新。这两个概念在后两篇会详细说一下。The text was updated successfully, but these errors were encountered: