ES6新增对象

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,表示是否成功。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值