对象的扩展

42 阅读7分钟

属性名表达式

JavaScript 语言定义对象的属性有两种方法

// 方法一
obj.foo = true

// 方法二
obj['a' + 'bc'] = 123

ES6 允许字面量定义对象时使用表达式作为对象的属性名

let propKey = 'foo'

let obj = {
    [propKey]: true,
    ['a' + 'bc']: 123
}

注意:属性名表达式和简洁表示法不能同时使用,会报错

var foo = 'bar'
var bar = 'abc'

var baz = { [foo] } // 报错
var foo = 'bar'
var baz = { [foo]: 'abc' }

属性名表达式如果是一个对象,默认情况下会被转化为对象字符串

方法的 name 属性

函数的 name 属性返回函数名,对象方法也是函数,name 属性返回函数名

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

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'

bind 方法创造的函数,name 属性返回「bound」加上原函数的名字

Function 构造函数创造的函数,name 属性返回「anonymous」

如果对象的方法是一个 Symbol 值,那么 name 属性返回的是这个 Symbol 值的描述

const key1 = Symbol('description)
const key2 = Symbol()

let obj = {
    [key1]() {},
    [key2]() {}
}

obj[key1].name // '[description]'
obj[key2].name // ''

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===

  • 相等运算符会自动转换数据类型
  • 严格相等运算符的 NaN 不等于自身,以及 +0 等于 -0

JavaScript 缺少一种在所有环境下,只要两个值一样,就应该相等的运算

Object.is 就是用来比较两个值是否严格相等的,和严格相等运算符的行为基本一致

不同之处有两个

  • NaN 等于自身
  • +0 不等于 -0

Object.assign()

Object.assign 用于将源对象的所有可枚举属性复制到目标对象

  • 如果只有一个参数,会直接返回该参数
  • 如果该参数不是对象,则会先转成对象,然后返回
  • 由于 undefined 和 null 无法转成对象,所以如果将它们作为参数,就会报错
  • 如果非对象参数出现在源对象的位置(即非首参数),那么这些参数都会转成对象,如果无法转成对象便跳过,这就意味着,如果 undefined 和 null 不在首参数便不会报错
  • 其它类型类型的值不在首参数也不会报错,但是,除了字符串会以数组形式复制到目标对象,其它值都不会产生效果

Object.assign 复制的属性是有限制的,只复制源对象的自身属性(不复制继承属性),也不复制不可枚举的属性

Object.assign 实行的是浅拷贝,不是深拷贝

Object.assign 的使用场景

  • 为对象添加属性
  • 为对象添加方法
  • 克隆对象
  • 合并多个对象
  • 为属性指定默认值

属性的可枚举性

对象的每一个属性都具有一个描述对象,用来控制该属性的行为

Object.getOwnPropertyDescriptor 可以获取该属性的描述对象

let obj = { foo: 123 }
Object.getOwnPropertyDescriptor(obj, 'foo')

/**
* {
*    value: 123,
*    writable: true,
*    enumerable: true,
*    configurable: true
* }
* /

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

  • for...in:只遍历对象自身和继承的可枚举属性
  • Object.keys():返回对象自身的所有可枚举属性的键名
  • JSON.stringify():只串行化对象自身的可枚举属性
  • Object.assign():只复制对象自身的可枚举属性

属性的遍历

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

  • for...in
    • 循环遍历对象自身和继承的可枚举属性(不含 Symbol 属性)
  • Object.keys()
    • 返回一个数据,包含对象自身(不含继承)的所有可枚举属性(不含 Symbol 属性)
  • Object.getOwnPropertyNames()
    • 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)
  • Object.getOwnPropertySymbol()
    • 返回一个数据,包含对象自身的所有 Symbol 属性
  • Reflect.ownKeys()
    • 返回一个数组,包含对象自身的所有属性

以上 5 种方法都遵循同样的属性遍历次序规则

  • 首先遍历所有属性名为数值的属性,按照数字排序
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序
  • 最后遍历所有属性名为 Symbol 的属性,按照生成时间排序

__proto__ 属性、Object.setPrototypeOf()、Object.getPrototypeOf()

__proto__ 用来读取或设置当前对象的 prototype 对象

该属性没有写入 ES6 正文,而是写入了附录,原因是 __proto__ 前后的双下划线说明它本质上是一个内部属性,而不是一个正式的对外 API

只有浏览器必须部署这个属性,而其它环境不一定要部署,所以最好认为这个属性是不存在的,而是用 Object.setPrototypeOf()Object.getPrototypeOf()Object.create() 代替

Object.setPrototypeOf() 用来设置一个对象的 prototype 对象,返回参数对象本身

  • 如果第一个参数不是对象,则会自动转为对象,但由于返回的还是第一个参数,所以这个操作不会产生任何效果
  • 由于 undefined 和 null 无法转为对象,所以如果第一个参数是 undefined 或 null ,会报错

Object.getPrototypeOf() 用来读取一个对象的 prototype 对象

  • 如果参数不是对象,则会转为对象
  • 如果参数是 undefined 或 null,它们无法转为对象,会报错

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

Object.keys() 返回一个数组,成员是参数对象自身的所有可遍历属性的键名

Object.values() 返回一个数组,成员是参数对象自身的所有可遍历属性的键值

  • 返回数组的成员顺序和「属性遍历」的排列规则一致
  • 如果参数是一个字符串,则会返回各个字符组成的一个数组
  • 如果参数不是对象,会将其转为对象。由于数值和布尔值的包装对象都不会为实例添加非继承的属性,所以会返回空数组

Object.entries() 返回一个数组,成员是参数对象的所有可遍历属性的键值对数组

对象的扩展运算符

对象的解构赋值用于从一个对象取值

  • 由于解构赋值要求等号右边是一个对象,所以如果是 undefined 或 null,就会报错,因为它们无法转为对象
  • 解构赋值必须是最后一个参数,否则会报错
  • 解构赋值时浅复制

扩展运算符(...)用于取出参数对象的所有可遍历属性,将其复制到当前对象之中

  • 对象的扩展运算符后面可以带有表达式
  • 如果扩展运算符后面是一个空对象,则没有任何效果
  • 如果扩展运算符的参数是 null 或 undefined ,则这两个值会被忽略,不会报错
  • 扩展运算符的参数对象之中如果有取值函数 get,这个函数将会被执行

Object.getOwnPropertyDescriptors()

ES5 的 Object.getOwnPropertyDescriptor() 方法用来返回某个对象属性的描述对象

ES2017 引入 Object.getOwnPropertyDescriptors 方法,返回指定对象所有自身属性的描述对象

Null 传导运算符

「Null传导运算符」(?),只要读取到的是 null 或 undefined ,就不再继续运算,而是返回 undefined

  • obj?.prop:读取对象属性
  • Obj?.[expr]:同上
  • func?.(...args):函数或对象方法的调用
  • new C?.(...args):构造函数的调用

传导运算符之所以写成 obj?.prop 而不是 obj?prop,是为了区分三目运算符