JS中的元编程

ES6(ECMAScript 2015)新增了对 Reflect 和 Proxy 对象的支持,使得我们能够便捷地进行元编程。让我们通过示例来学习它们的用法。

1、什么是元编程

元编程 无异于 编程中的魔法!如果编写一个“能够读取、修改、分析、甚至生成新程序”的程序将会如何?是不是听起来很神奇、很强大?

维基百科这样描述元编程:元编程 是一种编程技术,编写出来的计算机程序能够将其他程序作为数据来处理。意味着可以编写出这样的程序:它能够读取、生成、分析或者转换其它程序,甚至在运行时修改程序自身。

2、什么是反射

反射 是元编程的一个分支。反射又有三个子分支:

  • 自省(Introspection):代码能够自我检查、访问内部属性,我们可以据此获得代码的底层信息。
  • 自我修改(Self-Modification):顾名思义,代码可以修改自身。
  • 调解(Intercession):字面意思是“代他人行事”。在元编程中,调解的概念类似于包装(wrapping)、捕获(trapping)、拦截(intercepting)。

2.1:自省
在 ES6 引入 Reflect 对象 之前,我们也可以实现自省。下面是读取程序结构的示例:

var users = {'Tom': 32,'Bill': 50,'Sam': 65
};Object.keys(users).forEach(name => {const age = users[name];console.log(`User ${name} is ${age} years old!`);
});

我们读取了 users 对象的结构并以键值对的形式打印出来。

User Tom is 32 years old!
User Bill is 50 years old!
User Sam is 65 years old!

2.2:自我修改
以一个包含修改其自身的方法的 blog 对象为例:

var blog = {name: 'freeCodeCamp',modifySelf: function(key, value) {blog[key] = value}
}
blog.modifySelf('author', 'Tapas');

2.3:调解
元编程中的 调解 指的是改变其它对象的语义。在 ES6 之前,可以用 Object.defineProperty() 方法来改变对象的语义:

var sun = {};
Object.defineProperty(sun, 'rises', {value: true,configurable: false,writable: false,enumerable: false
});console.log('sun rises', sun.rises); //sun rises true
sun.rises = false;
console.log('sun rises', sun.rises); //sun rises true

如你所见,我们创建了一个普通对象 sun,之后改变了它的语义:为其定义了一个不可写的 rises 属性。所以修改无效。

ES6 为我们提供了 Reflect 对象(Reflect API)来实现 自省,还提供了 Proxy 对象帮助我们实现 调解。我们要尽力避免 自我修改,所以本文不会过多谈及这一点。

需要说明的是,元编程并不是由 ES6 引入的,JavaScript 语言从一开始就支持元编程,ES6 只是让它变得更易于使用。

3、Proxy

Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

术语含义
handler包含捕捉器(trap)的占位符对象,可译为处理器对象
traps提供属性访问的方法。这类似于操作系统中捕获器的概念。
target被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

在这里插入图片描述
先定义一个包含“捕获器”函数的 handler 对象,再使用这个 handler 和目标对象来创建一个代理对象,这个代理对象会应用 handler 中的自定义行为。

基本语法

const p = new Proxy(target, handler)
参数作用
target要使用 Proxy 包装的目标对象,可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
handler一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

基础示例
在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。

const handler = {get: function (obj, prop) {return prop in obj ? obj[prop] : 37;},
};const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;console.log(p.a, p.b); // 1, undefined
console.log("c" in p, p.c); // false, 37

无操作转发代理
在以下例子中,我们使用了一个原生 JavaScript 对象,代理会将所有应用到它的操作转发到这个对象上。

let target = {};
let p = new Proxy(target, {});p.a = 37; // 操作转发到目标console.log(target.a); // 37. 操作已经被正确地转发

验证
通过代理,你可以轻松地验证向一个对象的传值。下面的代码借此展示了 set handler 的作用。

