1
关注前端小讴,阅读更多原创技术文章

理解对象

  • ECMAScript 定义对象:无序属性的集合(一组没有特定顺序的值),其属性可以包含基本值对象函数,整个对象可以想象成一个散列表

相关代码 →

  • 创建自定义对象的 2 种方法:Object 构造函数对象字面量

    • 用构造函数创建一个 Object 实例,然后为它添加属性和方法
    var person = new Object()
    person.name = 'Nicholas'
    person.age = 29
    person.job = 'Software Engineer'
    person.sayName = function () {
      console.log(this.name)
    }
  • 对象字面量创建对象
var person = {
  name: 'Nicholas',
  age: 29,
  job: 'Software Engineer',
  sayName: function () {
    console.log(this.name)
  },
}

属性的类型

  • 为 JavaScript 实现引擎的规范定义,开发者不能直接访问,用两个中括号把特性名称括起来

数据属性

数据属性含义默认值
[[Configurable]]能否配置( delete 删除、修改特性、改为访问器属性)在对象上定义:true / Object.defineProperty()定义:false
[[Enumerable]]能否通过 for-in 循环返回属性在对象上定义:true / Object.defineProperty()定义:false
[[Writable]]能否修改属性的值在对象上定义:true / Object.defineProperty()定义:false
[[Value]]属性的数据值undefined
  • 数据属性包含一个保存数据值的位置,用 Object.defineProperty() 修改属性的默认特性,方法接收 3 个参数:

    • 属性所在对象、属性名、描述符对象(描述符对象的属性:configurableenumerablewritablevalue的一个或多个)
    • 严格模式下,修改[[Writable]]为 false 的值会报错
    • 严格模式下,用delete删除[[Configurable]]为 false 的属性会报错
var person = {}
Object.defineProperty(person, 'name', {
  writable: false, // 不可修改
  configurable: false, // 不可配置
  value: 'Nicholas',
})
console.log(person.name) // 'Nicholas'

person.name = 'Greg' // 试图重写(严格模式会报错)
console.log(person.name) // 'Nicholas',无法重写
delete person.name // 试图删除(严格模式会报错)
console.log(person.name) // 'Nicholas',无法删除
  • 对同一个属性多次调用 Object.defineProperty()会有限制:

    • configurablefalseconfigurablewritableenumerable属性不可再被修改
    • writablefalse ,描述符对象的value属性不可再被修改
Object.defineProperty(person, 'name', {
  // configurable: true, // 报错,configurable为true方可修改
  // value: 'Simon', // 报错,writable为true方可修改
  // writable: true, // 报错,configurable为true方可修改
  // enumerable: true, // 报错,configurable为true方可修改
})

访问器属性

访问器属性含义默认值
[[Configurable]]能否配置( delete 删除、修改特性、改为访问器属性)在对象上定义:true / Object.defineProperty()定义:false
[[Enumerable]]能否通过 for-in 循环返回属性在对象上定义:true / Object.defineProperty()定义:false
[[Get]]读取属性时调用的函数undefined
[[Set]]写入属性时调用的函数undefined
  • 访问器属性不包含数据值,用 Object.defineProperty() 定义属性:方法接收 3 个参数

    • 属性所在对象、属性名、描述符对象(和数据属性用法一样)
    • 只指定 getter() -> 属性只读不写;只指定 setter() -> 属性只写不读
    • 严格模式下,只指定 getter 或 setter 均会报错
var book = {
  _year: 2017, // 默认属性
  edition: 1, // 默认属性
}

Object.defineProperty(book, 'year', {
  // year是访问器属性
  get() {
    return this._year
  },
  set(newValue) {
    if (newValue > 2017) {
      this._year = newValue // this._year = 2018
      this.edition += newValue - 2017 // this.edition = 1 + 2018 - 2017
    }
  },
})
book.year = 2018 // 写入访问器属性。调用set()方法
console.log(book) // { _year: 2018, edition: 2 }
  • IE8 或更早,定义访问器属性的方法(遗留的方法,可在浏览器测试,vscode 会报错)
