Vue2.X源码学习笔记数据代理
Posted 我真的好难
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue2.X源码学习笔记数据代理相关的知识,希望对你有一定的参考价值。
接上一篇文章的内容,我们回到_init()函数。
Vue.prototype._init = function (options) { var vm = this; // a uid vm._uid = uid$3++; var startTag, endTag; /* istanbul ignore if */ if (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 */ { initProxy(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 (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); } };
我们在上一篇文章已经分析到了选项的合并操作的代码,接下来,代码将调用initProxy方法,对Vue的实例vm进行一层代理。这层代理可以为Vue在渲染模板时做一层数据筛选。
initProxy = function initProxy (vm) { if (hasProxy) { // determine which proxy handler to use var options = vm.$options; var handlers = options.render && options.render._withStripped ? getHandler : hasHandler; vm._renderProxy = new Proxy(vm, handlers); } else { vm._renderProxy = vm; } };
hasProxy代表浏览器支持Proxy对象,在上一篇文章中,我们已经对实例化传入的选项进行了一系列的合并操作,此时的options就是合并操作后的选项对象,这个时候,options选项是没有render属性的,所以handlers会被赋值hasHandler对象。此时通过Proxy创建一个新对象赋值给vm._renderProxy,并对vm实例进行代理。
var hasHandler = { has: function has (target, key) { var has = key in target;
//isAllowed用来判断模板上出现的变量是否合法。
//_和$开头的变量不允许出现在定义的数据中,因为它是Vue内部定义的变量开头。
//warnReserverdPrefix 警告变量不允许以$,_开头,warnNonPresent警告出现的变量在vue实例中未定义。 var isAllowed = allowedGlobals(key) || (typeof key === \'string\' && key.charAt(0) === \'_\' && !(key in target.$data)); if (!has && !isAllowed) { if (key in target.$data) { warnReservedPrefix(target, key); } else { warnNonPresent(target, key); } } return has || !isAllowed } };
当访问vm._renderProxy对象时,会触发对vm实例的数据筛选,那么,什么时候会访问到vm._renderProxy呢,在源码中有几处地方调用到render函数,我们截取一处来说明。
vnode = render.call(vm._renderProxy, vm.$createElement);
在这里我们先不管render函数的实现细节,我们只需要知道render函数是包装成with的执行语句,Proxy对象在with作用域下,变量的访问都会触发has钩子,通过代理会对vm实例进行一层层的数据筛选。通过代码注释我们可以知道筛选的策略。当浏览器不支持Proxy对象时,Vue依然能够对vm实例对象进行代理拦截,原因是在初始化数据阶段,Vue已经为数据进行了一层代理。
function initData(vm) { vm._data = typeof data === \'function\' ? getData(data, vm) : data || {} if (!isReserved(key)) { // 数据代理,用户可直接通过vm实例返回data数据 proxy(vm, "_data", key); } } function isReserved (str) { var c = (str + \'\').charCodeAt(0); // 首字符是$, _的字符串 return c === 0x24 || c === 0x5F }
function proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { // 当访问this[key]时,会代理访问this._data[key]的值 return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); }
由上可知,数据代理与拦截的方式有两种,一种是通过Object.defineProperty,另一种则是通过Proxy,两种方法结合使用,使得Vue内部实现了对数据的拦截处理。这一篇文章篇幅不大,主要分析了initProxy方法执行后究竟发生了什么,下一篇文章我们接着往下分析_init()方法的实现。
以上是关于Vue2.X源码学习笔记数据代理的主要内容,如果未能解决你的问题,请参考以下文章