0
点赞
收藏
分享

微信扫一扫

【JS】133-重温基础:元编程

【JS】133-重温基础:元编程_元编程

本文是 重温基础 系列文章的第十四篇。
这是第一个基础系列的最后一篇,后面会开始复习一些中级的知识了,欢迎持续关注呀
接下来会统一整理到我的【Cute-JavaScript】的JavaScript基础系列中。

今日感受:独乐乐不如众乐乐。

本章节复习的是JS中的元编程,涉及的更多的是ES6的新特性。

1. 概述

元编程,其实我是这么理解的:让代码自动写代码,可以更改源码底层的功能
元,是指程序本身。
有理解不到位,还请指点,具体详细的介绍,可以查看维基百科 元编程 。
从ES6开始,JavaScrip添加了对 ​​Proxy​​和 ​Reflect​对象的支持,允许我们连接并定义基本语言操作的自定义行为(如属性查找,赋值,枚举和函数调用等),从而实现JavaScrip的元级别编程。

  • Reflect​​: 用于替换直接调用 ​Object​的方法,并不是一个函数对象,也没有 ​constructor​方法,所以不能用 ​new​操作符。
  • Proxy​​: 用于自定义对象的行为,如修改 ​set​和 ​get​方法,可以说是ES5中 ​Object.defineProperty()​方法的ES6升级版。
  • 两者联系: API完全一致,但 ​Reflect​​一般在 ​Proxy​需要处理默认行为的时候使用。

参考资料

名称

地址

Reflect

MDN Reflect

Proxy

MDN Proxy

元编程

MDN 元编程

本文主要从Proxy介绍,还会有几个案例,实际看下怎么使用。

2. Proxy介绍

proxy​ 用于修改某些操作的默认行为,可以理解为一种拦截外界对目标对象访问的一种机制,从而对外界的访问进行过滤和修改,即代理某些操作,也称“代理器”。

2.1 基础使用

基本语法:

  1. ​let p = new Proxy(target, handler);​

proxy​​实例化需要传入两个参数, ​target​​参数表示所要拦截的目标对象, ​handler​参数也是一个对象,用来定制拦截行为。

  1. ​let p = new Proxy({}, {​
  2. ​    get: function (target, handler){​
  3. ​        return 'leo';​
  4. ​    }​
  5. ​})​
  6. ​p.name; // leo​
  7. ​p.age;  // leo​
  8. ​p.abcd; // leo​

上述 ​a​​实例中,在第二个参数中定义了 ​get​​方法,来拦截外界访问,并且 ​get​方法接收两个参数,分别是目标对象所要访问的属性,所以不管外部访问对象中任何属性都会执行 ​get​​方法返回 ​leo​。

注意

  • 只能使用 ​Proxy​实例的对象才能使用这些操作。
  • 如果 ​handler​没有设置拦截,则直接返回原对象。
  1. ​let target = {};​
  2. ​let handler = {};​
  3. ​let p = new Proxy(target, handler);​
  4. ​p.a = 'leo'; ​
  5. ​target.a;  // 'leo'​

同个拦截器函数,设置多个拦截操作

  1. ​let p = new Proxy(function(a, b){​
  2. ​    return a + b;​
  3. ​},{​
  4. ​    get:function(){​
  5. ​        return 'get方法';​
  6. ​    },​
  7. ​    apply:function(){​
  8. ​        return 'apply方法';​
  9. ​    }​
  10. ​})​

这里还有一个简单的案例:

  1. ​let handler = {​
  2. ​    get : function (target, name){​
  3. ​        return name in target ? target[name] : 16;​
  4. ​    }​
  5. ​}​

  6. ​let p = new Proxy ({}, handler);​
  7. ​p.a = 1;​
  8. ​console.log(p.a , p.b);​
  9. ​// 1   16​

这里因为 ​p.a=1​​ 定义了 ​p​​中的 ​a​​属性,值为 ​1​​,而没有定义 ​b​​属性,所以 ​p.a​​会得到 ​1​​,而 ​p.b​​会得到 ​undefined​​从而使用 ​nameintarget?target[name]:16;​​返回的默认值 ​16​;

Proxy​支持的13种拦截操作
13种拦截操作的详细介绍:打开阮一峰老师的链接。

  • 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)。

2.2 取消Proxy实例

使用 ​Proxy.revocable​​方法取消 ​Proxy​实例。

  1. ​let a = {};​
  2. ​let b = {​
  3. ​    get: function(target, name) {​
  4. ​        return "[[" + name + "]]";​
  5. ​    }​
  6. ​};​
  7. ​let revoke = Proxy.revocable(a, b);​
  8. ​let proxy = revoke.proxy;​

  9. ​proxy.age;           // "[[age]]"​
  10. ​revoke.revoke();​
  11. ​proxy.age;           // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked​

  12. ​proxy.age = 10;      // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked​
  13. ​delete proxy.age;    // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked​
  14. ​typeof proxy;        // "object"​