book.__defineGetter__('year', function () {
  return this._year
})
book.__defineSetter__('year', function (newValue) {
  if (newValue > 2017) {
    this._year = newValue
    this.edition += newValue - 2017
  }
})
book.year = 2018
console.log(book)

定义多个属性

  • Object.defineProperties()方法通过多个描述符一次性定义多个属性,方法接收 2 个参数:

    • 要添加/修改属性的对象、描述符对象
  • Object.defineProperties()方法定义的所有属性都是在同一时间定义
var book2 = {}
Object.defineProperties(book2, {
  _year: {
    writable: true,
    value: 2017,
  },
  edition: {
    writable: true,
    value: 1,
  },
  year: {
    get() {
      return this._year
    },
    set(newValue) {
      if (newValue > 2017) {
        this._year = newValue
        this.edition += newValue - 2017
      }
    },
  },
})
book2.year = 2018
console.log(book2.edition) // 2

读取属性的特性

  • Object.getOwnPropertyDescriptor()方法取得指定属性的属性描述符,接收 2 个参数:

    • 属性所在对象、要取得其描述符的属性名
var book3 = {}
Object.defineProperties(book3, {
  _year: {
    value: 2017,
  },
  edition: {
    value: 1,
  },
  year: {
    get: function () {
      return this._year
    },
    set: function (newValue) {
      if (newValue > 2017) {
        this._year = newValue
        this.edition += newValue - 2017
      }
    },
  },
})
var descriptor = Object.getOwnPropertyDescriptor(book3, '_year')
console.log(descriptor) // { value: 2017, writable: false, enumerable: false, configurable: false }
console.log(descriptor.value) // 2017
console.log(descriptor.configurable) // false,Object.getOwnPropertyDescriptor()定义属性的特性,默认值为false
console.log(typeof descriptor.get) // undefined,数据属性不含get函数

var descriptor2 = Object.getOwnPropertyDescriptor(book3, 'year')
console.log(descriptor2) // { get: [Function: get], set: [Function: set], enumerable: false, configurable: false }
console.log(descriptor2.value) // undefined,访问器属性不含value
console.log(descriptor2.configurable) // false,Object.getOwnPropertyDescriptor()定义属性的特性,默认值为false
console.log(typeof descriptor2.get) // 'function'
  • Object.getOwnPropertyDescriptors()方法获取参数对象每个属性的属性描述符(在每个属性上调用Object.getOwnPropertyDescriptor()方法并在一个新对象中返回)
console.log(Object.getOwnPropertyDescriptors(book3))
/* 
  {
    _year: {
      value: 2017,
      writable: false,
      enumerable: false,
      configurable: false
    },
    edition: { value: 1, writable: false, enumerable: false, configurable: false },
    year: {
      get: [Function: get],
      set: [Function: set],
      enumerable: false,
      configurable: false
    }
  }
*/
  • 调用 Object.defineProperty()Object.defineProperties()方法修改或定义属性时,configurableenumerablewritable的默认值 均为 false(用对象字面量则默认值为 true)
var book4 = {
  year: 2017,
}
var descriptorBook4 = Object.getOwnPropertyDescriptor(book4, 'year')
console.log(
  'book4', // book4
  descriptorBook4.configurable, // true
  descriptorBook4.enumerable, // true
  descriptorBook4.writable, // true
  typeof descriptorBook4.set, // undefined
  typeof descriptorBook4.get // undefined
)
var book5 = {}
Object.defineProperty(book5, 'year', {
  value: 2017,
})
var descriptorBook5 = Object.getOwnPropertyDescriptor(book5, 'year')
console.log(
  'book5', // book5
  descriptorBook5.configurable, // false
  descriptorBook5.enumerable, // false
  descriptorBook5.writable, // false
  typeof descriptorBook4.set, // undefined
  typeof descriptorBook4.get // undefined
)

