JavaScript 元编程

大家好,我是若川。今天给分享一篇来自freecodecamp的好文。我是freecodecamp杭州社区组织者之一,有一群小伙伴一起组织线下分享活动,不过2020年我们杭州社区几乎没有活跃,我也没有什么贡献。

另外,我的公众号「若川视野」,也可以是你们的舞台。如果写了好文章也可以投稿到我的公众号。

以下是正文~


JavaScript 有许多开发者熟知的有用特性,同时也有一些鲜为人知的特性能够帮助我们解决棘手问题。

很多人可能不太了解 JavaScript 元编程的概念,本文会介绍元编程的知识和它的作用。

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

什么是元编程?

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

元编程很神奇

维基百科这样描述元编程:

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

简而言之,元编程能够写出这样的代码:

  • 可以生成代码

  • 可以在运行时修改语言结构,这种现象被称为  反射编程  或  反射

什么是反射?

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

  1. 自省(Introspection):代码能够自我检查、访问内部属性,我们可以据此获得代码的底层信息。

  2. 自我修改(Self-Modification):顾名思义,代码可以修改自身。

  3. 调解(Intercession):字面意思是“代他人行事”。在元编程中,调解的概念类似于包装(wrapping)、捕获(trapping)、拦截(intercepting)。

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

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

ES6 之前的元编程

还记得  eval  吗?看看它的用法:

const blog = {name: 'freeCodeCamp'
}
console.log('Before eval:', blog);const key = 'author';
const value = 'Tapas';
testEval = () => eval(`blog.${key} = '${value}'`);// 调用函数
testEval();console.log('After eval magic:', blog);

eval  生成了额外的代码。示例代码执行时为  blog  对象增加了一个  author  属性。

Before eval: {name: freeCodeCamp}
After eval magic: {name: "freeCodeCamp", author: "Tapas"}

自省

在 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!

自我修改

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

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

blog  对象可以这样来修改自身:

blog.modifySelf('author', 'Tapas');

调解(Intercession)

元编程中的  调解  指的是改变其它对象的语义。在 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 = false;
console.log('sun rises', sun.rises);

输出:

sun rises true
sun rises true

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

现在,我们深入了解一下  Reflect  和  Proxy  对象以及它们的用法。

Reflect API

在 ES6 中,Reflect 是一个新的  全局对象(像 math 一样),它提供了一些工具函数,其中一些函数与  Object  或  Function  对象中的同名方法功能是相同的。

这些都是自省方法,可以用它们在运行时获取程序内部信息。

以下是  Reflect  对象提供的方法列表。点击此处可以查看这些方法的详细信息。

// Reflect 对象方法Reflect.apply()
Reflect.construct()
Reflect.get()
Reflect.has()
Reflect.ownKeys()
Reflect.set()
Reflect.setPrototypeOf()
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.getOwnPropertyDescriptor()
Reflect.getPrototypeOf()
Reflect.isExtensible()

等等,现在问题来了:既然  Object  或  Function  对象中已经有这些方法了,为什么还要引入新的 API 呢?

困惑吗?让我们一探究竟。

集中在一个命名空间

JavaScript 已经支持对象反射,但是这些 API 没有集中到一个命名空间中。从 ES6 开始,它们被集中到  Reflect  对象中。

与其他全局对象不同,Reflect 不是一个构造函数,不能使用 new 操作符来调用它,也不能将它当做函数来调用。Reflect  对象中的方法和 math 对象中的方法一样是  静态  的。

易于使用

Object  对象中的  自省  方法在操作失败的时候会抛出异常,这给开发者增加了处理异常的负担。

也许你更倾向于把操作结果当做布尔值来处理,而不是去处理异常,借助 Reflect 对象就可以做到。