2.3 实现 Web服务的客户端

  1. ​const service = createWebService('http://le.com/data');​
  2. ​service.employees().than(json =>{​
  3. ​    const employees = JSON.parse(json);​
  4. ​})​

  5. ​function createWebService(url){​
  6. ​    return new Proxy({}, {​
  7. ​        get(target, propKey, receiver{​
  8. ​            return () => httpGet(url+'/'+propKey);​
  9. ​        })​
  10. ​    })​
  11. ​}​

3. Proxy实践

3.1 数据拦截验证

通过 ​Proxy​​代理对象的 ​set​​和 ​get​​方法来进行拦截数据,像 ​Vue​就是用数据拦截来实现数据绑定。

  1. ​let handler = {​
  2. ​    // 拦截并处理get方法​
  3. ​    // 可以理解为设置get方法返回的默认值​
  4. ​    get : function (target, key){​
  5. ​        return key in target ? target[key] : 30;​
  6. ​    },​

  7. ​    // 拦截并处理set方法​
  8. ​    // 可以理解为设置set方法的默认行为​
  9. ​    set : function (target, key, value){​
  10. ​        if(key === "age"){​
  11. ​            if (!Number.isInteger(value)){​
  12. ​                throw new TypeError("age不是一个整数!");​
  13. ​            }​
  14. ​            if (value > 200){​
  15. ​                throw new TypeError("age不能大于200!");​
  16. ​            }​
  17. ​        }​
  18. ​        // 保存默认行为​
  19. ​        target[key] = value;​
  20. ​    }​
  21. ​}​

  22. ​let p = new Proxy({}, handler);​
  23. ​p.a = 10;         // p.a   => 10​
  24. ​p.b = undefined;  // p.b   => undefined​
  25. ​p.c;              // 默认值 30​
  26. ​p.age = 100;      // p.age => 100​
  27. ​p.age = 300;      // Uncaught TypeError: age不能大于200!​
  28. ​p.age = "leo";    // Uncaught TypeError: age不是一个整数!​

3.2 函数节流

通过拦截 ​handler.apply()​方法的调用,实现函数只能在1秒之后才能再次被调用,经常可以用在防止重复事件的触发。

  1. ​let p = (fun, time) => {​
  2. ​    // 获取最后点击时间​
  3. ​    let last = Date.now() - time;​
  4. ​    return new Proxy (fun, {​
  5. ​        apply(target, context, args){​
  6. ​            if(Date.now() - last >= time){​
  7. ​                fun.bind(target)(args);​
  8. ​                // 重复设置当前时间​
  9. ​                last = Date.now();​
  10. ​            }​
  11. ​        }​
  12. ​    })​
  13. ​}​

  14. ​let p1 = () => {​
  15. ​    console.log("点击触发");​
  16. ​}​
  17. ​let time = 1000; // 设置时间​
  18. ​let proxyObj = p(p1, time);​
  19. ​// 监听滚动事件​
  20. ​document.addEventListener('scroll', proxyObj);​

3.3 实现单例模式

通过拦截 ​construct​​方法,让不同实例指向相同的 ​constructer​,实现单例模式。

  1. ​let p = function(fun){​
  2. ​    let instance;​
  3. ​    let handler = {​
  4. ​        // 拦截construct方法​
  5. ​        construct: function(targer, args){​
  6. ​            if(!instance){​
  7. ​                instance = new fun();​
  8. ​            }​
  9. ​            return instance;​
  10. ​        }​
  11. ​    }​
  12. ​    return new Proxy(fun, handler);​
  13. ​}​

  14. ​// 创建一个construct案例​
  15. ​function Cons (){​
  16. ​    this.value = 0;​
  17. ​}​

  18. ​// 创建实例​
  19. ​let p1 = new Cons();​
  20. ​let p2 = new Cons();​

  21. ​// 操作​
  22. ​p1.value = 100; ​
  23. ​// p1.value => 100 , p2.value => 0​
  24. ​// 因为不是相同实例​

  25. ​// 通过Proxy实现单例​
  26. ​let singleton = p(Cons);​
  27. ​let p3 = new singleton();​
  28. ​let p4 = new singleton();​
  29. ​p3.value = 130; ​
  30. ​// p1.value => 130 , p2.value => 130​
  31. ​// 现在是相同实例​

参考资料

1.MDN 元编程
2. ES6中的元编程-Proxy & Reflect
【JS】133-重温基础:元编程_目标对象_02



举报

相关推荐

0 条评论