Skip to content

Object 下的方法大致如下:

  1. Object.assign
  2. Object.is
  3. Object.create
  4. Object.setPrototypeOf
  5. Object.getPrototypeOf
  6. Object.defineProperty
  7. Object.defineProperties
  8. Object.getOwnPropertyDescriptor
  9. Object.getOwnPropertyDescriptors
  10. Object.getOwnPropertyNames
  11. Object.keys
  12. Object.getOwnPropertySymbols
  13. Object.entries
  14. Object.fromEntries
  15. Object.freeze
  16. Object.isFrozen
  17. Object.seal
  18. Object.isSealed
  19. Object.preventExtensions
  20. Object.isExtensible
  21. Object.hasOwn

1.Object.assign

Object.assign 是一个 JavaScript 方法,用于将一个或多个源对象的属性拷贝到目标对象中。

它返回目标对象,并且支持浅拷贝,适合用于对象的合并、克隆等操作。

js
Object.assign(target, ...sources)

它具有以下特点:

  1. 浅拷贝Object.assign 只做浅拷贝,嵌套对象会引用相同的对象。
  2. 覆盖顺序:如果多个源对象中存在同名属性,最后一个对象的属性值会覆盖前面的。
  3. 无法拷贝不可枚举属性Object.assign 只会拷贝源对象中的可枚举属性。
  4. 原型链:它不会拷贝源对象的原型链属性,仅拷贝对象本身的属性。

1-1.合并对象

js
const target = { a: 1, b: 2 }
const source = { b: 4, c: 5 }

const result = Object.assign(target, source)

console.log(result) // 输出 { a: 1, b: 4, c: 5 }
console.log(target) // 输出 { a: 1, b: 4, c: 5 } (目标对象被修改)

1-2.对象浅拷贝

js
const original = { name: 'John', age: 30 }
const copy = Object.assign({}, original)

console.log(copy) // 输出 { name: 'John', age: 30 }

由于是浅拷贝,因此嵌套对象会引用相同的对象:

js
const obj1 = { a: { b: 1 } }
const obj2 = Object.assign({}, obj1)

obj2.a.b = 2
console.log(obj1.a.b) // 输出 2

2.Object.is

Object.is(v1, v2)

Object.is 用于比较两个值。它是为了弥补全等运算符在某些情况下的判断缺陷。譬如:

js
console.log(+0 === -0) // true
console.log(NaN === NaN) // false
console.log(Object.is(+0, -0)) // false
console.log(Object.is(NaN, NaN)) // true

对于 Object.is 方法来说,它与全等运算符的区别就在于对于 +0-0NaNNaN 的判断。

所以大多数情况下使用下全等运算符即可,上面的两种情况使用 Object.is

3.Object.create

Object.create(proto, propertiesObject)

Object.create 会创建一个空对象,并将该对象的 __proto__ 属性指向第一个参数。 第二个参数(可省略)的格式参照了 Object.defineProperties,在实际执行时,会将该参数混入到创建的空对象中。

js
// polyfill
if (typeof Object.create !== "function") {
  Object.create = function (proto, propertiesObject) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
      throw new TypeError('Object prototype may only be an Object: ' + proto);
    } else if (proto === null) {
      throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
    }

    if (typeof propertiesObject !== 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");

    function F() {}
    F.prototype = proto;

    return new F();
  };
}

4.Object.setPrototypeOf

Object.setPrototypeOf(obj, proto)

设置一个指定的对象的原型 (即, 内部 [[Prototype]] 属性)到另一个对象或 null

TIP

警告: 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]] 在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.__proto__ = ... 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何 [[Prototype]] 已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create() 来创建带有你想要的 [[Prototype]] 的新对象。

Object.setPrototypeOf()ECMAScript 6 最新草案中的方法,相对于 Object.prototype.__proto__ ,它被认为是修改对象原型更合适的方法。

js
// polyfill
if (!Object.setPrototypeOf) {
  // 仅适用于Chrome和FireFox,在IE中不工作:
  Object.setPrototypeOf = function(obj, proto) {
    if(obj.__proto__) {
      obj.__proto__ = proto;
      return obj;
    } else {
      // 如果你想返回 prototype of Object.create(null):
      var Fn = function() {
        for (var key in obj) {
          Object.defineProperty(this, key, {
            value: obj[key],
          });
        }
      };
      Fn.prototype = proto;
      return new Fn();
    }
  }
}

PS: Object.createObject.setPrototypeOf 的区别:

js
// Object.create(target, property) 与 Object.setPrototypeOf(v1, v2)

var o = {
  id: 19
}
var a = Object.create(o)

var b = {}
Object.setPrototypeOf(b, o)

console.log('a', a)
console.log('b', b)

两者实现效果一致 都能将某一实例的原型 __proto__ 指向新的指定地址。

