ES6——10.对象的扩展

属性的简洁表示法

  • ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

    const foo = 'bar';
    const baz = {foo};
    baz // {foo: "bar"}
    
    // 等同于
    const baz = {foo: foo};
    
  • 除了属性简写,方法也可以简写。

    const o = {
      method() {
        return "Hello!";
      }
    };
    
    // 等同于
    
    const o = {
      method: function() {
        return "Hello!";
      }
    };
    
  • CommonJS 模块输出一组变量,就非常合适使用简洁写法。

    let ms = {};
    
    function getItem (key) {
      return key in ms ? ms[key] : null;
    }
    
    function setItem (key, value) {
      ms[key] = value;
    }
    
    function clear () {
      ms = {};
    }
    
    module.exports = { getItem, setItem, clear };
    // 等同于
    module.exports = {
      getItem: getItem,
      setItem: setItem,
      clear: clear
    };
    
  • 注意,简写的对象方法不能用作构造函数,会报错。

    const obj = {
      f() {
        this.foo = 'bar';
      }
    };
    
    new obj.f() // 报错
    

属性名表达式

  1. ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

    let propKey = 'foo';
    
    let obj = {
      [propKey]: true,
      ['a' + 'bc']: 123
    };
    
  2. 表达式还可以用于定义方法名。

    let obj = {
      ['h' + 'ello']() {
        return 'hi';
      }
    };
    
    obj.hello() // hi
    
  3. 注意,属性名表达式与简洁表示法,不能同时使用,会报错。

    // 报错
    const foo = 'bar';
    const bar = 'abc';
    const baz = { [foo] };
    
    // 正确
    const foo = 'bar';
    const baz = { [foo]: 'abc'};
    

方法的 name 属性

  1. 函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

    const person = {
      sayName() {
        console.log('hello!');
      },
    };
    
    person.sayName.name   // "sayName"
    

    上面代码中,方法的name属性返回函数名(即方法名)。

  2. 如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的getset属性上面,返回值是方法名前加上getset

    const obj = {
      get foo() {},
      set foo(x) {}
    };
    
    obj.foo.name
    // TypeError: Cannot read property 'name' of undefined
    
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
    
    descriptor.get.name // "get foo"
    descriptor.set.name // "set foo"
    

属性的可枚举性和遍历

  1. 可枚举性

    • 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

      let obj = { foo: 123 };
      Object.getOwnPropertyDescriptor(obj, 'foo')
      //  {
      //    value: 123,
      //    writable: true,
      //    enumerable: true,
      //    configurable: true
      //  }
      

      描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

    • 目前,有四个操作会忽略enumerablefalse的属性。

      • for...in循环:只遍历对象自身的和继承的可枚举的属性。
      • Object.keys():返回对象自身的所有可枚举的属性的键名。
      • JSON.stringify():只串行化对象自身的可枚举的属性。
      • Object.assign(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。
  2. 属性的遍历

    • 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 或字符串,也不管是否可枚举。

    • 以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

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

super 关键字

  1. ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

    const proto = {
      foo: 'hello'
    };
    
    const obj = {
      foo: 'world',
      find() {
        return super.foo;
      }
    };
    
    Object.setPrototypeOf(obj, proto);
    obj.find() // "hello"
    
  2. 注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

    // 报错
    const obj = {
      foo: super.foo
    }
    
    // 报错
    const obj = {
      foo: () => super.foo
    }
    
    // 报错
    const obj = {
      foo: function () {
        return super.foo
      }
    }
    

    上面三种super的用法都会报错,因为对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。

对象的扩展运算符

  • 解构赋值

    1. 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

      let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
      x // 1
      y // 2
      z // { a: 3, b: 4 }
      
    2. 由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefinednull,就会报错,因为它们无法转为对象。

      let { ...z } = null; // 运行时错误
      let { ...z } = undefined; // 运行时错误
      
    3. 解构赋值必须是最后一个参数,否则会报错。

      let { ...x, y, z } = someObject; // 句法错误
      let { x, ...y, ...z } = someObject; // 句法错误
      
    4. 注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

      let obj = { a: { b: 1 } };
      let { ...x } = obj;
      obj.a.b = 2;
      x.a.b // 2
      
    5. 另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性。

      let o1 = { a: 1 };
      let o2 = { b: 2 };
      o2.__proto__ = o1;
      let { ...o3 } = o2;
      o3 // { b: 2 }
      o3.a // undefined
      
  • 扩展运算符

    1. 对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

      let z = { a: 3, b: 4 };
      let n = { ...z };
      n // { a: 3, b: 4 }
      
    2. 由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。

      let foo = { ...['a', 'b', 'c'] };
      foo
      // {0: "a", 1: "b", 2: "c"}
      
    3. 如果扩展运算符后面是一个空对象,则没有任何效果。

      {...{}, a: 1}
      // { a: 1 }
      
    4. 如果扩展运算符后面不是对象,则会自动将其转为对象。

      // 等同于 {...Object(1)}
      {...1} // {}
      
    5. 但是,如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。

      {...'hello'}
      // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
      
    6. 对象的扩展运算符等同于使用Object.assign()方法。

      let aClone = { ...a };
      // 等同于
      let aClone = Object.assign({}, a);
      
    7. 扩展运算符可以用于合并两个对象。

      let ab = { ...a, ...b };
      // 等同于
      let ab = Object.assign({}, a, b);
      

链判断运算符

  1. ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
  1. 下面是判断对象方法是否存在,如果存在就立即执行的例子。

    iterator.return?.()
    
  2. 对于那些可能没有实现的方法,这个运算符尤其有用。

    if (myForm.checkValidity?.() === false) {
      // 表单校验失败
      return;
    }
    
  3. 链判断运算符有三种用法。

    • obj?.prop // 对象属性
    • obj?.[expr] // 同上
    • func?.(...args) // 函数或对象方法的调用
  4. 使用这个运算符,有几个注意点。

    (1)短路机制

    ?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。

    a?.[++x]
    // 等同于
    a == null ? undefined : a[++x]
    

    上面代码中,如果aundefinednull,那么x不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。

    (2)delete 运算符

    delete a?.b
    // 等同于
    a == null ? undefined : delete a.b
    

    上面代码中,如果aundefinednull,会直接返回undefined,而不会进行delete运算。

    (3)括号的影响

    如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。

    (a?.b).c
    // 等价于
    (a == null ? undefined : a.b).c
    

    上面代码中,?.对圆括号外部没有影响,不管a对象是否存在,圆括号后面的.c总是会执行。

    一般来说,使用?.运算符的场合,不应该使用圆括号。

    (4)报错场合

    以下写法是禁止的,会报错。

    // 构造函数
    new a?.()
    new a?.b()
    
    // 链判断运算符的右侧有模板字符串
    a?.`{b}`
    a?.b`{c}`
    
    // 链判断运算符的左侧是 super
    super?.()
    super?.foo
    
    // 链运算符用于赋值运算符左侧
    a?.b = c
    

    (5)右侧不得为十进制数值

    为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。

Null 判断运算符

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

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

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

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

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

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

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

    const animationDuration = response.settings?.animationDuration ?? 300;
    
  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值