日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

get vue 和set 用法_深入剖析Vue源码 - 数据代理,关联子父组件

發布時間:2025/3/21 vue 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 get vue 和set 用法_深入剖析Vue源码 - 数据代理,关联子父组件 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡單回顧一下這個系列的前兩節,前兩節花了大篇幅講了vue在初始化時進行的選項合并。選項配置是vue實例化的第一步,針對不同類型的選項,vue提供的豐富選項配置策略以保證用戶可以使用不同豐富的配置選項。而在這一節中,我們會分析選項合并后的又兩步重要的操作: 數據代理和關聯子父組件關系,分別對應的處理過程為initProxy和initLifecycle。這章節的知識點也為后續的響應式系統介紹和模板渲染做鋪墊。

2.1 Object.defineProperty和Proxy

在介紹這一章的源碼分析之前,我們需要掌握一下貫穿整個vue數據代理,監控的技術核心:Object.defineProperty 和 Proxy

2.1.1 Object.defineProperty

官方定義:Object.defineProperty()方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 并返回這個對象。 基本用法: Object.defineProperty(obj, prop, descriptor)

我們可以用來精確添加或修改對象的屬性,只需要在descriptor中將屬性特性描述清楚,descriptor的屬性描述符有兩種形式,一種是數據描述符,另一種是存取描述符。

數據描述符

  • configurable:數據是否可刪除
  • enumerable:屬性是否可枚舉
  • value:屬性值,默認為undefined
  • writable:屬性是否可讀寫

存取描述符

  • configurable:數據可改變
  • enumerable:可枚舉
  • get:一個給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。
  • set:一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。

注意: 數據描述符的value,writable 和 存取描述符的get, set屬性不能同時存在,否則會拋出異常。 有了Object.defineProperty方法,我們可以方便的利用存取描述符中的getter/setter來進行數據監聽,在get,set鉤子中分別做不同的操作,這是vue雙向數據綁定原理的雛形,我們會在響應式系統的源碼分析時具體闡述。

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可以完美的解決這一類問題。

2.1.2 Proxy

Proxy 是es6的語法,和Object.defineProperty一樣,也是用于修改某些操作的默認行為,但是和Object.defineProperty不同的是,Proxy針對目標對象,會創建一個新的實例對象,并將目標對象代理到新的實例對象上, 本質的區別就是多了一層代理,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。外界通過操作新的實例對象從而操作真正的目標對象。針對getter和setter的基本用法如下:

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是否能做到呢?

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可以很容易的監聽到數組的變化。

2.2 initProxy

有了這些理論基礎,我們往下看vue的源碼,在初始化合并選項后,vue接下來的操作是為vm實例設置一層代理,代理的目的是為vue在模板渲染時進行一層數據篩選。如果瀏覽器不支持Proxy,這層代理檢驗數據則會失效。(檢測數據會放到其他地方檢測)

{// 對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屬性_renderProxyvm._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()) }

看到這里時,心中會有幾點疑惑。 - 什么時候會觸發這層代理進行數據檢測? - getHandler 和 hasHandler的場景分別是什么?

要解決這個疑惑,我們接著往下看:

  • 1.在組件的更新渲染時會調用vm實例的render方法(具體模板引擎如何工作,我們放到相關專題在分析),我們觀察到,vm實例的render方法在調用時會觸發這一層的代理。
Vue.prototype._render = function () {···// 調用vm._renderProxyvnode = render.call(vm._renderProxy, vm.$createElement); }

也就是說模板引擎<div>{{message}}</div>的渲染顯示,會通過Proxy這層代理對數據進行過濾,并對非法數據進行報錯提醒。

  • 2.handers函數會根據options.render 和 options.render._withStripped執行不同的代理函數getHandler,hasHandler。當使用類似webpack這樣的打包工具時,我們將使用vue-loader進行模板編譯,這個時候options.render 是存在的,并且_withStripped的屬性也會設置為true,關于編譯版本和運行版本的區別不在這一章節展開。先大致了解使用場景即可。

2.2.1 代理場景

接著上面的問題,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) // areturn 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的版本。

2.2.2 代理檢測過程

接著上面的分析,在模板引擎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 );

2.3 initLifecycle

分析完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的渲染。

喜歡本系列的朋友歡迎關注公眾號 "假前端",有源碼解析和算法精選哦

總結

以上是生活随笔為你收集整理的get vue 和set 用法_深入剖析Vue源码 - 数据代理,关联子父组件的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。