Vue中的代理Proxy和$ref

1、   代理Proxy

1.   介紹vue

1. Proxy又稱爲代理。在現實生活中,你們對代理二字並不會太陌生,好比某產品的代理。打個比方來講,咱們要買一臺手機,咱們不會直接到一家手機廠去買,會在手機的代理商中買。node

2. JavaScript中,Proxy用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種元編程,即對編程語言進行編程。web

   3.    Proxy能夠理解成,在目標對象以前架設一層攔截,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy這個詞的原意是代理,用在這裏表示由它來代理某些操做,能夠譯爲代理器。編程

2. 基本語法設計模式

1 // @param {Object} target 用來被代理的對象 api

2 // @param {Object} handler 用來設置代理的對象瀏覽器

3 let proxy = new Proxy(target, handler)安全

1 Proxy,見名知意,其功能很是相似於設計模式中的代理模式,該模式經常使用於三個方面:app

2 攔截和監視外部對對象的訪問編程語言

3 下降函數或類的複雜度

4 在複雜操做前對操做進行校驗或對所需資源進行管理

5 在支持 Proxy 的瀏覽器環境中,Proxy 是一個全局對象,能夠直接使用。Proxy(target, handler) 是一個構造函數,target 是被代理的對象,handlder 是聲明瞭各種代理操做的對象,最終返回一個代理對象。外界每次經過代理對象訪問 target 對象的屬性時,就會通過 handler 對象,從這個流程來看,代理對象很相似 middleware(中間件)。那麼 Proxy 能夠攔截什麼操做呢?最多見的就是 get(讀取)、set(修改)對象屬性等操做,完整的可攔截操做列表請點擊這裏。此外,Proxy 對象還提供了一個 revoke 方法,能夠隨時註銷全部的代理操做。

一個代理的例子:

1 const target = { name: 'Billy Bob', age: 15 };
  
  
  
  
2 const handler = { get(target, key, proxy) { const today = new Date();
3         console.log(`GET request made for ${key} at ${today}`);
 return Reflect.get(target, key, proxy); } };
4 const proxy = new Proxy(target, handler);
5 proxy.name; // => "GET request made for name at Thu Jul 21 2016 15:26:20 GMT+0800 (CST)" // => "Billy Bob"

 

 

1.Reflect稱爲反射。它也是ES6中爲了操做對象而提供的新的API,用來替代直接調用Object的方法。Reflect是一個內置的對象,它提供可攔截JavaScript操做的方法。方法與代理處理程序的方法相同。Reflect不是一個函數對象,所以它是不可構造的。

Reflect與大多數全局對象不一樣,Reflect沒有構造函數。你不能將其與一個new運算符一塊兒使用,或者將Reflect對象做爲一個函數來調用。

1.   處理器對象handler

處理器對象handler一共提供了14種可代理操做,每種操做的代號(屬性名/方法名)和觸發這種操做的方式以下:

 

handler.getPrototypeOf():在讀取代理對象的原型時觸發該操做,好比在執行Object.getPrototypeOf(proxy)

handler.setPrototypeOf():在設置代理對象的原型時觸發該操做,好比在執行Object.setprototypeOf(proxy, null)

handler.isExtensible():在判斷一個代理對象是不是可擴展時觸發該操做,好比在執行Object.isExtensible(proxy)

handler.preventExtensions():在讓一個代理對象不可擴展時觸發該操做,好比在執行Object.preventExtensions(proxy)

handler.getOwnPropertyDescriptor():在獲取代理對象某個屬性的屬性描述時觸發該操做,好比在執行Object.getOwnPropertyDescriptor(proxy, 'foo')

handler.defineProperty():在定義代理對象某個屬性時的屬性描述時觸發該操做,好比在執行Object.defineProperty(proxy,'foo',{})

handler.has():在判斷代理對象是否擁有某個屬性時觸發該操做,好比在執行'foo' in proxy

handler.get():在讀取代理對象的某個屬性時觸發該操做,好比在執行proxy.foo

handler.set():在給代理對象的某個賦值時觸發該操做,好比在執行proxy.foo = 1

handler.deleteProperty():在刪除代理對象的某個屬性時觸發該操做,好比在執行delete proxy.foo

handler.ownKeys():在獲取代理對象的全部屬性鍵時觸發該操做,好比在執行Object.getOwnPropertyNames(proxy)

