关注前端小讴,阅读更多原创技术文章
理解对象
- 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 个参数:
- 属性所在对象、属性名、描述符对象(描述符对象的属性:
configurable、enumerable、writable、value的一个或多个)
- 严格模式下,修改
[[Writable]]为 false 的值会报错
- 严格模式下,用
delete删除[[Configurable]]为 false 的属性会报错
var person = {}
Object.defineProperty(person, 'name', {
writable: false,
configurable: false,
value: 'Nicholas',
})
console.log(person.name)
person.name = 'Greg'
console.log(person.name)
delete person.name
console.log(person.name)
- 对同一个属性多次调用
Object.defineProperty()会有限制:
- 若
configurable 为 false ,configurable、writable和enumerable属性不可再被修改
- 若
writable 为 false ,描述符对象的value属性不可再被修改
Object.defineProperty(person, 'name', {
})
访问器属性
| 访问器属性 | 含义 | 默认值 |
|---|
[[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', {
get() {
return this._year
},
set(newValue) {
if (newValue > 2017) {
this._year = newValue
this.edition += newValue - 2017
}
},
})
book.year = 2018
console.log(book)
- 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)
读取属性的特性
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)
console.log(descriptor.value)
console.log(descriptor.configurable)
console.log(typeof descriptor.get)
var descriptor2 = Object.getOwnPropertyDescriptor(book3, 'year')
console.log(descriptor2)
console.log(descriptor2.value)
console.log(descriptor2.configurable)
console.log(typeof descriptor2.get)
Object.getOwnPropertyDescriptors()方法获取参数对象每个属性的属性描述符(在每个属性上调用Object.getOwnPropertyDescriptor()方法并在一个新对象中返回)
console.log(Object.getOwnPropertyDescriptors(book3))
- 调用
Object.defineProperty()或Object.defineProperties()方法修改或定义属性时,configurable、enumerable、writable的默认值 均为 false(用对象字面量则默认值为 true)
var book4 = {
year: 2017,
}
var descriptorBook4 = Object.getOwnPropertyDescriptor(book4, 'year')
console.log(
'book4',
descriptorBook4.configurable,
descriptorBook4.enumerable,
descriptorBook4.writable,
typeof descriptorBook4.set,
typeof descriptorBook4.get
)
var book5 = {}
Object.defineProperty(book5, 'year', {
value: 2017,
})
var descriptorBook5 = Object.getOwnPropertyDescriptor(book5, 'year')
console.log(
'book5',
descriptorBook5.configurable,
descriptorBook5.enumerable,
descriptorBook5.writable,
typeof descriptorBook4.set,
typeof descriptorBook4.get
)
合并对象
- 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)
console.log(dest === result)
console.log(dest === src)
dest = {}
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' })
console.log(result)
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)
dest = { id: 'dest' }
result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' })
console.log(result)
- 从源对象访问器属性取得的值,会作为静态值赋给目标对象,不能在两个对象间转移获取函数和设置函数
dest = {
set id(x) {
console.log(x)
},
}
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' })
console.log(dest)
Object.assign()实际上是对每个源对象执行浅复制
dest = {}
src = { a: {} }
Object.assign(dest, src)
console.log(dest)
console.log(dest === src)
console.log(dest.a === src.a)
Object.assign()也可以只有一个参数,参数为源对象,调用方法会直接返回源对象自己
- 只有一个参数的
Object.assign()更能体现“浅复制”
console.log(Object.assign(src) === src)
src = { a: 1 }
dest = Object.assign(src)
console.log(dest)
dest.a = 2
console.log(src)
- 如果赋值期间出错,操作中之并退出报错,
Object.assign()不会回滚,已完成的修改将保留
dest = {}
src = {
a: 'foo',
get b() {
throw new Error()
},
c: 'bar',
}
try {
Object.assign(dest, src)
} catch (e) {}
console.log(dest)
对象标识及相等判定
- ES6 新增
Object.is(),方法接收 2 个参数,用于判断两个值是否是相同的值,如果下列任何一项成立,则两个值相同:
- 两个值都是 undefined
- 两个值都是 null
- 两个值都是 true 或者都是 false
- 两个值是由相同个数的字符按照相同的顺序组成的字符串
- 两个值指向同一个对象
- 两个值都是数字并且:
- 都是正零 +0
- 都是负零 -0
- 都是 NaN
- 都是除零和 NaN 外的其它同一个数字
console.log(undefined === undefined)
console.log(null === null)
console.log(+0 === 0)
console.log(+0 === -0)
console.log(-0 === 0)
console.log(NaN === NaN)
console.log(Object.is(undefined, undefined))
console.log(Object.is(null, null))
console.log(Object.is(+0, 0))
console.log(Object.is(+0, -0))
console.log(Object.is(-0, 0))
console.log(Object.is(NaN, NaN))
增强的对象语法
属性值简写
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)
var uniqueToken = 0
function getUniqueKey(key) {
return `${key}_${uniqueToken++}`
}
var person = {
[getUniqueKey(nameKey)]: 'Matt',
[getUniqueKey(ageKey)]: 27,
[getUniqueKey(jobKey)]: 'Software engineer',
}
console.log(person)
简写方法名
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()
var methodKey = 'sayName'
var person = {
[methodKey](name) {
console.log(`My name is ${name}`)
},
}
person.sayName('Matt')
对象解构
- 在一条语句中使用嵌套数据实现一个或多个赋值操作(使用与对象相匹配的结构实现对象属性赋值)
var person = {
name: 'Matt',
age: 27,
}
var { name: personName, age: personAge } = person
console.log(personName, personAge)
var { name, age } = person
console.log(name, age)
- 引用的属性不存在,则该变量的值为 undefined
var { name, job } = person
console.log(name, job)
var { name, job = 'Sofrware engineer' } = person
console.log(name, job)
- 解构内部使用函数
ToObject()(不能直接在运行环境访问)把元数据转换为对象,因此原始值会被当做对象,null 和 undefined 不能被解构(会报错)
var { length } = 'foobar'
console.log(length)
var { constructor: c } = 4
console.log(c === Number)
var { _ } = null
var { _ } = undefined
- 给事先声明的变量赋值时,赋值表达式必须包含在一对括号中
var person = {
name: 'Matt',
age: 27,
}
let personName2,
personAge2
;({ name: personName2, age: personAge2 } = person)
console.log(personName, personAge)
嵌套解构
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)
console.log(personCopy)
var {
job: { title },
} = person
console.log(title)
- 无论源对象还是目标对象,外层属性未定义时,不能使用嵌套结构
var person = {
job: {
title: 'Software engineer',
},
}
var personCopy = {}
;({
foo: { bar: personCopy.bar },
} = person)
;({
job: { title: personCopy.job.title },
} = person)
部分解构
- 如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,解构赋值只会完成一部分,出错后面的不再赋值
var person = {
name: 'Matt',
age: 27,
}
var personName, personBar, personAge
try {
;({
name: personName,
foo: { bar: personBar },
age: personAge,
} = person)
} catch (e) {}
console.log(personName, personBar, personAge)
参数上下文匹配
- 函数参数列表也可以进行解构赋值,且不影响 arguments 对象,可以在函数签名中声明在函数体内使用局部变量
var person = {
name: 'Matt',
age: 27,
}
function printPerson(foo, { name, age }, bar) {
console.log(arguments)
console.log(name, age)
}
printPerson('1st', person, '2nd')
function printPerson2(foo, { name: personName, age: personAge }, bar) {
console.log(arguments)
console.log(name, age)
}
printPerson2('1st', person, '2nd')
总结 & 问点
| API | 含义 | 参数 | 返回值 |
|---|
Object.defineProperty() | 修改属性的默认特性/定义属性 | ① 属性所在对象 ② 属性名 ③ 描述符对象 | |
Object.defineProperties() | 一次性(同时)定义多个属性 | ① 属性所在对象 ② 描述符对象 | |
Object.getOwnPropertyDescriptor() | 取得指定属性的属性描述符 | ① 属性所在对象 ② 要取得描述符的属性名 | 属性描述符对象 |
Object.getOwnPropertyDescriptors() | 获取参数对象每个属性的属性描述符 | ① 属性所在对象 ② 要取得描述符的属性名 | 每个属性描述符对象组成的新对象 |
Object.assign() | 源对象可枚举且自有属性复制到目标对象 | ① 目标对象(选) ② 源对象 | 修改后的目标对象 |
Object.is() | 判断两个值是否是相同的值 | ① 值 1 ② 值 2 | true / false |
- JS 的对象是什么?其属性可以包含什么?创建自定义对象有哪些方法?
- 对象的数据属性有哪些特性?其含义和默认值分别是什么?
- 如何修改对象的数据属性?对同一个属性多次修改有哪些限制?
- 对象的访问器属性有哪些特性?其含义和默认值分别是什么?
- 如何定义对象的访问器属性?同时定义多个属性呢?
- 如何获取指定属性的属性描述符?全部属性的属性描述符呢?
- Object.assign()的用法和实质含义是什么?其能否转移 get()函数和 set()函数?
- 请用代码证明 Object.assign()是“浅复制”以及“不回滚”
- ES6 有哪些增强对象的语法?其用法分别是什么?
- 什么是解构赋值?原始值可以当作解构源对象么?为什么?
- 请用代码依次举例嵌套解构、部分解构和参数上下文匹配