亲宝软件园·资讯

展开

vue Proxy数据代理校验

神奇大叔 人气:2

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);
}

总结 

加载全部内容

相关教程
猜你喜欢
用户评论