Proxy
用於修改
某些操做的默認行爲
,等同於在語言層面
作出修改,因此屬於一種「元編程」
,即對編程語言進行編程
。javascript
Proxy
改變默認行爲vue
Proxy
能夠理解成,在目標對象以前架設一層「攔截」
,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問
進行過濾和改寫
。java
var proxy = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
複製代碼
上面代碼對一個空對象架設了一層攔截
,重定義
了屬性的讀取
(get)和設置
(set)行爲。對設置了攔截行爲的對象obj
,去讀寫它的屬性,就會獲得下面的結果。web
proxy.count = 1
// setting count!
++proxy.count
// getting count!
// setting count!
// 2
複製代碼
上面代碼說明,Proxy 實際上重載了點運算符
,即用本身的定義
覆蓋了語言的原始定義
。編程
var proxy = new Proxy(target, handler);
複製代碼
要使得
Proxy
起做用,必須針對Proxy實例
(上例是proxy對象)進行操做
,而不是針對目標對象
(上例是空對象)進行操做數組若是
handler沒有設置
任何攔截,那就等同於直接通向原對象
,沒有任何攔截效果,訪問proxy
就等同於訪問target
。markdown
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
複製代碼
var object = { proxy: new Proxy(target, handler) };
複製代碼
必須是
new
之後生成的實例,纔會觸發攔截app
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
//obj對象自己並無time屬性,因此根據原型鏈,會在proxy對象上讀取該屬性,致使被攔截。
複製代碼
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
複製代碼
get(target, propKey, receiver)
:攔截對象屬性的讀取
,好比proxy.foo
和proxy['foo']
。set(target, propKey, value, receiver)
:攔截對象屬性的設置
,好比proxy.foo = v
或proxy['foo'] = v
,返回一個布爾值。has(target, propKey)
:攔截propKey in proxy的操做
,返回一個布爾值。deleteProperty(target, propKey)
:攔截delete proxy[propKey]
的操做,返回一個布爾值。ownKeys(target)
:攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環
,返回一個數組。該方法返回目標對象
全部自身的屬性
的屬性名
,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。getOwnPropertyDescriptor(target, propKey)
:攔截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回屬性的描述對象。defineProperty(target, propKey, propDesc)
:攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs)
,返回一個布爾值。preventExtensions(target)
:攔截Object.preventExtensions(proxy)
,返回一個布爾值。getPrototypeOf(target)
:攔截Object.getPrototypeOf(proxy)
,返回一個對象。isExtensible(target)
:攔截Object.isExtensible(proxy)
,返回一個布爾值。setPrototypeOf(target, proto)
:攔截Object.setPrototypeOf(proxy, proto)
,返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。apply(target, object, args)
:攔截 Proxy 實例做爲函數調用的操做
,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
。construct(target, args)
:攔截 Proxy 實例做爲構造函數調用的操做
,好比new proxy(...args)
。方法 | 描述 |
---|---|
handler.apply() | 攔截 Proxy 實例做爲函數調用的操做 |
handler.construct() | 攔截 Proxy 實例做爲構造函數調用的操做 |
handler.defineProperty() | 攔截 Object.defineProperty() 的操做 |
handler.deleteProperty() | 攔截 Proxy 實例刪除屬性操做 |
handler.get() | 攔截 讀取屬性的操做 |
handler.set() | 攔截 屬性賦值的操做 |
handler.getOwnPropertyDescriptor() | 攔截 Object.getOwnPropertyDescriptor() 的操做 |
handler.getPrototypeOf() | 攔截 獲取原型對象的操做 |
handler.has() | 攔截 屬性檢索操做 |
handler.isExtensible() | 攔截 Object.isExtensible()操做 |
handler.ownKeys() | 攔截 Object.getOwnPropertyDescriptor() 的操做 |
handler.preventExtension() | 攔截 Object().preventExtension() 操做 |
handler.setPrototypeOf() | 攔截Object.setPrototypeOf()操做 |
Proxy.revocable() | 建立一個可取消的 Proxy 實例 |
攔截某個屬性的讀取操做dom
target
:必選、目標對象
編程語言
key
:必選、被讀取的屬性名
,在get內部是字符串
receiver
:可選、proxy
實例自己(嚴格地說,是操做行爲所針對的對象)
須要return
若是一個屬性
不可配置(configurable)且不可寫(writable)
,則 Proxy不能修改該屬性
,不然經過Proxy
對象訪問該屬性會報錯
。
若是
沒有
這個攔截函數,訪問不存在的屬性,只會返回undefined
。
var person = {
name: "張三"
};
var proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name // "張三"
proxy.age // 拋出一個錯誤
複製代碼
當攔截操做定義在
Prototype
對象上面時,讀取obj對象繼承的屬性(自身沒有的屬性)
時,攔截會生效。
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo // "GET foo"
複製代碼
上面代碼中,攔截
操做定義在Prototype對象上面
,因此若是讀取obj對象繼承的屬性時(自己沒有,經過原型鏈查找的屬性
),攔截會生效。
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
複製代碼
//簡略版
var arr=new Proxy([0,1,2,3,4],{
get(target, p, receiver) {
if(p<0){
var n=eval(target.length-1+p);
return target[n];
}
return target[p];
}
})
console.log(arr[-1]); //3
複製代碼
利用
Proxy get
攔截之後仍然返回Proxy
實例的特性,達到了將函數名鏈式使用的效果
var pipe = (function () {
return function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
//若是獲取的是get 對數組funcStack中的函數一次調用
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
//若是不是get 向數組funcStack中添加函數
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
}());
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
//有點像發佈訂閱
複製代碼
const dom=new Proxy({},{
get(target, p, receiver) {
//(attrs={},...children) 第一個參數爲attrs 剩餘的都是children
return function (attrs={},...children) {
//get屬性名 就是要建立的 元素名稱
const el=document.createElement(p);
//循環 attrs設置元素屬性
for (let prop of Object.keys(attrs)){
el.setAttribute(prop,attrs[prop]);
}
for (let child of children){
if(typeof child === 'string'){
child = document.createTextNode(child);
}
el.append(child)
}
return el;
}
}
})
const el = dom.div(
{},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
//相對與div :{}爲 attrs ,剩餘的都是子級
console.log(el);
document.body.appendChild(el);
複製代碼
老是指向原始的讀操做所在的那個對象,通常狀況下就是 Proxy 實例。
const proxy = new Proxy({}, {
get: function(target, property, receiver) {
return receiver;
}
});
proxy.getReceiver === proxy // true
複製代碼
const proxy = new Proxy({}, {
get: function(target, property, receiver) {
return receiver;
}
});
const d = Object.create(proxy);
d.a === d // true
複製代碼
上面代碼中,d
對象自己沒有a屬性
,因此讀取d.a
的時候,會去d的原型proxy對象找
。這時,receiver就指向d
,表明原始的讀操做所在的那個對象。
攔截
某個屬性的賦值操做
target
:目標對象
prop
:屬性名,在get內部是字符串
value
:屬性值
receiver
:可選,Proxy 實例自己若是目標對象自身的某個屬性,不可寫且不可配置,那麼set方法將不起做用。
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 對於知足條件的 age 屬性以及其餘屬性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 報錯
person.age = 300 // 報錯
複製代碼
const handler = {
get (target, key) {
invariant(key, 'get');
return target[key];
},
set (target, key, value) {
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
複製代碼
set方法的第四個參數receiver,指的是原始的操做行爲所在的那個對象,通常狀況下是proxy實例自己,跟get的第三個參數的運用相同
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true
複製代碼
target:
目標對象
context:目標對象的
上下文對象
(this
)args:目標對象的
參數數組
var handler = {
apply (target, ctx, args) {
return Reflect.apply(...arguments);
}
};
複製代碼
var fn=function () {
//不執行
console.log(this,'fn')
return 'I am fn'
}
var p=new Proxy(fn,{
apply(target, thisArg, argArray) {
console.log(thisArg,'p') //undefined 'p'
return 'I am p'
}
})
console.log(p()); //I am p
複製代碼
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
//直接調用Reflect.apply方法,也會被攔截。
Reflect.apply(proxy, null, [9, 10]) // 38
複製代碼
用於
攔截new命令
target:
目標對象
args:構造函數的
參數對象
newTarget:
可選
,創造實例對象時,new命令做用的構造函數
(new 後面的函數)
var handler = {
construct (target, args, newTarget) {
return new target(...args);
}
};
複製代碼
var p = new Proxy(function () {}, {
construct: function(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});
(new p(1)).value
// "called: 1"
// 10
複製代碼
攔截HasProperty操做
,即判斷對象是否具備某個屬性時
,這個方法會生效。典型的操做就是in運算符
。
依次兩個形參
target:目標對象
key:需查詢的屬性名
下面的例子使用has方法隱藏某些屬性,不被in運算符發現。
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
複製代碼
用於攔截delete操做
經過
拋出錯誤
或者返回false
,阻止delete命令刪除。
依次兩個形參:目標對象、需查刪除的屬性名
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
delete target[key];
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property
複製代碼
Proxy.revocable
方法返回一個對象,該對象的proxy
屬性是Proxy
實例,revoke
屬性是一個函數,能夠取消Proxy
實例。當執行
revoke
函數以後,再訪問Proxy
實例,就會拋出一個錯誤
。
使用場景是
,目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
複製代碼
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
複製代碼
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined
複製代碼
上面代碼中,目標對象jane
的name
屬性,實際保存在外部WeakMap
對象_name
上面,經過this
鍵區分。因爲經過proxy.name
訪問時,this
指向proxy
,致使沒法取到值,因此返回undefined
。
也就是說
this
必須是對應類的實例才能拿到的內部屬性
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate();
// TypeError: this is not a Date object.
複製代碼
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);
proxy.getDate() // 1
複製代碼