合并对象

  • ES6 新增Object.assign()方法,方法接收一个目标对象若干源对象作为参数,将每个源对象中可枚举自有属性复制到目标对象:

    • 可枚举:Object.propertyIsEnmerable()返回 true
    • 自有:Object.hasOwePropety()返回 true
    • 使用源对象上的[[Get]]取得属性值,使用目标对象上的[[Set]]设置属性值
    • 目标对象会被修改,方法返回修改后的目标对象
let dest, src, result

// 单个源对象
dest = {}
src = { id: 'src' }
result = Object.assign(dest, src) // 返回修改后的目标对象

console.log(result) // { id: 'src' }
console.log(dest === result) // true,修改后的目标对象
console.log(dest === src) // false,目标对象和源对象

// 多个源对象
dest = {}
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' })
console.log(result) // { a: 'foo', b: 'bar' }

// 获取函数与设置函数
dest = {
  set a(val) {
    console.log(`Invoked dest setter with param ${val}`)
  },
}
src = {
  get a() {
    console.log(`Invoked src better`)
    return 'foo'
  },
}
Object.assign(dest, src)
/* 
  'Invoked src better',调用源对象上的get方法获得返回值
  'Invoked dest setter with param foo',再调用目标对象的set()方法传入值
*/
  • 若多个源对象有相同的属性,则使用最后一个复制的值
dest = { id: 'dest' }
result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' })
console.log(result) // { id: 'src2', a: 'foo', b: 'bar' }
  • 从源对象访问器属性取得的值,会作为静态值赋给目标对象,不能在两个对象间转移获取函数和设置函数
dest = {
  set id(x) {
    console.log(x)
  },
}
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' }) // first second third,依次赋值给目标对象
console.log(dest) // set id: ƒ id(x),设置函数是不变的
  • Object.assign()实际上是对每个源对象执行浅复制
dest = {}
src = { a: {} }
Object.assign(dest, src)
console.log(dest) // { a: {} }
console.log(dest === src) // false
console.log(dest.a === src.a) // true,对源对象浅复制,复制对象的引用
  • Object.assign()也可以只有一个参数,参数为源对象,调用方法会直接返回源对象自己
  • 只有一个参数的Object.assign()更能体现“浅复制”
console.log(Object.assign(src) === src) // true

src = { a: 1 }
dest = Object.assign(src)
console.log(dest) // { a: 1 }
dest.a = 2
console.log(src) // { a: 2 }
  • 如果赋值期间出错,操作中之并退出报错,Object.assign()不会回滚,已完成的修改将保留
dest = {}
src = {
  a: 'foo', // 没遇到错误,执行复制
  get b() {
    throw new Error() // 注入错误,操作终止
  },
  c: 'bar', // 已遇到错误,不会执行
}
try {
  Object.assign(dest, src)
} catch (e) {}
console.log(dest) // { a: 'foo' },遇到错误前已经完成的修改被保留

对象标识及相等判定

  • ES6 新增Object.is(),方法接收 2 个参数,用于判断两个值是否是相同的值,如果下列任何一项成立,则两个值相同:

    • 两个值都是 undefined
    • 两个值都是 null
    • 两个值都是 true 或者都是 false
    • 两个值是由相同个数的字符按照相同的顺序组成的字符串
    • 两个值指向同一个对象
    • 两个值都是数字并且:

      • 都是正零 +0
      • 都是负零 -0
      • 都是 NaN
      • 都是除零和 NaN 外的其它同一个数字
console.log(undefined === undefined) // true
console.log(null === null) // true
console.log(+0 === 0) // true
console.log(+0 === -0) // true // true
console.log(-0 === 0) // true
console.log(NaN === NaN) // false

// Object.is()
console.log(Object.is(undefined, undefined)) // true
console.log(Object.is(null, null)) // true
console.log(Object.is(+0, 0)) // true
console.log(Object.is(+0, -0)) // false
console.log(Object.is(-0, 0)) // false
console.log(Object.is(NaN, NaN)) // true

