聊一聊vue的双向数据绑定,为啥子this.message和this._data.message 都能访问到呢?都发生了什么呢
初入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) {debuggervar vm = this;// a uidvm._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 observedvm._isVue = true;// merge optionsif (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 selfvm._self = vm;initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm, 'beforeCreate');initInjections(vm); // resolve injections before data/propsinitState(vm);initProvide(vm); // resolve provide after data/propscallHook(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.messagedata = 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.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {// hasOwn 判断对象里面是不是又这个keyif (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,望各位大神批评指正,祝大家学习愉快!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
