Skip to content

对象扩展

简洁表示

属性简写:属性名和变量名一致时

js
// ES5
{x: x, y: y}
// ES6
{x, y}

方法简写:去掉:,省略function

js
// ES5
method: function() {
  return "Hello!";
}
// ES6
method() {
  return "Hello!";
}

简写对象方法不能作构造函数

tip:对象的简洁表示法,每组键值对会打印对象名,更清晰

js
let user = { name: 'test' }
let foo = { bar: 'baz' }

console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({ user, foo })
// {user: {name: "test"}, foo: {bar: "baz"}}

属性名表达式

属性名

ES5 - 只能用 标识符定义属性

ES6 - 允许使用表达式定义属性,方法名同样适用

js
// ES5
obj.foo = true
obj = {
  foo: true,
  abc: 123
}
// ES6
obj['a' + 'bc'] = 123
obj = {
  [propKey]: true,
  ['a' + 'bc']: 123,
  ['h' + 'ello']() {
    return 'hi'
  }
}

表达式与简洁表示不能同时使用

js
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

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

属性名表达式为对象,默认转为 [object Object]

js
const keyA = { a: 1 }
const keyB = { b: 2 }

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
}

myObject // Object {[object Object]: "valueB"}

keyB 会覆盖 keyA

方法 name 属性

函数的name属性,返回函数名。

对象方法

js
const person = {
  sayName() {
    console.log('hello!')
  }
}

person.sayName.name // "sayName"

get,set 函数

js
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 函数、构造函数

js
new Function().name // "anonymous"

var doSomething = function () {
  // ...
}
doSomething.bind().name // "bound doSomething"

symbol

js
const key1 = Symbol('description')
const key2 = Symbol()
let obj = {
  [key1]() {},
  [key2]() {}
}
obj[key1].name // "[description]"
obj[key2].name // ""

属性枚举性和遍历

可枚举性

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

js
let obj = { foo: 123 }
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,  // 可枚举性
//    configurable: true
//  }

enumerablefalse,可以禁用对该属性遍历。

toStringlength属性的enumerable都是false,因此for...in不会遍历到这两个继承自原型的属性。

for...in遍历自身属性和继承的可枚举属性
Object.keys()遍历自身可枚举属性键名
JSON.stringify()串行化自身可枚举属性
Object.assign()拷贝自身可枚举属性

引入继承的属性会让问题复杂化,大多时候用 Object.keys()代替 for...in

ES6 规定,所有 class 的原型的方法都是不可枚举的

js
Object.getOwnPropertyDescriptor(
  class {
    foo() {}
  }.prototype,
  'foo'
).enumerable
// false

属性遍历(5 种)

for...in:循环遍历对象自身可枚举属性和继承可枚举属性

  • 自身可枚举属性、继承可枚举属性
  • 不含自身不可枚举、Symbol 属性

Object.keys:返回一个数组,包括对象自身可枚举属性的键名

  • 自身可枚举属性
  • 不含自身不可枚举属性、继承可枚举属性、Symbol 属性

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

  • 自身所有属性
  • 不含继承可枚举属性、不含 Symbol 属性

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

  • 自身所有 Symbol 属性

Reflect.ownKeys:返回一个数组,包含对象自身的所有键名

  • 自身属性(不管枚举性)、Symbol 属性
  • 不含继承属性

属性遍历的次序规则

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

super 关键字

this:总是指向函数所在的当前对象

ES6 - super:指向当前对象的原型对象

js
const proto = {
  foo: 'hello',
  print() {
    console.log(this.foo)
  }
}

const obj = {
  foo: 'world',
  find() {
    console.log(super.foo)
  },
  print() {
    super.print()
  }
}

Object.setPrototypeOf(obj, proto)
obj.find() // "hello"
obj.print() // "world"

说明:super.print()指向protoprint(),但是绑定的this还是obj

JavaScript 引擎内部super.foo等同于

js
super.foo // 属性
super.foo() // 方法
// 等价写法
Object.getPrototypeOf(this).foo
Object.getPrototypeOf(this).foo.call(this)

getPrototypeOf:返回指定对象原型

注意:

super 关键字表示原型对象,只能用在对象方法

以下三种写法super都未用在对象方法

对象方法:只有简写的形式能定义对象方法

js
// 报错 用在属性中
const obj = {
  foo: super.foo
}

// 报错 用在函数中,赋值给foo属性
const obj = {
  foo: () => super.foo
}

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

对象扩展运算符

解构赋值

