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數據代理校驗內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: