Object
Object
Object 概述
Object
用于存储各种键值集合和更复杂的实体,所有引用类型都从它继承了基本的行为。创建 Object
有两种方式:
- 使用
new
操作符和Object
构造函数。 - 使用对象字面量(object literal)表示法。在使用对象字面量表示法定义对象时,并不会实际调用
Object
构造函数。
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
对象属性的排序规则:整数属性会被进行排序,其他属性则按照创建的顺序显示。其中,整数属性为可以与整数进行相互转换的字符串,比如 '49'
可以,而 "+49"
、"1.2"
则不行。
// Number() 显式转换为数字
// Math.trunc() 将数字的小数部分去掉,只保留整数部分
console.log(String(Math.trunc(Number('49')))) // "49",与 "49" 相同,则是整数属性
console.log(String(Math.trunc(Number('+49')))) // "49",不同于 "+49",则不是整数属性
console.log(String(Math.trunc(Number('1.2')))) // "1",不同于 "1.2",不是整数属性
对象的相关操作:
// 使用 new 操作符和 Object 构造函数创建 Object
let person = new Object()
person.name = 'foo'
person.age = 18
// 使用对象字面量(object literal)定义对象
let user = {
name: 'foo',
age: 30,
5: true, // 数值属性会自动转换为字符串
'likes birds': true, // 多词属性必需添加引号
$key: '$key value',
__private_key__: 'private value',
}
// 通过【点符号】获取属性值,要求属性不包含空格,不以数字开头,也不包含特殊字符(允许使用 $ 和 _)
console.log('user.name: ', user.name) // user name: foo
console.log('user.age: ', user.age) // user age: 30
console.log('user.$key: ', user.$key) // user.$key: $key value
console.log('user.__private_key__: ', user.__private_key__) // user.__private_key__: private value
// 通过【方括号】获取属性值,可用于任何字符串
console.log('user[5]: ', user[5]) // user[5]: true
console.log("user['5']: ", user['5']) // user['5']: true
console.log("user['likes birds']: ", user['likes birds']) // user['likes birds']: true
// 动态设置对象属性,并获取属性值
let getkeyname = 'name'
console.log('user[namekey]: ', user[getkeyname]) // user[namekey]: foo
// 使用【点符号】或【方括号】,设置属性
user.addKey01 = 'addKey01 value'
user['addKey02'] = 'addKey02 value'
// 使用 delete 操作符删除对象属性
// > delete 操作符删除一个不存在的属性,delete 不报错,而且返回 true
// > delete 操作符返回 false,则表示该属性存在,且不得删除
// > delete 操作符只能删除对象本身的属性,无法删除继承的属性
delete user.addKey01
const isDelete = delete user['addKey02']
console.log('isDelete: ', isDelete) // isDelete: true
// 判断对象中是否存在对应属性
console.log(user.name === undefined) // false,表示 user 对象中存在 name 属性
console.log(user.name123 === undefined) // true,表示 user 对象中存在 name123 属性
// 使用 in 运算符判断
// > in 判断指定属性是否在指定的【对象】或其【原型链】中
// > 如果只是将一个属性赋值为 undefined,而没有删除,in 运算符也会返回 true
console.log('name' in user) // true,表示 user 对象中存在 name 属性
console.log('name123' in user) // false,表示 user 对象中不存在 name123 属性
// 遍历对象:for (let key in obj) 循环
for (var key in user) {
console.log(`${key} : `, user[key])
}
Object() 函数
Object
本身是一个函数,可以将任意值转换为对象,常用于保证某一个值一定是对象。
- 如果参数为空(或为
undefined
和null
),Object()
返回一个空对象。 - 如果参数是原始类型的值,
Object()
将其转为对应的包装对象的实例。 - 如果参数是一个对象,
Object()
返回该对象,即不用转换。
// 如果参数为空(或为 undefined 和 null),Object()返回一个空对象。
let obj = Object() // {}
// 等同于 let obj = Object(undefined) // {}
// 等同于 let obj = Object(null) // {}
obj instanceof Object // true
// 如果参数是原始类型的值,Object() 将其转为对应的包装对象的实例。
let obj = Object(1)
obj instanceof Object // true
obj instanceof Number // true
let obj = Object('foo')
obj instanceof Object // true
obj instanceof String // true
let obj = Object(true)
obj instanceof Object // true
obj instanceof Boolean // true
// 如果参数是一个对象,Object() 返回该对象,即不用转换。
let arrVal = []
let changeArrVal = Object(arrVal) // 返回原数组
changeArrVal === arrVal // true
let objVal = {}
let changeObjVal = Object(objVal) // 返回原对象
changeObjVal === objVal // true
let fnVal = function () {}
let changeFnVal = Object(fnVal) // 返回原函数
changeFnVal === fnVal // true
利用 Object()
函数判断变量是否为对象:
function isObject(value) {
return value === Object(value)
}
isObject([]) // true
isObject(true) // false
isObject(123) // false
isObject('abc') // false
isObject(undefined) // false
isObject(null) // false
Object 实例属性和方法
ECMAScript 中的 Object
也是派生其他对象的基类。Object
类型的所有属性和方法在派生的对象上同样存在。每个 Object
实例都有如下属性和方法。
Object.prototype.constructor
constructor
属性返回 Object
的构造函数(用于创建实例对象)。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。
- 原始类型的值是只读的,如:
1
、true
和"test"
。 - 所有对象(使用
Object.create(null)
创建的对象除外)都将具有constructor
属性。 - 在没有显式使用构造函数的情况下,创建的对象(例如:对象、数组文本)将具有
constructor
属性,这个属性指向该对象的基本对象构造函数类型。
const obj = {} // 或者 const obj = new Object()
obj.constructor === Object // true
const nullObj = Object.create(null)
console.log('num.constructor : ' + nullObj.constructor) // num.constructor : undefined
const arr = [] // 或者 const arr = new Array()
console.log('arr.constructor : ' + arr.constructor) // arr.constructor : function Array() { [native code] }
arr.constructor === Array // true
const num = new Number(3) // 或者 const num = 3
console.log('num.constructor : ' + num.constructor) // num.constructor : function Number() { [native code] }
num.constructor === Number // true
function Name(name) {
this.name = name
}
const theName = new Name('foo')
console.log('theName.constructor : ' + theName.constructor)
// theName.constructor : function Name(name) {
// this.name = name
// }
改变对象的 constructor
,除 null
、 undefined
(两者无相应的构造函数) 之外任何对象都可以更改 constructor
属性的值。
- 基本类型(如
String
、Number
、Boolean
等)不会保留这些更改,也不会抛出异常。每当基本类型当成对象使用时,其对应的构造函数的实例就会在语句执行后立即被创建和丢弃。 - 改变
constructor
的属性不会影响instanceof
运算符。 - 如果对象被密封或冻结,更改
constructor
将不会起作用,也不会抛出异常。
// 改变对象的 constructor
let foo = null
foo.constructor = 123
// Uncaught TypeError: Cannot set properties of null (setting 'constructor')
foo = 'foo'
foo.constructor = Number
console.log('foo.constructor: ' + foo.constructor) // foo.constructor : ƒ String() { [native code] }
console.log(foo.constructor === String) // true
foo.name = 'bar' // 创建了一个 String('foo') 的隐式实例并分配 name 属性
console.log(foo.name === undefined) // true,因为创建了一个新的 String('foo') 实例进行比较,没有 name 属性
// 改变 constructor 的属性不会影响 instanceof 运算符
let arr = []
arr.constructor = String
console.log(arr.constructor === String) // true
console.log(arr instanceof String) // false
console.log(arr instanceof Array) // true
arr = new Foo()
arr.constructor = 'bar'
console.log('arr.constructor: ' + arr.constructor) // arr.constructor: bar
arr.constructor === 'bar' // true
// 如果对象被密封或冻结,更改 `constructor` 将不会起作用,也不会抛出异常
let sealObj = Object.seal({})
sealObj.constructor = Number
console.log('sealObj.constructor: ' + sealObj.constructor) // sealObj.constructor: function Object() { [native code] }
console.log(sealObj.constructor === Object) // true
大多数情况下,constructor
属性用于定义一个函数的构造函数(function-constructor),并使用 new
和原型链继承进一步使用。
function Foo(name) {
this.name = name
}
let foo = new Foo('bar')
console.log(foo.name) // bar
console.log('foo.constructor: ' + foo.constructor)
// foo.constructor: function Foo(name) {
// this.name = name
// }
console.log(foo.constructor === Foo) // true
console.log(foo instanceof Object) // true
console.log(foo instanceof Foo) // true
Object.prototype.__proto__
警告
- 不再推荐使用该特性。虽然一些浏览器仍然支持它,但也许已从相关的 web 标准中移除,也许正准备移除或出于兼容性而保留。
- 通过现代浏览器的操作属性的便利性,可以改变一个对象的
[[Prototype]]
属性,通过这种方式改变和继承属性,在每一个 JavaScript 引擎和浏览器中都是一个非常慢且影响性能的操作。 - 创建一个新的且可以继承
[[Prototype]]
的对象,推荐使用Object.create()
。 - 指定对象的原型(内部
[[Prototype]]
属性的值),建议只使用Object.getPrototypeOf()
。
Object.prototype.__proto__
属性是一个访问器属性(一个 getter
函数和一个 setter
函数), 暴露了通过它访问的对象的内部 [[Prototype]]
(一个对象或 null
)。
__proto__
的读取器 (getter
) 暴露了一个对象的内部[[Prototype]]
。- 对于对象字面量创建的对象,该值为
Object.prototype
。 - 对于数字字面量创建的对象,该值为
Array.prototype
。 - 对于
function
,该值为Function.prototype
。 - 对于使用
new fun
创建的对象(fun
为由 JavaScript 提供的内建构造函数之一,包括Object
、Array
、Number
、String
、Boolean
、Date
等等),该值为fun.prototype
。 - 对于 JavaScript 定义的其他 JavaScript 构造函数创建的对象,该值为该构造函数的
prototype
属性。
- 对于对象字面量创建的对象,该值为
__proto__
的设置器 (setter
) 允许对象的[[Prototype]]
被变更。要变更的值必须是一个object
或null
,提供其他值将不起任何作用。该对象必须通过
Object.isExtensible()
判断为是可扩展的,如果不可扩展,则会抛出一个TypeError
错误。
let Circle = function () {}
let shape = {}
let circle = new Circle()
// 设置该对象的原型链引用
shape.__proto__ = circle
// 判断该对象的原型链引用是否属于 circle
console.log(shape.__proto__ === circle) // true
Object.prototype.hasOwnProperty()
Object.prototype.hasOwnProperty()
方法接受一个字符串作为参数,返回一个布尔值,表示该对象自身属性中是否具有指定的属性(即:是否有指定的键)。
- 即使属性的值是
null
或undefined
,只要属性存在,hasOwnProperty
依旧会返回true
。 - 和
in
运算符(判断指定的属性是否在指定的对象或其原型链中)不同,Object.prototype.hasOwnProperty()
忽略掉从原型链上继承到的属性。
let obj = {}
obj.foo = 'foo'
obj.bar = 'bar'
obj.hasOwnProperty('foo') // true
delete obj.foo
obj.hasOwnProperty('foo') // false
obj.hasOwnProperty('toString') // false,toString 为原型 Object 原型链上的属性
obj.hasOwnProperty('hasOwnProperty') // false,hasOwnProperty 为原型 Object 原型链上的属性
// for...in 以任意顺序迭代一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性。
// 不应该基于 for...in 循环中没有不可枚举的属性而得出 hasOwnProperty 是严格限制于可枚举项目的
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(`对象自身属性 ${key} : `, obj[key])
// 对象自身属性 bar : bar
} else {
console.log(`其他属性 ${key} : `, obj[key])
}
}
// 当某个对象的属性占用 hasOwnProperty 属性时,则会使用外部的 hasOwnProperty
obj.hasOwnProperty = () => false
obj.hasOwnProperty('abc') // 始终返回 false
// 可以直接使用对象或者 Object 原型链上的 hasOwnProperty 方法
({}).hasOwnProperty.call(obj, 'bar') // true
Object.prototype.hasOwnProperty.call(obj, 'bar') // true
Object.prototype.isPrototypeOf()
prototypeObj.isPrototypeOf(object)
返回一个布尔值,用于判断当前对象(object
)是否为另一个对象(prototypeObj
)的原型。如果 prototypeObj
为 undefined
或 null
,会抛出 TypeError
。
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype)
Baz.prototype = Object.create(Bar.prototype)
var baz = new Baz()
console.log(Baz.prototype.isPrototypeOf(baz)) // true,即 Baz.prototype 在 baz 对象的原型链上
console.log(Bar.prototype.isPrototypeOf(baz)) // true,即 Bar.prototype 在 baz 对象的原型链上
console.log(Foo.prototype.isPrototypeOf(baz)) // true,即 Foo.prototype 在 baz 对象的原型链上
console.log(Object.prototype.isPrototypeOf(baz)) // true,即 Object.prototype 在 baz 对象的原型链上
console.log(baz instanceof Baz) // true
console.log(baz instanceof Bar) // true
console.log(baz instanceof Foo) // true
console.log(baz instanceof Object) // true
Object.prototype.propertyIsEnumerable()
obj.propertyIsEnumerable(prop)
方法返回一个布尔值,表示对象(obj)中指定的属性(prop)是否可以被 for...in
循环枚举,通过原型链继承的属性除外。如果对象没有指定的属性,则此方法返回 false
。
const obj = {}
const arr = []
obj.enumerable = 'enumerable'
arr[0] = 'enumerable'
obj.propertyIsEnumerable('prop') // true
arr.propertyIsEnumerable(0) // true
arr.propertyIsEnumerable('length') // true,length 属性为原型链继承的属性
console.log('length : ', arr.propertyIsEnumerable('length'))
Math.propertyIsEnumerable('random') // false
function firstConstructor() {
this.method = function method() {
return 'is enumerable'
}
}
let firstObj = new firstConstructor()
firstObj.arbitraryProperty = 'enumerable'
firstObj.propertyIsEnumerable('arbitraryProperty') // true
firstObj.propertyIsEnumerable('method') // true
firstObj.propertyIsEnumerable('property') // false
firstObj.propertyIsEnumerable('constructor') // false
Object.prototype.toLocaleString()
obj.toLocaleString()
:返回对象(obj)的字符串表示,该字符串反映对象所在的本地化执行环境。
覆盖 toLocaleString
的对象:
Array
:Array.prototype.toLocaleString()
Number
:Number.prototype.toLocaleString()
Date
:Date.prototype.toLocaleString()
Object.prototype.toString()
Object.prototype.toString()
方法返回一个表示该对象的字符串,默认情况下返回类型字符串,可用于判断一个值的类型。该方法旨在重写(自定义)派生类对象的类型转换的逻辑。
- 字符串转换优先调用
toString()
。 - 数字转换和原始值转换会优先调用
valueOf()
。
对象可以通过定义 Symbol.toStringTag
属性来更改 Object.prototype.toString()
的行为
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(3.14) // '[object Number]'
Object.prototype.toString.call('str') // '[object String]'
Object.prototype.toString.call({ a: 100 }) // '[object Object]'
Object.prototype.toString.call([1, 2, 3]) // '[object Array]'
Object.prototype.toString.call(function () {}) // '[object Function]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(function* () {}) // '[object GeneratorFunction]'
Object.prototype.toString.call(Promise.resolve()) // '[object Promise]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(/regex/) // '[object RegExp]'
// 对象可以通过定义 Symbol.toStringTag 属性来更改 Object.prototype.toString() 的行为
const myDate = new Date()
Object.prototype.toString.call(myDate) // [object Date]
myDate[Symbol.toStringTag] = 'myDate'
Object.prototype.toString.call(myDate) // [object myDate]
Date.prototype[Symbol.toStringTag] = 'prototype polluted'
Object.prototype.toString.call(new Date()) // [object prototype polluted]
Object.prototype.valueOf()
Object.prototype.valueOf()
方法将对象转换为一个原始值(字符串、数值或布尔值),默认情况下返回对象本身。此方法旨在用于自定义类型转换的逻辑时,重写派生类对象。
- 数字转换和原始值转换优先调用
valueOf()
。 - 字符串转换会优先调用
toString()
,toString()
可能返回一个字符串类型,valueOf()
在这种情况下通常不会被调用。
const obj = { foo: 1 }
console.log(obj.valueOf()) // { foo: 1 }
console.log(obj.valueOf() === obj) // true
console.log(Object.prototype.valueOf.call('primitive')) // [String: 'primitive']
// 为自定义对象重写 valueOf
class Box {
$value
constructor(value) {
this.$value = value
}
valueOf() {
return this.$value
}
}
const box = new Box(123)
console.log(box.valueOf()) // 123
console.log(box + 456) // 579
console.log(box == 123) // true
在对象上使用一元加运算符,会执行强制数字转换,对于大多数没有 @@toPrimitive
的对象,意味这调用它的 valueOf()
。如果对象没有一个自定义的 valueOf()
方法,基本的实现将会导致 valueOf()
被忽略,转而使用 toString()
的返回值。
+new Date() // 返回当前时间戳,与 new Date().getTime() 相同
+{} // NaN (toString() 返回 "[object Object]")
+[] // 0 (toString() 返回一个空的 string list)
+[1] // 1 (toString() 返回 "1")
+[1, 2] // NaN (toString() 返回 "1,2")
+new Set([1]) // NaN (toString() 返回 "[object Set]")
+{ valueOf: () => 42 } // 42
Object 属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。一个描述符只能是这两者其中之一,不能同时是两者。
- 数据描述符:是一个具有值的属性,该值可以是可写的,也可以是不可写的。
- 存取描述符:是由
getter
函数和setter
函数所描述的属性。
数据描述符
value
:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为undefined
。var obj = {} obj.foo = 123 Object.getOwnPropertyDescriptor(obj, 'foo').value // 123 Object.defineProperty(obj, 'foo', { value: 246 }) obj.foo // 246
writable
:表示属性的值(value)是否可以被赋值。默认为false
。- 默认情况下,所有直接定义在对象上的属性
writable
键值为true
。 - 正常模式下,对
writable
为false
的属性赋值不会报错,只会默默失败。但是,严格模式下会报错,即使对属性重新赋予一个同样的值。
var proto = Object.defineProperty({}, 'foo', { value: 'a', writable: false, }) proto.foo // 'a' proto.foo = 'b' proto.foo // 'a' // 原型对象的某个属性的 writable 为 false,那么子对象将无法自定义这个属性 var obj = Object.create(proto) obj.foo = 'b' obj.foo // 'a' // 可通过覆盖属性描述对象,绕过规则。在该情况下,原型链会被完全忽视 var proto = Object.defineProperty({}, 'foo', { value: 'a', writable: false, }) var obj = Object.create(proto) Object.defineProperty(obj, 'foo', { value: 'b', }) obj.foo // 'b'
- 默认情况下,所有直接定义在对象上的属性
enumerable
(可遍历性) :表示属性是否可以遍历。 默认为false
。默认情况下,所有直接定义在对象上的属性
enumerable
键值为true
。如果一个属性的
enumerable
为false
,for...in
(包含继承的属性) 、Object.keys
(不包含继承的属性) 、JSON.stringify
将无法获取到该属性。- 如果需要获取对象自身的所有属性,不管是否可遍历,可以使用
Object.getOwnPropertyNames
方法。 - 如果对象的 JSON 格式输出要排除某些属性,可以把这些属性的
enumerable
设为false
。
- 如果需要获取对象自身的所有属性,不管是否可遍历,可以使用
Object.prototype.propertyIsEnume
:返回一个布尔值,表示指定的属性是否可枚举。只能用于判断对象自身的属性,对于继承的属性一律返回false
。let obj = {} let arr = [] obj.foo = 'foo' arr[0] = 'foo' obj.propertyIsEnumerable('foo') // true obj.propertyIsEnumerable('toString') // false,obj.toString 是继承的属性 arr.propertyIsEnumerable(0) // true
var obj = {} Object.defineProperty(obj, 'x', { value: 123, enumerable: false, }) obj.x // 123 for (var key in obj) { console.log(key) } // undefined Object.keys(obj) // [] JSON.stringify(obj) // "{}"
configurable
(可配置性) :表示属性是否可以通过delete
删除并重新定义,是否可以修改它的特性,以及是否可以改为存取属性。默认为false
。- 默认情况下,所有直接定义在对象上的属性
configurable
键值为true
。
// configurable 属性为 false。 x修改 writable、enumerable、configurable 报错 let fooObj = Object.defineProperty({}, 'foo', { value: 1, writable: false, enumerable: false, configurable: false, }) Object.defineProperty(fooObj, 'foo', { writable: true }) // TypeError: Cannot redefine property: foo Object.defineProperty(fooObj, 'foo', { enumerable: true }) // TypeError: Cannot redefine property: foo Object.defineProperty(fooObj, 'foo', { configurable: true }) // TypeError: Cannot redefine property: foo Object.defineProperty(fooObj, 'foo', { value: 2 }) // TypeError: Cannot redefine property: foo // writable 属性只有在 false 改为 true 时会报错,true 改为 false 是允许的 let barObj = Object.defineProperty({}, 'bar', { writable: true, configurable: false, }) Object.defineProperty(barObj, 'bar', { writable: false }) // 修改成功 // value 属性只要 writable 和 configurable 有一个为 true,则允许修改 value。 let bazObj = Object.defineProperty({}, 'baz', { value: 123, writable: true, configurable: false, }) Object.defineProperty(bazObj, 'baz', { value: 234 }) // writable 为 false 时,直接对目标属性赋值,不会报错,但不会成功。严格模式下,会报错。 var baxObj = Object.defineProperty({}, 'bax', { value: 123, writable: false, configurable: false, }) baxObj.bax = 234 baxObj.bax // 123 // configurable 可决定目标属性是否可以被 delete 删除 var baqObj = Object.defineProperties( {}, { baq1: { value: 123, configurable: true }, baq2: { value: 234, configurable: false }, } ) delete baqObj.baq1 // true delete baqObj.baq2 // false baqObj.baq1 // undefined baqObj.baq2 // 234
- 默认情况下,所有直接定义在对象上的属性
存取描述符
get
:获取函数(getter),在读取属性时调用。默认值为undefined
。- 获取函数(getter)不能接受参数。
- 执行是会传入
this
对象(由于继承关系,this
并不一定是定义该属性的对象)。 - 获取函数(getter)返回值会被用作属性的值。
set
:设置函数(setter),在写入属性是调用。默认值为undefined
。- 设置函数(setter)只能接受一个参数(即属性的值),会传入赋值时的
this
对象。
- 设置函数(setter)只能接受一个参数(即属性的值),会传入赋值时的
enumerable
(可遍历性) :表示属性是否可以遍历。默认为false
。默认情况下,所有直接定义在对象上的属性enumerable
键值为true
。configurable
(可配置性) :表示属性是否可以通过delete
删除并重新定义,是否可以修改它的特性,以及是否可以改为存取属性。默认为false
。默认情况下,所有直接定义在对象上的属性configurable
键值为true
。
// 写法一:属性 foo 的 configurable 和 enumerable 都为 false,从而导致属性 foo 是不可遍历的
var fooObj = Object.defineProperty({}, 'foo', {
get: function () {
return 'getter'
},
set: function (value) {
console.log('setter: ' + value)
},
})
fooObj.foo // 'getter'
fooObj.foo = 123 // 'setter: 123'
Object.getOwnPropertyDescriptor(obj, 'foo') // {enumerable: false, configurable: false, get: ƒ, set: ƒ}
// 写法二(更常用):属性 bar 的 configurable 和 enumerable 都为 true,从而导致属性 bar 是可遍历的
var barObj = {
get bar() {
return 'getter'
},
set bar(value) {
console.log('setter: ' + value)
},
}
barObj.bar // 'getter'
barObj.bar = 123 // 'setter: 123'
Object.getOwnPropertyDescriptor(barObj, 'bar') // {enumerable: true, configurable: true, get: ƒ, set: ƒ}
获取属性描述符
Object.getOwnPropertyDescriptor()
:返回指定对象上一个自有属性对应的属性描述符。注:只能用于对象自身的属性(直接赋予对象的属性),不能用于继承的属性。
let barObj = { bar: 'bar', } Object.getOwnPropertyDescriptor(barObj, 'bar') // { value: 'bar', writable: true, enumerable: true, configurable: true } let fooObj = { get foo() { return 'foo' }, bar: 'bar', } Object.getOwnPropertyDescriptor(fooObj, 'foo') // { set: undefined, enumerable: true, configurable: true, get: ƒ foo() }
Object.getOwnPropertyDescriptors()
:获取指定对象的所有自身属性的描述符。如果没有任何自身属性,则返回空对象。let barObj = { bar: 'bar', } Object.getOwnPropertyDescriptors(barObj, 'bar') // { bar: { value: 'bar', writable: true, enumerable: true, configurable: true } } let fooObj = { get foo() { return 'foo' }, bar: 'bar', } Object.getOwnPropertyDescriptors(fooObj, 'foo') // { // bar: { value: 'bar', writable: true, enumerable: true, configurable: true }, // foo: {set: undefined, enumerable: true, configurable: true, get: ƒ} // }
Object.getOwnPropertyNames()
:返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性,但不包括Symbol
值作为名称的属性)组成的数组。let obj01 = Object.defineProperties( {}, { foo: { value: 'foo', enumerable: true }, bar: { value: 'bar', enumerable: false }, [Symbol()]: { value: 'symbol', enumerable: true }, } ) Object.getOwnPropertyNames(obj01) // ['foo', 'bar'] let obj02 = Object.create(obj01) Object.getOwnPropertyNames(obj02) // [] Object.keys([]) // [],Object.keys 只返回对象自身的可遍历属性的全部属性名 Object.getOwnPropertyNames([]) // ['length'] Object.keys(['a', 'b', 'c']) // ['0', '1', '2'] Object.getOwnPropertyNames(['a', 'b', 'c']) // ['0', '1', '2', 'length'] Object.keys(Object.prototype) // [] Object.getOwnPropertyNames(Object.prototype) // ['constructor', '__defineGetter__', '__defineSetter__', 'hasOwnProperty', '__lookupGetter__', '__lookupSetter__', 'isPrototypeOf', 'propertyIsEnumerable', 'toString', 'valueOf', '__proto__', 'toLocaleString']
设置属性描述符
Object.defineProperty()
:允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象。Object.defineProperty(obj, prop, descriptor)
obj
:要定义的对象prop
:要定义或修改的属性名称或者Symbol
descriptor
:要定义或者修改的属性描述符
let obj = Object.defineProperty({}, 'foo', { value: 123, writable: false, // 不可赋值 enumerable: true, // 不可遍历 configurable: false, // 不可配置 }) obj.foo // 123 obj.foo = 246 obj.foo // 123
Object.defineProperties()
:允许通过属性描述对象,定义或修改多个属性,然后返回修改后的对象。let obj = Object.defineProperties( {}, { foo: { value: 123, enumerable: true }, bar: { value: 'abc', enumerable: true }, foobar: { // 定义了取值函数get(或存值函数set),不能将 writable 属性设为 true,或者同时定义 value 属性,否则会报错。 get: function () { return this.foo + this.bar }, enumerable: true, configurable: true, }, } ) obj.foo // 123 obj.bar // "abc" obj.foobar // "123abc"
冻结对象
有时需要冻结对象的读写状态,防止对象被改变。JavaScript 提供了三种冻结方法:
Object.preventExtensions()
:无法再添加新的属性。Object.seal()
:无法添加新属性,也无法删除旧属性。Object.freeze()
:无法添加新属性、无法删除旧属性、也无法改变属性的值。
冻结对象的方法存在局限性
- 可以通过改变原型对象,来为对象增加属性。
- 被冻结对象是浅冻结,要使对象不可变,需要递归冻结每个类型为对象的属性(深冻结)。
Object.preventExtensions()
Object.preventExtensions()
方法一个对象无法再添加新的属性。
- 仅阻止添加自身的属性。
- 其对象类型的原型依然可以添加新的属性。
- 添加新的属性会静默失败,严格模式下则会报错。
Object.isExtensible()
方法判断一个对象是否可以添加新的属性。
let obj = {
foo: 'foo'
}
let preventObj = Object.preventExtensions(obj)
preventObj === obj // true
Object.isExtensible(preventObj) // false
preventObj.bar = 'bar'
preventObj.bar // undefined
// 使用 Object.defineProperty 方法为一个不可扩展的对象添加新属性会抛出异常。
Object.defineProperty(preventObj, 'bar', { value: 'bar' })
// 抛出异常 TypeError: Cannot define property bar, object is not extensible
// 其对象类型的原型依然可以添加新的属性。
preventObj.__proto__.baz = 'baz'
Object.seal()
Object.seal()
方法使得一个对象既无法添加新属性,也无法删除旧属性。
- 不影响修改某个属性的值。
- 不会影响从原型链上继承的属性,可以进行添加、删除、修改。
- 实质是把属性描述对象的
configurable
属性设为false
。 - 添加新的属性或者删除属性会静默失败,严格模式下则会报错。
Object.isSealed()
方法判断一个对象是否使用了 Object.seal()
方法。
let obj = {
foo: 'foo'
}
Object.getOwnPropertyDescriptor(obj, 'foo')
// { value: 'foo', writable: true, enumerable: true, configurable: true }
Object.seal(obj)
Object.getOwnPropertyDescriptor(obj, 'foo')
// { value: 'foo', writable: true, enumerable: true, configurable: false }
Object.isSealed(obj) // true
delete obj.foo
obj.foo // 'foo'
obj.bar = 'bar'
obj.bar // undefined
obj.__proto__.baz = 'baz'
obj.__proto__.baz = 'baz123'
obj.__proto__.baz // baz123
delete obj.__proto__.baz
obj.__proto__.baz // undefined
Object.freeze()
Object.freeze()
方法使一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值。
- 被冻结对象自身的所有属性都不可能以任何方式被修改,也不能更改被冻结对象的原型
- 被冻结对象是浅冻结,要使对象不可变,需要递归冻结每个类型为对象的属性(深冻结)。
- 添加新的属性、删除旧属性、改变属性会静默失败,严格模式下则会报错。
Object.isFrozen()
方法判断一个对象是否被冻结。
let obj = {
foo: 'foo',
}
Object.freeze(obj)
Object.isFrozen(obj) // true
obj.foo = 'foo123' // 静默失败,不做任何处理
delete obj.foo // 静默失败,不做任何处理
obj.bar = 'bar' // 静默失败,不做任何处理
Object.defineProperty(obj, 'foo', { value: 'foo456' })
// 抛出异常 TypeError: Cannot redefine property: foo
// 修改原型
Object.setPrototypeOf(obj, { baz: 'baz' })
// 抛出异常 TypeError: #<Object> is not extensible
obj.__proto__ = { baz: 'baz' }
// 抛出异常 TypeError: #<Object> is not extensible
let arr = [1, 2, 3]
Object.freeze(arr)
arr[0] = 123 // 静默失败,不做任何处理
arr.push(2) // 静默失败,不做任何处理
// 浅冻结
let externalObj = {
internalObj: {},
}
Object.freeze(externalObj)
Object.isFrozen(externalObj) // true
externalObj.internalObj.foo = 'foo'
externalObj.internalObj.foo // 'foo'
// 深冻结
function deepFreeze(obj) {
// 取回定义在 obj 上的属性名
var propNames = Object.getOwnPropertyNames(obj)
// 在冻结自身之前冻结属性
propNames.forEach(function (name) {
var prop = obj[name]
// 如果 prop 是个对象,冻结它
if (typeof prop == 'object' && prop !== null) deepFreeze(prop)
})
// 冻结自身 (no-op if already frozen)
return Object.freeze(obj)
}
deepFreeze(externalObj)
externalObj.internalObj.bar = 'bar' // 静默失败,不做任何处理
externalObj.internalObj.bar // undefined
Object 方法
Object.create()
Object.create(proto, propertiesObject)
方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。
proto
:新创建对象的原型对象propertiesObject
:可选。如果该参数被指定且不为undefined
,则该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。属性对应Object.defineProperties()
的第二个参数。
const normalObj = {}
console.log('normalObj: ' + normalObj) // 'normalObj: [object Object]'
// ========================================
// 以 null 为原型的对象存在不可预期的行为,因为它未从 Object.prototype 继承任何对象方法。
const nullProtoObj = Object.create(null)
console.log('nullProtoObj: ' + nullProtoObj) // TypeError: Cannot convert object to primitive value
// ========================================
// 用 Object.create() 实现类式继承
function Shape() {
this.x = 0
this.y = 0
}
Shape.prototype.move = function (x, y) {
this.x += x
this.y += y
console.info('Shape moved.')
}
function Rectangle() {
Shape.call(this) // call super constructor.
}
Rectangle.prototype = Object.create(Shape.prototype)
Rectangle.prototype.constructor = Rectangle
const rect = new Rectangle()
rect instanceof Rectangle // true
rect instanceof Shape // true
rect.move(1, 1) // Shape moved.
// ========================================
// 使用 Object.create() 的 propertyObject 参数
const fooObj = Object.create(Object.prototype, {
foo: {
writable: true,
configurable: true,
value: 'foo',
},
bar: {
configurable: false,
get: function () {
return 10
},
set: function (value) {
console.log('Setting fooObj.bar to ', value)
},
},
})
fooObj.foo // 'foo'
fooObj.bar // 10
Object.assign()
Object.assign(target, ...sources)
方法将所有可枚举(Object.propertyIsEnumerable()
返回 true
)的自有属性(Object.hasOwnProperty()
返回 true
)从一个或多个源对象复制到目标对象,返回修改后的对象。
target
:目标对象,接收源对象属性的对象,也是修改后的返回值。sources
:源对象,包含将被合并的属性。
Object.assign()
方法只会拷贝 sources
(源对象)可枚举的和自身的属性到 target
目标对象。
- 该只复制属性值(浅拷贝)。
- 该方法使用
sources
(源对象)的[[Get]]
和目标对象的[[Set]]
,它会调用getters
和setters
。 - 如需将属性定义(包括其可枚举性)复制到原型,应使用
Object.getOwnPropertyDescriptor()
和Object.defineProperty()
,基本类型String
和Symbol
的属性会被复制。
Object.assign()
赋值期间出错,则会抛出 TypeError
。抛出异常之前添加的属性,则会修改 target
(目标对象) 。在 source
(源对象)值为 null
或 undefined
时,不会抛出错误。
const strSources = 'abc' // 可枚举
const numSources = 123 // 不可枚举
const booleanSources = true // 不可枚举
const sourcesObj = {
foo: 'foo',
[Symbol('symbol')]: 'symbol',
}
// 原型链上的属性和不可枚举属性不能被复制
const prototypeObject = Object.create(
{
prototypeFoo: 'prototypeFoo', // 原型连上的属性
},
{
prototypeBar: {
value: 2, // 不可枚举的属性
},
prototypeBaz: {
value: 3,
enumerable: true, // 可枚举的属性
},
}
)
// 源对象值为 null 或 undefined 时,不会抛出错误。
const resultObj = Object.assign(
{},
strSources,
null,
numSources,
undefined,
booleanSources,
sourcesObj,
prototypeObject
)
// {
// 0: 'a',
// 1: 'b',
// 2: 'c',
// foo: 'foo',
// prototypeBaz: 3,
// [Symbol(symbol)]: 'symbol',
// }
Object.hasOwn()
hasOwn(instance, prop)
:如果指定的对象(instance
)自身有指定的属性(prop
),则该方法返回 true
。如果属性是继承的或者不存在,该方法返回 false
。
instance
:要测试的 JavaScript 实例对象。prop
:要测试属性的String
类型的名称或者Symbol
。
该方法不检查对象的原型链中的指定属性。
如果使用 for...in
(可遍历除 Symbol
以外的可枚举属性,包括继承的可枚举属性) 遍历自有属性,应该使用 Object.hasOwn()
跳过继承属性。
const exampleObj = {
foo: 'foo',
bar: 123,
foz: null,
baz: undefined,
}
Object.hasOwn(exampleObj, 'foo') // true
Object.hasOwn(exampleObj, 'bar') // true
Object.hasOwn(exampleObj, 'foz') // true
Object.hasOwn(exampleObj, 'baz') // true
Object.hasOwn(exampleObj, 'toString') // false,原型链上的属性
Object.hasOwn(exampleObj, 'hasOwnProperty') // false,原型链上的属性
'foo' in exampleObj // true
'toString' in exampleObj // true
'hasOwnProperty' in exampleObj // true
const fruitsArr = ['Apple', 'Banana', 'Watermelon', 'Orange']
Object.hasOwn(fruitsArr, 3) // true
Object.hasOwn(fruitsArr, 4) // false
// ========================================
const foo = Object.create(null)
foo.prop = 'exists'
if (Object.hasOwn(foo, 'prop')) {
console.log(foo.prop) // exists
}
foo.hasOwnProperty(foo, 'prop')
// TypeError: foo.hasOwnProperty is not a function
// hasOwnProperty 为 Object.prototype 上的方法
// Object.create(null) 以 null 为原型创建的对象,未从 Object.prototype 继承任何对象方法
Object.is()
Object.is(value1, value2)
判断两个值是否为同一个值。如果满足以下任意条件,则两个值相等:
- 都是
undefined
- 都是
null
- 都是
true
或false
- 都是相同长度、相同字符、按相同顺序排列的字符串
- 都是相同对象(意味着都是同一个对象的值引用)
- 都是数字且都为
+0
或-0
或NaN
或 都是同一个值(非零且都不是NaN
)
Object.is(25, 25) // true
Object.is('foo', 'foo') // true
Object.is('foo', 'bar') // false
Object.is(null, null) // true
Object.is(undefined, undefined) // true
Object.is(window, window) // true
Object.is([], []) // false
var foo = { a: 1 }
var bar = { a: 1 }
Object.is(foo, foo) // true
Object.is(foo, bar) // false
Object.is(0, -0) // false
Object.is(+0, -0) // false
Object.is(-0, -0) // true
Object.is(0n, -0n) // true
Object.is(NaN, 0 / 0) // true
Object.is(NaN, Number.NaN) // true
// ========================================
// Object.is() Polyfill
if (!Object.is) {
Object.defineProperty(Object, 'is', {
value: function (x, y) {
if (x === y) {
return x !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
},
})
}
Object.keys()
Object.keys(obj)
返回一个由一个给定对象的自身可枚举属性组成的数组。数组中属性的顺序与正常遍历时返回的顺序一致。
const arr = ['a', 'b', 'c']
Object.keys(arr) // ['0', '1', '2']
const obj = { 0: 'a', 1: 'b', 2: 'c' }
Object.keys(obj) // ['0', '1', '2']
const anObj = { foo: 'foo', 100: 'a', 2: 'b', 7: 'c', bar: 'bar' }
Object.keys(anObj) // ['2', '7', '100', 'foo', 'bar']
const myObj = Object.create(
{},
{
getFoo: {
value() {
return this.foo
},
},
}
)
myObj.foo = 1
Object.keys(myObj) // ['foo']
Object.values()
Object.values(obj)
:返回一个给定对象自身的所有可枚举属性值的数组。数组中属性的顺序与正常遍历时返回的顺序一致。
Object.values('foo') // ['f', 'o', 'o']
const arr = ['a', 'b', 'c']
Object.values(arr) // ['a', 'b', 'c']
const obj = { 0: 'a', 1: 'b', 2: 'c' }
Object.values(obj) // ['a', 'b', 'c']
const anObj = { foo: 'foo', 100: 'a', 2: 'b', 7: 'c', bar: 'bar' }
Object.values(anObj) // ['b', 'c', 'a', 'foo', 'bar']
const myObj = Object.create(
{},
{
getFoo: {
value() {
return this.foo
},
},
}
)
myObj.foo = 'bar'
Object.values(myObj) // ['bar']
Object.entries()
Object.entries(obj)
返回一个给定对象自身可枚举属性的键值对数组。数组中属性的顺序与正常遍历时返回的顺序一致。
Object.entries('foo') // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]
const obj = { foo: 'bar', baz: 42 }
Object.entries(obj) // [ ['foo', 'bar'], ['baz', 42] ]
const arr = ['a', 'b', 'c']
Object.entries(arr) // [ ['0', 'a'], ['1', 'b'], ['2', 'c']]
const obj = { 0: 'a', 1: 'b', 2: 'c' }
Object.entries(obj) // [ ['0', 'a'], ['1', 'b'], ['2', 'c']]
const anObj = { foo: 'foo', 100: 'a', 2: 'b', 7: 'c', bar: 'bar' }
Object.entries(anObj)
// [ ['2', 'b'], ['7', 'c'], ['100', 'a'], ['foo', 'foo'], ['bar', 'bar']]
const myObj = Object.create(
{},
{
getFoo: {
value() {
return this.foo
},
},
}
)
myObj.foo = 'bar'
Object.entries(myObj) // [ ['foo', 'bar'] ]
const obj = { a: 5, b: 7, c: 9 }
for (const [key, value] of Object.entries(obj)) {
console.log(`${key} ${value}`) // "a 5", "b 7", "c 9"
}
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`) // "a 5", "b 7", "c 9"
})
// ========================================
// 将Object转换为Map
var obj = { foo: 'bar', baz: 42 }
var map = new Map(Object.entries(obj))
console.log(map)
// ========================================
// Object.entries() Polyfill
if (!Object.entries)
Object.entries = function (obj) {
var ownProps = Object.keys(obj),
i = ownProps.length,
resArray = new Array(i) // preallocate the Array
while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]
return resArray
}
Object.fromEntries()
Object.fromEntries(iterable)
把键值对列表转换为一个对象。
iterable
:类似Array
、Map
或者其他实现了可迭代协议的可迭代对象。迭代参数应该是一个能够实现
@@iterator
方法的的对象,返回一个迭代器对象。生成一个具有两个元素的类数组的对象:- 第一个元素是将用作属性键的值
- 第二个元素是与该属性键关联的值
// Map 转化为 Object
const map = new Map([
['foo', 'bar'],
['baz', 42],
])
const obj = Object.fromEntries(map)
console.log(obj) // { foo: "bar", baz: 42 }
// Array 转化为 Object
const arr = [
['0', 'a'],
['1', 'b'],
['2', 'c'],
]
const obj = Object.fromEntries(arr)
console.log(obj) // { 0: "a", 1: "b", 2: "c" }
// 对象转换
const object1 = { a: 1, b: 2, c: 3 }
const object2 = Object.fromEntries(
Object.entries(object1).map(([key, val]) => [key, val * 2])
)
console.log(object2) // { a: 2, b: 4, c: 6 }
Object.getOwnPropertySymbols()
Object.getOwnPropertySymbols(obj)
返回一个给定对象自身的所有 Symbol
属性的数组。
注:Object.getOwnPropertyNames()
本身不包含对象的 Symbol
属性,只包含字符串属性。
let obj = {}
Object.getOwnPropertySymbols(obj) // []
let a = Symbol('a')
let b = Symbol.for('b')
obj[a] = 'localSymbol'
obj[b] = 'globalSymbol'
Object.getOwnPropertySymbols(obj) // [Symbol(a), Symbol(b)]
Object.getPrototypeOf()
Object.getPrototypeOf(object)
返回指定对象的原型(内部 [[Prototype]]
属性的值)。
let proto = {}
let obj = Object.create(proto)
Object.getPrototypeOf(obj) === proto // true
let obj = new Object()
Object.prototype === Object.getPrototypeOf(obj) // true
Object.prototype === Object.getPrototypeOf({}) // true
Object.setPrototypeOf()
Object.setPrototypeOf(obj, prototype)
设置一个指定的对象的原型(即,内部 [[Prototype]]
属性)到另一个对象或 null
。
obj
:要设置其原型的对象。prototype
:该对象的新原型(一个对象或null
)。
注:由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]
在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。
Object.isExtensible(Object.prototype) // true,可以添加新的属性
Object.setPrototypeOf(Object.prototype, {})
// TypeError: Immutable prototype object '#<Object>' cannot have their prototype set
class Human {}
class SuperHero {}
Object.setPrototypeOf(SuperHero.prototype, Human.prototype)
Object.setPrototypeOf(SuperHero, Human)
const superMan = new SuperHero()