- 本章節介紹一下Proxy部分,在此以前須要瞭解一下元編程
一. 元編程
- 元編程是指某類計算機程序的編寫,這類計算機程序
編寫或者操縱其餘程序(或者自身)
做爲他們的數據,或者在運行時完成部分本應在編譯時完成的工做
。不少狀況下與手工編寫所有代碼相比工做效率更高。
- 編寫元程序的語言稱爲元語言,被操縱的語言被稱爲目標語言,
一門語言是自身的元語言的能力被稱爲反射!
- 從ES5開始,js得到對
Proxy,Reflect對象的支持,容許攔截並定義基本語言操做的自定義行爲
,例如(屬性查找,賦值,枚舉,函數調用等),藉助 Proxy,Reflect這兩個對象,能夠在js進行元編程
二. 概述
1. 基本含義
- Proxy用於修改某些操做的
默認行爲
,等同於在語言層面作出修改,因此屬於元編程(meta programming),即對編程語言進行編程
- Proxy能夠理解爲
在目標對象以前架設一層攔截
,外界對該對象的訪問,都必須先經過這層攔截。
- 所以能夠對
外界的訪問進行過濾和改寫
,Proxy的本意就是代理,在js中就是表示用Proxy來代理某些操做,做爲代理器
- 一個簡單的get讀取操做攔截的例子
// 聲明一個攔截器對象,屬性就是要監聽的對象屬性
var handler={ get:function(target,name){ if(target.hasOwnProperty(name)){ return `對象包含有${name}屬性` }else{ return `對象沒有${name}屬性` } } } // 給obj對象綁定攔截器,而後建立攔截器實例 var obj={name:'yiye',f:()=>{}} var p=new Proxy(obj,handler) // 1. 直接使用Proxy實例的屬性 console.log(p.name);//對象包含有name屬性 console.log(p.age);//對象沒有age屬性 // 1.2 給Proxy實例賦值(可是被攔截了,因此修改不生效,除非Proxy去修改) p.name="改變" console.log(p.name);//對象包含有name屬性 // 2.此時沒有攔截函數,因此會提示不是函數 console.log(p.f());//p.f is not a function // 3. 若是調用對象不存在的函數則會提示變量不是函數 console.log(p.foo());//p.foo is not a function 複製代碼
- 能夠把proxy做爲對象屬性綁定到對象中
var obj={proxy:new Proxy(target,handler)}
proxy有也能夠做爲其餘對象的原型對象,Object.create(obj)
var handler={
get:function(target,proxyKey){ return 35 } } var a={name:'hh'} // 經過obj.proxy.xxx的形式使用代理器 // new Proxy(obj,handler)的形式建立代理器 var obj={proxy:new Proxy(Object.create(a),handler)} console.log(obj.proxy.name);//35 console.log(obj.proxy.age);//35 複製代碼
2. 參數
- new Proxy(target,handler)接收兩個參數,表示目標對象和內部監聽對象,handler爲{}空對象
表示不設置攔截,至關於直接訪問原對象
- handler內部監聽的方法爲
set的時候
有三個參數,(target,name,value),target表示目標對象,name表示屬性名,value表示值
handler監聽器監聽的方法不同的時候,參數個數和含義也都不同
- 下面是一個監聽set操做的例子
// 聲明一個攔截器
var handler={ set:function(target,name,value){ target[name]="set+"+value; } } // 給obj對象綁定攔截器,而後建立攔截器實例 var obj={name:'yiye',f:()=>{}} var p=new Proxy(obj,handler) // 1. 直接使用Proxy實例的屬性 console.log(p.name);//yiye // 1.2 給Proxy實例賦值 p.name="改變" console.log(p.name);//set+改變 複製代碼
3.proxy做爲一種設計模式
- 程序設計中存在一種設計模式爲
代理模式,Proxy Pattern
- 所謂的代理者是指一個類別能夠
做爲其餘東西的接口
,代理者能夠做爲任何東西的接口:網絡鏈接,內存中的大對象,文件或其餘昂貴或沒法複製的資源。
垃圾回收機制中的引用計數方法就是使用了代理模式
- 當一個對象被引用時,建立代理者,每一個代理者都會引用到對象。而做用在代理者的運算會傳遞到對象中。
一旦全部的代理者都不存在,那麼對象就會被清除,也就是當成垃圾回收了
三.Proxy實例的方法
- proxy實例的方法共有13種,在此只介紹使用較多的幾種。
1. get()
- get方法用於攔截對象屬性的
讀取操做
,接受三個參數,依次爲目標對象、屬性名和 proxy 實例自己
(嚴格地說,是操做行爲所針對的對象),其中最後一個參數可選。
- 訪問目標對象不存在的屬性,
也會執行到proxy監聽的get讀取操做
,若是不存在攔截器,那麼返回的是undefined
監聽的get方法能夠繼承!
var a={name:'a'}
var proxy=new Proxy(a,{ get(target,name,value){ if(target.hasOwnProperty(name)) return 'get+'+target[name] return '不存在該屬性' } }) var child=Object.create(proxy) console.log(child);//{name: "eee",__proto__:Proxy},此處的Proxy是監聽器對象Proxy,而不是函數 console.log(child.__proto__);//不存在該屬性 console.log(child.prototype);//不存在該屬性 // 當對象child不存在屬性name的時候,指向原型對象的屬性 console.log(child.name);//get+a // 此時給對象添加屬性name,那麼就指向對象自身的屬性 child.name='eee' console.log(child.name);//eee 複製代碼
var pipe = function (value) {
var funcStack = []; // 存儲函數 var oproxy = new Proxy({} , { get : function (pipeObject, fnName) { if (fnName === 'get') { return funcStack.reduce(function (val, fn) { return fn(val); },value); } funcStack.push(window[fnName]); return oproxy; // 返回proxy代理器 } }); return oproxy; // 返回proxy代理器 } var double = n => n * 2; var pow = n => n * n; var reverseInt = n => n.toString().split("").reverse().join("") | 0; // 數字反轉 // 至關於 reverseInt(pow(double(3)));//3*2=6,6*6=36,36的倒序爲63 console.log(pipe(3).double.pow.reverseInt.get) // 63 複製代碼
2. set()
- set方法有四個參數,分別是
目標對象,屬性名,屬性值,proxy實例自己,最後一個參數可選
- 一個實例:
age屬性的值大於200就提示錯誤
var obj={age:10,name:'yiye'}
var person=new Proxy(obj,{ set:function(newobj,name,val,pro){ if(name==='age'){ if(val>200){ throw new Error("this age is too max") } } newobj[name]=val;// 更新 } }) console.log(person);//Proxy {age: 10, name: "yiye"} console.log(obj);//{age: 10, name: "yiye"} // 直接修改目標對象的屬性 obj.age=210 console.log(obj);//{age: 210, name: "yiye"} console.log(person);//Proxy {age: 210, name: "yiye"} // 修改proxy代理對象的屬性(也生效!) person.age=20;// 此時都不會報錯,而且數值一直 console.log(person);//Proxy {age: 20, name: "yiye"} console.log(obj);//{age: 20, name: "yiye"} // person.age=220;// 此時超過限制 console.log(obj); //Uncaught Error: this age is too max console.log(person); //Uncaught Error: this age is too max 複製代碼
限制
- 當對象的某個屬性
不可寫,那麼set方法的監聽將失效
var obj={age:10,name:'yi'}
Object.defineProperty(obj,'foo',{ writable:false, value:'foo' }) var person=new Proxy(obj,{ set:function(target,name,value,receiver){ target[name]="set+"+value; } }) console.log(person);//Proxy {age: 10, name: "yi", foo: "foo"} // 修改foo屬性 person.foo="f" // 可是是修改不生效,依舊爲foo! console.log(person);//Proxy {age: 10, name: "yi", foo: "foo"} // 此時修改其餘屬性,會生效的! person.age=10000 person.name='good' console.log(person);//Proxy {age: "set+10000", name: "set+good", foo: "foo"} 複製代碼
3. apply()
apply方法攔截的有三種,函數的調用!!!,call綁定,apply綁定!
- 接受三個參數,分別是目標對象,目標對象的上下文,目標對象的參數數組
// 1. 建立一個監聽apply操做的代理器
var sum=function(a,b){ return a+b } var proxy=new Proxy(sum,{ apply:function(target,ctx,args){ console.log(target,ctx,args); /* ƒ (a,b){ return a+b } undefined (2) [1, 2] */ return sum(...args); } }) // 2.函數調用 console.log(proxy(1,2));//3 // 3. call調用 console.log(proxy.call(null,2,3));//5 // 4. apply調用 console.log(proxy.apply(null,[4,5]));//9 複製代碼
三.Proxy.revocable()
- Proxy.revocable()返回一個可取消的Proxy實例
- 返回的是一個對象,對象的
proxy屬性是proxy實例,revoke屬性是一個函數,能夠取消proxy實例
,執行revoke函數以後,再訪問proxy實例,就會拋出一個錯誤
var obj={age:22,name:'ww'}
var handler={} var {proxy,revoke}=Proxy.revocable(obj,handler); console.log(proxy);//Proxy {age: 22, name: "ww"} // 執行revoke函數,取消代理 revoke(); console.log(proxy);//Proxy {} // 此時再訪問proxy實例的屬性會報錯提示代理已被取消 console.log(proxy.age);// Cannot perform 'get' on a proxy that has been revoked 複製代碼
四. this指向問題
- proxy會進行代理,可是這種代理
不會使得this指向改變
- 若是在監聽的方法中不對this指向作綁定,那麼使用的是this指向的規則
var obj={
m:function(){ console.log(this===proxy) } } var handler={} var proxy=new Proxy(obj,handler) // obj對象調用m屬性方法,因此內部this指的是obj obj.m();//false // proxy代理器實例對象調用m屬性方法,因此內部this指向的是proxy proxy.m();//true 複製代碼
若是須要綁定this指向target目標對象,那麼就
const target = new Date('2015-01-01');
const handler = { get(target, prop) { if (prop === 'getDate') { return target.getDate.bind(target); } return Reflect.get(target, prop); } }; const proxy = new Proxy(target, handler); console.log(target.getDate());//1 // 此時即便是proxy對象調用方法,屬性內部的this依舊指向目標對象target console.log(proxy.getDate()) // 1 複製代碼