您的位置:首页 > Web前端 > JavaScript

[JS]对象

2021-07-18 20:42 1011 查看

对象的定义

对象汇聚多个值(原始值或其他对象),是一个无序的集合(散列、散列表、字典、关联数组),我们可以通过属性名进行存储和获取值。不过,对象还可以从其他对象继承属性,这个其他对象称之为“原型”。

对象是动态的,即可以动态添加和删除属性,对象是按照引用操作而不是按值操作的,如果变量x指向一个对象,则

let y = x
执行后,变量y保存的是同一个对象的引用,而不是该对象的副本。请阅读[JS]对象的引用与拷贝

对象字面量

创建对象最简单的方式是对象字面量。

let empty = {}
let number = { x: 0, y: 0}

new 关键字

new 关键字用于创建和初始化一个新对象,new 关键字后面必须使用构造函数,目的是初始化新创建的对象。

let a = new Array()
let o = new Object()

Object.create()

Object.create() 函数用于创建一个新对象,该对象的原型对象是参数提供的对象。请阅读[JS]原型对象和原型链

let o = {
x: 1,
y: 2
}
let a = Object.create(o)

a.x // 1

传入 null 创建一个没有原型对象的新对象。

属性

通过属性访问表达式可以创建对象、设置(或添加)属性、删除属性。

let o = { x: 1 }
o.y = 2 // 添加属性
o['x'] = 3 // 修改属性

关联数组

JavaScript 对象本质是关联数组。关联数组是一种具有特殊索引方式的数组。不仅可以通过整数来索引它,还可以使用字符串或者其他类型的值(除了NULL)来索引它。

如图,这种普遍访问对象的方式对于字符串是不可行的:

关联数组访问属性类似于数组访问元素一样,这种属性访问表达式就支持字符串的索引方式:

for (let i = 0; i < 4; i++) {
o[`hobby${i}`]
}

对象继承

对象有可能从它的原型对象中继承一组属性,但会有一组自己的属性,叫做“自有属性”。

let a = {}
a.x = 2
let b = Object.create(a)
b.y = 2
let c = Object.create(b)
c.z = 2
console.log(c.x) // 2

访问 c.x,若对象 c 没有自有属性 x,则查询它的原型对象 b 是否拥有属性 x。若对象 b 也没有属性 x,则查询它的原型对象 a 是否拥有属性 x。

该过程会一直持续,直到找到属性 x 或者查询到一个原型为 null 的对象。

属性访问错误

在访问对象的自有属性或继承属性时,若没有找该属性,则属性访问表达式的求值结果为 undefined 。比如,对象 o 有 sub-title 属性,没有 subtitle 属性:

o.subtitle // => undefined

在此基础之上,继续访问属性,会抛出 TypeError 错误:

o.subtitle.length // => TypeError

为了防止 TypeError 错误,可以通过

?.
条件式属性访问)防止 TypeError 错误:

o?.subtitle?.length // => undefined

在 Vue 的 mounted 生命周期函数中实现异步请求数据,极有可能会出现Error in render: "TypeError: Cannot read property '' of undefined"的错误。

删除属性

delete操作符用于从对象中移除属性,它唯一的操作数是一个数学访问表达式,delete删除的不是属性的值,而是操作属性本身。

delete o.subtitle // 删除o对象的subtitle属性
delete o['subtitle']

delete操作符只删除自有属性,不删除继承属性。如果delete操作成功或没影响(如删除不存在的属性),则delete返回true,对非属性访问表达式使用delete,同样也会返回true。

let o = { x: 1, y: 2 }
delete o.x // true,删除属性x
delete o.y // true,什么也不做,因为属性y不存在
delete o.toString // true,什么也不做,因为toString不是自有属性

测试属性

实际开发中经常需要测试这组属性的成员关系,即检查对象是否有一个给定名字的属性。为此,可以使用in操作符,或者hasOwnProperty()、propertyIsEnumerable()方法,或者直接查询相应属性。

in操作符

in操作符要求左边是一个属性名,右边是一个对象,如果对象有包含相应名字的自由属性或继承属性,将返回true:

let o = { x: 1 }
'x' in o // true,o有自有属性x
'y' in o // false,o没有属性y
'toString' in o // true,o继承了Object的toString属性

hasOwnProperty()

hasOwnProperty()方法用于测试对象的属性是否为自有属性,但不能测试继承的属性:

let o = { x: 1 }
o.hasOwnProperty('x') // true,o有自有属性x
o.hasOwnProperty('y') // false,o没有属性y
o.hasOwnProperty('toString') // false,toString是继承属性

propertyIsEnumerable()

