作者简介:一名大三的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:JavaScript进阶指南
学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气
前言:
这里是关于代理对象的使用Proxy,有部分方法的具体讲解,简单的方法没有做补充,大家有需要的话,可以去MDN上面查看。 这是我自己的学习JavaScript的笔记,希望可以帮助到大家,欢迎大家的补充和纠正
Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程” 即对语言进行编程
Proxy可以理解成在目标对象前架设一个拦截层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,Proxy这个词的原意是代理,用这里表示由它来代理某些操作,可以译为代理器
var obj=new Proxy({},{
get:function(target,key,receiver){
console.log(`getter ${key}`);
return Reflect.get(target,key,receiver)
},
set:function(target,key,value,receiver){
console.log(`setting ${key}`);
return Reflect.get(target,key,value,receiver)
}
})
obj.count=1
++obj.count
上面的代码说明,Proxy实际上重载了点运算符,即用自己定义的覆盖了语言的原始定义
ES6提供了Proxy构造函数,用于生成Proxy实例
var proxy=new Proxy(target,handler)
参数的含义为:
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。即如果没有proxy介入,操作原来访问的就是这个对象例子如下:
var proxy=new Proxy({},{
get:function(target,property){
return 35
}
})
proxy.time //35
proxy.name //35
proxy.title //35
上面的代码中,配置对象有一个get方法用来拦截对目标对象属性的访问请求。get的两个参数,一个为目标对象,一个为所要访问的属性,可以看到,由于拦截函数总数返回35,所以访问任意的属性都是返回35
❗️ 注意:要使Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例的空对象)进行操作
正是这样设置,所以才更加符合叫为 代理
使用细节:
可以将Proxy对象设置到object.proxy属性中,从而更好在object对象上调用
var object={proxy:new Proxy(target,handler)};
Proxy实例也可以作为其他对象的原型对象
var proxy=new Proxy({},{
get:function(target,property){
return 35
}
})
let obj=Object.create(proxy)
obj.name //35
同一个拦截器函数可以设置拦截多个操作
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]}
}
}
没有打上星号的请去查看MDN文档,MDN文档写的更好
概述:get 方法用于拦截某个属性的读取操作
语法:
get(target,propKey,receiver)
参数:
示例:
1.拦截读取操作
var person={
name:'老六'
}
var proxy=new Proxy(person,{
get:function(target,property){
if(property in target){
return target[property]
}else{
throw new ReferenceError("该属性不存在")
}
}
})
console.log(proxy.name)
console.log(proxy.age)
上面的代码表示,如果访问目标对象不存在的属性,会抛出一个错误,如果没有这个拦截,访问不存在的属性只会返回undefined
2.get方法可以继承
let proto=new Proxy({},{
get(target,property,receiver){
console.log('获取原型的'+property)
return target[property]
}
})
let obj=Object.create(proto);
console.log(obj.name)
//获取原型的name
上面的代码中,拦截操作定义在Prototype对象上,所以如果读取obj对象继承的属性,拦截会生效
⭐️ 3.数组读取负数索引
function createArray(...element){
let handler={
get(target,property,receiver){
let index=Number(property)
if(index<0){
property=String(target.length+index)
}
return Reflect.get(target,property,receiver)
}
}
let target=[]
target.push(...element)
return new Proxy(target,handler)
}
let arr=createArray('a','b','c')
console.log(arr[-2]);
//'b'
⭐️ 4.实现属性的链式操作
<script>
var pipe = (function () {
return 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)
}
//如果直接传入fnName是没有用的,只会单纯当作字符串去处理,实现不了上一步的fn(val) 函数调用,会报错
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
console.log(pipe(3).double.pow.reverseInt.get)
script>
个人整理的实现思路:
4.生成DOM节点的通用函数dom
<script>
const dom = new Proxy({}, {
get(target, property) {
return function (attrs = {}, ...children) {
const el = document.createElement(property)
//设置标签属性
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.appendChild(child)
}
return el;
}
}
})
const el = dom.div({},
'Hello my name is',
dom.a({ href: 'www.baidu.com' }, '百度'),
'. I like',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '...actually that \'s it')
)
)
document.body.appendChild(el)
script>
个人整理的实现思路:
❗️ 注意:如果一个属性不可配置(configurable)或不可写(writable),则该属性不能被代理,通过Proxy对象访问该属性将报错
const target1=Object.defineProperties({},{
foo:{
value:123,
writable:false,
configurable:false
}
})
const handler1={
get(target,property){
return 'abc'
}
};
const proxy1=new Proxy(target1,handler1);
console.log(proxy1.foo);
概述:方法用于拦截某个属性的赋值操作
语法:
const p = new Proxy(target, {
set: function(target, property, value, receiver) {
}
});
参数:
返回值:
true
代表属性设置成功。set()
方法返回 false
,那么会抛出一个 TypeError
异常。实例:
1.有一个age属性,该属性应该是一个不大于200的整数
let validator={
set:function(obj,prop,value){
if(prop==='age'){
if(!Number.isInteger(value)){
throw new TypeError('这个值不是整数')
}
if(value>200){
throw new RangeError('age属性值大于200')
}
}
obj[prop]=value
}
}
let person=new Proxy({},validator)
person.age=100
person.age //100
person.age='123' //报错
person.age=300 //报错
2.防止内部属性被外部读写
var 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(`无法${action}内部属性${key}`)
}
}
var target={}
var proxy=new Proxy(target,handler)
proxy._prop='1'
//无法set内部属性_prop
console.log(proxy._prop);
//无法get内部属性_prop
❗️ 注意:如果目标对象的某个属性不可写也不可配置,那么set不得改变这个属性值,只能返回同样的值。否则报错
概述:拦截函数的调用,call和apply操作
语法:
var p = new Proxy(target, {
apply: function (target, thisArg, argumentsList) {},
});
参数:
实例:
1.方法的基本使用
var target=function(){return '我是目标对象'}
var handler={
apply:function(){
return '我是代理对象'
}
}
var p=new Proxy(target,handler)
console.log(p());
2.方法的进阶使用
var twice={
apply(target,ctx,args){
return Reflect.apply(...arguments) * 2;
}
}
function sum(left,right){
return left+right
}
var proxy=new Proxy(sum,twice)
console.log(proxy(1,2))
console.log(proxy.call(null,5,6))
console.log(proxy.apply(null,[7,8]))
概念:拦截HasProperty操作,即判断对象是否具有某个属性,这个方法会生效,典型的操作就是in运算符
语法:
var p = new Proxy(target, {
has: function (target, prop) {},
});
参数:
使用细节:
实例:
1.使用has方法隐藏某些属性,不被in方法发现
var handler={
has(target,key){
if(key[0] === '_'){
return false
}
return key in target
}
}
var target={
_prop:'foo',
prop:'bar'
}
var proxy=new Proxy(target,handler)
console.log('_prop' in proxy);
概念:用于拦截new命令
语法:
var p = new Proxy(target, {
construct: function (target, argumentsList, newTarget) {},
});
参数
使用细节:
案例:
var p = new Proxy(function () {}, {
construct: function (target, argumentsList) {
console.log("called: " + argumentsList.join(", "));
return { value: argumentsList[0] * 10 };
},
});
console.log(new p(1).value); // "called: 1"; outputs 10
概念:拦截Object.defineProperty操作
语法:
var p = new Proxy(target, {
defineProperty: function (target, property, descriptor) {},
});
参数
for...in
循环和 Object.keys()
方法枚举。默认为 false
。true
,则可以使用 Object.defineProperty()
方法修改属性的描述符。如果为 false
,则不能修改属性的描述符,也不能删除属性。默认为 false
。true
,则属性的值可以被修改。如果为 false
,则属性的值不可修改。默认为 false
。undefined
。当属性的 writable
特性为 true
时,可以修改属性的值。返回值
defineProperty
方法必须以一个 Boolean
返回,表示定义该属性的操作成功与否。
使用细节:
实例:
var handler={
defineProperty(target,key,descriptor){
return false
}
}
var target={};
var proxy=new Proxy(target,handler)
proxy.foo='bar'
//TypeError:proxy defineProperty handler returned false for property "foo"
概述: 拦截获取对象原型,具体来说,用于拦截以下操作
ownKeys方法用来拦截对象自身属性的读取操作,具体来说,拦截以下操作
使用细节:
实例:
1. 拦截Object.keys()的例子
let target={
a:1,
b:2,
c:3
}
let handler={
ownKeys(target){
console.log('输出啦')
return ['a']
}
}
let proxy=new Proxy(target,handler)
console.log(Object.keys(proxy))
//输出啦
//[ 'a' ]
2. 目标对象自身包含不可配置的属性,会报错
var obj={}
Object.defineProperty(obj,'a',{
configurable:false,
enumerable:true,
value:10
})
var p=new Proxy(obj,{
ownKeys:function(target){
return ['b']
}
})
console.log(Object.getOwnPropertyNames(p));
//TypeError: 'ownKeys' on proxy: trap result did not include 'a'
proxy.revocable方法返回一个可取消的Proxy实例
let target1={};
let handler1={};
let {proxy,revoke}=Proxy.revocable(target,handler)
proxy.foo=123
console.log(proxy.foo)
//123
revoke()
console.log(proxy.foo);
//TypeError: Cannot perform 'get' on a proxy that has been revoked
上述代码中,Proxy.revocable方法返回一个对象,其proxy属性是Proxy实例,revoke属性是一个函数,可以取消proxy实例,上面的代码中,当执行revoke函数后访问Proxy实例,就会抛出一个错误。
虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下也无法保证与目标对象的行为一致
主要的原因就是在Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理
this指向会引起下面两种情况的实例:
由于this指向问题的变化导致Proxy无法代理目标对象
const _name=new WeakMap()
class Person{
constructor(name){
_name.set(this,name)
}
get name(){
return _name.get(this)
}
}
const jane=new Person('Jane')
console.log(jane.name);
//'Jane'
const proxy=new Proxy(jane,{});
console.log(proxy.name);
//undefined
原生对象的内部属性只有通过正确的this才能获取
const target=new Date()
const handler={}
const proxy=new Proxy(target,handler)
console.log(proxy.getDate())
//TypeError:this is not a Date object
上面的代码中,getDate方法只能在Date对象实例上面获取,如果this不是Date对象实例就会报错,这时,this绑定原始对象就可以解决这个问题。
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(y=target,handler)
proxy.getDate() //1