JS中的元编程

news2024/11/17 6:01:03

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 value
    obj[prop] = value;

    // 表示成功
    return true;
  },
};

let person = new Proxy({}, validator);

person.age = 100;

console.log(person.age);
// 100

person.age = "young";
// 抛出异常:Uncaught TypeError: The age is not an integer

person.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.coloradmin.cn/o/1152058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提高小程序SEO 排名,9招优化技巧!

在当今移动互联网时代,小程序已经成为企业必不可少的一种营销手段,而如何让用户能够更容易地找到自己的小程序,就需要进行SEO优化,提升小程序的排名,本文将 为大家介绍9个小程序SEO优化技巧,帮助您的小程序…

黑客技术(网络安全)——如何高效学习

前言 前几天发布了一篇 网络安全(黑客)自学 没想到收到了许多人的私信想要学习网安黑客技术!却不知道从哪里开始学起!怎么学 今天给大家分享一下,很多人上来就说想学习黑客,但是连方向都没搞清楚就开始学习…

OPENCV 闭运算实验示例代码morphologyEx()函数

void CrelaxMyFriendDlg::OnBnClickedOk() {hdc this->GetDC()->GetSafeHdc();// TODO: 在此添加控件通知处理程序代码string imAddr "c:/Users/actorsun/Pictures/";string imAddr1 imAddr"rice.png";Mat relax, positive;relax imread(imAddr1…

Linux的基础常用指令

常用指令汇及其功能 ls 列出当前文件夹有哪些文件 ls -a显示所有文件,包含隐藏的文件和文件夹pwd显示当前是在哪个文件夹下mkdirmkdir名字→创建文件夹cdcd名字→进入某个指定文件夹cd .. 退回上层文件夹(cd后有空格) Tab键自动补全:文件或文件名太长&a…

信息系统项目管理师教程 第四版【第8章-项目整合管理-思维导图】

信息系统项目管理师教程 第四版【第8章-项目整合管理-思维导图】 课本里章节里所有蓝色字体的思维导图

【设计模式】第11节:结构型模式之“装饰器模式”

一、简介 装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。…

LeetCode169——多数元素(众数)

LeetCode169——多数元素(众数) 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 Result01&#xff08…

顺序表的模拟

前言: 数据结构无论是以后就业还是考研,对于计算机方向的同学来说都是必修的一门课,所以,深入理解各种数据结构是一名合格程序员的基本素养。这里我就先带大家了解最简单的数据结构--顺序表吧。 1.什么是顺序表 顺序表是一种线性…

菜单管理中icon图标回显

<el-table-column prop"icon" label"图标" show-overflow-tooltip algin"center"><template v-slot"{ row }"><el-icon :class"row.icon"></el-icon></template></el-table-column>

Xilinx 7 系列 1.8V LVDS 和 2.5V LVDS 信号之间的 LVDS 兼容性

如果通过LVDS进行接口&#xff0c;可以按照以程图中的步骤操作&#xff0c;以确保满足正确使用LVDS的所有要求。 40191 - 7 系列 - 1.8V LVDS 和 2.5V LVDS 信号之间的 LVDS 兼容性 与LVDS兼容驱动器和接收器连接时&#xff0c;7系列LVDS和LVDS_25输入和输出应该不存在兼容性问…

基于若依的ruoyi-nbcio流程管理系统增加仿钉钉流程设计(一)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 仿钉钉的开源项目网上也不少&#xff0c;而且很多功能已经也比较完善了&#xff0c;但大部分都不是MIT协议…

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;即可进入搜索模式…

计数排序(秒懂版)

public class CountingSort {int[] sort() {int N 10000;int M 100;//假设A中最大值为100int A[] new int[N];//1~k,原数组int B[] new int[N];//1~k,Stores incompletely sorted numbersint C[] new int[M 1];//0~n,辅助排序数组for (int i 0; i < M; i) {C[i] 0;}…

案例分析真题-信息安全

案例分析真题-信息安全 2009年真题 【问题1】 【问题2】 【问题3】 2010年真题 【问题1】 【问题2】 【问题3】 2011 年真题 【问题1】 【问题2】 【问题3】 骚戴理解&#xff1a;这个破题目完全考的知识储备&#xff0c;不知道的连手都动不了&#xff0c;没法分析 2013年真题…

Linux基础指令[下]

文章目录 0.英语单词3.Linux的重要指令[下]1. 输入/输出重定向1.1echo和输出重定向1.2cat和输入重定向1.3Linux文件理念 2.长文本逐行查看2.1more的介绍2.2建议使用less2.3more/less的选项 3.head&&tail显示头尾对head&&tail的认识练习head和tail 4.时间指令4.…

vue2+ant-design-vue a-select组件二次封装(支持单选/多选添加全选/分页(多选跨页选中)/自定义label)

一、效果图 二、参数配置 1、代码示例 <t-antd-selectv-model"selectVlaue":optionSource"stepList"change"selectChange" />2、配置参数&#xff08;Attributes&#xff09;继承 a-select Attributes 参数说明类型默认值v-model绑定值…

【陈老板赠书活动 - 16期】- 【快捷学习Spring】

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍&am…

1 — NLP 的文本预处理技术

一、说明 在本文中&#xff0c;我们将讨论以下主题&#xff1a;1为什么文本预处理很重要&#xff1f;2 文本预处理技术。这个文对预处理做一个完整化、程序化处理&#xff0c;这对NLP处理项目中有很大参考性。 二、为什么文本预处理很重要&#xff1f; 数据质量显着影响机器学习…

学习笔记|正态分布|图形法|偏度和峰度|非参数检验法|《小白爱上SPSS》课程:SPSS第三讲 | 正态分布怎么检验?看这篇文章就够了

目录 学习目的软件版本原始文档为什么要假设它服从正态分布呢?t检验一、图形法1、频数分布直方图解读 2、正态Q-Q图操作解读 3、正态P-P图SPSS实战操作解读 二、偏度和峰度解读&#xff1a; 三、非参数检验法注意事项 四、规范表达五、小结划重点 学习目的 SPSS第三讲 | 正态…

线性代数 第三章 向量

一、运算 加法、数乘、内积 施密特正交化 二、线性表出 概念&#xff1a;如果&#xff0c;则称可由线性表出&#xff08;k不要求不全为0&#xff09; 判定&#xff1a; 非齐次线性方程组有解无关&#xff0c;相关 如果两个向量组可以互相线性表出&#xff0c;则称这两个…