Skip to content
本页目录

ES6 对象的扩展

属性的简洁表示法

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

js
const foo = 'bar'
const baz = { foo }
baz // {foo: "bar"}

// 等同于
const baz = { foo: foo }

上面代码中,变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。下面是另一个例子。

js
function f(x, y) {
  return { x, y }
}

// 等同于

function f(x, y) {
  return { x: x, y: y }
}

f(1, 2) // Object {x: 1, y: 2}

除了属性简写,方法也可以简写。

js
const o = {
  method() {
    return 'Hello!'
  }
}

// 等同于

const o = {
  method: function () {
    return 'Hello!'
  }
}

下面是一个实际的例子。

js
let birth = '2000/01/01'

const Person = {
  name: '张三',

  //等同于birth: birth
  birth,

  // 等同于hello: function ()...
  hello() {
    console.log('我的名字是', this.name)
  }
}

这种写法用于函数的返回值,将会非常方便。

js
function getPoint() {
  const x = 1
  const y = 10
  return { x, y }
}

getPoint()
// {x:1, y:10}

方括号语法

方括号语法的用法

javascript
const prop = 'age'
const person = {}
person.prop = 18
console.log(person) // { prop: 18 }

// -----------------------------------------

const prop = 'age'
const person = {}
person[prop] = 18
console.log(person) // { age: 18 }

// -----------------------------------------

// ES6 增强
const prop = 'age'
const person = {
  [prop]: 18
}
console.log(person) // { age: 18 }

方括号中可以放什么

javascript
// [值、可以得到值的表达式]
const prop = 'age'
const func = () => 'age2'
const person = {
  [prop]: 18,
  [func()]: 24,
  ['sex']: 'man',
  ['s' + 'ex2']: 'womam'
}
console.log(person) // { age: 18, age2: 24, sex: 'man', sex2: 'womam' }

注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。

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

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

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

上面代码中,[keyA][keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。

方括号语法和点语法的区别

  1. 点语法是方括号语法的特殊形式
  2. 属性名由数字、字母、下划线以及 $ 构成,并且数字还不能打头的时候可以使用点语法(合法标识符)
  3. 能用点语法优先使用点语法
javascript
const person = {
    age: 18
};

person.age 等价于 person['age']

super 关键字

我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

js
const proto = {
  foo: 'hello'
}

const obj = {
  foo: 'world',
  find() {
    return super.foo
  }
}

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

上面代码中,对象obj.find()方法之中,通过super.foo引用了原型对象protofoo属性。

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

js
// 报错
const obj = {
  foo: super.foo
}

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

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

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

JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)。

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

const obj = {
  x: 'world',
  foo() {
    super.foo()
  }
}

Object.setPrototypeOf(obj, proto)

obj.foo() // "world"

上面代码中,super.foo指向原型对象protofoo方法,但是绑定的this却还是当前对象obj,因此输出的就是world

对象的展开运算符

展开对象

对象不能直接展开,必须在 {} 中展开。

javascript
const apple = {
  color: '红色',
  shape: '球形',
  taste: ''
}
console.log({ ...apple }) // { color: '红色', shape: '球形', taste: '甜' }
console.log({ ...apple } === apple) // false

合并对象

javascript
const apple = {
  color: '红色',
  shape: '球形',
  taste: ''
}

const pen = {
  color: '黑色',
  shape: '圆柱形',
  use: '写字'
}

// 新对象拥有全部属性,相同属性,后者覆盖前者
console.log({ ...apple, ...pen }) // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
console.log({ ...pen, ...apple }) // { color: '红色', shape: '球形', use: '写字', taste: '甜' }

注意事项

空对象的展开

如果展开一个空对象,则没有任何效果。

javascript
console.log({ ...{} }) // {}
console.log({ ...{}, a: 1 }) // { a: 1 }

非对象的展开

如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来(没有属性便为空)。

javascript
console.log({ ...1 }) // {}
console.log(new Object(1)) // [Number: 1]
console.log({ ...undefined }) // {}
console.log({ ...null }) // {}
console.log({ ...true }) // {}

字符串的展开

如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。

javascript
// 字符串在对象中展开
console.log({ ...'alex' }) // { '0': 'a', '1': 'l', '2': 'e', '3': 'x' }

// 字符串在数组中展开
console.log([...'alex']) // [ 'a', 'l', 'e', 'x' ]

// 字符串直接展开
console.log(...'alex') // a l e x

数组的展开

javascript
console.log({ ...[1, 2, 3] }) // { '0': 1, '1': 2, '2': 3 }

对象中对象属性的展开

不会展开对象中的对象属性。

javascript
const apple = {
  feature: {
    taste: ''
  }
}

const pen = {
  feature: {
    color: '黑色',
    shape: '圆柱形'
  },
  use: '写字'
}

console.log({ ...apple }) // { feature: { taste: '甜' } }

// feature 会直接覆盖,因为 feature 不能展开
console.log({ ...apple, ...pen }) // { feature: { color: '黑色', shape: '圆柱形' }, use: '写字' }

对象展开运算符的应用

复制对象

