sSymbol
1. ES5的对象属性名都是字符串,容易造成命名冲突,ES6引入Symbol机制,以保证每个属性名都是独一无二的; 2. Symbol是JS的第七种原始数据类型,其他六种分别是undefined、null、布尔值、字符串、数值、对象; 3. Symbol值通过Symbol函数生成,凡是属于 Symbol 类型的属性名,保证不会与其他属性名产生冲突; let s1 = Symbol(); let s2 = Symbol('foo'); --> s2.toString(); // 1. 为Symbol函数添加的参数,就等于加上了描述,更容易区分,即使参数名相同,两个Symbol值也是不同的; 2. 如果参数是一个对象,就会调用该对象的toString(),将其转为字符串,然后才生成一个 Symbol 值; const obj = { toString() { return 'abc'; } }; const sym = Symbol(obj); //Symbol(abc) 4. Symbol 值不能与其他类型的值进行运算,即使是字符串拼接也不行; 1. 但是,Symbol 值可以显式转为字符串; String(s2); s2.toString(); // "Symbol(foo)" 2. Symbol 值也可以转为布尔值,但是不能转为数值。 Boolean(s2) // true !s2 // false 5. Symbol值作为对象的属性 let sym = Symbol(); 1. 第一种写法 let a = {}; a[sym] = 'Hello!'; 2. 第二种写法 let s = Symble(); let a = { [sym]: 'Hello!', [s](arg) { ... } }; 3. 第三种写法 let a = {}; Object.defineProperty(a, sym, { value: 'Hello!' }); 4. 获取属性值,不能通过 . 访问或设置 a[sym] // "Hello!" a[s](123); 6. 把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'); const COLOR_RED = Symbol(); const COLOR_GREEN = Symbol(); function getComplement(color) { switch (color) { case COLOR_RED: ...... case COLOR_GREEN: ...... default: ...... } } 7. Symbol作为属性名时,不会被 for-in、for-of 循环中 1. 也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回; 2. 但Symbol绝不是私有属性,Object.getOwnPropertySymbols()可以获取对象的所有 Symbol 属性名; 3. Object.getOwnPropertySymbols() 返回一个 Symbol 属性名的数组 const obj = {}; let a = Symbol('a'); let b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; const syms = Object.getOwnPropertySymbols(obj); //[Symbol(a), Symbol(b)] 4. Reflect.ownKeys():可以返回所有类型的键名,包括常规键名和 Symbol 键名; let obj = { [Symbol('ab')]: 1, enum: 2 }; Reflect.ownKeys(obj); //["enum", "Symbol(ab)] 5. 由于 Symbol 作为属性名时,不会被常规方法遍历得到,可以为对象定义一些非私有的、但又希望只用于内部的方法。 8. Symbol.for(),Symbol.keyFor() 1. Symbol.for('key'):复用Symbol,如果以 key 作为参数的Symbol存在,则直接返回,不存在则创建; let s1 = Symbol.for('foo'); //新建 let s2 = Symbol.for('foo'); //复用 s1 === s2; // true 2. Symbol.for() 会被登记在全局环境中以供搜索,如果全局环境中已经存在了同 key 的Symbol,则复用; 而 Symbol() 每次都会新建一个不同的Symbol,且不会被登记在全局环境,所以不会被搜索到; 3. Symbol.keyFor():在全局环境中搜索一个已登记的Symbol let s1 = Symbol.for("foo"); Symbol.keyFor(s1); // "foo" let s2 = Symbol("foo"); //不会被登记 Symbol.keyFor(s2); // undefined 4. Symbol.for 登记在全局环境中,可以在不同的 iframe 或 service worker 中取到同一个值。
内置的Symbol值
ES6提供了11个内置的Symbol值,指向语言内部使用的方法 1. Symbol.hasInstance:对象的Symbol.hasInstance属性,指向一个内部方法; 1. 当其他对象调用 instanceof 时,会调用此内部方法 class Test { [Symbol.hasInstance](foo) { return foo instanceof Array; --> 如果foo是数组,则返回true } } [1, 2, 3] instanceof new Test(); // true 2. 静态方式 class Even { static [Symbol.hasInstance](obj) { return Number(obj) % 2 === 0; } } // 等同于: const Even = { [Symbol.hasInstance](obj) { return Number(obj) % 2 === 0; } }; 1 instanceof Even // false 2 instanceof Even // true 2. Symbol.isConcatSpreadable:对象的布尔值属性,表示该对象用于Array.prototype.concat()时,是否可以展开; 3. Symbol.species,Symbol.match,Symbol.replace,Symbol.search,Symbol.split, 4. Symbol.iterator:对象的Symbol.iterator属性,指向该对象的默认遍历器方法; 1. for-of遍历对象时,会调用Symbol.iterator方法,返回该对象的默认遍历器 const myIter = {}; myIter[Symbol.iterator] = function* () { yield 1; yield 2; }; [...myIter] // [1, 2] 5. Symbol.toPrimitive,Symbol.toStringTag,Symbol.unscopables
Proxy
1. Proxy:代理,在目标对象之前架设的一层拦截,外界对该对象的访问,都必须先通过这层拦截; var proxy = new Proxy(target, handler); 1. target 所要拦截的目标对象 2. handler 也是一个对象,用来定制拦截行为 2. get() 1. 用于拦截某个属性的读取操作,接受三个参数:目标对象、属性名、proxy实例本身(可选) var person = { name: "张三" }; var proxy = new Proxy(person, { get: function(target, property) { if (property in target) { ---> 访问的属性存在,则返回属性值 return target[property]; } else { -----> 访问的属性不存在,则抛出一个异常 throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); proxy.name // "张三" proxy.age // 抛出一个错误 2. get方法可以继承,拦截操作定义在 Prototype 对象上面 let obj = Object.create(proxy); obj.age //抛出一个错误 3. 如果一个属性不可配置且不可写,则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错 const target = Object.defineProperties({}, { foo: { value: 123, writable: false, //不可写 configurable: false //不可配置 }, }); const proxy = new Proxy(target, { get(target, propKey) { return 'abc'; ---> 修改属性值 } }); proxy.foo //访问报错 2. set() 1. 用来拦截某个属性的赋值操作,接受四个参数:目标对象、属性名、属性值、Proxy实例本身(可选)。 let person = new Proxy({}, { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { --> 不是数字,抛出异常 throw new TypeError('The age is not an integer'); } if (value > 200) { --> 数字大于200,抛出异常 throw new RangeError('The age seems invalid'); } } obj[prop] = value; //设置属性 } }); person.age = 100; //赋值成功 person.age = 'young'; // 报错 person.age = 300; // 报错 2. 在 set() 和 get() 中判断访问的属性名是否以 _ 开头,如果是,则抛出异常,从而禁止访问"私有"属性; 3. 如果目标对象自身的某个属性,不可写且不可配置,那么 set() 将不起作用; 4. 另外,严格模式下,set代理必须显式返回true,否则就会报错 'use strict'; const proxy = new Proxy({}, { set: function(obj, prop, value, receiver) { obj[prop] = receiver; return false; // 无论有没有下面这一行,都会报错 } }); proxy.foo = 'bar'; //TypeError 3. apply():拦截函数的调用、call() 和 apply() 操作 1. apply() 接受三个参数:目标对象、目标对象的上下文对象(this)、目标对象的参数数组; var fn = function (left, right) { return left + right; }; var p = new Proxy(fn, { apply: function (target, ctx, args) { return target.call(ctx, ...args) * 10; // Reflect.apply(...arguments)*10; } }); p(1, 2); //30 p.call(null, 2, 2) //40 p.apply(null, [3, 2]); //50 2. 另外,直接调用Reflect.apply(),也会被拦截 Reflect.apply(p, null, [4, 2]); // 60 4. has():拦截 HasProperty 操作,判断对象是否具有某个属性,典型的操作就是 in 运算符 1. has() 接受两个参数:目标对象、需查询的属性名; 2. 使用 has() 隐藏 _ 开头的属性,不被in运算符发现 var target = { _prop: 'foo', prop: 'foo' }; var proxy = new Proxy(target, { has (target, key) { if (key[0] === '_') { return false; //隐藏 _ 开头的属性 } return key in target; } }); '_prop' in proxy // false 3. 如果原对象不可配置或者禁止扩展,has() 拦截会报错; 4. 注意:has方法拦截的是HasProperty操作,而不是 HasOwnProperty 操作, 也就是说,has() 不判断一个属性是对象自身的属性,还是继承的属性; 5. 另外,虽然 for-in 循环也用到了in运算符,但是 has() 拦截对 for-in 循环不生效。 5. construct():用于拦截 new 命令 1. 接受三个参数:目标对象、构造函数的参数对象、new命令作用的构造函数 var p = new Proxy(function(){}, { construct: function(target, args) { return { value: args[0] * 10 }; } }); (new p(1)).value //10 2. construct() 返回的必须是一个对象,否则会报错 6. deleteProperty():拦截 delete 操作; 1. 若返回false或抛出异常,则属性会删除失败; 2. 目标对象自身的不可配置的属性,不能被删除,否则报错. 7. defineProperty():拦截 Object.defineProperty() 操作 1. 返回false时,新属性添加无效; 2. 如果目标对象不可扩展,defineProperty() 不能增加目标对象上不存在属性,否则会报错; 3. 如果目标对象的某个属性不可写或不可配置,defineProperty() 也不得改变这两个设置。 8. ownKeys():拦截对象自身属性的读取操作 1. 拦截操作 Object.getOwnPropertyNames()、Object.keys() Object.getOwnPropertySymbols()、for-in 2. 拦截 Object.keys() 时,会自动过滤三类属性: 目标对象上不存在的属性、属性名为 Symbol值、不可遍历(enumerable)的属性 let target = { a:1, b:2, c:3, [Symbol.for('secret')]:'4' }; Object.defineProperty(target, 'key', { ---> 新添加一个属性, enumerable: false, -----> 属性不可遍历 configurable: true, writable: true, value: 'static' -----> 属性值为'static' }); let proxy = new Proxy(target, { ownKeys(target) { return ['a', 'd', Symbol.for('secret'), 'key']; } }); Object.keys(proxy) // ['a'],自动过滤了 3. ownKeys() 返回的只能是 字符串 或 Symbol值 的数组,否则就会报错; 4. 如果目标对象自身包含不可配置的属性,则 ownKeys() 必须返回该属性,否则报错; 5. 如果目标对象是不可扩展的,ownKeys() 返回的数组之中必须包含原对象的所有属性,且不能包含多余的属性,否则报错; var obj = { a: 1 }; Object.preventExtensions(obj); //配置不可扩展 var p = new Proxy(obj, { ownKeys: function(target) { return ['a', 'b']; //返回多余属性 } }); Object.getOwnPropertyNames(p); //报错Uncaught TypeError 9. getPrototypeOf():主要用来拦截获取对象原型 1. 拦截操作 Object.prototype.__proto__、Object.prototype.isPrototypeOf() Object.getPrototypeOf()、Reflect.getPrototypeOf()、instanceof 2. getPrototypeOf() 的返回值必须是对象或者null,否则报错; 3. 如果目标对象不可扩展, getPrototypeOf() 必须返回目标对象的原型对象。 10. setPrototypeOf():主要用来拦截Object.setPrototypeOf() 1. 该方法只能返回布尔值,否则会被自动转为布尔值; 2. 如果目标对象不可扩展,setPrototypeOf() 不得改变目标对象的原型. 11. Proxy.revocable():返回一个可取消的 Proxy 实例 let {proxy, revoke} = Proxy.revocable({}, {}); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked 1. 此方法返回一个对象,该对象的 proxy 属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例; 2. 当执行 revoke() 之后,再访问Proxy实例,就会抛出一个错误; 3. 使用场景:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。 12. this 问题 1. 虽然 Proxy 可以代理目标对象的访问,但它不是透明代理,即使不做任何拦截,也无法保证与目标对象的行为一致, 主要是因为:使用 Proxy 代理后,目标对象内部的this关键字会指向 Proxy 代理; 2. 有些原生对象的内部属性,只有通过正确的 this 才能拿到,所以 Proxy 也无法代理这些原生对象的属性 const target = new Date(); const proxy = new Proxy(target, {}); proxy.getDate(); // TypeError 3. 让 this 绑定原始对象,以访问Date的 getDate() const target = new Date('2015-01-01'); const proxy = new Proxy(target, { get(target, prop) { if (prop === 'getDate') { return target.getDate.bind(target); } return Reflect.get(target, prop); } }); proxy.getDate() // 1 13. Proxy 可以拦截目标对象的任意属性,这使得它很合适用于 Web 服务的客户端; 14. 同理,Proxy 也可以用来实现数据库的 ORM 层。
Reflect
1. ES6 设计 Reflect 的目的 1. 将Object对象的一些明显属于语言内部的方法部署在Reflect上,如Object.defineProperty() 2. 修改某些Object方法的返回结果,让其变得更合理; Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出错误; 而 Reflect.defineProperty(obj, name, desc) 则会返回false 3. 让Object操作都变成函数形式 name in obj ---> Reflect.has(obj, name) delete obj[name] ---> Reflect.deleteProperty(obj, name) 4. Reflect的方法与Proxy的方法一一对应,让Proxy可以调用Reflect上的方法,完成默认行为; 也就是说,不管Proxy怎么修改默认行为,总可以在Reflect上获取对应方法的默认行为。 var proxy = new Proxy(obj, { get(target, name) { console.log('get', target, name); return Reflect.get(target, name); }, deleteProperty(target, name) { console.log('delete' + name); return Reflect.deleteProperty(target, name); }, has(target, name) { console.log('has' + name); return Reflect.has(target, name); } }); 5. Proxy对象的拦截 get、delete、has 操作,内部又都调用对应的Reflect方法,保证原生行为能够正常执行。 2. Reflect.get(target, name, receiver):返回target对象的name属性值,没有则返回undefined 1. 第一个参数target必须是对象,否则会报错; 2. 如果name属性部署了读取函数(getter),则读取函数的this绑定receiver var target = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, }; var receiver = { foo: 4, bar: 4 }; Reflect.get(target, 'baz'); // 3 Reflect.get(target, 'baz', receiver); // 8 3. Reflect.set(target, name, value, receiver):设置target对象的name属性值为value 1. 如果name属性设置了赋值函数(setter),则赋值函数的this绑定receiver var target = { foo: 4, set bar(value) { return this.foo = value; }, }; var receiver = { foo: 0 }; Reflect.set(target, 'bar', 1, receiver); target.foo // 4,target对象不受影响 receiver.foo // 1,显式绑定了receiver,只影响receiver Reflect.set(target, 'foo', 2); target.foo // 2 2. Proxy 和 Reflect 联合使用时,Proxy拦截赋值操作,Reflect完成赋值的默认行为,而且传入了receiver, 那么 Reflect.set() 会触发 Proxy.defineProperty() 拦截 let obj = { a: 'a' }; let p = new Proxy(obj, { 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); } }); p.a = 'A'; //触发set()、defineProperty() 1. 因为 Proxy.set() 的 receiver 参数总是指向当前的 Proxy实例p, 一旦 Reflect.set() 传入 receiver,就会将属性赋值到 receiver 上,导致触发 defineProperty 拦截。 2. 所以,此时不要给 Reflect.set() 传入 receiver 4. Reflect.has(obj, name):对应 name in obj 里的in运算符,存在则返回true,否则返回false 5. Reflect.deleteProperty(obj, name):等同于 delete obj[name],返回true/false 6. Reflect.construct(target, args):等同于new target(...args),一种不使用new 来调用构造函数的方法; 7. Reflect.getPrototypeOf(obj):用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj) const myObj = new FancyThing(); Object.getPrototypeOf(myObj) === FancyThing.prototype; Reflect.getPrototypeOf(myObj) === FancyThing.prototype; 1. 区别:如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行; 而Reflect.getPrototypeOf会报错。 Object.getPrototypeOf(1) // Number{[[PrimitiveValue]]: 0} Reflect.getPrototypeOf(1) // 报错 8. Reflect.setPrototypeOf(obj, newProto):设置目标对象的原型,对应Object.setPrototypeOf(obj, newProto) 1. 返回一个布尔值,表示是否设置成功 const obj = {}; Reflect.setPrototypeOf(obj, Array.prototype); obj.length // 0,数组Array的原型被设置到obj对象上 2. 如果目标对象禁止扩展,Reflect.setPrototypeOf() 返回false 3. 如果obj不是对象,Object.setPrototypeOf() 会返回obj,而Reflect.setPrototypeOf会报错; 9. Reflect.apply(func, thisArg, args) 1. 等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数; 2. 一般来说,fn.apply(obj, args)可以绑定一个函数的this对象,但如果函数定义了自己的apply(), 就只能写成 Function.prototype.apply.call(fn, obj, args),Reflect则可以简化 const ages = [11, 33, 12, 54, 18, 96]; // 旧写法 const youngest = Math.min.apply(Math, ages); 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, []); 10. Reflect.defineProperty(target, propertyKey, attributes) 1. 用于替代 Object.defineProperty(),用于为对象定义属性 2. 可以与Proxy.defineProperty() 配合使用 const p = new Proxy({}, { defineProperty(target, prop, descriptor) { console.log(descriptor); return Reflect.defineProperty(target, prop, descriptor); } }); p.foo = 'bar'; // {value: "bar", writable: true, enumerable: true, configurable: true} p.foo // "bar" 11. Reflect.ownKeys(target):用于返回对象的所有属性名 1. 基本等同于 Object.getOwnPropertyNames() 与 Object.getOwnPropertySymbols() 之和 var myObject = { foo:1, bar:2, [Symbol.for('baz')]:3, [Symbol.for('bing')]:4 }; Reflect.ownKeys(myObject) // ['foo', 'bar', Symbol(baz), Symbol(bing)] 12. Reflect.isExtensible(target):对应Object.isExtensible(),当前对象是否可扩展 13. Reflect.preventExtensions(target):让一个对象变为不可扩展,返回true/false,表示是否成功。