Object方法

117 阅读15分钟

1.Object.keys()

Object.keys()  静态方法返回一个由给定对象自身的可枚举的字符串键属性名组成的数组。

语法

Object.keys(obj)

参数

  • obj

    一个对象。

返回值

一个由给定对象自身可枚举的字符串键属性键组成的数组。

描述

Object.keys() 返回一个数组,其元素是字符串,对应于直接在对象上找到的可枚举的字符串键属性名。这与使用 [for...in] 循环迭代相同,只是 for...in 循环还会枚举原型链中的属性。Object.keys() 返回的数组顺序和与 [for...in]环提供的顺序相同。

示例

使用 Object.keys()

// 简单数组
const arr = ["a", "b", "c"];
console.log(Object.keys(arr)); // ['0', '1', '2']

// 类数组对象
const obj = { 0: "a", 1: "b", 2: "c" };
console.log(Object.keys(obj)); // ['0', '1', '2']

// 键的顺序随机的类数组对象
const anObj = { 100: "a", 2: "b", 7: "c" };
console.log(Object.keys(anObj)); // ['2', '7', '100']

// getFoo 是一个不可枚举的属性
const myObj = Object.create(
  {},
  {
    getFoo: {
      value() {
        return this.foo;
      },
    },
  },
);
myObj.foo = 1;
console.log(Object.keys(myObj)); // ['foo']

在基本类型中使用 Object.keys()

非对象参数会强制转换为对象只有字符串可以有自己的可枚举属性,而其他所有基本类型都返回一个空数组。 JSCopy to Clipboard

// 字符串具有索引作为可枚举的自有属性
console.log(Object.keys("foo")); // ['0', '1', '2']

// 其他基本类型没有自有属性
console.log(Object.keys(100)); // []

2.Object.values()

Object.values()  静态方法返回一个给定对象的自有可枚举字符串键属性值组成的数组。

尝试一下

语法

Object.values(obj)

参数

  • obj

    一个对象。

返回值

一个包含了给定对象的自有可枚举字符串键属性值的数组。

描述

Object.values() 返回一个数组,其元素是直接在 object 上找到的可枚举字符串键属性值。这与使用 for...in循环迭代相同,只是 for...in 循环还枚举原型链中的属性。Object.values() 返回的数组顺序和 for...in 循环提供的数组顺序相同。

示例

使用 Object.values()