propertyIsEnumerable()方法用于测试对象的属性是否可枚举,方法参数接收对象的属性,该属性是自有属性且enumerable为true。某些内置属性是不可枚举的,常规创建的属性都是可枚举的,除非使它们限制为不可枚举:

let o = { x: 1 }
o.propertyIsEnumerable('x') // true,o有自有属性x
o.propertyIsEnumerable('toString') // false,toString不是自有属性
Object.prototype.propertyIsEnumerable('toString') // false,toString不可枚举

!==

除了使用上面几种操作符以外,还可以使用简单的

!==
测试属性是否存在于对象中:

let o = { x: 1 }
o.x !== undefined // true,o有自有属性x
o.y !== undefined // false,o没有属性y
o.toString !== undefined // true,o继承了Object的toString属性

!==
不能区分undefined的属性,但
in
可以区分不存在的属性和存在但被设置为undefined的属性。

枚举属性

for/in

for/in循环对指定对象的每个可枚举属性(包括自有属性或继承属性)

let o = { x: 1, y: 2, z: 3 }
Object.defineProperty(o, 'v', { // 不可枚举属性v
value: 'notEnumerable',
writable: true,
enumerable: false,
configurable: true
})
for (let p in o) {
console.log(p) // x y z ,v属性为不可枚举属性
}

Object.keys()

Object.keys()返回对象可枚举自有属性名的数组,不包含不可枚举属性、继承属性:

let a = {}
a['x'] = 'successfully'
let b = Object.create(a)
b['z'] = 'hello'
b['w'] = 'world'
Object.defineProperty(b, 'v', { // 不可枚举属性v
value: 'notEnumerable',
writable: true,
enumerable: false,
configurable: true
})
let keys = Object.keys(b) // ['z', 'w'] 可枚举属性且自有属性名的数组

Object.getOwnPropertyNames()

Object.getOwnPropertyNames()返回对象不可枚举和可枚举自有属性名的数组:

let a = {}
a['x'] = 'successfully'
let b = Object.create(a)
b['z'] = 'hello'
b['w'] = 'world'
Object.defineProperty(b, 'v', { // 不可枚举属性v
value: 'notEnumerable',
writable: true,
enumerable: false,
configurable: true
})
let keys = Object.getOwnPropertyNames(b) // ['z', 'w', 'v'] 可枚举的和不可枚举的属性名数组

Object.getOwnPropertySymbols()

Object.getOwnPropertySymbols()返回对象符号属性:

let b = Object.create(a)
b['z'] = 'hello'
b['w'] = 'world'
b[Symbol('symbol')] = 'symbol'
let keys = Object.getOwnPropertySymbols(b) // [Symbol(symbol)]

Reflect.ownKeys()

Reflect.ownKeys()返回所有可枚举和不可枚举,以及字符串属性和符号属性:

let a = {}
a['x'] = 'successfully'
let b = Object.create(a)
b['z'] = 'hello'
b['w'] = 'world'
b[Symbol('symbol')] = 'symbol'
Object.defineProperty(b, 'v', { // 不可枚举属性v
value: 'notEnumerable',
writable: true,
enumerable: false,
configurable: true
})
let keys = Reflect.ownKeys(b) // ["z", "w", "v", Symbol(symbol)] 可枚举和不可枚举属性,字符串属性、符号属性

扩展对象

请阅读[JS]对象的引用与拷贝

对象序列化

对象序列化是把对象的状态转换为字符串的过程,之后可以从中恢复对象的状态。函数JSON.stringify()和JSON.parse()用于序列化和恢复JS对象。这两个函数使用JSON数据交换格式。JSON表示JavaScript Object Notation(JavaScript对象表示语法),其语法与JavaScript对象和数组字面量非常相似:

let o = { x: 1, y: { z: [false, null, ''] } }
let s = JSON.stringify(o) // {"x":1,"y":{"z":[false,null,""]}}
let p = JSON.parse(s)

JSON语法是JS语法的子集,可以序列化和恢复的值包括对象、数组、字符串、布尔、null。

对象方法

所有JS对象(除了显示创建为没有原型的)都从Object.prototype继承属性,如hasOwnProperty()、propertyIsEnumerable()等等。

toString()

默认的toString()只会得到对象的类型:

let s = { x: 1 }.toString() // [object Object]

一般会重新定义自己的toString()方法。

toJSON()

Object.prototype实际上并未定义toJSON()方法,但JSON.stringify()方法会从要序列化的对象上寻找toJSON()方法。Date类定义了自己的toJSON()方法,返回一个表示日期的序列化字符串。

let o = {
x: 1,
y: 2,
toString: function() { return `(${this.x}, ${this.y})` }
toJSON: function() { return this.toString() }
}

