本文是 重温基础 系列文章的第十四篇。 这是第一个基础系列的最后一篇,后面会开始复习一些中级的知识了,欢迎持续关注呀! 接下来会统一整理到我的【CuteECMAScript】的JavaScript基础系列中。 今日感受:独乐乐不如众乐乐。系列目录:【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)【重温基础】1.语法和数据类型【重温基础】2.流程控制和错误处理【重温基础】3.循环和迭代【重温基础】4.函数【重温基础】5.表达式和运算符【重温基础】6.数字【重温基础】7.时间对象【重温基础】8.字符串【重温基础】9.正则表达式【重温基础】10.数组【重温基础】11.Map和Set对象【重温基础】12.使用对象【重温基础】13.迭代器和生成器本章节复习的是JS中的元编程,涉及的更多的是ES6的新特性。1. 概述元编程,其实我是这么理解的:让代码自动写代码,可以更改源码底层的功能。 元,是指程序本身。 有理解不到位,还请指点,具体详细的介绍,可以查看维基百科 元编程 。 从ES6开始,JavaScrip添加了对Proxy和Reflect对象的支持,允许我们连接并定义基本语言操作的自定义行为(如属性查找,赋值,枚举和函数调用等),从而实现JavaScrip的元级别编程。Reflect: 用于替换直接调用Object的方法,并不是一个函数对象,也没有constructor方法,所以不能用new操作符。Proxy: 用于自定义对象的行为,如修改set和get方法,可以说是ES5中Object.defineProperty()方法的ES6升级版。两者联系: API完全一致,但Reflect一般在Proxy需要处理默认行为的时候使用。参考资料:名称地址ReflectMDN ReflectProxyMDN Proxy元编程MDN 元编程本文主要从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.revocale方法取消Proxy实例。let a = {};let b = {};let {proxy, revoke} = Proxy.revocale(a, b);proxy.name = ’leo’; // ’leo’revoke();proxy.name; // TypeError: Revoked2.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代理对象的set和get方法来进行拦截数据,像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// 现在是相同实例参考资料1. MDN 元编程 2. ES6中的元编程-Proxy & Reflect 本部分内容到这结束Author王平安E-mailpingan8787@qq.com博 客www.pingan8787.com微 信pingan8787每日文章推荐https://github.com/pingan8787…ES小册es.pingan8787.com