响应式对象
Vue.js 实现响应式的核心是利用了 ES5 的 Object.defineProperty,这也是为什么 Vue.js 不能兼容 IE8 及以下浏览器的原因,我们先来对它有个直观的认识。
Object.defineProperty
Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,先来看一下它的语法:
Object.defineProperty(obj, prop, descriptor)
- obj 是要在其上定义属性的对象;
- prop 是要定义或修改的属性的名称;
- descriptor 是将被定义或修改的属性描述符
详情请看文档
initState
在Vue做初始化阶段是执行_init(options)方法中会执行一个initState(vm)的方法,这个方法的主要操作:
- 初始化props
- 初始化data
- 初始化Computed
- 初始化Watcher
- 初始化Methods
这里就是判断对应的props、data、computed、watcher、methods是否存在,存在执行对应的初始化方法export function initState (vm: Component) {vm._watchers = []const opts = 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)}}
先重点分析props、data
initProps
function initProps (vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}const props = vm._props = {}// cache prop keys so that future props updates can iterate using Array// instead of dynamic object key enumeration.const keys = vm.$options._propKeys = []const isRoot = !vm.$parent// root instance props should be convertedif (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)const value = validateProp(key, propsOptions, propsData, vm)/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {const hyphenatedKey = 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 (!(key in vm)) {proxy(vm, `_props`, key)}}toggleObserving(true)}
initProps主要实现就是遍历props中的每个属性,执行defineReactive(props,key,value)将每个值转为响应式数据
defineReactive()如何将对象的每个属性转为响应式数据后面在详细介绍。
initData
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === '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 instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = 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)} else if (!isReserved(key)) {proxy(vm, `_data`, key)}}// observe dataobserve(data, true /* asRootData */)}
initData也做了两件事,第一就是遍历data返回的对象属性,通过proxy把每个属性都可以通过vm.key访问值,第二则是调用ovserve观察这个data的变化,把data中的每个属性变为响应式。