JSON.stringify([point]) // '["(1, 2)"]'

对象字面量扩展语法

计算的属性名

有时候,我们需要创建一个具有特定属性的对象,但该属性的名字不是编译时可以直接写在源代码中的常量。相反,你需要的这个属性名保存在一个变量里,或者是调用的某个函数的返回值。不能对这种属性使用基本对象字面量,为此需要先创建一个对象然后再为它添加想要的属性:

const PROPERTY_NAME = 'computed1'

function computePropertyName() {
return 'computed2'
}

let o = {
[PROPERTY_NAME]: 'hello world'
[computePropertyName()]: 'hello world'
}

let keys = Object.keys(o)
console.log(`key => ${keys[0]}`, `value => ${o['computed1']}`) // key => computed1, value => hello world
console.log(`key => ${keys[1]}`, `value => ${o['computed2']}`) // key => computed2, value => hello world

符号作为属性名

在ES6只有,属性名可以是字符串或符号,如果把符号赋值给一个变量或常量,那么可以使用计算属性语法将该符号作为属性名:

const extension = Symbol('mySymbol')

let o = {
[extension]: { /* object ... */ }
}

o[extension].x = 0

符号除了用作属性名之外,不能用它们做任何事情。不过每个符号都与其他符号不同,这意味着符号非常使用用于创建唯一属性名。创建符号需要调用Symbol()工厂函数(符号不是对象,而是原始值,因此Symbol()不是构造函数,不能用new调用),使用相同符号创建的两个符号依旧是不同的符号。

符号是为了JavaScript对象定义安全的扩展机制,如果你从第三方代码得到一个对象,然后需要为该对象添加一些自己的属性,但又不希望新属性与该对象原有的任何属性冲突,那就可以使用符号作为属性名。

扩展操作符

在ES2018及以后,可以在对象字面量中使用“扩展操作符”——

...
把已有的对象属性复制到新对象中:

let p = { x: 0, y: 0 }
let d = { width: 100, height: 75 }
let o = { ...p, ...d }
o.x + o.y + o.width + o.height // 175

...
操作符把p对象和d对象的属性扩展到了o对象字面量中。实际上,它仅在对象字面量中有效的一种特殊语法,即复制已有的对象属性到新对象中。

扩展操作符只扩展对象的自由属性,不扩展任何继承属性:

let o = Object.create({x: 1})
let p = { ...o }
p.x // undefined

若对象有n个属性,把这个属性扩展到另一个对象可能是O(n)操作。这意味着,如果循环或递归函数中向一个大对象不断追加属性,可能是o(n²)。随着n越来越大,扩展性能就越低。

简写方法

把函数定义为对象属性时,我们称函数为方法。在ES6以前,需要像定义对象的其他属性一样,通过函数定义表达式在对象字面量中定义一个方法:

let square = {
area: function() { return this.side * this.side },
side: 10
}

square.area() // 100

在ES6中,对象字面量语法允许省略

function
关键字和冒号的简写方法:

let square = {
area() { return this.side * this.side },
side: 10
}

square.area() // 100

在使用这种简写方法时,属性名可以是对象字面量允许的任何形式,也可以使用字符串字面量和计算的属性名,包括符号属性名:

const METHOD_NAME = 'm'
const symbol = Symbol()

let wm = {
'method with space'(x) { return x + 1 },
[METHOD_NAME](x) { return x + 2 },
[symbol](x) { return x + 3 }
}

wm['method with space'](1) // 2
wm[METHOD_NAME](1) // 3
wm[symbol](1) // 4

访问器属性

JS还支持为对象定义访问器属性,这种属性不是一个值,而是一个或两个访问器方法:getter和setter。

当获取一个访问器属性的值时,JS会调用获取方法。这个方法的返回值就是属性访问表达式的值。当设置一个访问器属性的值时,JS会调用设置方法,传入赋值语句右边的值。

当只有一个设置方法时,那它就是只写属性,读取这种属性始终会得到undefined:

let o = {
_x: 0,
set x(x) {
this._x = x
}
}

o.x = 10 // undefined

当只有一个获取方法,那它就是只读属性:

let o = {
_x: 0,
get x(x) {
return this._x = x
}
}

o.x // 0

使用访问器属性的其他场景还有写入属性时进行合理性检测,以及每次读取属性时返回不同的值:

const serialnum = {
_n: 0,
get next() { return this._n++ },
set next(n) {
if (n > this._n) this._n = n
else throw new Error('serial number can only be set to a larger value')
}
}

serialnum.next = 10 // 初始化值
serialnum.next // 10
serialnum.next // 11,每次读取的值不同
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: