初入vue 的大门,对vue 在data 中定义一个值,在DOM 中通过双花括号{{}} 就可以访问的到,对此我是很好奇的,本着好奇的心思,打开了vue 的源码,探索这个奇妙的过程。问题的根源就在于这个方法:Object.defineProperty()定义对象的属性相关描述符, 其中的set和get函数对于完成数据双向绑定起到了至关重要的作用。下面我们就通过一个简单的例子来解释一个这个方法:var obj = { name: “south Joe” } Object.defineProperty(obj, ‘foo’, { get: function () { console.log(‘将要读取obj.name属性’); }, set: function (newVal) { console.log(‘当前值为’, newVal); } }); obj.name; // 将要读取obj.foo属性 obj.name = ‘yangyang’; // 当前值为 name那么解释完这个属性之后呢,我们就深入vue 源码,了解一下在源码背后做了一些什么呢?我们调用Vue,并传入一个data;import Vue from ‘vue’new Vue({ el: ‘#app’, mounted() { console.log(this.message); console.log(this._data.message); }, data() { return { message: ‘hello vue’ } }})我们实例化一个Vue 的实例,并传入了一个对象;调用了源码中的这个方法:function Vue (options) { if (process.env.NODE_ENV !== ‘production’ && !(this instanceof Vue) ) { warn(‘Vue is a constructor and should be called with the new
keyword’); } // 执行_init 方法;_init 是Vue 原型上的方法,该函数在调用initMixin 的时候,在Vue的原型上定义的;同时传入options; this._init(options);}在_init 方法中,做了一堆初始化的操作; 定义uid,合并options,我们这里主要看一个方法initState(vm);Vue.prototype._init = function (options) { debugger var vm = this; // a uid vm._uid = uid$3++; var startTag, endTag; /* istanbul ignore if / if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) { startTag = “vue-perf-start:” + (vm._uid); endTag = “vue-perf-end:” + (vm._uid); mark(startTag); } // a flag to avoid this being observed vm._isVue = true; // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options); } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } / istanbul ignore else / if (process.env.NODE_ENV !== ‘production’) { initProxy(vm); } else { vm._renderProxy = vm; } // expose real self vm._self = vm; initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, ‘beforeCreate’); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, ‘created’); / istanbul ignore if / if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) { vm._name = formatComponentName(vm, false); mark(endTag); measure((“vue " + (vm._name) + " init”), startTag, endTag); } if (vm.$options.el) { vm.$mount(vm.$options.el); } };在initState 中我们初始化了一个initData();function initState (vm) { vm._watchers = []; var opts = vm.$options; if (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); }}看到这里,大家是不是有些着急了呢,关键的地方马上就要来了,请大家耐心。在initData中我们主要获取到data 中的对象,data 可以是一个对象,也可以是一个函数;还在里面做了一个代理proxy(vm, _data, key)function initData (vm: Component) { // 拿到¥options 上的data; data 可以是一个函数,但也可以是一个对象; let data = vm.$options.data // 这里的赋值导致可以通过this._data.message data = 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 instance // 拿到对象的key;data,props,method 不能重名,因为他们最终都会挂载到vm上,当前这个实例上,那么是怎么实现的,就是通过proxy(vm, _data
, key); const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i–) { const key = keys[i] if (process.env.NODE_ENV !== ‘production’) { // hasOwn 判断对象里面是不是又这个key 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 顾名思义就是一层代理, proxy(vm, _data
, key) } } // observe data // 响应式处理; observe(data, true / asRootData */)}将数据代理到实例上const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop}export function proxy (target: Object, sourceKey: string, key: string) { // 通过一个对象定义了一个get,和一个set; sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } // 通过Object.defineProperty 代理了这个target,key ,对target,key 的访问做了一层get,set; // target 就是vm, key 就是_data // 当我们访问this.message ,就是访问this._data.message,执行的是get方法; // 不要通过这种方式去访问,this._data.message, 下划线在编程界默认是私有属性; Object.defineProperty(target, key, sharedPropertyDefinition)}当我们执行this.message 的时候,就会调用get 方法,当我们给this.message 赋值的时候就会调用set方法;到这里,大家是不是就很清楚vue 的双向数据绑定了呢?职场小白south Joe,望各位大神批评指正,祝大家学习愉快!