但在方法使用上,有一些区别:

  1. Object.create(o) 有返回值 该返回值的 __proto__ 指向 oObject.setPrototypeOf(v1, v2) 也有返回值,而且返回值等同于 v1v1__proto__ 指向 v2
  2. Object.create 可以传入第二个参数。 该参数的形式与 Object.defineProperties 的第二个参数形式一致。执行结果会将该参数合并进所创建的新对象中。譬如:
js
var o = Object.create(null, {
  prop: {
    value: 'propValue',
    enumerable: true,
    write: true,
    configable: true
  }
})
console.log(o) // { prop: 'propValue' }

TIP

__proto__ 已逐渐被废弃, [[prototype]] 也是不支持实例直接访问的。所以更好的方式是利用 constructorprototype 属性。

5.Object.getPrototypeOf

Object.getPrototypeOf(obj)

与其说是 Object.getPrototypeOf 不如说是 Object.get[[Prototype]]Of

API 获取的是实例的 [[prototype]],而不是构造函数的 prototype

6.Object.defineProperty

Object.defineProperty(obj, property, descriptor)

其中,描述符 descriptor 分为 数据描述符访问器描述符

描述符descriptor是一个对象。数据描述符与访问器描述符共有的键属性是:

  • enumerable 属性是否可被枚举。是否可以在 for...in 循环和 Object.keys() 中被枚举。
  • configable 属性是否可以更改描述符(除 valuewritable 外的描述符),是否可被删除。

另外,数据描述符所特有的属性是:

  1. value 属性的值。
  2. writable 属性是否可被更改,可被重写。

访问器描述符所特有的属性是:

  1. get 属性的 getter 函数。当属性被访问时,会调用此函数。
  2. set 属性的 setter 函数。当属性被设置时,会调用此函数。

数据描述符与访问器描述符不能混用。也就是要么使用数据描述符,要么使用访问器描述符。只能使用其一。

7.Object.defineProperties

Object.defineProperties(obj, properObj)

Object.defineProperty 类似,只不过这个是复数属性值。

js
var obj = {}
Object.defineProperties(obj, {
  id: {
    configable: true,
    enumerable: true,
    value: 19,
    writable: true
  },
  name: {
    configable: false,
    enumerable: true,
    get() {
      return ''
    },
    set(value) {
      this.id = value
    }
  }
})

8.Object.getOwnPropertyDescriptor

Object.getOwnPropertyDescriptor(obj, property)

js
var obj = { id: 19 }
var descriptor = Object.getOwnPropertyDescriptor(obj, 'id')
console.log(descriptor)

9.Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors(obj)

js
var obj = { id: 19, name: 'yxp' }
var descriptors = Object.getOwnPropertyDescriptors(obj)
console.log(descriptors)
// {
//   id: {value: 19, writable: true, enumerable: true, configurable: true},
//   name: {value: 'yxp', writable: true, enumerable: true, configurable: true}
// }

10.Object.getOwnPropertyNames

Object.getOwnPropertyNames(obj)

js
var obj = { id: 19 }
Object.defineProperty(obj, 'name', {
  configable: true,
  enumerable: false,
  value: 'yxp',
  writable: true
})
var propertyNames = Object.getOwnPropertyNames(obj)
console.log(propertyNames)
// ['id', 'name']

11.Object.keys

Object.keys(obj)

Object.getOwnPropertyNames 都是返回自身的属性,不会返回原型链上的。

如果某属性的属性描述符 enumerablefalse (不可枚举),则 Object.keys 不会返回该属性。

Object.getOwnPropertyNames 会返回该属性。

js
var obj = { id: 19 }
Object.defineProperty(obj, 'name', {
  configable: true,
  enumerable: false,
  value: 'yxp',
  writable: true
})
var propertyNames = Object.keys(obj)
console.log(propertyNames)
// ['id']

12.Object.getOwnPropertySymbols

该方法针对的是对象属性中利用 Symbol() 创建的属性。

因为 Object.keysObject.getOwnPropertyNames 都不能检索出对象属性中的 Symbol

所以 Es6 额外提供了该方法。

js
var myName = Symbol('my name')
var job = Symbol('my job')
var obj = {
  [myName]: 'yxp',
  [job]: 'programmer'
}
// 1.Object.keys
var keys = Object.keys(obj)
console.log(keys)
// 2.Object.getOwnPropertyNames
var propertyNames = Object.getOwnPropertyNames(obj)
console.log(propertyNames)
// 3.Object.getOwnPropertySymbols
var symbols = Object.getOwnPropertySymbols(obj)
console.log(symbols)

13.Object.entries

Object.entries(obj)

返回一个子项是该对象属性键值对的数组的二维数组。

Object.keys 相同的一点是,不会涉及到原型上的属性或者是不可枚举的属性。

js
function F() {
  this.id = 19
  this.name = 'yxp'
}
F.prototype.age = 'my-age'
var obj = new F()

Object.defineProperty(obj, 'job', {
  configable: true,
  enumerable: false,
  value: 'my-job',
  writable: true
})

var entries = Object.entries(obj)
console.log(entries)
// [['id', 19], ['name', 'yxp']]

