JS中的元编程

这篇具有很好参考价值的文章主要介绍了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 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

JS中的元编程,JavaScript,javascript,前端,开发语言
先定义一个包含“捕获器”函数的 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 对象就可以做到。

5、提问:利用Reflect检测一个对象是否有某个参数和用hasOwnProperty有啥区别?

当您想要检查一个对象是否拥有某个属性时,通常有两种方式:使用 Reflect.has 方法和使用 Object.prototype.hasOwnProperty 方法。这两种方法在大多数情况下都能返回正确的结果,但是它们在某些特殊情况下的行为会有所不同。
Reflect.has(obj, prop):
Reflect.has() 方法是ES6引入的,它的工作方式与 in 操作符类似。它会检查对象自身属性以及原型链上是否存在指定属性。

const obj = { a: 1 };
console.log(Reflect.has(obj, 'a')); // true
console.log(Reflect.has(obj, 'toString')); // true,因为toString是在原型链上

Object.prototype.hasOwnProperty(prop):
hasOwnProperty() 方法会检查属性是否是对象的自有属性,也就是说它不会查找原型链。这个方法只关心对象本身是否直接含有这个属性。

const obj = { a: 1 };
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('toString')); // false,因为toString不是obj的自有属性

区别总结:

  1. 原型链: Reflect.has 会检查原型链,hasOwnProperty 不会。
  2. 用法: Reflect.hasReflect 对象的一个静态方法,而 hasOwnProperty 是任何JavaScript对象都继承的一个方法(从 Object.prototype 继承)。
  3. 在原型方法被改变的情况下: 如果一个对象的 hasOwnProperty 方法被覆盖或改变,它可能不能正确地工作,而 Reflect.has 仍然会照常工作,因为它不依赖于 Object.prototype

示例场景:
假设有一个对象,它的 hasOwnProperty 方法被覆盖了:

const obj = {
  hasOwnProperty: () => false,
  a: 1
};
console.log(obj.hasOwnProperty('a')); // false, 不正确的结果!
console.log(Reflect.has(obj, 'a')); // true

在这种特殊情况下,Reflect.has 给出了准确的结果,而 hasOwnProperty 就不能可靠地工作了。文章来源地址https://www.toymoban.com/news/detail-726090.html

到了这里,关于JS中的元编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 快速认识,前端必学编程语言:JavaScript

    JavaScript是构建Web应用必学的一门编程语言,也是最受开发者欢迎的热门语言之一。所以,如果您还不知道JavaScript的用处、特点的话,赶紧补充一下这块基础知识。 JavaScript 是一种高级、单线程、垃圾收集、解释或即时编译、基于原型、多范式、动态语言,具有非阻塞事件循

    2024年02月05日
    浏览(52)
  • web前端框架JS学习之JavaScript类型转换

    vascript有多种数据类型,如字符串、数字、布尔等,可以通过typeof语句来查看变量的数据类型。数据类型转换就是数据类型之间相互转换,比如把数字转成字符串、把布尔值转成字符串、把字符串转成数字等,这在工作也是经常碰到的。 本期我们就给大家说说web前端框架JS学

    2024年02月10日
    浏览(64)
  • JavaScript中的模块化编程

    JavaScript是一种强大的编程语言,它可以在浏览器中进行客户端脚本编写,并且在服务器端也有广泛的应用。随着JavaScript应用的增多,JavaScript代码的复杂度也不断增加。为了提高代码的可维护性和重用性,模块化编程变得越来越重要。本文将讨论JavaScript中的模块化编程,包括

    2024年02月02日
    浏览(65)
  • 【前端灵魂脚本语言JavaScript⑤】——JS中数组的使用

    🐚 作者: 阿伟 💂 个人主页: Flyme awei 🐋 希望大家多多支持😘一起进步呀! 💬 文章对你有帮助👉关注✨点赞👍收藏📂 第一种: var 数组名 = new Array(); 创建一个空数组 第二种: var arr2 = new Array(10); 创建一个定长为10的数组 第三种 var arr3 = new Array(a,b,c); 创建时直接指定元素值

    2023年04月08日
    浏览(65)
  • 【JS】JavaScript中的this关键字

    目录 this是什么? this的指向 ①全局环境 ②构造函数 ③对象的方法 this的四类调用方式 ①作为对象方法调用 ②纯粹的函数调用 ③作为构造函数调用 ④使用apply、call、bind调用 举例说明 JavaScript  this  指的是它所属的对象。 它拥有不同的值,具体取决于它的使用位置:

    2024年02月14日
    浏览(46)
  • 〖大前端 - 基础入门三大核心之JS篇㉓〗- JavaScript 的「数组」

    当前子专栏 基础入门三大核心篇 是免费开放阶段 。 推荐他人订阅,可获取扣除平台费用后的35%收益,文末名片加V! 说明:该文属于 大前端全栈架构白宝书专栏, 目前阶段免费开放 , 购买任意白宝书体系化专栏可加入 TFS-CLUB 私域社区。 福利:除了通过订阅\\\"白宝书系列专

    2024年02月04日
    浏览(70)
  • 前端开发——Javascript知识(介绍)

    目录 有关JavaScript的知识  JavaScript的优点   JavaScript的领域 JavaScript的组成 JavaScript的特点 第一个JavaScript程序 在 HTML 文档中嵌入 JavaScript 代码 在脚本文件中编写 JavaScript 代码 JavaScript内容  Html内容  JavaScript 代码执行顺序 JavaScript中的几个重要概念 标识符 保留字 区分

    2024年02月01日
    浏览(47)
  • 前端开发——JavaScript的条件语句

      世界不仅有黑,又或者白 世界而是一道精致的灰  ——Lungcen     目录 条件判断语句 if 语句 if else 语句 if else if else 语句  switch语句 break case 子句 default语句 while循环语句 do while循环语句 for循环语句 for 循环中的三个表达式 for 循环嵌套 for 循环变体——for in for 循环

    2023年04月21日
    浏览(46)
  • [前端系列第3弹]JS入门教程:从零开始学习JavaScript

    本文将带领大家,从零开始学习JavaScript,fighting~ 目录 一、JavaScript简介 二、变量和数据类型 三、注释和分号 四、算术运算符 五、表达式和语句 六、代码块和作用域 七、函数(最重要)          JavaScript(简称JS)是一种运行在浏览器中的脚本语言,它可以让网页变得

    2024年02月13日
    浏览(65)
  • 【前端|Javascript第5篇】全网最详细的JS的内置对象文章!

    前言 在当今数字时代,前端技术正日益成为塑造用户体验的关键。我们在开发中需要用到很多js的内置对象的一些属性来帮助我们更快速的进行开发。或许你是刚踏入前端领域的小白,或者是希望深入了解内置对象的开发者,不论你的经验如何,本篇博客都将给你详细的讲解

    2024年02月12日
    浏览(114)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包