vue Proxy数据代理进行校验部分源码实例解析
作者:神奇大叔
initProxy
数据拦截的思想除了为构建响应式系统准备,它也可以为数据进行筛选过滤,我们接着往下看初始化的代码,在合并选项后,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); }
总结
到此这篇关于vue Proxy数据代理进行校验部分源码解析的文章就介绍到这了,更多相关vue Proxy数据代理校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!