javascript
const a = { x: 1, y: 2 }
const c = { ...a }
console.log(c, c === a)
// { x: 1, y: 2 } false

用户参数和默认参数

javascript
const logUser = (userParam) => {
  const defaultPeram = {
    username: 'ZhangSan',
    age: 0,
    sex: 'male'
  }

  const param = { ...defaultPeram, ...userParam }
  console.log(param.username, param.age, param.sex)
}

logUser({ username: 'jerry' }) // jerry 0 male

再优化:

javascript
const logUser = (userParam) => {
  const defaultPeram = {
    username: 'ZhangSan',
    age: 0,
    sex: 'male'
  }

  const { username, age, sex } = { ...defaultPeram, ...userParam }
  console.log(username, age, sex)
}

logUser({ username: 'jerry' }) // jerry 0 male

对象的新增方法

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

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

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

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

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

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

Object.assign()

Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

js
const target = { a: 1 }

const source1 = { b: 2 }
const source2 = { c: 3 }

Object.assign(target, source1, source2)
target // {a:1, b:2, c:3}

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

js
const target = { a: 1, b: 1 }

const source1 = { b: 2, c: 2 }
const source2 = { c: 3 }

Object.assign(target, source1, source2)
target // {a:1, b:2, c:3}

汇总

js
// 基本用法
// Object.assign(目标对象, 源对象1, 源对象2, ...);
const apple = {
  color: '红色',
  shape: '圆形',
  taste: ''
}
const pen = {
  color: '黑色',
  shape: '圆柱形',
  use: '写字'
}
console.log(Object.assign(apple, pen))
// 后面的覆盖前面的(最终返回的不是新的,而是修改了前面的)
// { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
// Object.assign 直接合并到了第一个参数中,返回的就是合并后的对象
console.log(apple) // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
console.log(Object.assign(apple, pen) === apple) // true

// 可以合并多个对象
// 第一个参数使用一个空对象来实现合并返回一个新对象的目的
console.log(Object.assign({}, apple, pen)) // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
console.log(apple) // { color: '红色', shape: '圆形', taste: '甜' }
console.log({ ...apple, ...pen }) // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }

// 注意事项
// (1) 基本数据类型作为源对象
// 与对象的展开类似,先转换成对象,再合并
console.log(Object.assign({}, undefined)) // {}
console.log(Object.assign({}, null)) // {}
console.log(Object.assign({}, 1)) // {}
console.log(Object.assign({}, true)) // {}
console.log(Object.assign({}, 'str')) // { '0': 's', '1': 't', '2': 'r' }
// (2) 同名属性的替换
// 后面的直接覆盖前面的
const apple = {
  color: ['红色', '黄色'],
  shape: '圆形',
  taste: ''
}
const pen = {
  color: ['黑色', '银色'],
  shape: '圆柱形',
  use: '写字'
}
console.log(Object.assign({}, apple, pen)) // { color: [ '黑色', '银色' ], shape: '圆柱形', taste: '甜', use: '写字' }

// 应用
// 合并默认参数和用户参数
const logUser = (userOptions) => {
  const DEFAULTS = {
    username: 'ZhangSan',
    age: 0,
    sex: 'male'
  }

  const options = Object.assign({}, DEFAULTS, userOptions)
  console.log(options)
}
logUser() // { username: 'ZhangSan', age: 0, sex: 'male' }
logUser({}) // { username: 'ZhangSan', age: 0, sex: 'male' }
logUser({ username: 'Alex' }) // { username: 'Alex', age: 0, sex: 'male' }

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

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

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

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

js
// 基本用法
const person = {
  name: 'Alex',
  age: 18
}
// 返回键数组
console.log(Object.keys(person)) // [ 'name', 'age' ]
// 返回值数组
console.log(Object.values(person)) // [ 'Alex', 18 ]
// 返回键值二维数组
console.log(Object.entries(person)) // [ [ 'name', 'Alex' ], [ 'age', 18 ] ]

// 与数组类似方法的区别
console.log([1, 2].keys()) // Object [Array Iterator] {}
console.log([1, 2].values()) // Object [Array Iterator] {}
console.log([1, 2].entries()) // Object [Array Iterator] {}
// 数组的 keys()、values()、entries() 等方法是实例方法,返回的都是 Iterator
// 对象的 Object.keys()、Object.values()、Object.entries() 等方法是构造函数方法,返回的是数组

// 应用(使用 for...of 循环遍历对象)
const person = {
  name: 'Alex',
  age: 18
}
for (const key of Object.keys(person)) {
  console.log(key)
}
// name
// age
for (const value of Object.values(person)) {
  console.log(value)
}
// Alex
// 18
for (const entries of Object.entries(person)) {
  console.log(entries)
}
// [ 'name', 'Alex' ]
// [ 'age', 18 ]
for (const [key, value] of Object.entries(person)) {
  console.log(key, value)
}
// name Alex
// age 18

// Object.keys()/values()/entires() 并不能保证顺序一定是你看到的样子,这一点和 for in 是一样的
// 如果对遍历顺序有要求那么不能用 for in 以及这种方法,而要用其他方法

lemon's personal blog.