簡單回顧一下這個系列的前兩節,前兩節花了大篇幅講了vue在初始化時進行的選項合併。選項配置是vue實例化的第一步,針對不一樣類型的選項,vue提供的豐富選項配置策略以保證用戶可使用不一樣豐富的配置選項。而在這一節中,咱們會分析選項合併後的又兩步重要的操做: 數據代理和關聯子父組件關係,分別對應的處理過程爲initProxy和initLifecycle。這章節的知識點也爲後續的響應式系統介紹和模板渲染作鋪墊。
在介紹這一章的源碼分析以前,咱們須要掌握一下貫穿整個vue數據代理,監控的技術核心:Object.defineProperty 和 Proxyjavascript
官方定義:Object.defineProperty()方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。
基本用法: Object.defineProperty(obj, prop, descriptor)
咱們能夠用來精確添加或修改對象的屬性,只須要在descriptor中將屬性特性描述清楚,descriptor的屬性描述符有兩種形式,一種是數據描述符,另外一種是存取描述符。前端
數據描述符vue
存取描述符java
注意: 數據描述符的value,writable 和 存取描述符的get, set屬性不能同時存在,不然會拋出異常。
有了Object.defineProperty方法,咱們能夠方便的利用存取描述符中的getter/setter來進行數據監聽,在get,set鉤子中分別作不一樣的操做,這是vue雙向數據綁定原理的雛形,咱們會在響應式系統的源碼分析時具體闡述。node
var o = {} var value; Object.defineProperty(o, 'a', { get() { console.log('獲取值') return value }, set(v) { console.log('設置值') value = v } }) o.a = 'sss' // 設置值 console.log(o.a) // 獲取值 // 'sss'
然而Object.defineProperty的get和set方法只能觀測到對象屬性的變化,對於數組類型的變化並不能檢測到,這是用Object.defineProperty進行數據監控的缺陷,而vue中對於數組類型的方法作了特殊的處理。
es6的proxy能夠完美的解決這一類問題。webpack
Proxy 是es6的語法,和Object.defineProperty同樣,也是用於修改某些操做的默認行爲,可是和Object.defineProperty不一樣的是,Proxy針對目標對象,會建立一個新的實例對象,並將目標對象代理到新的實例對象上, 本質的區別就是多了一層代理,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。外界經過操做新的實例對象從而操做真正的目標對象。針對getter和setter的基本用法以下:git
var obj = {} var nobj = new Proxy(obj, { get(target, property) { console.log('獲取值') return target[property] }, set(target, key, value) { console.log('設置值') return target[key] } }) nobj.a = 1111 // 經過操做代理對象從而映射到目標對象上 // 設置值 // 獲取值 // 1111 console.log(nobj.a)
Proxy 支持的攔截操做有13種之多,具體能夠參照Proxy,上面提到,Object.defineProperty的get和set方法並不能監測到數組的變化,而Proxy是否能作到呢?es6
var arr = [1, 2, 3] let obj = new Proxy(arr, { get: function (target, key, receiver) { console.log("獲取數組"); return Reflect.get(target, key, receiver); }, set: function (target, key, receiver) { console.log('設置數組'); return Reflect.set(target, key, receiver); } }) obj.push(222) // '獲取數組' // '設置數組'
顯然proxy能夠很容易的監聽到數組的變化。github
有了這些理論基礎,咱們往下看vue的源碼,在初始化合並選項後,vue接下來的操做是爲vm實例設置一層代理,代理的目的是爲vue在模板渲染時進行一層數據篩選。若是瀏覽器不支持Proxy,這層代理檢驗數據則會失效。(檢測數據會放到其餘地方檢測)web
{ // 對vm實例進行一層代理 initProxy(vm); } // 代理函數 var initProxy = function initProxy (vm) { // 瀏覽器若是支持es6原生的proxy,則會進行實例的代理,這層代理會在模板渲染時對一些非法或者不存在的字符串進行判斷,作數據的過濾篩選。 if (hasProxy) { var options = vm.$options; var handlers = options.render && options.render._withStripped ? getHandler : hasHandler; // 代理vm實例到vm屬性_renderProxy vm._renderProxy = new Proxy(vm, handlers); } else { vm._renderProxy = vm; } }; 如何判斷瀏覽器支持原生proxy // 是否支持Symbol 和 Reflect var hasSymbol = typeof Symbol !== 'undefined' && isNative(Symbol) && typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); function isNative (Ctor) { // Proxy自己是構造函數,且Proxy.toString === 'function Proxy() { [native code] }' return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) }
看到這裏時,心中會有幾點疑惑。
要解決這個疑惑,咱們接着往下看:
Vue.prototype._render = function () { ··· // 調用vm._renderProxy vnode = render.call(vm._renderProxy, vm.$createElement); }
也就是說模板引擎<div>{{message}}</div>
的渲染顯示,會經過Proxy這層代理對數據進行過濾,並對非法數據進行報錯提醒。
接着上面的問題,vm實例代理時會根據是不是編譯的版本決定使用hasHandler或者getHandler,咱們先默認使用的是編譯版本,所以咱們單獨分析hasHandler的處理函數,getHandler的分析相似。
var hasHandler = { // key in obj或者with做用域時,會觸發has的鉤子 has: function has (target, key) { ··· } };
hasHandler函數定義了has的鉤子,前面介紹過proxy有多達13個鉤子,has是其中一個,它用來攔截propKey in proxy的操做,返回一個布爾值。除了攔截 in 操做符外,has鉤子一樣能夠用來攔截with語句下的做用對象。例如
var obj = { a: 1 } var nObj = new Proxy(obj, { has(target, key) { console.log(target) // { a: 1 } console.log(key) // a return true } }) with(nObj) { a = 2 }
而在vue的render函數的內部,本質上也是調用了with語句,當調用with語句時,該做用域下變量的訪問都會觸發has鉤子,這也是模板渲染時會觸發代理攔截的緣由。
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))])} }
再次思考:咱們知道with語句是不推薦使用的,一個最主要的緣由是性能問題,查找不是變量屬性的變量,較慢的速度會影響性能一系列性能問題。
官方給出的解釋是: 爲了減小編譯器代碼大小和複雜度,而且也提供了經過vue-loader這類構建工具,不含with的版本。
接着上面的分析,在模板引擎render渲染時,因爲with語句的存在,訪問變量時會觸發has鉤子函數,該函數會進行數據的檢測,好比模板上的變量是不是實例中所定義,是否包含_, $這類vue內部保留關鍵字爲開頭的變量。同時模板上的變量將容許出現javascript的保留變量對象,例如Math, Number, parseFloat等。
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內部保留屬性的開頭。 // warnReservedPrefix警告不能以$ _開頭的變量 // warnNonPresent 警告模板出現的變量在vue實例中未定義 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 );
分析完initProxy方法後,接下來是initLifecycle的過程。簡單歸納,initLifecycle的目的是將當前實例添加到父實例的$children
屬性中,並設置自身的$parent
屬性指向父實例。這爲後續子父組件之間的通訊提供了橋樑。舉一個具體的應用場景:
<div id="app"> <component-a></component-a> </div> Vue.component('component-a', { template: '<div>a</div>' }) var vm = new Vue({ el: '#app'}) console.log(vm) // 將實例對象輸出
因爲vue實例向上沒有父實例,因此vm.$parent
爲undefined,vm的$children
屬性指向子組件componentA 的實例。
子組件componentA的 $parent
屬性指向它的父級vm實例,它的$children
屬性指向爲空
源碼解析以下:
function initLifecycle (vm) { var options = vm.$options; // 子組件註冊時,會把父組件的實例掛載到自身選項的parent上 var parent = options.parent; // 若是是子組件,而且該組件不是抽象組件時,將該組件的實例添加到父組件的$parent屬性上,若是父組件是抽象組件,則一直往上層尋找,直到該父級組件不是抽象組件,並將,將該組件的實例添加到父組件的$parent屬性 if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent; } parent.$children.push(vm); } // 將自身的$parent屬性指向父實例。 vm.$parent = parent; vm.$root = parent ? parent.$root : vm; vm.$children = []; vm.$refs = {}; vm._watcher = null; vm._inactive = null; vm._directInactive = false; // 該實例是否掛載 vm._isMounted = false; // 該實例是否被銷燬 vm._isDestroyed = false; // 該實例是否正在被銷燬 vm._isBeingDestroyed = false; }
最後簡單講講抽象組件,在vue中有不少內置的抽象組件,例如<keep-alive></keep-alive>,<slot><slot>
等,這些抽象組件並不會出如今子父級的路徑上,而且它們也不會參與DOM的渲染。
喜歡本系列的朋友歡迎關注公衆號 假前端,有源碼解析和算法精選哦