JavaScript 定义对象的属性,有两种方法。
// 方法一 用标识符作为属性名 obj.foo = true; // 方法二 用表达式作为属性名 obj['a' + 'bc'] = 123;上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };表达式还可以用于定义方法名。
l
et obj = { ['h' + 'ello']() { return 'hi'; } }; obj.hello() // hi注意,属性名表达式与简洁表示法,不能同时使用,会报错。
// 报错 const foo = 'bar'; const bar = 'abc'; const baz = { [foo] }; // 正确 const foo = 'bar'; const baz = { [foo]: 'abc'};ɪˈnjuːmərəbəl 目前,有四个操作会忽略enumerable为false的属性。
for...in循环:只遍历对象自身的和继承的可枚举的属性。 Object.keys():返回对象自身的所有可枚举的属性的键名。 JSON.stringify():只串行化对象自身的可枚举的属性。 Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。这四个操作之中,前三个是 ES5 就有的,最后一个Object.assign()是 ES6 新增的。其中,只有for…in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。 对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for…in遍历到。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable // false Object.getOwnPropertyDescriptor([], 'length').enumerable // false所有 Class 的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable // false尽量不要用for…in循环,而用Object.keys()代替。
Object.setPrototypeOf(obj, prototype)方法设置一个指定的对象的原型 ( 即内部[[Prototype]]属性)到另一个对象或 null。
object.setPrototypeOf(obj, prototype) 参数: obj 要设置其原型的对象 prototype 该对象的新原型(一个对象 或 null).我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
const proto = { foo: 'hello' }; const obj = { foo: 'world', find() { return super.foo; } }; Object.setPrototypeOf(obj, proto); obj.find() // "hello"上面代码中,对象obj.find()方法之中,通过super.foo引用了原型对象proto的foo属性。
// 报错 const obj = { foo: super.foo } // 报错 const obj = { foo: () => super.foo } // 报错 const obj = { foo: function () { return super.foo } }上面三种super的用法都会报错,因为对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)。
上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。 注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: { b: 1 } }; let { ...x } = obj; obj.a.b = 2; x.a.b // 2扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = { a: 1 }; let o2 = { b: 2 }; o2.__proto__ = o1; let { ...o3 } = o2; o3 // { b: 2 } o3.a // undefined上面代码中,对象o3复制了o2,但是只复制了o2自身的属性,没有复制它的原型对象o1的属性。
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 proto 新创建对象的原型对象。
const o = Object.create({ x: 1, y: 2 }); o.z = 3; let { x, ...newObj } = o; let { y, z } = newObj; x // 1 y // undefined z // 3上面代码中,变量x是单纯的解构赋值,所以可以读取对象o继承的属性;变量y和z是扩展运算符的解构赋值,只能读取对象o自身的属性,所以变量z可以赋值成功,变量y取不到值。ES6 规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,所以上面代码引入了中间变量newObj,如果写成下面这样会报错。 rap2:70440070
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 }; const source1 = { b: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
Object.assign({b: 'c'}, Object.defineProperty({}, 'invisible', { enumerable: false, value: 'hello' }) ) // { b: 'c' }属性名为 Symbol 值的属性,也会被Object.assign拷贝。
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) // { a: 'b', Symbol(c): 'd' }取值函数get xxx()的处理
Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
const source = { get foo() { return 1 } }; const target = {}; Object.assign(target, source) // { foo: 1 }上面代码中,source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。
(1)为对象添加属性
class Point { constructor(x, y) { Object.assign(this, {x, y}); } }上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。
(2)为对象添加方法
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };(3)克隆对象
function clone(origin) { return Object.assign({}, origin); }不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。 会返回某个对象属性的描述对象
const obj = { foo: 123, get bar() { return 'abc' } }; console.log(Object.getOwnPropertyDescriptors(obj)); // { foo: // { value: 123, // writable: true, // enumerable: true, // configurable: true }, // bar: // { get: [Function: get bar], // set: undefined, // enumerable: true, // configurable: true } }Object.getOwnPropertyDescriptors()与Object.assign()区别: 该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
该Object.defineProperties()方法直接在对象上定义新属性或修改现有属性,并返回该对象。 Object.defineProperties(obj, props) Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以实现正确拷贝。 正确拷贝get属性和set属性:
const source = { set foo(value) { console.log(value); } }; const target2 = {}; Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); console.log(Object.getOwnPropertyDescriptor(target2, 'foo')); // { get: undefined, // set: [Function: set foo], // enumerable: true, // configurable: true }上面代码中,两个对象合并的逻辑可以写成一个函数。
const shallowMerge = (target, source) => Object.defineProperties( target, Object.getOwnPropertyDescriptors(source) );Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
const person = { isHuman: false, printIntroduction: function() { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = Object.create(person); me.name = 'Matthew'; // "name" is a property set on "me", but not on "person" me.isHuman = true; // inherited properties can be overwritten me.printIntroduction(); // expected output: "My name is Matthew. Am I human? true" Object.create(proto[, propertiesObject]) proto:新创建对象的原型对象。 propertiesObject:这些属性对应Object.defineProperties()的第二个参数。 返回值: 一个新对象,带着指定的原型对象和属性。Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。
const prototype1 = {}; const object1 = Object.create(prototype1); console.log(Object.getPrototypeOf(object1) === prototype1); // expected output: true该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。
Object.getPrototypeOf(obj); obj:要返回其原型的对象。 返回值:给定对象的原型。如果没有继承属性,则返回 null 。Object.getPrototypeOf()方法返回[[Prototype]]指定对象的原型(即内部属性的值)。[[Prototype]]即为__proto__
function Rectangle() { // ... } const rec = new Rectangle(); Object.getPrototypeOf(rec) === Rectangle.prototype // true Object.setPrototypeOf(rec, Object.prototype); Object.getPrototypeOf(rec) === Rectangle.prototype // false Object.getPrototypeOf(1) === Number.prototype // true Object.getPrototypeOf('foo') === String.prototype // true Object.getPrototypeOf(true) === Boolean.prototype // trueObject.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
let proto = {}; let obj = { x: 10 }; Object.setPrototypeOf(obj, proto); proto.y = 20; proto.z = 40; obj.x // 10 obj.y // 20 obj.z // 40上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的 API,使用Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。 Object.values只返回对象自身的可遍历属性。
const obj = Object.create({}, {p: {value: 42}}); Object.values(obj) // []上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的,因为p的属性描述对象的enumerable默认是false,Object.values不会返回这个属性。只要把enumerable改成true,Object.values就会返回属性p的值。
const obj = Object.create({}, {p: { value: 42, enumerable: true } }); Object.values(obj) // [42]Object.values会过滤属性名为 Symbol 值的属性。
Object.values({ [Symbol()]: 123, foo: 'abc' }); // ['abc']如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo') // ['f', 'o', 'o']如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。 ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
let s = Symbol(); typeof s // "symbol"注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo'); let s2 = Symbol('bar'); s1 // Symbol(foo) s2 // Symbol(bar) s1.toString() // "Symbol(foo)" s2.toString() // "Symbol(bar)"注意,Symbol 值作为对象属性名时,不能用点运算符。
const mySymbol = Symbol(); const a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。 同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let s = Symbol(); let obj = { [s]: function (arg) { ... } }; obj[s](123);采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。
let obj = { [s](arg) { ... } };Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。
const log = {}; log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') }; console.log(log.levels.DEBUG, 'debug message'); console.log(log.levels.INFO, 'info message');由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
let size = Symbol('size'); class Collection { constructor() { this[size] = 0; //Collection长度 } add(item) { this[this[size]] = item; this[size]++; } static sizeOf(instance) { return instance[size]; } } let x = new Collection(); Collection.sizeOf(x) // 0 x.add('foo'); Collection.sizeOf(x) // 1 Object.keys(x) // ['0'] Object.getOwnPropertyNames(x) // ['0'] Object.getOwnPropertySymbols(x) // [Symbol(size)]这就造成了一种非私有的内部方法的效果。
Symbol.for()为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。 作用:重新使用同一个 Symbol 值。会被登记在全局环境中供搜索。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”)30 次,每次都会返回同一个 Symbol 值,但是调用Symbol(“cat”)30 次,会返回 30 个不同的 Symbol 值。
Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
let s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined1.Symbol.species 对象的Symbol.species属性,指向一个构造函数。创建衍生对象时,会使用该属性。
class MyArray extends Array { } const a = new MyArray(1, 2, 3); const b = a.map(x => x); const c = a.filter(x => x > 1); b instanceof MyArray // true c instanceof MyArray // true上面代码中,子类MyArray继承了父类Array,a是MyArray的实例,b和c是a的衍生对象。你可能会认为,b和c都是调用数组方法生成的,所以应该是数组(Array的实例),但实际上它们也是MyArray的实例。 Symbol.species属性就是为了解决这个问题而提供的。
class MyArray extends Array { static get [Symbol.species]() { return Array; } } const a = new MyArray(); const b = a.map(x => x); b instanceof MyArray // false b instanceof Array // true上面代码中,a.map(x => x)生成的衍生对象,就不是MyArray的实例,而直接就是Array的实例。 static修饰的方法被称之为静态方法也叫作类方法,加static的方法,可以通过类名直接访问,也可以通过对象名访问,而不加static只能通过对象名访问,加了static的方法,不能再内部写this,因为直接用类名点方法的时候,没有当前对象。 https://www.cnblogs.com/wanggang1987/p/12173802.html 2.Symbol.replace 对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
String.prototype.replace(searchValue, replaceValue) // 等同于 searchValue[Symbol.replace](this, replaceValue)下面是一个例子。
const x = {}; //rest参数写法:rest 参数搭配的变量是一个数组,该变量s将多余的参数放入数组中。 x[Symbol.replace] = (...s) => console.log(s); 'Hello'.replace(x, 'World') // ["Hello", "World"]Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是Hello,第二个参数是替换后的值,上面例子是World。
indexOf() 方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。
str.indexOf(searchValue [, fromIndex])扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构。
let set = new Set(['red', 'green', 'blue']); let arr = [...set]; // ['red', 'green', 'blue']扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5]; let unique = [...new Set(arr)]; // [3, 5, 2] //方法一 [...new Set(array)] //方法二 function dedupe(array) { return Array.from(new Set(array)); } dedupe([1, 1, 2, 3]) // [1, 2, 3]Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] // 例二 const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); items.size // 5 // 例三 const set = new Set(document.querySelectorAll('div')); set.size // 56 // 类似于 const set = new Set(); document .querySelectorAll('div') .forEach(div => set.add(div)); set.size // 56上面代码中,例一和例二都是Set函数接受数组作为参数,例三是接受类似数组的对象作为参数。 Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。
let set = new Set(); let a = NaN; let b = NaN; set.add(a); set.add(b); set // Set {NaN}Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // (a 相对于 b 的)差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}Set 实例的属性和方法: Set.prototype.constructor:构造函数,默认就是Set函数。 Set.prototype.size:返回Set实例的成员总数 Set.prototype.add(value):添加某个值,返回 Set 结构本身。 Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。 Set.prototype.clear():清除所有成员,没有返回值。
Set 结构的实例有四个遍历方法,可以用于遍历成员: Set.prototype.keys():返回键名的遍历器 Set.prototype.values():返回键值的遍历器 Set.prototype.entries():返回键值对的遍历器 Set.prototype.forEach():使用回调函数遍历每个成员
上面代码使用 Map 结构的set方法,将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。 事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
const set = new Set([ ['foo', 1], ['bar', 2] ]); const m1 = new Map(set); m1.get('foo') // 1 const m2 = new Map([['baz', 3]]); const m3 = new Map(m2); m3.get('baz') // 3由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
let map = new Map(); map.set(-0, 123); map.get(+0) // 123 map.set(true, 1); map.set('true', 2); map.get(true) // 1 map.set(undefined, 3); map.set(null, 4); map.get(undefined) // 3 map.set(NaN, 123); map.get(NaN) // 123set方法返回的是当前的Map对象,因此可以采用链式写法。
let map = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c');forEach方法还可以接受第二个参数,用来绑定this。
const reporter = { report: function(key, value) { console.log("Key: %s, Value: %s", key, value); } }; map.forEach(function(value, key, map) { this.report(key, value); }, reporter);上面代码中,forEach方法的回调函数的this,就指向reporter。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, { get: function (target, propKey, receiver) { console.log(`getting ${propKey}!`); return Reflect.get(target, propKey, receiver); }, set: function (target, propKey, value, receiver) { console.log(`setting ${propKey}!`); return Reflect.set(target, propKey, value, receiver); } });ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。
var pipe = function (value) { var funcStack = []; var oproxy = new Proxy({}, { // get方法参数依次为target,propKey get: function (pipeObject, fnName) { if (fnName === 'get') { // array.prototype.reduce(function(total,val),value); // value为传递给total的初始值,如果value无,total为array[0] return funcStack.reduce(function (val, fn) { return fn(val); }, value); } funcStack.push(window[fnName]); //将get之前的函数(即window下名称为double,pow,reverseInt的函数)依次放到数组中。 return oproxy; 返回new Proxy对象 } }); return oproxy; } var double = n => n * 2; var pow = n => n * n; var reverseInt = n => n.toString().split("").reverse().join("") | 0; // console.log(pipe(3)); //返回空的Proxy对象,此时为new Proxy({},{})。 console.log(pipe(3).double.pow.reverseInt.get); // 63下面的例子则是利用get拦截,实现一个生成各种 DOM 节点的通用函数dom。
const dom = new Proxy({}, { get(target, property) { return function (attrs = {}, ...children) { const el = document.createElement(property); for (let prop of Object.keys(attrs)) { //设置标签属性 el.setAttribute(prop, attrs[prop]); } // 填充标签内容 for (let child of children) { if (typeof child === 'string') { child = document.createTextNode(child); } el.appendChild(child); } return el; //返回dom节点 } } }); // dom.a对象读取属性时m在,执行get方法,返回的是函数。 // dom.tagName({attrName:attrValue},content); const el = dom.div({}, 'Hello, my name is ', dom.a({ href: '//example.com' }, 'Mark'), '. I like:', dom.ul({}, dom.li({}, 'The web'), dom.li({}, 'Food'), dom.li({}, '…actually that\'s it') ) ); document.body.appendChild(el);apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。 apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
var handler = { apply (target, ctx, args) { return Reflect.apply(...arguments); } };如果 Proxy对象和 Reflect对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且如果传入了receiver,那么Reflect.set会触发Proxy.defineProperty拦截。
let p = { a: 'a' }; let handler = { set(target, key, value, receiver) { console.log('set'); Reflect.set(target, key, value, receiver) }, defineProperty(target, key, attribute) { console.log('defineProperty'); Reflect.defineProperty(target, key, attribute); } }; let obj = new Proxy(p, handler); obj.a = 'A'; // set // defineProperty上面代码中,Proxy.set拦截里面使用了Reflect.set,而且传入了receiver,导致触发Proxy.defineProperty拦截。这是因为Proxy.set的receiver参数总是指向当前的 Proxy实例(即上例的obj),而Reflect.set一旦传入receiver,就会将属性赋值到receiver上面(即obj),导致触发defineProperty拦截。如果Reflect.set没有传入receiver,那么就不会触发defineProperty拦截。
Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。
一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。
const ages = [11, 33, 12, 54, 18, 96]; // 旧写法 const youngest = Math.min.apply(Math, ages); //相当于Math.min.apply(null, ages);未改变this指向 const oldest = Math.max.apply(Math, ages); const type = Object.prototype.toString.call(youngest); // 新写法 const youngest = Reflect.apply(Math.min, Math, ages); const oldest = Reflect.apply(Math.max, Math, ages); const type = Reflect.apply(Object.prototype.toString, youngest, []);参照:<<JavaScript高级程序设置>> <<ES6入门教程>>