在 “NodeJS系列(3)- ECMAScript 6 (ES6) 语法(一)” 和 “NodeJS系列(4)- ECMAScript 6 (ES6) 语法(二)” 里,我们介绍并演示 let、const、Symbol、函数扩展、类 等 ES6 语法和概念。
本文在 “NodeJS系列(2)- NPM 项目 Import/Export ES6 模块” 的 npmdemo 项目的基础上,继续介绍并演示 Reflect、Proxy 等 ES6 语法和概念。
NodeJS ES6:https://nodejs.org/en/docs/es6
ECMA:https://www.ecma-international.org/publications-and-standards/standards/ecma-262/
1. Reflect
ES6 中将 Object 的一些内部方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。
Reflect 对象对某些方法的返回结果进行了修改,使其更合理。Reflect 对象使用函数的方式实现了 Object 的命令式操作。
Reflect 对象是一个全局的普通对象,它的原型是 Object。查看 Reflect 的原型,代码如下:
console.log(Reflect.__proto__ === Object.prototype); // 输出 true
Reflect 对象的静态方法列表:
名称 描述
Reflect.get(target,name,receiver) 读取对象的属性。参数 target 是目标对象;name 是属性名;receiver 是可选项,可以理解为上下文 this 对象。
Reflect.set(target,name,value,receiver) 设置对象的属性。参数 value 是要设置的属性值。
Reflect.apply(target,thisArg,args) 通过指定的参数列表对目标函数的调用。参数 target 是目标函数;thisArg 是目标函数调用时绑定的 this 对象;args 是函数参数列表。
Reflect.construct(target,args[, newTarget]) 类似 new 的功能,创建一个实列方法。参数 target 是目标函数;args 是调用构造函数传递的参数数组或伪数组;newTarget 是可选项,可以把实例方法的构造函数指向 newTarget。
Reflect.defineProperty(target,name,desc) 定义新的属性或修改原有的属性。参数 target 是目标对象;name 是属性名;desc 是属性的描述。
Reflect.deleteProperty(target,name) 删除对象的属性。参数 target 是目标对象;name 是属性名。
Reflect.has(target,name) 查看对象的属性是否存在。
Reflect.ownKeys(target) 获取目标对象的属性键组成的数组。
Reflect.preventExtensions(target) 阻止对象扩展新属性和方法。
Reflect.isExtensible(target) 检查对象是否可以扩展新属性和方法。
Reflect.getOwnPropertyDescriptor(target, name) 获取对象的属性描述。
Reflect.getPrototypeOf(target) 获取对象的原型,即 prototype 属性。
Reflect.setPrototypeOf(target, prototype) 设置对象的原型。
示例,创建 D:\workshop\nodejs\npmdemo\es6_08.js 文件,内容如下
let obj1 = { name: 'Test', age: 18, get info() { return this.name + ', ' + this.age }, set info(value) { return this.age = value } } let obj2 = { name: 'Demo', age: 20 } console.log(Reflect.__proto__ === Object.prototype); console.log(Reflect.get(obj1, 'name')) // 输出:Test console.log(Reflect.get(obj1, 'info', obj2)) // 输出:Demo, 20 Reflect.set(obj1, 'age', '25') console.log(obj1.age) // 输出:25 Reflect.set(obj1, 'age') // value 为空,将 age 属性清除 console.log(obj1.age) // 输出:undefined Reflect.set(obj1, 'info', 5, obj2) console.log(obj2.age) // 输出:5 console.log('--------------------------------------------------------------') console.log(Reflect.has(obj1, 'name')) // 输出:true Reflect.deleteProperty(obj1, 'name') console.log(obj1.name) // 输出:undefined Reflect.defineProperty(obj2, 'now', { value: () => Date.now() }) console.log(Reflect.getOwnPropertyDescriptor(obj2, 'now')) // 输出: { value: [Function: value],writable: false,enumerable: false,configurable: false} console.log(obj2.now()) // 输出:1687943288174 (当前时间戳) console.log(Reflect.isExtensible(obj2)) // 输出:true Reflect.preventExtensions(obj2) console.log(Reflect.isExtensible(obj2)) // 输出:false console.log(Reflect.ownKeys(obj2)) // 输出:[ 'name', 'age', 'now' ] console.log('--------------------------------------------------------------') // function func(name){ this.name = name; } func.prototype.age = 12 let obj3 = Reflect.construct(func, ['ES 6']) console.log(obj3) // 输出:func { name: 'ES 6' } let obj4 = new func('NodeJS') console.log(obj4) // 输出:func { name: 'NodeJS' } console.log(Reflect.getPrototypeOf(obj4)) // 输出:{ age: 12 } Reflect.setPrototypeOf(obj4, Array.prototype) console.log(Reflect.getPrototypeOf(obj4)) // 输出:Object(0) [] console.log(Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1])) // 输出: 5
运行
D:\workshop\nodejs\npmdemo> node es6_08
true Test Demo, 20 25 undefined 5 -------------------------------------------------------------- true undefined { value: [Function: value], writable: false, enumerable: false, configurable: false } 1687949712324 true false [ 'name', 'age', 'now' ] -------------------------------------------------------------- func { name: 'ES 6' } func { name: 'NodeJS' } { age: 12 } Object(0) [] 5
2. Proxy
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
一个 Proxy 对象由两个部分组成:target、handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
Proxy 的代理(或拦截)方法列表:
名称 描述
get(target, prop, receiver) 拦截读取属性操作。
set(target, prop, value, receiver) 拦截新增或修改属性操作。如果目标对象自身的某个属性,不可写且不可配置,那么 set 方法将不起作用。参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。
apply(target, thisArg, args) 拦截方法调用、call 和 reply 操作。参数 thisArg 是目标函数调用时绑定的 this 对象;args 是函数参数列表。
has(target, prop) 拦截判断属性是否存在操作,即在判断 target 对象是否存在 prop 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性。此方法不拦截 for ... in 循环。
getOwnPropertyDescriptor(target, prop) 拦截获取属性描述操作。
construct(target, args[, newTarget]) 拦截 new 命令。
defineProperty(target, prop, desc) 拦截定义新的属性或修改原有的属性操作。若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性。
deleteProperty(target, prop) 拦截删除属性操作。
ownKeys(target) 拦截获取属性 prop 列表操作。
isExtensible(target) 拦截判断属性是否可扩展操作。
preventExtensions(target) 拦截阻止对象扩展新属性操作。
getPrototypeOf(target) 拦截获取原型对象操作。
setPrototypeOf(target,prototype) 拦截设置原型对象操作。
1) get/set/apply 方法
示例,创建 D:\workshop\nodejs\npmdemo\es6_09.js 文件,内容如下
let obj1 = { name: 'Default' } let handler1 = { get: function(target, prop) { console.log("get('" + prop + "')") return target[prop] // 不是 target.prop }, set: function(target, prop, value) { console.log("set('" + prop + "', " + value + ")") target[prop] = value return true // 严格模式下,set 代理如果没有返回 true,就会报错。 }, apply: function(target, thisArg, args) { console.log("apply(" + args + ")") return Reflect.apply(...arguments) } } let proxy1 = new Proxy(obj1, handler1) console.log("proxy1.name: " + proxy1.name) // 执行 handler1.get proxy1.name = 'Test1' // 执行 handler1.set console.log("proxy1.name: " + proxy1.name) // target 可以为空对象 let obj2 = {} let proxy2 = new Proxy(obj2, handler1) console.log("proxy2.name: " + proxy2.name) // 执行 handler1.get proxy2.name = 'Test2' // 执行 handler1.set console.log("proxy2.name: " + proxy2.name) // handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象 let obj3 = {} let handler3 = {} let proxy3 = new Proxy(obj3, handler3) console.log("proxy3.name: " + proxy3.name) proxy3.name = 'Test3' console.log("proxy3.name: " + proxy3.name) // function sum(a, b) { return a+b } let proxy4 = new Proxy(sum, handler1) console.log("proxy4(2, 1): " + proxy4(2, 1)) // apply
运行
D:\workshop\nodejs\npmdemo> node es6_09
get('name') proxy1.name: Default set('name', Test1) get('name') proxy1.name: Test1 get('name') proxy2.name: undefined set('name', Test2) get('name') proxy2.name: Test2 proxy3.name: undefined proxy3.name: Test3 apply(2,1) proxy4(2, 1): 3
2) has/getOwnPropertyDescriptor/construct/defineProperty/deleteProperty 方法
示例,创建 D:\workshop\nodejs\npmdemo\es6_10.js 文件,内容如下
let obj1 = { name: 'Test' } let handler1 = { has: function(target, prop) { console.log("has('" + prop + "')") return Reflect.has(target, prop) }, defineProperty: function(target, prop, desc) { console.log("defineProperty('" + prop + "')") return Reflect.defineProperty(target, prop, desc) }, getOwnPropertyDescriptor: function(target, prop) { console.log("getOwnPropertyDescriptor(" + prop + ")") return Reflect.getOwnPropertyDescriptor(target, prop) }, deleteProperty: function(target, prop) { console.log("deleteProperty('" + prop + "')") return Reflect.deleteProperty(target, prop) }, construct: function(target, args, newTarget) { console.log("construct('" + args + "')") return Reflect.construct(target, args, newTarget) } } let proxy1 = new Proxy(obj1, handler1) console.log('name' in proxy1) // has console.log('tt' in proxy1) // has proxy1.age = 12 // defineProperty console.log(Object.getOwnPropertyDescriptor(proxy1, 'age')) delete proxy1.age // deleteProperty class C1 { constructor(name) { this.name = name } } let proxy2 = new Proxy(C1, handler1) let proxyObj = new proxy2('Test3') // construct console.log(proxyObj)
运行
D:\workshop\nodejs\npmdemo> node es6_10
has('name') true has('tt') false getOwnPropertyDescriptor(age) { value: 16, writable: true, enumerable: true, configurable: true } C1 { name: 'Test3' }
3) ownKeys/isExtensible/preventExtensions/getPrototypeOf/setPrototypeOf 方法
示例,创建 D:\workshop\nodejs\npmdemo\es6_11.js 文件,内容如下
let obj1 = { color: 'red' } let handler2 = { ownKeys: function(target) { console.log("ownKeys()") return Reflect.ownKeys(target) }, isExtensible: function(target) { console.log("isExtensible()") return Reflect.isExtensible(target) }, preventExtensions: function(target) { console.log("preventExtensions()") return Reflect.preventExtensions(target) }, getPrototypeOf: function(target) { console.log("getPrototypeOf()") return Reflect.getPrototypeOf(target) }, setPrototypeOf: function(target, prototype) { console.log("setPrototypeOf('" + prototype + "')") return Reflect.setPrototypeOf(target, prototype) } } let proxy1 = new Proxy(obj1, handler1) console.log(Object.keys(proxy1)) // ownKeys console.log(Object.isExtensible(proxy1)) // isExtensible console.log(Object.preventExtensions(proxy1)) // preventExtensions console.log(Object.isExtensible(proxy1)) // isExtensible class C1 { } let proxy2 = new Proxy(C1, handler1) Object.setPrototypeOf(proxy2, { name: 'object'}) // setPrototypeOf console.log(Object.getPrototypeOf(proxy2)) // getPrototypeOf
运行
D:\workshop\nodejs\npmdemo> node es6_11
ownKeys() [ 'color' ] isExtensible() true preventExtensions() { color: 'red' } isExtensible() false setPrototypeOf('[object Object]') getPrototypeOf() { name: 'object' }
4) 可取消的 Proxy 实例
使用 Proxy.revocable() 方法创建可取消的 Proxy 实例, 格式如下:
let {proxy, revoke} = Proxy.revocable({}, {}); proxy.name = "Test"; revoke(); // 取消 Proxy 实例 proxy.name // 报错: “TypeError: Cannot perform 'get' on a proxy that has been revoked”