zoukankan      html  css  js  c++  java
  • ECMAScript 6笔记(Symbol, Proxy 和 Reflect)

    一、Symbol

    ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

    1.1 概述

    Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分

    var s1 = Symbol('foo');
    var s2 = Symbol('bar');
    
    s1 // Symbol(foo)
    s2 // Symbol(bar)
    
    s1.toString() // "Symbol(foo)"
    s2.toString() // "Symbol(bar)"

    Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。

    // 没有参数的情况
    var s1 = Symbol();
    var s2 = Symbol();
    
    s1 === s2 // false
    
    // 有参数的情况
    var s1 = Symbol("foo");
    var s2 = Symbol("foo");
    
    s1 === s2 // false

    1.2 作为属性名的Symbol

    var mySymbol = Symbol();
    
    // 第一种写法
    var a = {};
    a[mySymbol] = 'Hello!';
    
    // 第二种写法
    var a = {
      [mySymbol]: 'Hello!'
    };
    
    // 第三种写法
    var a = {};
    Object.defineProperty(a, mySymbol, { value: 'Hello!' });//ES5写法
    
    // 以上写法都得到同样结果
    a[mySymbol] // "Hello!"

    注意,Symbol值作为对象属性名时,不能用点运算符。同理,在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。

    var mySymbol = Symbol();
    var a = {};
    
    a.mySymbol = 'Hello!';
    a[mySymbol] // undefined
    a['mySymbol'] // "Hello!"
    
    let s = Symbol();
    
    let obj = {
      [s]: function (arg) { ... }
    };
    
    obj[s](123);

    1.3 消除魔术字符串

    魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。

    function getArea(shape, options) {
      var area = 0;
    
      switch (shape) {
        case 'Triangle': // 魔术字符串
          area = .5 * options.width * options.height;
          break;
        /* ... more code ... */
      }
    
      return area;
    }
    
    getArea('Triangle', {  100, height: 100 }); // 魔术字符串'Triangle'

    上面代码中,字符串“Triangle”就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
    常用的消除魔术字符串的方法,就是把它写成一个变量。

    var shapeType = {
      triangle: 'Triangle'
    };
    
    function getArea(shape, options) {
      var area = 0;
      switch (shape) {
        case shapeType.triangle:
          area = .5 * options.width * options.height;
          break;
      }
      return area;
    }
    
    getArea(shapeType.triangle, {  100, height: 100 });

    上面代码中,我们把“Triangle”写成shapeType对象的triangle属性,这样就消除了强耦合。
    如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用Symbol值。

    const shapeType = {
      triangle: Symbol()
    };

    1.4 属性名的遍历

    Symbol作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。返回一个数组,成员是当前对象的所有用作属性名的Symbol值。

    var obj = {};
    
    var foo = Symbol("foo");
    
    Object.defineProperty(obj, foo, {
      value: "foobar",
    });
    
    for (var i in obj) {
      console.log(i); // 无输出
    }
    
    Object.getOwnPropertyNames(obj)
    // []
    
    Object.getOwnPropertySymbols(obj)
    // [Symbol(foo)]

    另一个新的API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。

    let obj = {
      [Symbol('my_key')]: 1,
      enum: 2,
      nonEnum: 3
    };
    
    Reflect.ownKeys(obj)
    // [Symbol(my_key), 'enum', 'nonEnum']
    var size = Symbol('size');
    
    class Collection {
      constructor() {
        this[size] = 0;
      }
    
      add(item) {
        this[this[size]] = item;
        this[size]++;
      }
    
      static sizeOf(instance) {
        return instance[size];
      }
    }
    
    var 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)]

    上面代码中,对象x的size属性是一个Symbol值,所以Object.keys(x)、Object.getOwnPropertyNames(x)都无法获取它。这就造成了一种非私有的内部方法的效果。利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法

    1.5 Symbol.for(),Symbol.keyFor()

    Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”)30次,每次都会返回同一个Symbol值,但是调用Symbol(“cat”)30次,会返回30个不同的Symbol值。

    var s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"
    
    var s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined

    Symbol.keyFor方法返回一个已登记的Symbol类型值的key。

    二、Proxy

    Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

    2.1 概述

    var proxy = new Proxy(target, handler)

    Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

    var obj = new Proxy({}, {
      get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
      },
      set: function (target, key, value, receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
      }
    });
    
    obj.count = 1
    //  setting count!
    ++obj.count
    //  getting count!
    //  setting count!
    //  2

    注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

    如果handler没有设置任何拦截,那就等同于直接通向原对象。

    var target = {};
    var handler = {};
    var proxy = new Proxy(target, handler);
    proxy.a = 'b';
    target.a // "b"
    var proxy = new Proxy({}, {
      get: function(target, property) {
        return 35;
      }
    });
    
    let obj = Object.create(proxy);
    obj.time // 35
    
    //proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。

    同一个拦截器函数,可以设置拦截多个操作。

    var handler = {
      get: function(target, name) {
        if (name === 'prototype') return Object.prototype;
        return 'Hello, '+ name;
      },
      apply: function(target, thisBinding, args) { return args[0]; },
      construct: function(target, args) { return args[1]; }
    };
    
    var fproxy = new Proxy(function(x,y) {
      return x+y;
    },  handler);
    
    fproxy(1,2); // 1
    new fproxy(1,2); // 2
    fproxy.prototype; // Object.prototype
    fproxy.foo; // 'Hello, foo'

    2.2 Proxy方法

    (1)get(target, propKey, receiver)

    拦截对象属性的读取,比如proxy.foo和proxy[‘foo’],返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。

    (2)set(target, propKey, value, receiver)

    拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。

    (3)has(target, propKey)

    拦截propKey in proxy的操作,返回一个布尔值。

    (4)deleteProperty(target, propKey)

    拦截delete proxy[propKey]的操作,返回一个布尔值。

    (5)enumerate(target)

    拦截for (var x in proxy),返回一个遍历器。

    (6)ownKeys(target)

    拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。

    (7)getOwnPropertyDescriptor(target, propKey)

    拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

    (8)defineProperty(target, propKey, propDesc)

    拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。

    (9)preventExtensions(target)

    拦截Object.preventExtensions(proxy),返回一个布尔值。

    (10)getPrototypeOf(target)

    拦截Object.getPrototypeOf(proxy),返回一个对象。

    (11)isExtensible(target)

    拦截Object.isExtensible(proxy),返回一个布尔值。

    (12)setPrototypeOf(target, proto)

    拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。

    如果目标对象是函数,那么还有两种额外操作可以拦截。

    (13)apply(target, object, args)

    拦截Proxy实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。

    (14)construct(target, args, proxy)

    拦截Proxy实例作为构造函数调用的操作,比如new proxy(…args)。

    实例方法查看阮一峰的ES6入门第11章Proxy和Reflect

    三、Reflect

    Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新API。Reflect对象的设计目的有这样几个。

    3.1概述

    (1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。

    (2)修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。

    (3)让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true

    (4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

    var loggedObj = 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);
      }
    });

    上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

    3.2 Reflect对象的方法

    Reflect对象的方法清单如下,共14个。

    • Reflect.apply(target,thisArg,args)
    • Reflect.construct(target,args)
    • Reflect.get(target,name,receiver)
    • Reflect.set(target,name,value,receiver)
    • Reflect.defineProperty(target,name,desc)
    • Reflect.deleteProperty(target,name)
    • Reflect.has(target,name)
    • Reflect.ownKeys(target)
    • Reflect.enumerate(target)
    • Reflect.isExtensible(target)
    • Reflect.preventExtensions(target)
    • Reflect.getOwnPropertyDescriptor(target, name)
    • Reflect.getPrototypeOf(target)
    • Reflect.setPrototypeOf(target, prototype)

    具体方法查看阮一峰的ES6入门第11章Proxy和Reflect

    参考阮一峰的书籍ECMAScript 6 入门,感谢阮大神!

  • 相关阅读:
    自考-信息系统开发与管理(一)首篇概述
    html乱码问题
    Failed to create client: error while trying to communicate with apiserver: 报错解决
    Helm(bitnami)部署zookeeper和kafka集群
    Helm部署RabbitMQ集群
    Kubernetes通过插件,自动发现注册Rabbitmq集群
    Kubernetes使用operator安装Redis集群
    Kubernetes部署单Redis
    Kubernetes搭建RooK+Ceph
    Kubernetes的服务质量(QoS)
  • 原文地址:https://www.cnblogs.com/wkyseo/p/5880897.html
Copyright © 2011-2022 走看看