跳至主要內容

Object

Mr.LRH大约 28 分钟

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 本身是一个函数,可以将任意值转换为对象,常用于保证某一个值一定是对象。

  • 如果参数为空(或为 undefinednull),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 的构造函数(用于创建实例对象)。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

  • 原始类型的值是只读的,如:1true"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 ,除 nullundefined (两者无相应的构造函数) 之外任何对象都可以更改 constructor 属性的值。

  • 基本类型(如 StringNumberBoolean 等)不会保留这些更改,也不会抛出异常。每当基本类型当成对象使用时,其对应的构造函数的实例就会在语句执行后立即被创建和丢弃。
  • 改变 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 提供的内建构造函数之一,包括 ObjectArrayNumberStringBooleanDate 等等),该值为 fun.prototype
    • 对于 JavaScript 定义的其他 JavaScript 构造函数创建的对象,该值为该构造函数的 prototype 属性。
  • __proto__ 的设置器 (setter) 允许对象的 [[Prototype]] 被变更。要变更的值必须是一个 objectnull,提供其他值将不起任何作用。

    该对象必须通过 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() 方法接受一个字符串作为参数,返回一个布尔值,表示该对象自身属性中是否具有指定的属性(即:是否有指定的键)。

  • 即使属性的值是 nullundefined,只要属性存在,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)的原型。如果 prototypeObjundefinednull,会抛出 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
    • 正常模式下,对 writablefalse 的属性赋值不会报错,只会默默失败。但是,严格模式下会报错,即使对属性重新赋予一个同样的值。
    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

    • 如果一个属性的 enumerablefalsefor...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 对象。
  • 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]],它会调用 getterssetters
  • 如需将属性定义(包括其可枚举性)复制到原型,应使用 Object.getOwnPropertyDescriptor()Object.defineProperty(),基本类型 StringSymbol 的属性会被复制。

Object.assign() 赋值期间出错,则会抛出 TypeError。抛出异常之前添加的属性,则会修改 target (目标对象) 。在 source (源对象)值为 nullundefined 时,不会抛出错误。

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
  • 都是 truefalse
  • 都是相同长度、相同字符、按相同顺序排列的字符串
  • 都是相同对象(意味着都是同一个对象的值引用)
  • 都是数字且都为 +0-0NaN 或 都是同一个值(非零且都不是 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 :类似 ArrayMap 或者其他实现了可迭代协议的可迭代对象。

    迭代参数应该是一个能够实现 @@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()
上次编辑于:
贡献者: lingronghai,lrh21g