handler.apply():在調用一個目標對象爲函數的代理對象時觸發該操做,好比在執行proxy()

handler.construct():在給一個目標對象爲構造函數的代理對象構造實例時觸發該操做,好比在執行new proxy()

Reflect對象擁有對應的能夠控制各類元編程任務的靜態方法。這些功能和Proxy一一對應。

下面的這些名稱你可能看起來很眼熟(由於他們也是Object上的方法):

 

Reflect.getOwnPropertyDescriptor(..)

Reflect.defineProperty(..)

Reflect.getPrototypeOf(..)

Reflect.setPrototypeOf(..)

Reflect.preventExtensions(..)

Reflect.isExtensible(..)

這些方法和在Object上的同名方法同樣。而後,一個區別在於,Object上這麼方法的第一個參數是一個對象,Reflect遇到這種狀況會扔出一個錯誤。

 

 

 

 

3.   陷阱代理

使用set陷阱驗證屬性

假設建立一個屬性值是數字的對象,對象中每新增一個屬性都要加以驗證,若是不是數字必須拋出錯誤。爲了實現這個任務,能夠定義一個set陷阱來覆寫設置值的默認特性。

set陷阱接受4個參數:

trapTaqget 用於接收屬性(代理的目標)的對象

key 要寫入的屬性鍵(字符串或Symbol類型)

value 被寫入屬性的值

receiver 操做發生的對象(一般是代理)

Reflect.set()set陷阱對應的反射方法和默認特性,它和set代理陷阱同樣也接受相同的4個參數,以方便在陷阱中使用。若是屬性已設置陷阱應該返回true,若是未設置則返回false(Reflect.set()方法基於操做是否成功來返回恰當的值)

 

可使用set陷阱並檢查傳入的值來驗證屬性值:

 

1 //set陷阱並檢查傳入的值來驗證屬性值

 2 let target = { name: "target" };

 3 let proxy = new Proxy(target, {

 4     set(trapTarget, key, value, receiver) {

 5         // 忽略已有屬性,避免影響它們

 6         if (!trapTarget.hasOwnProperty(key)) {

 7             if (isNaN(value)) { throw new TypeError("Property must be a number."); }

 8         } // 添加屬性

 9         return Reflect.set(trapTarget, key, value, receiver);

10     }

11 });

12 // 添加一個新屬性

13 proxy.count = 1;

14 console.log(proxy.count); // => 1

15 console.log(target.count); // => 1 // 你能夠爲 name 賦一個非數值類型的值,由於該屬性已經存在

16 proxy.name = "proxy";

17 console.log(proxy.name); // => "proxy"

18 console.log(target.name); // => "proxy"

// 拋出錯誤 proxy.anotherName = "proxy";

這段代碼定義了一個代理來驗證添加到target的新屬性,當執行proxy.count=1時,set陷阱被調用,此時trapTarget的值等於targetkey等於"count"value等於1receiver等於proxy

因爲target上沒有count屬性,所以代理繼續將value值傳入isNaN(),若是結果是NaN,則證實傳入的屬性值不是數字,同時也拋出一個錯誤。在這段代碼中,count被設置爲1,因此代理調用Reflect.set()方法並傳入陷阱接受的4個參數來添加新屬性。

proxy.name能夠成功被賦值爲一個字符串,這是由於target已經擁有一個name屬性,但經過調用trapTarget.hasownproperty()方法驗證檢查後被排除了,因此目標已有的非數字屬性仍然能夠被操做。

然而,將proxy.anotherName賦值爲一個字符串時會拋出錯誤。目標上沒有anotherName屬性,因此它的值須要被驗證,而因爲"Proxy"不是一個數字值,所以拋出錯誤。

set代理陷阱能夠攔截寫入屬性的操做,get代理陷阱能夠攔截讀取屬性的操做

用get陷阱驗證對象結構(Object Shape)

JS有一個時常使人感到困惑的特殊行爲,即讀取不存在的屬性時不會拋出錯誤,而是用undefined代替被讀取屬性的值。

1 //get陷阱驗證對象結構(Object Shape)

2 let target = {};

3 console.log(target.name); // => undefined

對象結構是指對象中全部可用屬性和方法的集合,JS引擎經過對象結構來優化代碼,一般會建立類來表示對象,若是能夠安全地假定一個對象將始終具備相同的屬性和方法,那麼當程序試圖訪問不存在的屬性時會拋出錯誤。代理讓對象結構檢驗變得簡單。

 