const obj = { foo: "bar", baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

// 类数组对象
const arrayLikeObj1 = { 0: "a", 1: "b", 2: "c" };
console.log(Object.values(arrayLikeObj1)); // ['a', 'b', 'c']

// 具有随机键排序的类数组对象
// 使用数字键时,将按键的数字顺序返回值
const arrayLikeObj2 = { 100: "a", 2: "b", 7: "c" };
console.log(Object.values(arrayLikeObj2)); // ['b', 'c', 'a']

// getFoo 是一个不可枚举的属性
const myObj = Object.create(
  {},
  {
    getFoo: {
      value() {
        return this.foo;
      },
    },
  },
);
myObj.foo = "bar";
console.log(Object.values(myObj)); // ['bar']

在基本类型中使用 Object.values()

非对象参数会强制转换为对象。只有字符串可以有自己的可枚举属性,而其他所有基本类型都返回一个空数组。

// 字符串具有索引作为可枚举的自有属性
console.log(Object.values("foo")); // ['f', 'o', 'o']

// 其他基本类型没有自有属性
console.log(Object.values(100)); // []

3.Object.defineProperties()

Object.defineProperties()  静态方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

[语法]

JSCopy to Clipboard

Object.defineProperties(obj, props)

[参数]

  • obj

    在其上定义或修改属性的对象。

  • props

    一个对象,其中每个键表示要定义或修改的属性的名称,每个值是描述该属性的对象。在 props 中的每个值必须是且只能是数据描述符或访问器描述符之一;不能同时为两者(更多详细信息,请参见[Object.defineProperty()])。

    数据描述符和访问器描述符可以包含以下可选键:

    • configurable

      如果此属性描述符的类型可以更改并且属性可以从相应的对象中删除,则为 true默认为 false

    • enumerable

      如果此属性在枚举相应对象的属性时应显示出来,则为 true默认为 false

    数据描述符还具有以下可选键:

    • value

      与属性关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认为 [undefined]。

    • writable

      如果与属性关联的值可以使用[赋值运算符]更改,则为 true默认为 false

    访问器描述符还具有以下可选键:

    • get

      作为该属性的 getter 函数,如果没有 getter 则为 [undefined]。函数返回值将被用作属性的值。默认为 [undefined]。

    • set

      作为该属性的 setter 函数,如果没有 setter 则为 [undefined]。该函数将只接收一个参数,即被分配给属性的新值。**默认为 [undefined]

    如果一个属性描述符没有 valuewritablegetset 键中的任何一个,那么它被视为一个数据描述符。如果一个属性描述符同时具有 value 或 writable 和 get 或 set 键中的任意一个组合,就会抛出异常。

返回值

传递给函数的对象。

示例

使用 Object.defineProperties

const obj = {};
Object.defineProperties(obj, {
  property1: {
    value: true,
    writable: true,
  },
  property2: {
    value: "Hello",
    writable: false,
  },
  // 等等……
});

4.Object.create()

Object.create()  静态方法以一个现有对象作为原型,创建一个新对象。

[尝试一下]

[语法]

Object.create(proto)
Object.create(proto, propertiesObject)

[参数]

  • proto

    新创建对象的原型对象。

  • propertiesObject 可选

    如果该参数被指定且不为 [undefined],则该传入对象[可枚举的自有属性]将为新创建的对象添加具有对应属性名称的属性描述符。这些属性对应于 [Object.defineProperties()]的第二个参数。

[返回值]

根据指定的原型对象和属性创建的新对象。

[异常]

  • [TypeError]

    如果 proto 既不是 [null],也不是 [Object]则抛出此错误。

[示例]

[用 Object.create() 实现类式继承]

下面的例子演示了如何使用 Object.create() 来实现类式继承。这是一个所有版本 JavaScript 都支持的单继承。

JSCopy to Clipboard

// Shape——父类
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类方法
Shape.prototype.move = function (x, y) {
  this.x += x;
  this.y += y;
  console.info("Shape moved.");
};

// Rectangle——子类
function Rectangle() {
  Shape.call(this); // 调用父类构造函数。
}

// 子类继承父类
Rectangle.prototype = Object.create(Shape.prototype, {
  // 如果不将 Rectangle.prototype.constructor 设置为 Rectangle,
  // 它将采用 Shape(父类)的 prototype.constructor。
  // 为避免这种情况,我们将 prototype.constructor 设置为 Rectangle(子类)。
  constructor: {
    value: Rectangle,
    enumerable: false,
    writable: true,
    configurable: true,
  },
});

const rect = new Rectangle();

console.log("rect 是 Rectangle 类的实例吗?", rect instanceof Rectangle); // true
console.log("rect 是 Shape 类的实例吗?", rect instanceof Shape); // true
rect.move(1, 1); // 打印 'Shape moved.'

需要注意的是,使用 create() 也有一些要注意的地方,比如重新添加 [constructor]属性以确保正确的语义。虽然 Object.create() 被认为比使用 [Object.setPrototypeOf()]修改原型更具有性能优势,但如果没有创建实例并且属性访问还没有被优化,它们之间的差异实际上是可以忽略不计的。在现代代码中,无论如何都应该优先使用[类]语法。

[使用 Object.create() 的 propertyObject 参数]

Object.create() 方法允许对对象创建过程进行精细的控制。实际上,[字面量初始化对象语法]是 Object.create() 的一种语法糖。使用 Object.create(),我们可以创建具有指定原型和某些属性的对象。请注意,第二个参数将键映射到属性描述符,这意味着你还可以控制每个属性的可枚举性、可配置性等,而这在字面量初始化对象语法中是做不到的。

JSCopy to Clipboard

o = {};
// 等价于:
o = Object.create(Object.prototype);

o = Object.create(Object.prototype, {
  // foo 是一个常规数据属性
  foo: {
    writable: true,
    configurable: true,
    value: "hello",
  },
  // bar 是一个访问器属性
  bar: {
    configurable: false,
    get() {
      return 10;
    },
    set(value) {
      console.log("Setting `o.bar` to", value);
    },
  },
});

// 创建一个新对象,它的原型是一个新的空对象,并添加一个名为 'p',值为 42 的属性。
o = Object.create({}, { p: { value: 42 } });

使用 Object.create(),我们可以创建一个[原型为 null] 的对象。在字面量初始化对象语法中,相当于使用 [__proto__]键。

JSCopy to Clipboard

o = Object.create(null);
// 等价于:
o = { __proto__: null };

默认情况下,属性是不可写可枚举可配置的。

JSCopy to Clipboard

o.p = 24; // 在严格模式下会报错
o.p; // 42

o.q = 12;
for (const prop in o) {
  console.log(prop);
}
// 'q'

delete o.p;
// false;在严格模式下会报错

如果要指定与字面量对象中相同的属性,请显式指定 writableenumerable 和 configurable

JSCopy to Clipboard

o2 = Object.create(
  {},
  {
    p: {
      value: 42,
      writable: true,
      enumerable: true,
      configurable: true,
    },
  },
);
// 这与以下语句不等价:
// o2 = Object.create({ p: 42 })
// 后者将创建一个原型为 { p: 42 } 的对象。

你可以使用 Object.create() 来模仿 [new]运算符的行为。

function Constructor() {}
o = new Constructor();
// 等价于:
o = Object.create(Constructor.prototype);

当然,如果 Constructor 函数中有实际的初始化代码,那么 Object.create() 方法就无法反映它。

5.Object.assign()

Object.assign()  静态方法将一个或者多个源对象中所有[可枚举]的[自有属性]复制到目标对象,并返回修改后的目标对象。

[语法]

Object.assign(target, ...sources)

[参数]

  • target

    需要应用源对象属性的目标对象,修改后将作为返回值。

  • sources

    一个或多个包含要应用的属性的源对象。

[返回值]

修改后的目标对象。

[描述]

如果目标对象与源对象具有相同的[键(属性名)],则目标对象中的属性将被源对象中的属性覆盖,后面的源对象的属性将类似地覆盖前面的源对象的同名属性。

Object.assign() 方法只会拷贝源对象可枚举的自有属性到目标对象。该方法在源对象上使用 [[Get]],在目标对象上使用 [[Set]],因此它会调用 [getter]和 [setter]。故它对属性进行赋值,而不仅仅是复制或定义新的属性。如果合并源对象包含 getter 的新属性到原型中,则可能不适合使用此方法。

如果要将属性定义(包括它们的可枚举性)复制到原型中,则应改用 [Object.getOwnPropertyDescriptor()] 和 [Object.defineProperty()]方法。

[字符串]和 [Symbol] 类型属性都会被复制。

如果赋值期间出错,例如如果属性不可写,则会抛出 [TypeError];如果在抛出异常之前已经添加了一些属性,则这些属性会被保留,而 target 对象也会被修改。

备注:  Object.assign() 不会在源对象值为 [null]或 [undefined]时抛出错误。

[示例]

[复制对象]

const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

[深拷贝问题]

针对[深拷贝],需要使用其他办法,因为 Object.assign() 只复制属性值。

假如源对象是一个对象的引用,它仅仅会复制其引用值。

JSCopy to Clipboard

const obj1 = { a: 0, b: { c: 0 } };
const obj2 = Object.assign({}, obj1);
console.log(obj2); // { a: 0, b: { c: 0 } }

obj1.a = 1;
console.log(obj1); // { a: 1, b: { c: 0 } }
console.log(obj2); // { a: 0, b: { c: 0 } }

obj2.a = 2;
console.log(obj1); // { a: 1, b: { c: 0 } }
console.log(obj2); // { a: 2, b: { c: 0 } }

obj2.b.c = 3;
console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 2, b: { c: 3 } }