14.Object.fromEntries

Object.fromEntries(iterable)

把键值对列表(二维数组)转换为一个对象。与 Object.keys 是相反操作。

参数 iterable:类似 ArrayMap 或者其它实现了可迭代协议的可迭代对象。

返回值:一个由该迭代对象条目提供对应属性的新对象。

js
var arr = [['id', 19], ['name', 'yxp']]
var fromEntries = Object.fromEntries(arr)
console.log(fromEntries)

15.Object.freeze

Object.freeze(obj)

冻结一个对象,该对象不能修改现有属性,不能再添加新属性,不能更改原型指向。

另外该对象的所有属性的属性操作符也不能再操作更改( configable 会自动变为 false )。

js
var a = {id: 19}
var desA = Object.getOwnPropertyDescriptors(a)
console.log(desA) // {id: {value: 19, writable: true, enumerable: true, configurable: true}}

// b 和 a 指向同一块内存地址
var b = Object.freeze(a)
var desB = Object.getOwnPropertyDescriptors(b)
console.log(desB) // {id: {value: 19, writable: false, enumerable: true, configurable: false}}

Object.freeze 实际上是浅冻结。

js
'use strict'
var obj = {
  id: 10,
  info: {
    id: 19
  }
}
Object.freeze(obj)
obj.info.id = '19' // work
obj.id = '10' // error

使用递归函数实现一个深冻结:

js
function deepFreeze(obj) {
  // 使用Object.getOwnPropertyNames,而不是Object.keys。因为要对不可枚举的属性也做冻结处理。
  var propertyNames = Object.getOwnPropertyNames(obj)
  propertyNames.forEach(item => {
    var prop = obj[item]
    if (typeof prop === 'object' && prop !== 'null') {
      deepFreeze(prop)
    }
  })
  return Object.freeze(obj)
}

TIP

Object.freezevue 项目当中可以用来做性能优化

16.Object.isFrozen

Object.isFrozen(obj)

判断一个对象是否被冻结。

js
var obj = {
  id: 19
}
Object.freeze(obj)
console.log(Object.isFrozen())

17.Object.seal

Object.seal(obj)

使一个对象密封。该对象不能再添加新属性,不能更改原型指向。

另外该对象的所有属性的属性操作符也不能再操作更改( configable 会自动变为 false )。

js
var obj = {
  id: 10,
  info: {
    id: 19
  }
}
var descriptor1 = Object.getOwnPropertyDescriptors(obj)
console.log(descriptor1)
/*
{
  id: {value: 10, writable: true, enumerable: true, configurable: true},
  info: {value: {…}, writable: true, enumerable: true, configurable: true}
}
*/ 
var o = Object.seal(obj)
console.log(o === obj)
var descriptor2 = Object.getOwnPropertyDescriptors(obj)
console.log(descriptor2)
/*
{
  id: {value: 10, writable: true, enumerable: true, configurable: false},
  info: {value: {…}, writable: true, enumerable: true, configurable: false}
}
*/

TIP

可以发现,Object.seal(obj) 会将 configable 属性描述符修改为 false,但并不会更改现有属性的 writable属性描述符。这也是它与 Object.freeze 的唯一不同之处。

Object.seal(obj) 可以对现有属性进行修改操作。

18.Object.isSealed

Object.isSealed(obj)

判断一个对象是否被密封。

19.Object.preventExtensions

Object.preventExtensions(obj)

让一个对象变的不可扩展,也就是永远不能再添加新的属性,也不能更改原型指向。(但现有属性的属性描述符并不受影响)。

可以发现,对于对象的限制程度,这三个方法是依次递减的:

Object.freeze() > Object.seal() > Object.preventExtensions(obj)

20.Object.isExtensible

Object.isExtensible(obj)

判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

默认情况下,对象是可扩展的:即可以为他们添加新的属性。以及它们的 __proto__ 属性可以被更改。

Object.preventExtensionsObject.sealObject.freeze 方法都可以标记一个对象为不可扩展(non-extensible)。

21.Object.hasOwn

Object.hasOwn(obj, prop)

判断一个对象是否拥有某属性。

js
function F() {
  this.id = 19
}
F.prototype.name = 'yxp'

var obj = new F()
// 添加一个不可枚举属性
Object.defineProperty(obj, 'job', {
  configable: true,
  enumerable: true,
  value: 'programmer',
  writable: true
})

console.log(obj) // {id: 19, job: 'programmer'}
console.log(Object.hasOwn(obj, 'id'))   // true
console.log(Object.hasOwn(obj, 'name')) // false
console.log(Object.hasOwn(obj, 'job'))  // true
console.log(obj.hasOwnProperty('id'))   // true
console.log(obj.hasOwnProperty('name')) // false
console.log(obj.hasOwnProperty('job'))  // true

可以发现,无论是 Object.hasOwn() 还是 Object.prototype.hasOwnProperty(),都不会拿到原型上的属性,但可以拿到不可枚举的属性。(即writable 属性描述符是 false)。