es6之Proxy

什么Proxy

proxy从字面的意思去理解就是代理、代理模式。

在JavaScript中,是ES6的新增对象。

在MDN上,对Proxy的解释为:Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

个人理解是:Proxy可以让你对JavaScript中的对象的基本操作进行自定义,想咋折腾就咋折腾。当对象去执行基本操作时,就会去执行你自定义的操作,而不是对象本身的操作。

他人理解:Proxy可以在目标对象之前架设一层‘拦截’,外界对该对象的访问,都必须先通过这层拦截,因此提供了这样一层机制,可以对外界的访问进行过滤和修改。

Proxy使用

let proxy = new Proxy(target, handler);

target——是要包装的对象,可以是任何东西,包括函数

handler——代理配置:带有拦截操作方法的对象,如get用于读取target的属性,set用于写入target的属性

//创建一个空handler的proxy
let target = {};
let proxy = new Proxy(target,{}); //空的handler对象

proxy.test = 5;  //写入proxy对象
console.log(target.test); //5——test属性出现在了target中

console.log(proxy.test); //5——从proxy中也可以读取到test属性
for(let key in proxy) console.log(key);  //test——迭代也能正常工作

上述代码中,handler是一个空对象,没有任何拦截效果,因此访问proxy就等同于访问target.

Proxy拦截方法

对于对象的大多数操作,JavaScript规范中有一个内部方法,它描述了最底层的工作方式。例如[[Get]],用于读取属性的内部方法,[[Set]]用于写入属性的内部方法。这些方法仅在规范中使用,我们不能直接通过方法名调用它们。Proxy会拦截这些方法的调用,下表是规定的方法:

内部方法Handler 方法何时触发
[[Get]]get读取属性
[[Set]]set写入属性
[[HasProperty]]hasin 操作符
[[Delete]]deletePropertydelete 操作符
[[Call]]apply函数调用
[[Construct]]constructnew 操作符
[[GetPrototypeOf]]getPrototypeOfObject.getPrototypeOf
[[SetPrototypeOf]]setPrototypeOfObject.setPrototypeOf
[[IsExtensible]]isExtensibleObject.isExtensible
[[PreventExtensions]]preventExtensionsObject.preventExtensions
[[DefineOwnProperty]]definePropertyObject.defineProperty, Object.defineProperties
[[GetOwnProperty]]getOwnPropertyDescriptorObject.getOwnPropertyDescriptor, for…in, Object.keys/values/entries
[[OwnPropertyKeys]]ownKeysObject.getOwnPropertyNames, Object.getOwnPropertySymbols, for…in, Object/keys/values/entries

get方法

该方法用于读取属性

而要拦截读取操作,handler应该有get(target, property, receiver)方法

读取属性时触发该方法,参数如下:

  1. target——目标对象,
  2. property——目标属性名
  3. receiver——如果目标属性时一个getter访问器属性,则receiver就是本次读取属性所在的this对象,也就是proxy对象本身。目前不需要该参数
//实现一个对不存在的数组项返回0的数组
let array = [0,1,2];
array = new Proxy(array,{
   get(target, prop){
       if(prop in target){
           return target[prop];
       }else{
           return 0; //默认值
       }
   } 
});

console.log(array[1]);  //1
console.log(array[5]); //0,不存在该项,本应该返回undefined,但是修改后可以返回0

代理应该在所有地方都完全替代目标对象,目标对象被代理后,就不应该再去引用目标对象,否则就容易造成混乱,而是通过代理来操作。

set方法

//set用于设置属性,当写入属性时,set方法就会被触发

set(target, property, value, receiver)

  1. target——目标对象,该对象被作为第一个参数传递给new Proxy

  2. property——目标属性名称

  3. value——目标属性值

  4. receiver——与get方法类似

如果写入操作(setting)成功,set方法将返回true,否则返回false

//一个只能用用于存放数字的数组,如果添加了其他类型的值,则报错
let numbers = [];
numbers = new Proxy(numbers, {
    set(target, prop, val){  //拦截写入属性操作
        if(typeof val == 'number'){
            target[prop] = val;
            return true;  //必须要返回true,否则也会报错TyperError
        }else{
            return false;
        }
    }
});
//proxy拦截的只是[[Set]]操作,但是数组的其他方法不会拦截
numbers.push(1);
numbers.push(2);
console.log(numbers.length);  //2
numbers.push('test'); //TypeError

ownKeys & getOwnPropertyDescriptor

Object.keys,for…in循环和大多数遍历对象属性的方法都是使用内部方法[[OwnPropertyKeys]]来获取属性列表,参照上面的方法表,该内部方法对应了一些方法:

  • Object.getOwnPropertyNames(obj) 返回非 Symbol 键。
  • Object.getOwnPropertySymbols(obj) 返回 symbol 键。
  • Object.keys/values() 返回带有 enumerable 标志的非 Symbol 键/值
  • for…in 循环遍历所有带有 enumerable 标志的非 Symbol 键,以及原型对象的键
