ES6新增三大对象
2019-03-29 本文已影响0人
hellomyshadow
Symbol
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,表示是否成功。