vue Proxy数据代理校验
神奇大叔 人气:2initProxy
数据拦截的思想除了为构建响应式系统准备,它也可以为数据进行筛选过滤,我们接着往下看初始化的代码,在合并选项后,vue接下来会为vm实例设置一层代理,这层代理可以为vue在模板渲染时进行一层数据筛选
Vue.prototype._init = function(options) { // 选项合并 ... { // 对vm实例进行一层代理 initProxy(vm); } ... }
initProxy
// 代理函数 var initProxy = function initProxy (vm) { //是否支持Proxy if (hasProxy) { var options = vm.$options; //当使用类似webpack这样的打包工具时,通常会使用vue-loader插件进行模板的编译,这个时候options.render是存在的,并且_withStripped的属性也会设置为true(关于编译版本和运行时版本的区别可以参考后面章节),所以此时代理的选项是hasHandler //在其他场景下,代理的选项是getHandler var handlers = options.render && options.render._withStripped ? getHandler : hasHandler; // 代理vm实例到vm属性_renderProxy vm._renderProxy = new Proxy(vm, handlers); } else { vm._renderProxy = vm; } };
hasProxy
var hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy);
hasHandler
var hasHandler = { // key in obj或者with作用域时,会触发has的钩子 has: function has (target, key) { ··· } };
触发代理
源码中vm._renderProxy的使用出现在Vue实例的_render方法中,Vue.prototype._render是将渲染函数转换成Virtual DOM的方法,这部分是关于实例的挂载和模板引擎的解析
当我们调用render函数时,代理的vm._renderProxy对象便会访问到
而这个render函数就是包装成with的执行语句,在执行with语句的过程中,该作用域下变量的访问都会触发has钩子,这也是模板渲染时之所有会触发代理拦截的原因
之所以会触发数据代理拦截是因为模板中使用了变量,例如<div>{{message}}}
Vue.prototype._render = function () { ··· // 调用vm._renderProxy vnode = render.call(vm._renderProxy, vm.$createElement); } ========================================= var vm = new Vue({ el: '#app' }) console.log(vm.$options.render) //输出, 模板渲染使用with语句 ƒ anonymous() { with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(message)+_s(_test))])} }
数据过滤
通过data选项去设置实例数据,那么这些数据可以随着个人的习惯任意命名吗?显然不是的,如果你使用js的关键字(像Object,Array,NaN)去命名,这是不被允许的。另一方面,Vue源码内部使用了以$,_作为开头的内部变量,所以以$,_开头的变量名也是不被允许的,这就构成了数据过滤监测的前提。
var hasHandler = { has: function has (target, key) { var has = key in target; // isAllowed用来判断模板上出现的变量是否合法。 var isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); // _和$开头的变量不允许出现在定义的数据中,因为他是vue内部保留属性的开头。 // 1. warnReservedPrefix: 警告不能以$ _开头的变量 // 2. warnNonPresent: 警告模板出现的变量在vue实例中未定义 //has判断是否是target对象中的变量 if (!has && !isAllowed) { if (key in target.$data) { warnReservedPrefix(target, key); } else { warnNonPresent(target, key); } } return has || !isAllowed } }; // 模板中允许出现的非vue实例定义的变量 var allowedGlobals = makeMap( 'Infinity,undefined,NaN,isFinite,isNaN,' + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 'require' // for Webpack/Browserify );
在浏览器不支持proxy的情况下的数据过滤
在没有经过代理的情况下,使用_开头的变量依旧会 报错,但是它变成了js语言层面的错误。但是这个报错无法在Vue这一层知道错误的详细信息,而这就是能使用Proxy的好处。
在初始化数据阶段,Vue已经为数据进行了一层筛选的代理。具体看initData对数据的代理
有了isReserved的筛选,即使this._data._test存在,我们依旧无法在访问this._test时拿到_test变量
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 }
proxy
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); }
总结
加载全部内容