乐趣区

【重温基础】14.元编程

本文是 重温基础 系列文章的第十四篇。这是第一个基础系列的最后一篇,后面会开始复习一些中级的知识了,欢迎持续关注呀!接下来会统一整理到我的【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 需要处理默认行为的时候使用。

参考资料:

名称
地址

Reflect
MDN Reflect

Proxy
MDN 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; // leo
p.age; // leo
p.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: Revoked
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 代理对象的 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 => 10
p.b = undefined; // p.b => undefined
p.c; // 默认值 30
p.age = 100; // p.age => 100
p.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-mail
pingan8787@qq.com

博 客
www.pingan8787.com

微 信
pingan8787

每日文章推荐
https://github.com/pingan8787…

ES 小册
es.pingan8787.com

退出移动版