• 【ECMAScript6】对象的扩展


    一、属性的遍历

    ES6 一共有 5 种方法可以遍历对象的属性。

    1. for...in

    for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

    2. Object.keys(obj)

    Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

    3. Object.getOwnPropertyNames(obj)

    Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

    4. Object.getOwnPropertySymbols(obj)

    Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

    5. Reflect.ownKeys(obj)

    Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

    以上几种遍历方法都遵循以下规则:

    • 首先遍历所有数值键,按照数值升序排列。
    • 其次遍历所有字符串键,按照加入时间升序排列。
    • 最后遍历所有 Symbol 键,按照加入时间升序排列。

    二、链式判断运算符

    在实际编程中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName,安全的写法是写成下面这样。

    const firstName = (message
      && message.body
      && message.body.user
      && message.body.user.firstName) || 'default';

    或者使用三元运算符?:,判断一个对象是否存在。

    const fooInput = myForm.querySelector('input[name=foo]')
    const fooValue = fooInput ? fooInput.value : undefined

    这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。

    const firstName = message?.body?.user?.firstName || 'default';
    const fooValue = myForm.querySelector('input[name=foo]')?.value

    上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。

    链判断运算符有三种用法。

    • obj?.prop // 对象属性
    • obj?.[expr] // 同上
    • func?.(...args) // 函数或对象方法的调用

    对于那些可能没有实现的方法,这个运算符尤其有用。

    if (myForm.checkValidity?.() === false) {
      // 表单校验失败
      return;
    }
    a?.b
    // 等同于
    a == null ? undefined : a.b
    
    a?.[x]
    // 等同于
    a == null ? undefined : a[x]
    
    a?.b()
    // 等同于
    a == null ? undefined : a.b()
    
    a?.()
    // 等同于
    a == null ? undefined : a()

    使用链式判断运算符也格外注意,切记不能滥用。

    三、Null判断运算符

    读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。

    const headerText = response.settings.headerText || 'Hello, world!';
    const animationDuration = response.settings.animationDuration || 300;
    const showSplashScreen = response.settings.showSplashScreen || true;

    上面的三行代码都通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为nullundefined,默认值就会生效,但是属性的值如果为空字符串或false0,默认值也会生效。

    为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

    const headerText = response.settings.headerText ?? 'Hello, world!';
    const animationDuration = response.settings.animationDuration ?? 300;
    const showSplashScreen = response.settings.showSplashScreen ?? true;

    上面代码中,默认值只有在属性值为nullundefined时,才会生效。

    这个运算符的一个目的,就是跟链判断运算符?.配合使用,为nullundefined的值设置默认值。

    const animationDuration = response.settings?.animationDuration ?? 300;

    上面代码中,response.settings如果是nullundefined,就会返回默认值300。

    ??有一个运算优先级问题,它与&&||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。

    // 报错
    lhs && middle ?? rhs
    lhs ?? middle && rhs
    lhs || middle ?? rhs
    lhs ?? middle || rhs
    
    //修改为
    (lhs && middle) ?? rhs;
    lhs && (middle ?? rhs);
    
    (lhs ?? middle) && rhs;
    lhs ?? (middle && rhs);
    
    (lhs || middle) ?? rhs;
    lhs || (middle ?? rhs);
    
    (lhs ?? middle) || rhs;
    lhs ?? (middle || rhs);

    四、新增的方法

    1. Object.is()

    ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0

    ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

    Object.is('foo', 'foo')
    // true
    Object.is({}, {})
    // false

    不同之处只有:一是+0不等于-0,二是NaN等于自身。

    +0 === -0 //true
    NaN === NaN // false
    
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true

    ES5 可以通过下面的代码,部署Object.is

    Object.defineProperty(Object, 'is', {
      value: function(x, y) {
        if (x === y) {
          // 针对+0 不等于 -0的情况
          return x !== 0 || 1 / x === 1 / y;
        }
        // 针对NaN的情况
        return x !== x && y !== y;
      },
      configurable: true,
      enumerable: false,
      writable: true
    });

    2. Object.assign()

    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方法的第一个参数是目标对象,后面的参数都是源对象。

    注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

    如果该参数不是对象,则会先转成对象,然后返回。由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

    Object.assign(undefined) // 报错
    Object.assign(null) // 报错

    如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefinednull不在首参数,就不会报错。

    let obj = {a: 1};
    Object.assign(obj, undefined) === obj // true
    Object.assign(obj, null) === obj // true

    Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。

    注意点:

    (1) 浅拷贝

    Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

    const obj1 = {a: {b: 1}};
    const obj2 = Object.assign({}, obj1);
    
    obj1.a.b = 2;
    obj2.a.b // 2

    (2) 同名属性的替换

    对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

    const target = { a: { b: 'c', d: 'e' } }
    const source = { a: { b: 'hello' } }
    Object.assign(target, source)
    // { a: { b: 'hello' } }

    (3) 数组的处理

    Object.assign可以用来处理数组,但是会把数组视为对象。

    Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]

    上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。

    (4) 取值函数的处理

    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});
      }
    }

    (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);
    }

    (4) 合并多个对象

    将多个对象合并到某个对象。

    const merge =
      (target, ...sources) => Object.assign(target, ...sources);

    如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。

    const merge =
      (...sources) => Object.assign({}, ...sources);

    (5) 为属性指定默认值

    const DEFAULTS = {
      logLevel: 0,
      outputFormat: 'html'
    };
    
    function processContent(options) {
      options = Object.assign({}, DEFAULTS, options);
      console.log(options);
      // ...
    }

    3. Object.keys()、Object.values()、Object.entries

    (1) Object.keys()

    ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

    var obj = { foo: 'bar', baz: 42 };
    Object.keys(obj)
    // ["foo", "baz"]

    (2) Object.values()

    返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

    const obj = { foo: 'bar', baz: 42 };
    Object.values(obj)
    // ["bar", 42]

    Object.values只返回对象自身的可遍历属性。

    const obj = Object.create({}, {p: {value: 42}});
    Object.values(obj) // []

    上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的,因为p的属性描述对象的enumerable默认是falseObject.values不会返回这个属性。只要把enumerable改成trueObject.values就会返回属性p的值。

    const obj = Object.create({}, {p:
      {
        value: 42,
        enumerable: true
      }
    });
    Object.values(obj) // [42]

    Object.values会过滤属性名为 Symbol 值的属性。

    如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

    如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

    Object.values(42) // []
    Object.values(true) // []

    (3) Object.entries()

    返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

    const obj = { foo: 'bar', baz: 42 };
    Object.entries(obj)
    // [ ["foo", "bar"], ["baz", 42] ]

    4. Object.fromEntries()

    Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

    Object.fromEntries([
      ['foo', 'bar'],
      ['baz', 42]
    ])
    // { foo: "bar", baz: 42 }

    该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。

    // 例一
    const entries = new Map([
      ['foo', 'bar'],
      ['baz', 42]
    ]);
    
    Object.fromEntries(entries)
    // { foo: "bar", baz: 42 }
    
    // 例二
    const map = new Map().set('foo', true).set('bar', false);
    Object.fromEntries(map)
    // { foo: true, bar: false }

    该方法的另一个用处是配合URLSearchParams对象,将查询字符串转为对象。

    Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
    // { foo: "bar", baz: "qux" }
  • 相关阅读:
    C——联合体(共同体)总结
    JMX操作ActiveMQ(1)
    使用xml和java代码混合控制UI界面
    Hive Metastore ObjectStore PersistenceManager自动关闭bug解析
    (算法课大报告)大数据的查找与排序
    编程珠玑---读书笔记---使用后缀数组查找最长重复子串
    VMware vSphere服务器虚拟化实验十一高可用性之三Fault Tolerance
    签名应用例子
    fopen()惹的祸
    Bigcommerce: 给已完成购买的客户发送一封产品评论邮件,让客户直接进行产品评论
  • 原文地址:https://www.cnblogs.com/myitnews/p/12191539.html
Copyright © 2020-2023  润新知