由於只有當讀取屬性時纔會檢驗屬性,因此不管對象中是否存在某個屬性,均可以經過get陷阱來檢測,它接受3個參數:

 

trapTarget 被讀取屬性的源對象(代理的目標)

key 要讀取的屬性鍵(字符串或Symbol)

receiver 操做發生的對象(一般是代理)

因爲get陷阱不寫入值,因此它復刻了set陷阱中除value外的其餘3個參數,Reflect.get()也接受一樣3個參數並返回屬性的默認值。

 

若是屬性在目標上不存在,則使用get陷阱和Reflect.get()時會拋出錯誤:

 

 

let proxy = new Proxy({}, {

    get(trapTarget, key, receiver) {

        if (!(key in receiver)) {

            throw new TypeError("Property " + key + " doesn't exist.");

        }

        return Reflect.get(trapTarget, key, receiver);

    }

});

// 添加屬性的功能正常

proxy.name = "proxy";

console.log(proxy.name); // => "proxy"

// 讀取不存在屬性會拋出錯誤

console.log(proxy.nme); // => 拋出錯誤

 

 

 

原型代理陷阱

Object.setPrototypeOf()方法被用於做爲ES5中的Object.getPrototypeOf()方法的補充。經過代理中的setPrototypeOf陷阱和getPrototypeOf陷阱能夠攔截這兩個方法的執行過程,在這兩種狀況下,Object上的方法會調用代理中的同名陷阱來改變方法的行爲。

兩個陷阱均與代理有關,但具體到方法只與每一個陷阱的類型有關,setPrototypeOf陷阱接受如下這些參數:

trapTarget 接受原型設置的對象(代理的目標)

proto 做爲原型使用的對象

傳入Object.setPrototypeOf()方法和Reflect.setPrototypeOf()方法的均是以上兩個參數,另外一方面,getPrototypeOf陷阱中的Object.getPrototypeOf()方法和Reflect.getPrototypeOf()方法只接受參數trapTarget

原型代理陷阱的運行機制

原型代理陷阱有一些限制。首先,getPrototypeOf陷阱必須返回對象或null,不然將致使運行時錯誤,返回值檢查能夠確保Object.getPrototypeOf()返回的老是預期的值;其次,在setPrototypeOf陷阱中,若是操做失敗則返回的必定是false,此時Object.setPrototypeOf()會拋出錯誤,若是setPrototypeOf返回了任何不是false的值,那麼Object.setPrototypeOf()便假設操做成功。

 

如下示例經過老是返回null,且不容許改變原型的方式隱藏了代理的原型:

1 let target = {};

 2 let proxy = new Proxy(target, {

 3     getPrototypeOf(trapTarget) {

 4         return null;

 5     },

 6     setPrototypeOf(trapTarget, proto) {

 7         return false;

 8     }

 9 });

10 let targetProto = Object.getPrototypeOf(target);

11 let proxyProto = Object.getPrototypeOf(proxy);

12 console.log(targetProto === Object.prototype); // => true

13 console.log(proxyProto === Object.prototype); // => false

14 console.log(proxyProto); // => null

// 成功

15 Object.setPrototypeOf(target, {});

// 拋出錯誤

16 Object.setPrototypeOf(proxy, {});

 

代碼強調了target和proxy的行爲差別。Object.getPrototypeOf()給target返回的是值,而給proxy返回值時,因爲getPrototypeOf陷阱被調用,返回的是null;一樣,Object.setPrototypeOf()成功爲target設置原型,而給proxy設置原型時,因爲setPrototypeOf陷阱被調用,最終拋出一個錯誤。

1、   vue$ref

 

一個象,持有註冊 ref 特性 的全部 DOM 元素和例。

ref 被用來元素或子件註冊引用信息。引用信息將會註冊在父件的 $refs 對象上。若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子件上,引用就指向例:

<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>

<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>

ref屬性不是一個標準的HTML屬性,只是Vue中的一個屬性。實際上,它甚至不會是DOM的一部分,因此在瀏覽器中你查看渲染的HTML,你是看不到有關於ref的任何東西。由於在它前面沒有添加:,並且它也不是一個指令

<div ref="demo"></div>

document.querySelector('[ref=demo]');

相關文章
相關標籤/搜索