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的值等於target,key等於"count",value等於1,receiver等於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陷阱被調用,最終拋出一個錯誤。
一個對象,持有註冊過 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]');