// 深拷贝
const obj3 = { a: 0, b: { c: 0 } };
const obj4 = JSON.parse(JSON.stringify(obj3));
obj3.a = 4;
obj3.b.c = 4;
console.log(obj4); // { a: 0, b: { c: 0 } }

[合并对象]

JSCopy to Clipboard

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 },目标对象本身发生了变化

[合并具有相同属性的对象]

JSCopy to Clipboard

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

属性会被后续参数中具有相同属性的其他对象覆盖。

[拷贝 Symbol 类型属性]

JSCopy to Clipboard

const o1 = { a: 1 };
const o2 = { [Symbol("foo")]: 2 };

const obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]

[原型链上的属性和不可枚举的属性不能被复制]

JSCopy to Clipboard

const obj = Object.create(
  // foo 在 obj 的原型链上
  { foo: 1 },
  {
    bar: {
      value: 2, // bar 是不可枚举的属性
    },
    baz: {
      value: 3,
      enumerable: true, // baz 是可枚举的自有属性
    },
  },
);

const copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }

[基本类型会被封装为对象]

JSCopy to Clipboard

const v1 = "abc";
const v2 = true;
const v3 = 10;
const v4 = Symbol("foo");

const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 基本类型将被封装,null 和 undefined 将被忽略。
// 注意,只有字符串封装对象才拥有可枚举的自有属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