let validator = {set: function (obj, prop, value) {if (prop === "age") {if (!Number.isInteger(value)) {throw new TypeError("The age is not an integer");}if (value > 200) {throw new RangeError("The age seems invalid");}}// The default behavior to store the valueobj[prop] = value;// 表示成功return true;},
};let person = new Proxy({}, validator);person.age = 100;console.log(person.age);
// 100person.age = "young";
// 抛出异常:Uncaught TypeError: The age is not an integerperson.age = 300;
// 抛出异常:Uncaught RangeError: The age seems invalid

扩展构造函数

方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了construct和apply。

function extend(sup, base) {var descriptor = Object.getOwnPropertyDescriptor(base.prototype,"constructor",);base.prototype = Object.create(sup.prototype);var handler = {construct: function (target, args) {var obj = Object.create(base.prototype);this.apply(target, obj, args);return obj;},apply: function (target, that, args) {sup.apply(that, args);base.apply(that, args);},};var proxy = new Proxy(base, handler);descriptor.value = proxy;Object.defineProperty(base.prototype, "constructor", descriptor);return proxy;
}var Person = function (name) {this.name = name;
};var Boy = extend(Person, function (name, age) {this.age = age;
});Boy.prototype.sex = "M";var Peter = new Boy("Peter", 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13

4、Reflect

是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

与大多数全局对象不同 Reflect 并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

Reflect 对象提供了以下静态方法,这些方法与 proxy handler 方法的命名相同。

静态方法功能
Reflect.apply(target, thisArgument, argumentsList)对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。
Reflect.construct(target, argumentsList[, newTarget])对构造函数进行 new 操作,相当于执行 new target(…args)。
Reflect.defineProperty(target, propertyKey, attributes)和 Object.defineProperty() 类似。如果设置成功就会返回 true
Reflect.deleteProperty(target, propertyKey)作为函数的delete操作符,相当于执行 delete target[name]。
Reflect.get(target, propertyKey[, receiver])获取对象身上某个属性的值,类似于 target[name]。
Reflect.getOwnPropertyDescriptor(target, propertyKey)类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回 undefined。
Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf()。
Reflect.has(target, propertyKey)判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
Reflect.isExtensible(target)类似于 Object.isExtensible().
Reflect.ownKeys(target)返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable 影响).
Reflect.preventExtensions(target)类似于 Object.preventExtensions()。返回一个Boolean。
Reflect.set(target, propertyKey, value[, receiver])将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
Reflect.setPrototypeOf(target, prototype)设置对象原型的函数。返回一个 Boolean,如果更新成功,则返回 true。

检测一个对象是否存在特定属性

const duck = {name: 'Maurice',color: 'white',greeting: function() {console.log(`Quaaaack! My name is ${this.name}`);}
}Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false

返回这个对象自身的属性

Reflect.ownKeys(duck);
// [ "name", "color", "greeting" ]

为这个对象添加一个新的属性

Reflect.set(duck, 'eyes', 'black');
// returns "true" if successful
// "duck" now contains the property "eyes: 'black'"

等等,现在问题来了:既然 Object 或 Function 对象中已经有这些方法了,为什么还要引入新的 API 呢?
困惑吗?让我们一探究竟。

  • 集中在一个命名空间: JavaScript 已经支持对象反射,但是这些 API 没有集中到一个命名空间中。从 ES6 开始,它们被集中到 Reflect 对象中。与其他全局对象不同,Reflect 不是一个构造函数,不能使用 new 操作符来调用它,也不能将它当做函数来调用。Reflect 对象中的方法和 math 对象中的方法一样是 静态 的。
  • 易于使用:Object 对象中的 自省 方法在操作失败的时候会抛出异常,这给开发者增加了处理异常的负担。也许你更倾向于把操作结果当做布尔值来处理,而不是去处理异常,借助 Reflect 对象就可以做到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/175017.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】rpm和yum的使用

不知道是不是有和我一样的宝子们,在rpm上卡了老久老久,但其实搞通了,理解了原理之后,不难的,所以不管你现在遇到的困难是什么,都不要放弃,一定要坚持,加油。 一、rpm 1.rpm rpm的…

单例模式及其使用场景

单例模式(Singleton):指在一个系统中某个类只存在一个实例,类中自行实例化,实例向该系统提供统一的访问接口。 单例模式有两种表现形式,饿汉式:类加载时,就进行实例化;懒…

案例分析真题-系统建模

案例分析真题-系统建模 2009年真题 【问题1】 【问题2】 【问题3】 2012年真题 【问题1】 【问题2】 【问题3】 2014年真题 【问题1】 【问题2】 骚戴理解:这个题目以前经常考,不知道今年会不会考,判断的话就是看加工有没有缺少输入和输出&a…

【SEC 学习】美化 Linux 终端

一、步骤 1. 进入 /etc/bash.bashrc vim /etc/bash.bashrc2. 重新加载 bash.bashrc source /etc/bash.bashrc二、各参数指标 符号含义\u当前用户的账号名称\h仅取主机的第一个名字,如上例,则为fc4,.linux则被省略\H完整的主机名称。例如&…

HSTS详解,以及HSTS VS 301 redirect

最近看了一些文章,感觉不错,这里记录一下 之前基于nginx为网站配置了HTTPS服务,配置过程中涉及到两个知识点: 301 永久重定向HTST(HTTP Transport Security Protocol) 本文主要是通过梳理一下相关概念,理清这两种配置…

设计工位卡片

因为今天接到了一个设计工位卡片的任务,于是在这里记录一哈,设计卡片所需要的一些东西: 设计卡片的网站有以下几个可供参考: Canva:提供各种类型的卡片设计模板,包括圣诞贺卡、生日贺卡、感谢卡等&#x…

css列表样式

html文件如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> <link href"css/style.css" rel"stylesheet" type"text/css">&…

2023年第四届MathorCup高校数学建模挑战赛——大数据竞赛B题解题思路

比赛时长为期7天的妈杯大数据挑战赛如期开赛&#xff0c;为了帮助对B题有更深的理解&#xff0c;这里为大家带来B题的初步解题思路。 赛道B&#xff1a;电商零售商家需求预测及库存优化问题 由于妈杯竞赛分为初赛复赛&#xff0c;因此&#xff0c;对于B题大家仅仅看到了预测相…

计算机毕业设计选题推荐-社区志愿者服务微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

UE4 HLSL学习笔记

在Custom配置对应ush文件路径 在HLSL中写入对应代码 Custom里面增加两个Input&#xff0c;名字必须和ush文件内的未知变量名字一样 然后就对应输出对应效果的颜色 这就是简单的加法运算 减法同理&#xff1a; 乘法除法同理 HLSL取最小值 HLSL取最大值 绝对值&#xff1a; 取余…

设计模式(全23种)

1.前言 1.CUML类图 面向对象设计主要就是使用UML的类图&#xff0c;类图用于描述系统中所包含的类以及它们之间的相互关系&#xff0c;帮助人们简化对系统的理解&#xff0c;它是系统分析和设计阶段的重要产物&#xff0c;也是系统编码和测试的重要模型依据。下面基于C这门语…

【AOP进阶】实现重试机制

&#x1f4da;目录 ⚙️简介&#xff1a;✨注解定义&#xff1a;⛳RetryMechanism ⌛编写AOP代码&#xff1a;⚓RetryMechanismAspect 切面 ⛵演示&#xff1a;⛴如何使用RetryMechanism&#xff1a;⚡️正常请求如下&#xff1a;☘️测试异常并且重试&#xff1a;☄️测试异常…

Ubuntu系统HUSTOJ 用 vim 修改php.ini 重启PHP服务

cd / sudo find -name php.ini 输出&#xff1a; ./etc/php/7.4/cli/php.ini ./etc/php/7.4/fpm/php.ini sudo vim /etc/php/7.4/cli/php.ini sudo vim /etc/php/7.4/fpm/php.ini 知识准备&#xff1a; vim的搜索与替换 在正常模式下键入 / &#xff0c;即可进入搜索模式…

大彩串口屏读写文件问题

分区 本文使用的是大彩串口屏M系列的&#xff1a; 串口屏内部有三个分区&#xff0c;分别为A、B、C三个区&#xff1a; A区&#xff1a;系统区&#xff0c;存储组态工程文件 B区&#xff1a;数据区&#xff0c;存储配置信息&#xff0c;记录数据、历史曲线等 C区&#xff1a;备…

机器学习(python)笔记整理

目录 一、数据预处理&#xff1a; 1. 缺失值处理&#xff1a; 2. 重复值处理&#xff1a; 3. 数据类型&#xff1a; 二、特征工程: 1. 规范化&#xff1a; 2. 归一化&#xff1a; 3. 标准化(方差)&#xff1a; 三、训练模型&#xff1a; 如何计算精确度&#xff0c;召…

数据结构——双向链表的实现

一、双向链表的结构 注意&#xff1a;双向链表又称带头双向循环链表 这⾥的“带头”跟前⾯我们说的“头节点”是两个概念&#xff0c;实际前⾯的在单链表阶段称呼不严 谨&#xff0c;但是为了同学们更好的理解就直接称为单链表的头节点。 带头链表⾥的头节点&#xff0c;实际…

轻量封装WebGPU渲染系统示例<8>- 渲染器基本场景管理(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/RSceneTest.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 5. 用户操作和渲…

浅谈js代码的封装方法(2023.10.30)

常见的js代码封装方法 2023.10.30 需求1、js代码封装的优缺点2、js代码封装方式2.1 方式一&#xff1a;function function declarations2.1.1 示例 2.2 方式二&#xff1a;class2.2.1 class declarations2.2.2 Class expressions 2.3 变量函数2.4 变量闭包匿名函数2.5 闭包函数…

随机链表的复制(C++解法)

题目 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节…

基于SpringBoot的垃圾分类管理系统

基于SpringBootVue的垃圾分类管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven主要功能&#xff1a;包括前台和后台两部分、首页列表展示、垃圾分类、垃圾图谱、查看详…