响应式对象Vue.js实现响应式的核心是ES5的Obect.defineProperty的使用。Obect.definePropertyObect.defineProperty会直接在一个对象上定义一个新的属性,或者修改一个对象已有的属性,语法如下:Object.defineProperty(obj,prop,descriptor)obj是定义属性的对象;prop是要定义或修改的属性名;描述符是要定义或修改的属性的名称。其中,描述符的可选键值get和set值得注意,get是为属性提供的getter方法;set是为属性提供的setter方法。当我们访问属性时会触发getter方法,当我们修改属性时会触发setter方法。如果一个对象有getter和setter方法,那么我们可以简单地认为这个对象是一个反应对象。但是vue是怎么把对象变成响应式的呢?接下来看源码~initState是vue初始化阶段。当_init方法执行时,会执行initState(vm)方法,在src/core/instance/state.js可以找到它的定义:exportfunctioninitState(vm:Component){vm._watchers=[]constopts=vm.$optionsif(opts.props)initProps(vm,opts.props)if(opts.methods)initMethods(vm,opts.methods)if(opts.data){initData(vm)}else{observe(vm._data={},true/*asRootData*/)}if(opts.computed)initComputed(vm,opts.computed))if(opts.watch&&opts.watch!==nativeWatch){initWatch(vm,opts.watch)}}initState方法主要是初始化props、data、methods、computed、watch等属性。下面只分析道具和数据。initPropsfunctioninitProps(vm:Component,propsOptions:Object){constpropsData=vm.$options.propsData||}{}constprops=vm._props={}//缓存道具键,以便未来的道具更新可以使用数组迭代//而不是动态对象键枚举。constkeys=vm.$options._propKeys=[]constisRoot=!vm.$parent//根实例props应该被转换if(!isRoot){toggleObserving(false)}for(constkeyinpropsOptions){keys.push(key)constvalue=validateProp(key,propsOptions,propsData,vm)/*伊斯坦布尔忽略其他*/if(process.env.NODE_ENV!=='production'){consthyphenatedKey=hyphenate(key)if(isReservedAttribute(hyphenatedKey)||config.isReservedAttr(hyphenatedKey)){warn(`"${hyphenatedKey}"是保留属性,不能用作组件prop.`,vm)}defineReactive(props,key,value,()=>{如果(!isRoot&&!isUpdatingChildComponent){warn(`避免直接修改prop,因为每当父组件重新渲染时,值将被`+`覆盖。`+`相反,使用基于prop的数据或计算属性`+`value.Propbeingmutated:"${key}"`,vm)}})}else{defineReactive(props,key,value)}//在Vue.extend()期间,静态属性已经在组件的原型上代理//.我们只需要代理定义在//此处实例化的道具。if(!(keyinvm)){proxy(vm,`_props`,key)}}toggleObserving(true)}props初始化过程就是props的Configure遍历,遍历主要做两件事:一是使用defineReactive方法使每个prop对应的value成为responsive,可以通过vm._props.xxx访问对应的properties;另一种是使用代理方法将vm._props.xxx代理到vm.xxxinitDatafunctioninitData(vm:Component){letdata=vm.$options.datadata=vm._data=typeofdata==='function'?getData(数据,虚拟机):数据||{}if(!isPlainObject(data)){data={}process.env.NODE_ENV!=='production'&&warn('数据函数应该返回一个对象:\n'+'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}//在实例上代理数据constkeys=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(`方法"${key}"已经被定义为数据属性。`,vm)}}if(props&&hasOwn(props,key)){process.env.NODE_ENV!=='production'&&warn(`数据属性“${key}”已经被设置作为道具宣布。`+`使用prop默认值代替。`,vm)}elseif(!isReserved(key)){proxy(vm,`_data`,key)}}//观察数据observe(data,true/*asRootData*/)}从源码中我们可以看出data其实是一个返回对象的函数,它会检查data中的属性名是否与methods和props中的相同。如果重复,它会给出警告。Next数据的初始化过程也做了两件事:一是使用proxy将每个值vm._data.xxx代理到vm.xxx;另一种是用observe观察这个数据的变化,把数据改成Responsive。从这两个方法可以看出,props和data的初始化将它们变成了响应式对象。这个过程使用了proxy,下面我们来分析一下他们做了什么。Proxy首先让我们了解下代理。proxy就是将props和data的属性代理到vm实例中,这也是为什么我们可以通过下面的方式来访问属性。props:{msg:'hello,vue'},methods:{sayMsg(){console.log(this.msg)}}我们可以在syaMsg函数中通过this.msg访问到props中定义的msg,这个过程就发生了在代理阶段constsharedPropertyDefinition={enumerable:true,configurable:true,get:noop,set:noop}exportfunctionproxy(target:Object,sourceKey:string,key:string){sharedPropertyDefinition.get=functionproxyGetter(){returnthis[sourceKey][key]}sharedPropertyDefinition.set=functionproxySetter(val){this[sourceKey][key]=val}Object.defineProperty(target,key,sharedPropertyDefinition)}proxy是这样实现的:通过Object.defineProperty将targetsourceKey的读写改为target[key]的读写。所以对于props来说,读写vm._props.xxx就变成了读写vm.xxx,使用vm._props.xxx可以访问props中的属性,所以我们也可以使用vm.xxx访问props中的属性.同理,数据也是如此。注意在实际开发中不要直接使用vm._props.xxx来访问对象属性,因为_一般表示内部访问的observeobserve函数是用来检测数据变化的,函数定义位于src/core/observe/index.js/***尝试为一个值创建一个观察者实例,*如果观察成功,则返回新的观察者,*如果该值已经有一个,则返回现有的观察者。*/exportfunctionobserve(value:any,asRootData:?boolean):观察者|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}observe方法的作用是给一个非Observer增加一个ObserverVNode对象。如果已经添加,则直接返回,否则,如果满足条件,则实例化一个Observer对象。接下来我们看看Observer是干什么用的。ObserverObserver是一个类,它的作用是给象添加getter和setter,用于依赖收集和派发更新:/***Observerclassthatisattachedtoeachobserved*object.一旦附加,观察者将目标*对象的属性键转换为收集依赖项和调度更新的getter/setter。*/exportclassObserver{值:任意;部门:部门;vmCount:数量;//以这个对象为根的虚拟机数量$dataconstructor(value:any){this.value=valuethis.dep=newDep()this.vmCount=0def(value,'__ob__',this)if(Array.isArray(value)){if(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}this.observeArray(value)}else{this.walk(value)}}/***遍历所有属性并将它们转换为*getter/setter。仅当*值类型为Object时才应调用此方法。*/walk(obj:Object){constkeys=Object.keys(obj)for(l我=0;我<键.长度;i++){defineReactive(obj,keys[i])}}/***观察数组项列表。*/observeArray(items:Array