[异常会中断后续的复制]

JSCopy to Clipboard

const target = Object.defineProperty({}, "foo", {
  value: 1,
  writable: false,
}); // target.foo 是一个只读属性

Object.assign(target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
// TypeError: "foo" is read-only
// 这个异常会在给 target.foo 赋值的时候抛出

console.log(target.bar); // 2,第一个源对象成功复制。
console.log(target.foo2); // 3,第二个源对象的第一个属性也成功复制。
console.log(target.foo); // 1,异常在这里被抛出
console.log(target.foo3); // undefined,属性赋值已经结束,foo3 不会被复制
console.log(target.baz); // undefined,第三个源对象也不会被复制

[拷贝访问器]

JSCopy to Clipboard

const obj = {
  foo: 1,
  get bar() {
    return 2;
  },
};

let copy = Object.assign({}, obj);
console.log(copy);
// { foo: 1, bar: 2 }
// copy.bar 的值是 obj.bar 的 getter 的返回值。

// 这是一个将完整描述符复制的赋值函数
function completeAssign(target, ...sources) {
  sources.forEach((source) => {
    const descriptors = Object.keys(source).reduce((descriptors, key) => {
      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
      return descriptors;
    }, {});

    // 默认情况下,Object.assign 也会复制可枚举的 Symbol 属性
    Object.getOwnPropertySymbols(source).forEach((sym) => {
      const descriptor = Object.getOwnPropertyDescriptor(source, sym);
      if (descriptor.enumerable) {
        descriptors[sym] = descriptor;
      }
    });
    Object.defineProperties(target, descriptors);
  });
  return target;
}

copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }

6.Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor()  静态方法返回一个对象,该对象描述给定对象上特定属性(即直接存在于对象上而不在对象的原型链中的属性)的配置。返回的对象是可变的,但对其进行更改不会影响原始属性的配置。

[尝试一下]

[语法]

JSCopy to Clipboard

Object.getOwnPropertyDescriptor(obj, prop)

[参数]

  • obj

    要查找其属性的对象。

  • prop

    要检索其描述的属性的名称或 [Symbol]

[返回值]

如果指定的属性存在于对象上,则返回其属性描述符,否则返回 [undefined]

[描述]

该方法允许查看属性的精确描述。在 JavaScript 中,一个属性由一个字符串值的名称或一个 [Symbol] 和一个属性描述符组成。关于属性描述符类型及其特性的更多信息可以在 [Object.defineProperty()] 中找到。

一个属性描述符是一个记录,具有以下一些特性:

  • value

    与属性关联的值(仅限数据描述符)。

  • writable

    当且仅当与属性关联的值可以更改时,为 true(仅限数据描述符)。

  • get

    作为属性 getter 的函数,如果没有 getter 则为 [undefined](仅限访问器描述符)。

  • set

    作为属性 setter 的函数,如果没有 setter 则为 [undefined](仅限访问器描述符)。

  • configurable

    当且仅当此属性描述符的类型可以更改且该属性可以从相应对象中删除时,为 true

  • enumerable

    当且仅当此属性在相应对象的属性枚举中出现时,为 true

[示例]

[使用 Object.getOwnPropertyDescriptor()]

JSCopy to Clipboard

let o, d;

o = {
  get foo() {
    return 17;
  },
};
d = Object.getOwnPropertyDescriptor(o, "foo");
console.log(d);
// {
//   configurable: true,
//   enumerable: true,
//   get: [Function: get foo],
//   set: undefined
// }

o = { bar: 42 };
d = Object.getOwnPropertyDescriptor(o, "bar");
console.log(d);
// {
//   configurable: true,
//   enumerable: true,
//   value: 42,
//   writable: true
// }

o = { [Symbol.for("baz")]: 73 };
d = Object.getOwnPropertyDescriptor(o, Symbol.for("baz"));
console.log(d);
// {
//   configurable: true,
//   enumerable: true,
//   value: 73,
//   writable: true
// }

o = {};
Object.defineProperty(o, "qux", {
  value: 8675309,
  writable: false,
  enumerable: false,
});
d = Object.getOwnPropertyDescriptor(o, "qux");
console.log(d);
// {
//   value: 8675309,
//   writable: false,
//   enumerable: false,
//   configurable: false
// }

[非对象强制转换]

在 ES5 中,如果该方法的第一个参数不是对象(而是一个基本类型值),则会导致 [TypeError]。在 ES2015 中,首先会将非对象的第一个参数强制转换为对象。