以下是使用 Object.defineProperty 方法的示例:

 try {Object.defineProperty(obj, name, desc);// 执行成功} catch (e) {// 执行失败,处理异常}

使用 Reflect API 的方式如下:

if (Reflect.defineProperty(obj, name, desc)) {// 执行成功
} else {// 处理执行失败的情况。(这种处理方式好多了)
}

一等函数的魅力

我们可以通过  (prop in obj)  操作来判断对象中是否存在某个属性。如果多次用到这个操作,我们需要把它封装成函数。

在 ES6 的  Reflect API  中已经包含了这些方法,例如,Reflect.has(obj, prop) 和 (prop in obj) 功能是一样的。

看看另一个删除对象属性的示例:

const obj = { bar: true, baz: false};// We define this function
function deleteProperty(object, key) {delete object[key];
}
deleteProperty(obj, 'bar');

使用 Reflect API 的方式如下:

// 使用 Reflect API
Reflect.deleteProperty(obj, 'bar');

以更可靠的方式来使用 apply() 方法

在 ES5 中,我们可以使用  apply()  方法来调用一个函数,并指定  this  上下文、传入一个参数数组。

Function.prototype.apply.call(func, obj, arr);
// or
func.apply(obj, arr);

这种方式比较不可靠,因为  func  可能是一个具有自定义  apply  方法的对象。

ES6 提供了一个更加可靠、优雅的方式来解决这个问题:

Reflect.apply(func, obj, arr);

这样,如果  func  不是可调用对象,会抛出  TypeError。此外  Reflect.apply()  也更简洁、易于理解。

帮助实现其他类型的反射

等我们了解  Proxy  对象之后就能理解这句话意味着什么。在许多场景中,Reflect API 方法可以和 Proxy 结合使用。

Proxy 对象

ES6 的  Proxy  对象可以用于  调解(intercession)

proxy  对象允许我们自定义一些基本操作的行为(例如属性查找、赋值、枚举、函数调用等)。

以下是一些有用的术语:

  • target:代理为其提供自定义行为的对象。

  • handler:包含“捕获器”方法的对象。

  • trap:“捕获器”方法,提供了访问目标对象属性的途径,这是通过 Reflect API 中的方法实现的。每个“捕获器”方法都对应着 Reflect API 中的一个方法。

它们的关系如图所示:

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

如果你对上面介绍的内容不太理解也没关系,我们可以通过代码示例来掌握它。

以下是创建代理对象的语法:

let proxy = new Proxy(target, handler);

有许多捕获器(handler 方法)可以用来访问或者自定义目标对象。以下是捕获器方法列表,可以在此处查看更多详细介绍。

handler.apply()
handler.construct()
handler.get()
handler.has()
handler.ownKeys()
handler.set()
handler.setPrototypeOf()
handler.getPrototypeOf()
handler.defineProperty()
handler.deleteProperty()
handler.getOwnPropertyDescriptor()
handler.preventExtensions()
handler.isExtensible()

注意每个捕获器都对应着  Reflect  对象的方法,也就是说可以在许多场景下同时使用  Reflect  和  Proxy

如何获取不可用的对象属性值

以下是一个打印  employee  对象的属性的示例:

const employee = {firstName: 'Tapas',lastName: 'Adhikary'
};console.log(employee.firstName);
console.log(employee.lastName);
console.log(employee.org);
console.log(employee.fullName);

预期输出:

Tapas
Adhikary
undefined
undefined

使用 Proxy 对象为  employee  对象增加一些自定义行为。

步骤 1:创建一个使用 get 捕获器的 Handler

我们使用名为  get  的捕获器,可以通过它来获取对象的属性值。handler 代码如下:

let handler = {get: function(target, fieldName) {        if(fieldName === 'fullName' ) {return `${target.firstName} ${target.lastName}`;}return fieldName in target ?target[fieldName] :`No such property as, '${fieldName}'!`}
};

以上 handler 代码创建了  fullName  属性的值,还为访问的属性不存在的情况提供了更优雅的错误提示。

步骤 2:创建 Proxy 对象

目标对象  employee  和 handler 都准备好了,可以这样来创建 Proxy 对象:

let proxy = new Proxy(employee, handler);

步骤 3:访问 Proxy 对象的属性

现在可以通过 proxy 对象来访问 employee 对象的属性,如下所示:

console.log(proxy.firstName);
console.log(proxy.lastName);
console.log(proxy.org);
console.log(proxy.fullName);

预期输出:

Tapas
Adhikary
No such property as, 'org'!
Tapas Adhikary

注意我们是如何神奇地改变  employee  对象的。

使用 Proxy 来验证属性值

创建一个 proxy 对象来验证整数值。

步骤 1:创建一个使用 set 捕获器的 handler

handler 代码如下:

const validator = {set: function(obj, prop, value) {if (prop === 'age') {if(!Number.isInteger(value)) {throw new TypeError('Age is always an Integer, Please Correct it!');}if(value < 0) {throw new TypeError('This is insane, a negative age?');}}}
};

步骤 2:创建一个 Proxy 对象

代码如下:

let proxy = new Proxy(employee, validator);

步骤 3:将一个非整数值赋值给 age 属性

代码如下:

proxy.age = 'I am testing a blunder'; // string value

预期输出:

TypeError: Age is always an Integer, Please Correct it!at Object.set (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:28:23)at Object.<anonymous> (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:40:7)at Module._compile (module.js:652:30)at Object.Module._extensions..js (module.js:663:10)at Module.load (module.js:565:32)at tryModuleLoad (module.js:505:12)at Function.Module._load (module.js:497:3)at Function.Module.runMain (module.js:693:10)at startup (bootstrap_node.js:188:16)at bootstrap_node.js:609:3

再试试以下操作:

p.age = -1; // 抛出 TypeError

如何同时使用 Proxy 和 Reflect

下面是一个在 handler 中使用 Reflect API 方法的示例:

const employee = {firstName: 'Tapas',lastName: 'Adhikary'
};let logHandler = {get: function(target, fieldName) {        console.log("Log: ", target[fieldName]);// Use the get method of the Reflect objectreturn Reflect.get(target, fieldName);}
};let func = () => {let p = new Proxy(employee, logHandler);p.firstName;p.lastName;
};func();

其它使用场景

还有许多场景可以用到 Proxy 概念:

  • 保护对象的  ID  字段不被删除(deleteProperty 捕获器)

  • 追踪属性访问的过程(get、set 捕获器)

  • 数据绑定(set 捕获器)

  • 可撤销的引用

  • 控制  in  操作符的行为

......以及更多

元编程陷阱

尽管  元编程  概念为我们提供了强大的功能,但是使用不当也会引发错误。

要当心强大功能的副作用

注意:

  • 功能过于强大,务必理解了之后再使用。

  • 可能会影响性能。

  • 可能会使代码难以调试。

总结

总而言之:

  • Reflect  和  Proxy  是 JavaScript 的优秀特性,有助于实现元编程。

  • 利用它们可以解决许多复杂的问题。

  • 同时也要注意它们的弊端。

  • ES6 Symbols 也能用来改变现有的类或对象的行为。

希望本文对你有所帮助,文中所有源码都可以在我的 GitHub 仓库 中查看。

欢迎分享本文。欢迎关注我的 Twitter 账号(@tapasadhikary)并留言讨论。


原文链接:https://www.freecodecamp.org/news/what-is-metaprogramming-in-javascript-in-english-please/

作者:TAPAS ADHIKARY

译者:Humilitas

在线技术翻译工作坊预告

在线技术翻译工作坊将于北京时间 1 月 9 日 周六下午 13:00 - 15:00 开展(每周都在这个时间开展)。

欢迎大家添加小助手微信 fcczhongguo,加入会议室。

非营利组织 freeCodeCamp.org 自 2014 年成立以来,以“帮助人们免费学习编程”为使命,创建了大量免费的编程教程,包括交互式课程、视频课程、文章等。我们正在帮助全球数百万人学习编程,希望让世界上每个人都有机会获得免费的优质的编程教育资源,成为开发者或者运用编程去解决问题。

你也想成为

freeCodeCamp 社区的贡献者吗

欢迎点击以下文章了解

✨✨

freeCodeCamp 在线翻译工作坊丨学编程,练英语,成为开源贡献者

成为 freeCodeCamp 专栏作者,与世界各地的开发者分享技术知识

点击“阅读原文”

在 freeCodeCamp 专栏

推荐阅读

若川知乎高赞:有哪些必看的 JS库?
我在阿里招前端,我该怎么帮你?(现在还可以加模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?

末尾

你好,我是若川,江湖人称菜如若川,历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以点个在看呀^_^另外欢迎留言交流~

精选前端好文,伴你不断成长

若川原创文章精选!可点击

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

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

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

相关文章

手写一个合格的前端脚手架

为什么我们需要一套脚手架为什么我们需要一套脚手架&#xff0c;它能帮助我们解决哪些痛点问题。•前端项目配置越来越繁琐、耗时&#xff0c;重复无意义的工作•项目结构不统一、不规范•前端项目类型繁多&#xff0c;不同项目不同配置&#xff0c;管理成本高•脚手架也可以是…

第一篇cnblog!

本人才疏学浅&#xff0c;终于通过了cnblog的审核&#xff0c;兴奋之余&#xff0c;发表感言——不容易啊&#xff01;在我的博闻里面&#xff0c;随笔类当然就是技术类的比较多的&#xff0c;特别是实例类的。理论类的大部分放在文章板块&#xff0c;本人e文特别好(哈哈&#…

利用JMeter进行压力测试(1)(转)

转自&#xff1a;http://www.cnblogs.com/game-over/archive/2010/01/08/1642685.html压力测试以软件响应速度为测试目标&#xff0c;尤其是在较短时间内大量并发用户的同时访问时&#xff0c;软件的性能和抗压能力。 JMeter是一款开源的压力测试工具&#xff0c;目前最新Rele…

Git 内部原理图解——对象、分支以及如何从零开始建仓库

我们中的许多人每天都在使用 git&#xff0c;但是有多少人知道它的内部是怎么运作的呢&#xff1f;例如我们使用 git commit 时发生了什么&#xff1f;提交&#xff08;commit&#xff09;与提交之间保存的是什么&#xff1f;两次提交之间难道只是文件的差异&#xff08;dif…

Google, 请不要离开我们!

虽然我是.net阵营, 力挺Silverlight, 但是我真心希望谷歌留在中国, 如果她能够靠谈判求的言论自由的权利, 那将对中国的拥有自由信仰的一族产生重大的影响. 谷歌离开了中国, 不是她想抛弃中国市场, 而是中国决策者背叛了人性. 在此留下 Google 2010年1月14日的logo, 智慧的幽默…

28岁自学3年前端成功转行的励志故事

为什么转行因为混得不好。在成为程序员之前&#xff0c;我干过很多工作。由于学历的问题&#xff08;高中&#xff09;&#xff0c;我的工作基本上都是体力活。包括但不限于&#xff1a;工厂普工、销售&#xff08;没有干销售的才能&#xff09;、搬运工、摆地摊等&#xff0c;…

usb 驱动

usb 驱动学习总结&#xff1a; usb 采用分层的拓扑结构&#xff0c;金字塔型&#xff0c;最多是7层。usb 是主从结构&#xff0c;主和主或者从和从之间不能交换数据。理论上一个usb主控制器最多可接127个设备&#xff0c;协议规定每个usb设备具有一个7bit的地址&#xff0c;范围…

面试字节跳动后的2点总结,建议收藏!

首先我来辟个谣&#xff1a;随便打开一个招聘网站&#xff0c;你会发现前端工程师的岗位需求依旧庞大&#xff0c;大厂人才奇缺&#xff0c;就业薪资起点高&#xff0c;无行业限制。&#xff08;数据来源&#xff1a;职友集&#xff09;前端开发的行业大环境行业升级&#xff0…

phpexcel中文教程-设置表格字体颜色背景样式、数据格式、对齐方式、添加图片、批注、文字块、合并拆分单元格、单元格密码保护

转载连接&#xff1a;http://www.cnblogs.com/huangcong/p/3687665.html phpexcel中文教程-设置表格字体颜色背景样式、数据格式、对齐方式、添加图片、批注、文字块、合并拆分单元格、单元格密码保护 首先到phpexcel官网上下载最新的phpexcel类&#xff0c;下周解压缩一个cla…

2021年的今天,如何成为一名专业的前端工程师?

大家好&#xff0c;我是若川。今天给分享一篇来自阿里克军大佬的好文。以下是正文~如果你想成为一名专业的前端工程师&#xff0c;那么你需要了解要学什么&#xff0c;学到什么程度&#xff0c;以及如何有效地学习。大学里没有正规的前端技术课程&#xff0c;普遍缺少比较权威的…

nc65右键生成菜单_DbSchema生成表单和报表,原来如此简单

DbSchema 8 for Mac是mac上一款非常实用的商业数据库ER图绘制软件&#xff0c;可以轻松的对文档进行注释或标注&#xff0c;而且Dbschema集成了SQL和数据工具&#xff0c;能生成直观的图表、PDF文件或HTML 5文档等&#xff0c;非常的实用。现在就来给大家分享DbSchema如何生成表…

若川的2020年度总结,水波不兴

前言从2014年开始&#xff0c;每一年都会写年度总结&#xff0c;坚持了6个年头。回顾2014&#xff0c;约定2015&#xff08;QQ空间日志&#xff09;2015年总结&#xff0c;淡化旧标签&#xff0c;无惧未来&#xff08;QQ空间日志&#xff09;2016年度总结&#xff0c;毕业工作2…

年度总结文章的抽奖结果公布

大家好&#xff0c;我是若川。2月4日&#xff0c;发表了我的2020年度总结文章《若川的2020年度总结&#xff0c;水波不兴》&#xff0c;本以为阅读量应该突破一千会比较快&#xff0c;实际上比较艰难&#xff0c;而且还掉粉10来人。2020年运营公众号以来&#xff0c;不知不觉发…

Elon Musk

人物事件 成长学习 1971年6月28日&#xff0c;埃隆马斯克在南非的比勒陀利亚出生&#xff0c;他的 埃隆马斯克 父亲是一名南非机电工程师&#xff0c;母亲是加拿大人&#xff0c;从事营养师兼模特。[8] 1981年&#xff0c;10岁的马斯克就拥有了自己的第一台电脑&#xff0c;并…

真诚推荐这7个大佬的公众号,碎片化学习

逆水行舟&#xff0c;不进则退。我们的工作已经占用了大块的时间了&#xff0c;剩下的只有各种碎片&#xff0c;最适合碎片时间学习的&#xff0c;莫过于优质的技术干货公众号啦~以下这些是小编精选&#xff0c;里面有很多资讯和资源&#xff0c;内含干货&#xff0c;希望能给大…

[转]Windows 7 产品密钥是否安全

提到Windows 7&#xff08;或Windows Server 2008&#xff09;有些人认为自己的产品密钥&#xff08;Product Key&#xff09;很安全&#xff0c;甚至在公司内部有些网管也认为公司部署的Windows 7 系统的密钥不会泄露。但其实并非如此&#xff0c;众所周知我们的密钥都是写在注…

HttpWatch的Result中出现Aborted的原因分析[配图]

转载链接&#xff1a;http://www.cnblogs.com/yutiansanshou/archive/2013/02/01/2889486.html 我们在使用HttpWatch进行Web调试的过程中有时候会看到非HTTP Status Code&#xff08;状态码&#xff09;的值&#xff0c; 例如&#xff1a;(Aborted)。 (Aborted)是HttpWatch中定…

《大规模分布式系统架构与设计实战》

《大规模分布式系统架构与设计实战》 基本信息 作者&#xff1a; 彭渊 丛书名&#xff1a; 大数据技术丛书 出版社&#xff1a;机械工业出版社 ISBN&#xff1a;9787111455035 上架时间&#xff1a;2014-2-21 出版日期&#xff1a;2014 年2月 开本&#xff1a;16开 页码&…

WINDOWS下的squid

今天写这篇教程目的在于分享自己在WINDOWS主机下配置squid的方法。哪些地方写的不完善或是不完整或是需要修改的地方&#xff0c;大家可以提出。我会第一时间纠正。下面看正文部分。先提条件&#xff0c;您预安装配置squid的这台计算机必须是联入网络的&#xff0c;系统版本是w…

Spring Batch 批量处理策略

为了帮助设计和实现批量处理系统&#xff0c;基本的批量应用是通过块和模式来构建的&#xff0c;同时也应该能够为程序开发人员和设计人员提供结构的样例和基础的批量处理程序。当你开始设计一个批量作业任务的时候&#xff0c;商业逻辑应该被拆分一系列的步骤&#xff0c;而这…