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!
推薦閱讀:
- Vue實例初始化為渲染函數設置檢查源碼剖析
- Vue2為何能通過this訪問到data與methods的屬性
- Vue2 this 能夠直接獲取到 data 和 methods 的原理分析
- 源碼揭秘為什麼 Vue2 this 能夠直接獲取到 data 和 methods
- Vue Computed底層原理深入探究