增强的对象语法

属性值简写

  • 使用变量名(不再写冒号)会自动被解释为同名属性键
var name = 'Matt'
var person = {
  name: name,
}
var person = { name }

可计算属性

  • 可以在对象字面量中完成动态属性赋值
var nameKey = 'name'
var ageKey = 'age'
var jobKey = 'job'
var person = {
  [nameKey]: 'Matt',
  [ageKey]: 27,
  [jobKey]: 'Software engineer',
}
console.log(person) // { name: 'Matt', age: 27, job: 'Software engineer' }
  • 可计算属性本身可以是复杂的表达式,实例化时再求值
var uniqueToken = 0
function getUniqueKey(key) {
  return `${key}_${uniqueToken++}`
}
var person = {
  [getUniqueKey(nameKey)]: 'Matt',
  [getUniqueKey(ageKey)]: 27,
  [getUniqueKey(jobKey)]: 'Software engineer',
}
console.log(person) // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }

简写方法名

  • 放弃给函数表达式明名,明显缩短方法声明
var person = {
  sayName: function (name) {
    console.log(`My name is ${name}`)
  },
}
var person = {
  sayName(name) {
    console.log(`My name is ${name}`)
  },
}
  • 对获取函数和设置函数也适用
var person = {
  name_: '',
  get name() {
    return this.name_
  },
  set name(name) {
    this.name_ = name
  },
  sayName() {
    console.log(`My name is ${this.name_}`)
  },
}
person.name = 'Matt'
person.sayName() // 'My name is Matt'
  • 可计算属性键相互兼容
var methodKey = 'sayName'
var person = {
  [methodKey](name) {
    console.log(`My name is ${name}`)
  },
}
person.sayName('Matt') // 'My name is Matt'

