[JS]对象
对象的定义
对象汇聚多个值(原始值或其他对象),是一个无序的集合(散列、散列表、字典、关联数组),我们可以通过属性名进行存储和获取值。不过,对象还可以从其他对象继承属性,这个其他对象称之为“原型”。
对象是动态的,即可以动态添加和删除属性,对象是按照引用操作而不是按值操作的,如果变量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,每次读取的值不同
- js中this对象用法分析
- JS中多种方式创建对象
- JS创建对象方法
- dhl: js判断网页对象是否存在
- js将js数组或者对象转换成json字符串
- js: js中数组对象的使用
- js用for循环为对象添加事件并传递参数
- js 实现图片预加载 (js操作 Image对象属性complete ,事件onload 异步加载图片)
- 在网页上使用 JSON 数据——使用 JSON.parse() 方法将json数据转换为 js对象:
- js对象的深度克隆
- js 对象深复制,创建对象和继承
- [转载]JS对象定义
- js获取对象位置的方法
- Node.js exports 和 require 两个对象
- js对象的特点
- 对js 面对对象编程的一些简单的理解
- JS中的浏览器对象
- JS中的自带对象Date
- js Date对象扩展
- JS 对象的访问器属性setter getter函数