对象的解构赋值:将目标对象自身所有可遍历、但尚未被读取的属性,分配到指定的对象上面

  1. 不能解构 null 和 undefined
  2. 扩展运算符必须在末尾
  3. 解构赋值为浅拷贝
  4. 不能复制继承自原型对象属性
  5. 扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式
js
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };// 局部解构
// 1
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误

// 2
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误

// 4
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o; // 1 {}
let { y, z } = newObj; // undefined 3

// 5
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts

x为解构赋值,可以读取继承属性,yz为扩展运算符解构赋值,不能取到继承属性

扩展运算符

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

  1. 数组是特殊对象,扩展运算符可用于数组
  2. 空对象,无效果
  3. 不是对象自动转为对象
  4. 字符串,自动转为类似数组对象
  5. 等效于 Object.assign(),浅拷贝
js
// 等同于 {...Object(1)}
{...1} // {}
{...true} // {}
{...undefined} // {}
{...null} // {}

{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

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

Object.assign:只拷贝了对象实例属性

以下三种拷贝完整对象

js
// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
}

// 写法二
const clone2 = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj)

// 写法三
const clone3 = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))

__proto__属性在非浏览器的环境不一定部署,推荐写法二、三

对象同名属性规则:后面的属性会覆盖前面的属性

js
let x = 1,
  y = 2,
  aWithOverrides = { ...a, x, y }
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 })

应用:修改部分属性

js
let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
}

自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

js
let aWithDefaults = { x: 1, y: 2, ...a }
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a)

与数组相同,后面可跟表达式

js
const obj = {
  ...(x > 1 ? { a: 1 } : {}),
  b: 2
}

会执行取值函数get

js
let a = {
  get x() {
    throw new Error('not throw yet')
  }
}

let aWithXGetter = { ...a } // 报错

AggregateError 错误对象

ES2021 - 为了配合新增的Promise.any()方法,还引入一个新的错误对象AggregateError

作用:封装了多个错误对象,单一操作可以抛出多个错误

js
AggregateError(errors[, message])
  • errors(必): 数组,它的每个成员都是一个错误对象
  • message(选):字符串,表示 AggregateError 抛出时的提示信息
js
const error = new AggregateError(
  [
    new Error('ERROR_11112'),
    new TypeError('First name must be a string'),
    new RangeError('Transaction value must be at least 1'),
    new URIError('User profile link must be https')
  ],
  'Transaction cannot be processed'
)

对象的新增方法

Object.is()

ES5 比较值,=====

  • ==:会自动转换数据类型
  • ===NaN不等于自身,+0等于-0

ES6 - Object.is():比较两个值是否严格相等。与===基本一致,唯一不同 +0 不等于 -0

js
;+0 === -0 //true
NaN === NaN // false

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

polyfill

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

Object.assign()

Object.assign():将源自身对象所有可枚举属性(含Symbol),复制到目标对象

参数

第一参数:目标对象

  • 只有一个参数,直接返回
  • 不是对象转换为对象,undefinednull无法转为对象,报错

后面参数:源对象

  • 不是对象转换为对象(数组也是)
  • 字符串会以数组形式拷贝,其他值(Boolean,Number,undefined、null)忽略

注意

  1. 浅拷贝
  2. 同名参数,源对象覆盖目标对象,后面源对象覆盖前面源对象
  3. 数组处理
js
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
  1. 取值函数处理:先取值再处理
js
const source = {
  get foo() {
    return 1
  }
}
const target = {}

Object.assign(target, source)
// { foo: 1 }

用途

  1. 为对象添加属性
  2. 为对象添加方法
  3. 克隆对象
js
// 只能克隆自身值,无法保证继承链
function clone(origin) {
  return Object.assign({}, origin)
}
js
function clone(origin) {
  let originProto = Object.getPrototypeOf(origin)
  return Object.assign(Object.create(originProto), origin)
}
  1. 合并对象
  2. 指定默认值

getOwnPropertyDescriptors()

ES5 - Object.getOwnPropertyDescriptor():返回某个对象属性的描述对象(descriptor)。

ES2017 - Object.getOwnPropertyDescriptors():返回指定对象所有自身属性的描述对象

polyfill

js
function getOwnPropertyDescriptors(obj) {
  const result = {}
  for (let key of Reflect.ownKeys(obj)) {
    result[key] = Object.getOwnPropertyDescriptor(obj, key)
  }
  return result
}

问题:Object.assign()无法正确拷贝 getset 属性。Object.assign 只会拷贝属性值,不会赋值、取值方法

js
const source = {
  set foo(value) {
    console.log(value)
  }
}

const target1 = {}
Object.assign(target1, source)

Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
//   writable: true,
//   enumerable: true,
//   configurable: true }

解决:defineProperties + getOwnPropertyDescriptors

js
const source = {
  set foo(value) {
    console.log(value)
  }
}

const target2 = {}
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source))
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
//   set: [Function: set foo],
//   enumerable: true,
//   configurable: true }

配合Object.create()方法,将对象属性克隆到新对象(浅拷贝

js
const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))

对象继承

ES5

js
const obj = {
  __proto__: prot,
  foo: 123
}

ES6

js
const obj = Object.create(prot)
obj.foo = 123

// 或者

const obj = Object.assign(Object.create(prot), {
  foo: 123
})

另一种写法

js
const obj = Object.create(
  prot,
  Object.getOwnPropertyDescriptors({
    foo: 123
  })
)

实现混入模式

js
let mix = (object) => ({
  with: (...mixins) => mixins.reduce((c, mixin) => Object.create(c, Object.getOwnPropertyDescriptors(mixin)), object)
})

// multiple mixins example
let a = { a: 'a' }
let b = { b: 'b' }
let c = { c: 'c' }
let d = mix(c).with(a, b)

d.c // "c"
d.b // "b"
d.a // "a"

__proto__

__proto__:读取或设置当前对象原型对象,实际是调用Object.prototype.__proto__

js
// es5 的写法
const obj = {
  method: function() { ... }
};
obj.__proto__ = someOtherObj;

// es6 的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

实现如下

js
Object.defineProperty(Object.prototype, '__proto__', {
  get() {
    let _thisObj = Object(this)
    return Object.getPrototypeOf(_thisObj)
  },
  set(proto) {
    if (this === undefined || this === null) {
      throw new TypeError()
    }
    if (!isObject(this)) {
      return undefined
    }
    if (!isObject(proto)) {
      return undefined
    }
    let status = Reflect.setPrototypeOf(this, proto)
    if (!status) {
      throw new TypeError()
    }
  }
})

function isObject(value) {
  return Object(value) === value
}

setPrototypeOf()

__proto__相同,设置对象原型对象(推荐

js
Object.setPrototypeOf(object, prototype)
// 等效
function setPrototypeOf(obj, proto) {
  obj.__proto__ = proto
  return obj
}

说明

  1. 第一参数不是对象,会自动转对象,此函数返回值为第一参数,所以操作无效。
js
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true
  1. 第一参数为undefinednull会报错
js
Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined

Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined

getPrototypeOf()

读取一个对象的原型对象

不是对象,转对象,undefinednull报错

js
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true

keys()、values()、entries()

ES5 - Object.keys:返回一个数组,成员是键名。(可遍历属性、非 Symbol、不含继承)

ES2017 - Object.values:返回一个数组,成员是键值。(可遍历属性、非 Symbol、不含继承)

说明

  1. 会过滤属性名为 Symbol 值的属性
js
Object.values({ [Symbol()]: 123, foo: 'abc' })
// ['abc']
  1. 值为字符串,返回字符串组成的数组
js
Object.values('foo')
// ['f', 'o', 'o']
  1. 数值、布尔值,返回空数组(不是对象转对象,而这两者包装对象都不会添加非继承属性)

Object.entries():返回一个数组,成员是键名 + 键值(可遍历属性、非 Symbol、不含继承)

js
const obj = { foo: 'bar', baz: 42 }
const map = new Map(Object.entries(obj))
map // Map { foo: "bar", baz: 42 }

polyfill

js
// Generator函数的版本
function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]]
  }
}

// 非Generator函数的版本
function entries(obj) {
  let arr = []
  for (let key of Object.keys(obj)) {
    arr.push([key, obj[key]])
  }
  return arr
}

formEntries()

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

应用:将 Map 结构转为对象

js
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对象,查询字符串转对象

js
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

补充:URLSearchParams

js
var paramsString = 'q=URLUtils.searchParams&topic=api'
var searchParams = new URLSearchParams(paramsString)

for (let p of searchParams) {
  console.log(p)
}

searchParams.has('topic') === true // true
searchParams.get('topic') === 'api' // true
searchParams.getAll('topic') // ["api"]
searchParams.get('foo') === null // true
searchParams.append('topic', 'webdev')
searchParams.toString() // "q=URLUtils.searchParams&topic=api&topic=webdev"
searchParams.set('topic', 'More webdev')
searchParams.toString() // "q=URLUtils.searchParams&topic=More+webdev"
searchParams.delete('topic')
searchParams.toString() // "q=URLUtils.searchParams

相关链接

[-] 对象扩展

[-] 对象方法