对象解构

  • 一条语句中使用嵌套数据实现一个或多个赋值操作(使用与对象相匹配的结构实现对象属性赋值
var person = {
  name: 'Matt',
  age: 27,
}
var { name: personName, age: personAge } = person
console.log(personName, personAge) // 'Matt' 27
  • 变量直接使用属性名称,可进一步简化语法
var { name, age } = person // 变量直接使用属性名称
console.log(name, age) // 'Matt' 27
  • 引用的属性不存在,则该变量的值为 undefined
var { name, job } = person // job不存在
console.log(name, job) // 'Matt' undefined
  • 可在解构赋值时定义默认值
var { name, job = 'Sofrware engineer' } = person // 定义job的默认值
console.log(name, job) // 'Matt' 'Sofrware engineer'
  • 解构内部使用函数ToObject()(不能直接在运行环境访问)把元数据转换为对象,因此原始值会被当做对象,null 和 undefined 不能被解构(会报错)
var { length } = 'foobar' // 'foobar'转换为String包装对象
console.log(length) // 6,字符串长度
var { constructor: c } = 4 // 4转换为Number包装对象
console.log(c === Number) // true,constructor指向构造函数

var { _ } = null // TypeError
var { _ } = undefined // TypeError
  • 事先声明的变量赋值时,赋值表达式必须包含在一对括号中
var person = {
  name: 'Matt',
  age: 27,
}
let personName2,
  personAge2 // 事先声明的变量
;({ name: personName2, age: personAge2 } = person) // 给实现声明的变量赋值,赋值表达式必须包含在一对括号中
console.log(personName, personAge) // 'Matt' 27

嵌套解构

  • 可以通过解构来复制对象属性
var person = {
  name: 'Matt',
  age: 27,
  job: {
    title: 'Software engineer',
  },
}
var personCopy = {}
;({ name: personCopy.name, age: personCopy.age, job: personCopy.job } = person) // 解构赋值,复制对象属性

person.job.title = 'Hacker' // 修改属性,源对象和赋值对象都受影响
console.log(person) // { name: 'Matt', age: 27, job: { title: 'Hacker' } }
console.log(personCopy) // { name: 'Matt', age: 27, job: { title: 'Hacker' } }
  • 可以使用嵌套结构,以匹配嵌套的属性
var {
  job: { title },
} = person
console.log(title) // 'Hacker',嵌套解构,title = person.job.title
  • 无论源对象还是目标对象,外层属性未定义时,不能使用嵌套结构
var person = {
  job: {
    title: 'Software engineer',
  },
}
var personCopy = {}

;({
  foo: { bar: personCopy.bar },
  // personCopy.bar = person.foo.bar,foo在源对象person上是undefined
} = person) // TypeError: Cannot read property 'bar' of undefined
;({
  job: { title: personCopy.job.title },
  // personCopy.job.title = person.job.title,job在目标对象persoCopy上是undefined
} = person) // TypeError: Cannot set property 'title' of undefined

部分解构

  • 如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,解构赋值只会完成一部分,出错后面的不再赋值
var person = {
  name: 'Matt',
  age: 27,
}
var personName, personBar, personAge
try {
  ;({
    name: personName, // personName = person.name,赋值成功,
    foo: { bar: personBar }, // personBar = person.foo.bar,foo未在person定义,赋值失败操作中断
    age: personAge, // 操作已中断,赋值失败
  } = person)
} catch (e) {}
console.log(personName, personBar, personAge) // 'Matt' undefined undefined

参数上下文匹配

  • 函数参数列表也可以进行解构赋值,且不影响 arguments 对象,可以在函数签名中声明在函数体内使用局部变量
var person = {
  name: 'Matt',
  age: 27,
}
function printPerson(foo, { name, age }, bar) {
  console.log(arguments)
  console.log(name, age)
}
printPerson('1st', person, '2nd')
// ["1st", {name: "Matt", age: 27}, "2nd"]
// 'Matt' 27
function printPerson2(foo, { name: personName, age: personAge }, bar) {
  console.log(arguments)
  console.log(name, age)
}
printPerson2('1st', person, '2nd')
// ["1st", {name: "Matt", age: 27}, "2nd"]
// 'Matt' 27

总结 & 问点

API含义参数返回值
Object.defineProperty()修改属性的默认特性/定义属性① 属性所在对象 ② 属性名 ③ 描述符对象
Object.defineProperties()一次性(同时)定义多个属性① 属性所在对象 ② 描述符对象
Object.getOwnPropertyDescriptor()取得指定属性的属性描述符① 属性所在对象 ② 要取得描述符的属性名属性描述符对象
Object.getOwnPropertyDescriptors()获取参数对象每个属性的属性描述符① 属性所在对象 ② 要取得描述符的属性名每个属性描述符对象组成的新对象
Object.assign()源对象可枚举且自有属性复制到目标对象① 目标对象(选) ② 源对象修改后的目标对象
Object.is()判断两个值是否是相同的值① 值 1 ② 值 2true / false
  • JS 的对象是什么?其属性可以包含什么?创建自定义对象有哪些方法?
  • 对象的数据属性有哪些特性?其含义和默认值分别是什么?
  • 如何修改对象的数据属性?对同一个属性多次修改有哪些限制?
  • 对象的访问器属性有哪些特性?其含义和默认值分别是什么?
  • 如何定义对象的访问器属性?同时定义多个属性呢?
  • 如何获取指定属性的属性描述符?全部属性的属性描述符呢?
  • Object.assign()的用法和实质含义是什么?其能否转移 get()函数和 set()函数?
  • 请用代码证明 Object.assign()是“浅复制”以及“不回滚”
  • ES6 有哪些增强对象的语法?其用法分别是什么?
  • 什么是解构赋值?原始值可以当作解构源对象么?为什么?
  • 请用代码依次举例嵌套解构、部分解构和参数上下文匹配

小讴
217 声望16 粉丝