//使用ownKeys方法来拦截for...in的遍历,跳过下划线_开头的属性
let user = {
    name:'simon',
    age:24,
    _password: '******'
};
user = new Proxy(user,{
    ownKeys(target){
        return Object.keys(target).filter(key=>!key.startsWith('_'));
    }
});

for(let key in user) console.log(key);  //name,age
console.log(Object.keys(user)); //name,age
console.log(Object.values(user)); //simone,24

deleteProperty方法

JavaScript没有公共变量与私有变量之分,但是平常遵循一个约定,就是下划线_开头的属性和方法都是内部的,不应该从外部访问它们。

//实际上我们在外部也是可以访问的
let user = {
  name:'simon',
  _password:'******',
};
console.log(user._password); //******

但是通过Proxy可以禁止访问下划线开头的属性和方法:

  1. get 读取此类型属性时抛出错误
  2. set写入此类型属性时抛出错误
  3. deleteProperty删除属性时抛出错误
  4. ownKeys在使用for…in和Object.keys这样的方法时也排除_开头的属性
let user = {
    name:'simon',
    _password:'******'
};
user = new Proxy(user,{
   get(target,prop){  //拦截属性读取
       if(prop.startsWith('_')){
           throw new Error('禁止访问');
       }
       let value = target[prop];
       //需要value.bind(target)时因为防止当对象方法需要用到内部变量时,就不会触发错误,如需要检查密码,就需要用到内部变量,通过bidn来绑定到对象上,也就能访问了
       return (typeof(value) === 'function') ? value.bind(target) : value;
   },
   set(target, prop, val){  //拦截属性写入
       if(prop.startsWith('_')){
           throw new Error('禁止写入');
       }else{
           target[prop] = val;
           return true;
       }
   },
    deleteProperty(target, prop){ //拦截属性删除
        if(prop.startsWith('_')){
            throw new Error('禁止删除');
        }else{
            delete target[prop];
            return true;
        }
    },
    ownKeys(target){  //拦截属性读取属性列表
        return Object.keys(target).filter(key=>!key.startsWith('_'));
    },
});

try{  //不允许访问
    console.log(user._password); //Error:禁止访问
}catch(e){console.log(e.message);}

try{  //不允许修改
    user._password = 'test';  //Error:禁止写入
}catch(err){console.log(err.message);}

try{ //也不能新增带_的属性
    user._age = 24;  //Error:禁止写入
}catch(err){console.log(err.message);}

try{ //能新增普通属性
    user.job = 'student';  //
}catch(err){console.log(err.message);}

try{
    delete user._password;  //Error:禁止删除
}catch(e){console.log(e.message);}

for(let key in user) console.log(key); //name,job

has方法

has 会拦截in的调用

has(target, property):

target——目标对象,

property——属性名称

let range = {
  start: 1,
  end: 10
};
console.log(2 in range); //false
range = new Proxy(range,{
   has(target, prop){
       return prop >= target.start && prop <= target.end; 
   } 
});

console.log(8 in range); //true
console.log(11 in range); //false

apply

proxy可以对apply方法代理,基本语法apply(target, thisArg,args):

  1. target——目标对象,这里一般指的函数
  2. thisArg——this的值
  3. args——参数列表
function delay(fun,ms){
    return ()=>{
        setTimeout(()=> fun.apply(this,arguments),ms);
    }
}
function sayHello(user){
    console.log(`Hello, ${user}!`);
}
console.log(sayHello.length); //1——length表示函数参数的个数
sayHello = delay(sayHello, 3000);
console.log(sayHello.length); //0——在包装器中,参数个数为0
//proxy可以将所有东西转发到目标对象
function delay(f, ms) {
  return new Proxy(f, {
    apply(target, thisArg, args) {
      setTimeout(() => target.apply(thisArg, args), ms);
    }
  });
}
function sayHi(user) {
  alert(`Hello, ${user}!`);
}
sayHi = delay(sayHi, 3000);
console.log(sayHi.length); // 1——原函数中的参数会全部传过来
sayHi("simon"); // Hello, simon!

proxy与Object.defineProperty的区别

proxy和Object.defineProperty都可用于数据绑定,都能监听数据的变化,但是两者也存在区别:

  1. Object.defineProperty(object,prop,description)是ES5提供的方法,该方法能在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。description所表示的属性描述符有两种形式:数据描述符和存取描述符,具体可见认识Object.defineProperty
  2. Proxy(target,handler)是ES6提供的方法,Object.defineProperty只能重定义属性的读取(get)和设置(set)内部方法,Proxy提供了更多的内部方法,如in、delelte、ownKeys等

参考链接: https://zh.javascript.info/proxy#proxy-apply

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值