博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【重温基础】14.元编程
阅读量:5937 次
发布时间:2019-06-19

本文共 5887 字,大约阅读时间需要 19 分钟。

本文是 重温基础 系列文章的第十四篇。

这是第一个基础系列的最后一篇,后面会开始复习一些中级的知识了,欢迎持续关注呀! 接下来会统一整理到我的的JavaScript基础系列中。

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

系列目录:

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

1. 概述

元编程,其实我是这么理解的:让代码自动写代码,可以更改源码底层的功能

元,是指程序本身。
有理解不到位,还请指点,具体详细的介绍,可以查看 。
从ES6开始,JavaScrip添加了对ProxyReflect对象的支持,允许我们连接并定义基本语言操作的自定义行为(如属性查找,赋值,枚举和函数调用等),从而实现JavaScrip的元级别编程。

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

参考资料

名称 地址
Reflect
Proxy
元编程

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

2. Proxy介绍

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

2.1 基础使用

基本语法:

let p = new Proxy(target, handler);复制代码

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

let p = new Proxy({}, {    get: function (target, handler){        return 'leo';    }})p.name; // leop.age;  // leop.abcd; // leo复制代码

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

注意

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

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

let p = new Proxy(function(a, b){    return a + b;},{    get:function(){        return 'get方法';    },    apply:function(){        return 'apply方法';    }})复制代码

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

let handler = {    get : function (target, name){        return name in target ? target[name] : 16;    }}let p = new Proxy ({}, handler);p.a = 1;console.log(p.a , p.b);// 1   16复制代码

这里因为 p.a = 1 定义了p中的a属性,值为1,而没有定义b属性,所以p.a会得到1,而p.b会得到undefined从而使用name in target ? 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实例。

let a = {};let b = {    get: function(target, name) {        return "[[" + name + "]]";    }};let revoke = Proxy.revocable(a, b);let proxy = revoke.proxy;proxy.age;           // "[[age]]"revoke.revoke();proxy.age;           // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revokedproxy.age = 10;      // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revokeddelete proxy.age;    // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revokedtypeof proxy;        // "object"复制代码

2.3 实现 Web服务的客户端

const service = createWebService('http://le.com/data');service.employees().than(json =>{    const employees = JSON.parse(json);})function createWebService(url){    return new Proxy({}, {        get(target, propKey, receiver{            return () => httpGet(url+'/'+propKey);        })    })}复制代码

3. Proxy实践

3.1 数据拦截验证

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

let handler = {    // 拦截并处理get方法    // 可以理解为设置get方法返回的默认值    get : function (target, key){        return key in target ? target[key] : 30;    },        // 拦截并处理set方法    // 可以理解为设置set方法的默认行为    set : function (target, key, value){        if(key === "age"){            if (!Number.isInteger(value)){                throw new TypeError("age不是一个整数!");            }            if (value > 200){                throw new TypeError("age不能大于200!");            }        }        // 保存默认行为        target[key] = value;    }}let p = new Proxy({}, handler);p.a = 10;         // p.a   => 10p.b = undefined;  // p.b   => undefinedp.c;              // 默认值 30p.age = 100;      // p.age => 100p.age = 300;      // Uncaught TypeError: age不能大于200!p.age = "leo";    // Uncaught TypeError: age不是一个整数!复制代码

3.2 函数节流

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

let p = (fun, time) => {    // 获取最后点击时间    let last = Date.now() - time;    return new Proxy (fun, {        apply(target, context, args){            if(Date.now() - last >= time){                fun.bind(target)(args);                // 重复设置当前时间                last = Date.now();            }        }    })}let p1 = () => {    console.log("点击触发");}let time = 1000; // 设置时间let proxyObj = p(p1, time);// 监听滚动事件document.addEventListener('scroll', proxyObj);复制代码

3.3 实现单例模式

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

let p = function(fun){    let instance;    let handler = {        // 拦截construct方法        construct: function(targer, args){            if(!instance){                instance = new fun();            }            return instance;        }    }    return new Proxy(fun, handler);}// 创建一个construct案例function Cons (){    this.value = 0;}// 创建实例let p1 = new Cons();let p2 = new Cons();// 操作p1.value = 100; // p1.value => 100 , p2.value => 0// 因为不是相同实例// 通过Proxy实现单例let singleton = p(Cons);let p3 = new singleton();let p4 = new singleton();p3.value = 130; // p1.value => 130 , p2.value => 130// 现在是相同实例复制代码

参考资料

本部分内容到这结束

Author 王平安
E-mail
博 客
微 信 pingan8787
每日文章推荐
JS小册

欢迎关注我的微信公众号【前端自习课】

转载地址:http://gkttx.baihongyu.com/

你可能感兴趣的文章
阿里云服务器ECS开放8080端口
查看>>
前端常用排序详解
查看>>
Spring中实现监听的方法
查看>>
使用Tooltip会出现一个问题,如果行上出现复选框
查看>>
11.03T1 DP
查看>>
P2924 [USACO08DEC]大栅栏Largest Fence
查看>>
jQuery操作table tr td
查看>>
工作总结:MFC自写排序算法(升序)
查看>>
螺旋队列问题之二
查看>>
扩展运算符和解构赋值的理解
查看>>
后缀数组(suffix array)详解
查看>>
手机H5显示一像素的细线
查看>>
Menu 菜单栏
查看>>
Integer跟int的区别(备份回忆)
查看>>
集合解析
查看>>
详解分布式应用程序协调服务Zookeeper
查看>>
软件工程之构建之法
查看>>
scrollView + tableview 上下滑动失效
查看>>
UVa 10902
查看>>
Mathf.Sin正弦
查看>>