JSCopy to Clipboard

Object.getOwnPropertyDescriptor("foo", 0);
// TypeError: "foo" is not an object  // ES5 code

Object.getOwnPropertyDescriptor("foo", 0);
// Object returned by ES2015 code: {
//   configurable: false,
//   enumerable: true,
//   value: "f",
//   writable: false
// }

7.Object.hasOwn()

如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn()  返回 true。如果属性是继承的或者不存在,该方法返回 false

备注:  Object.hasOwn() 旨在取代 [Object.prototype.hasOwnProperty()]

[语法]

Object.hasOwn(obj, prop)

[参数]

  • obj

    要测试的 JavaScript 实例对象。

  • prop

    要测试属性的 [String]类型的名称或者 [Symbol]。

[返回值]

如果指定的对象中直接定义了指定的属性,则返回 true;否则返回 false

[描述]

如果指定的属性是该对象的直接属性——Object.hasOwn()  方法返回 true,即使属性值是 null 或 undefined。如果属性是继承的或者不存在,该方法返回 false。它不像 [in]运算符,这个方法不检查对象的原型链中的指定属性。

建议使用此方法替代 [Object.prototype.hasOwnProperty()],因为它适用于使用 Object.create(null) 创建的对象,以及重写了继承的 hasOwnProperty() 方法的对象。尽管可以通过在外部对象上调用 Object.prototype.hasOwnProperty() 解决这些问题,但是 Object.hasOwn() 更加直观。

[示例]

[使用 hasOwn 去测试属性是否存在]

以下代码展示了如何确定 example 对象中是否包含名为 prop 的属性。

JSCopy to Clipboard

const example = {};
Object.hasOwn(example, "prop"); // false——目标对象的属性 'prop' 未被定义

example.prop = "exists";
Object.hasOwn(example, "prop"); // true——目标对象的属性 'prop' 已被定义

example.prop = null;
Object.hasOwn(example, "prop"); // true——目标对象本身的属性存在,值为 null

example.prop = undefined;
Object.hasOwn(example, "prop"); // true——目标对象本身的属性存在,值为 undefined

[直接属性和继承属性]

以下示例区分了直接属性和通过原型链继承的属性:

JSCopy to Clipboard

const example = {};
example.prop = "exists";

// `hasOwn` 静态方法只会对目标对象的直接属性返回 true:
Object.hasOwn(example, "prop"); // 返回 true
Object.hasOwn(example, "toString"); // 返回 false
Object.hasOwn(example, "hasOwnProperty"); // 返回 false

// `in` 运算符对目标对象的直接属性或继承属性均会返回 true:
"prop" in example; // 返回 true
"toString" in example; // 返回 true
"hasOwnProperty" in example; // 返回 true

[迭代对象的属性]

要迭代对象的可枚举属性,你应该这样使用:

JSCopy to Clipboard

const example = { foo: true, bar: true };
for (const name of Object.keys(example)) {
  // …
}

但是如果你使用 for...in,你应该使用 Object.hasOwn() 跳过继承属性:

JSCopy to Clipboard

const example = { foo: true, bar: true };
for (const name in example) {
  if (Object.hasOwn(example, name)) {
    // …
  }
}

[检查数组索引是否存在]

[Array] 中的元素被定义为直接属性,所以你可以使用 hasOwn() 方法去检查一个指定的索引是否存在:

JSCopy to Clipboard

const fruits = ["Apple", "Banana", "Watermelon", "Orange"];
Object.hasOwn(fruits, 3); // true ('Orange')
Object.hasOwn(fruits, 4); // false——没有定义的

[hasOwnProperty 的问题案例]

本部分证明了影响 hasOwnProperty 的问题对 hasOwn() 是免疫的。首先,它可以与重新实现的 hasOwnProperty() 一起使用:

JSCopy to Clipboard

const foo = {
  hasOwnProperty() {
    return false;
  },
  bar: "The dragons be out of office",
};

if (Object.hasOwn(foo, "bar")) {
  console.log(foo.bar); //true——重新实现 hasOwnProperty() 不会影响 Object
}

它也可以用于测试使用 [Object.create(null)]创建的对象。这些对象不会继承自 Object.prototype,因此 hasOwnProperty() 方法是无法访问的。

JSCopy to Clipboard

const foo = Object.create(null);
foo.prop = "exists";
if (Object.hasOwn(foo, "prop")) {
  console.log(foo.prop); //true——无论对象是如何创建的,它都可以运行。
}