Object.prototype.constructor

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Object 实例的 constructor 数据属性返回一个引用,指向创建该实例对象的构造函数。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

备注:这是 JavaScript 对象的一个属性。关于类的 constructor 方法,请参见其参考页面

对创建该实例对象的构造函数的引用。

Object.prototype.constructor 的属性特性
可写
可枚举
可配置

备注:这个属性默认会在每个构造函数的 prototype 属性上创建,并由该构造函数创建的所有对象继承。

描述

除了 null 原型对象之外,任何对象都会在其 [[Prototype]] 上有一个 constructor 属性。使用字面量创建的对象也会有一个指向该对象构造函数类型的 constructor 属性,例如,数组字面量创建的 Array 对象和对象字面量创建的普通对象。

js
const o1 = {};
o1.constructor === Object; // true

const o2 = new Object();
o2.constructor === Object; // true

const a1 = [];
a1.constructor === Array; // true

const a2 = new Array();
a2.constructor === Array; // true

const n = 3;
n.constructor === Number; // true

请注意,constructor 属性通常来自构造函数的 prototype 属性。如果你有一个更长的原型链,通常可以假定链中的每个对象都有一个 constructor 属性。

js
const o = new TypeError(); // 继承关系:TypeError -> Error -> Object
const proto = Object.getPrototypeOf;
proto(o).constructor === TypeError; // true
proto(proto(o)).constructor === Error; // true
proto(proto(proto(o))).constructor === Object; // true

示例

打印对象的构造函数

下面这个示例创建一个构造函数(Tree),以及该类型的对象(theTree)。然后打印了 theTree 对象的 constructor 属性。

js
function Tree(name) {
  this.name = name;
}

const theTree = new Tree("Redwood");
console.log(`theTree.constructor 是 ${theTree.constructor}`);

这个示例会打印以下输出:

theTree.constructor 是 function Tree(name) {
  this.name = name;
}

为对象的 constructor 属性赋值

可以为非基本类型对象的 constructor 属性赋值。

js
const arr = [];
arr.constructor = String;
arr.constructor === String; // true
arr instanceof String; // false
arr instanceof Array; // true

const foo = new Foo();
foo.constructor = "bar";
foo.constructor === "bar"; // true

// 等等…

这不会覆盖旧的 constructor 属性——它实际上存在于实例的 [[Prototype]] 中,而不是作为其自有属性。

js
const arr = [];
Object.hasOwn(arr, "constructor"); // false
Object.hasOwn(Object.getPrototypeOf(arr), "constructor"); // true

arr.constructor = String;
Object.hasOwn(arr, "constructor"); // true——实例属性会覆盖原型链上的同名属性

但是,即使对 Object.getPrototypeOf(a).constructor 重新赋值,它也不会改变对象的其他行为。例如,instanceof 的行为由 Symbol.hasInstance 控制,而不是由 constructor 控制:

js
const arr = [];
arr.constructor = String;
arr instanceof String; // false
arr instanceof Array; // true

constructor 属性没有受到保护,可以被重新赋值或被覆盖,因此在检测变量类型时,通常应避免使用它,而应该使用更不易出错的方法,如对于对象使用 instanceofSymbol.toStringTag,对于基本类型使用 typeof

更改构造函数原型对象的 constructor 属性

每个构造函数都有一个 prototype 属性,当通过 new 运算符调用时,该属性将成为实例的 [[Prototype]]。因此,ConstructorFunction.prototype.constructor 将成为实例的 [[Prototype]] 上的属性,如前面所述。

然而,如果对 ConstructorFunction.prototype 重新赋值,constructor 属性将丢失。例如,以下是创建继承模式的常见方式:

js
function Parent() {
  // …
}
Parent.prototype.parentMethod = function () {};

function Child() {
  Parent.call(this); // 确保所有内容都已正确初始化
}
// 将 Child.prototype 的 [[Prototype]] 指向 Parent.prototype
Child.prototype = Object.create(Parent.prototype);

由于重新赋值了 Child.prototypeChild 实例的 constructor 将是 Parent

通常情况下,这不是什么大问题——JavaScript 几乎从不读取对象的 constructor 属性。唯一的例外是在使用 [Symbol.species] 创建类的新实例时,但这种情况很少见,并且你应该使用 extends 语法来子类化内置对象。

然而,在某些调用使用 constructor 从实例中访问原始类时,确保 Child.prototype.constructor 总是指向 Child 本身非常重要。考虑这种情况:对象具有 create() 方法来创建自身。

js
function Parent() {
  // …
}
function CreatedConstructor() {
  Parent.call(this);
}

CreatedConstructor.prototype = Object.create(Parent.prototype);

CreatedConstructor.prototype.create = function () {
  return new this.constructor();
};

new CreatedConstructor().create().create(); // TypeError: new CreatedConstructor().create().create is undefined,因为 constructor === Parent

在上面的示例中,会抛出一个异常,因为 constructor 链接到 Parent。为了避免这种情况,只需将其赋值为你将要使用的必要构造函数即可。

js
function Parent() {
  // …
}
function CreatedConstructor() {
  // …
}

CreatedConstructor.prototype = Object.create(Parent.prototype, {
  // 将原始构造函数返回给 Child
  constructor: {
    value: CreatedConstructor,
    enumerable: false, // 使其不可枚举,这样它就不会出现在 `for...in` 循环中
    writable: true,
    configurable: true,
  },
});

CreatedConstructor.prototype.create = function () {
  return new this.constructor();
};

new CreatedConstructor().create().create(); // 跑起来没毛病

请注意,当手动添加 constructor 属性时,将属性设置为不可枚举非常重要,这将确保 constructor 就不会在 for...in 循环中被访问——尽管通常情况下不会被访问。

如果上面的代码看起来太死板,你也可以考虑使用 Object.setPrototypeOf() 来操作原型链。

js
function Parent() {
  // …
}
function CreatedConstructor() {
  // …
}

Object.setPrototypeOf(CreatedConstructor.prototype, Parent.prototype);

CreatedConstructor.prototype.create = function () {
  return new this.constructor();
};

new CreatedConstructor().create().create(); // 在不重新创建 constructor 属性的情况下仍然有效

Object.setPrototypeOf() 存在潜在的性能缺陷,因为所有先前创建的涉及该原型链的对象都必须重新编译;但是,如果上述初始化代码发生在 ParentCreatedConstructor 构造之前,其影响应该是很小的。

接下来,看另外一个相关示例。

js
function ParentWithStatic() {}

ParentWithStatic.startPosition = { x: 0, y: 0 }; // 静态成员属性
ParentWithStatic.getStartPosition = function () {
  return this.startPosition;
};

function Child(x, y) {
  this.position = { x, y };
}

Child.prototype = Object.create(ParentWithStatic.prototype, {
  // 将原始构造函数返回给 Child
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true,
  },
});

Child.prototype.getOffsetByInitialPosition = function () {
  const position = this.position;
  // 使用 `this.constructor`,以期 `getStartPosition` 存在于一个静态方法中。
  const startPosition = this.constructor.getStartPosition();

  return {
    offsetX: startPosition.x - position.x,
    offsetY: startPosition.y - position.y,
  };
};

new Child(1, 1).getOffsetByInitialPosition();
// Error: this.constructor.getStartPosition is undefined,
// 因为构造函数是 Child,它没有 getStartPosition 静态方法

如果想要保证示例正常运行,我们需要让 Parent 作为构造函数,或给 Child 的构造分配静态属性:

js
// …
Object.assign(Child, ParentWithStatic); // 注意,在创建 Child 的原型前我们先分配它的值
Child.prototype = Object.create(ParentWithStatic.prototype, {
  // 将原始构造函数返回给 Child
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true,
  },
});
// …

但更好的方法是,我们可以使构造函数本身相互继承,就像类的 extends 一样。

js
function ParentWithStatic() {}

ParentWithStatic.startPosition = { x: 0, y: 0 }; // 静态成员属性
ParentWithStatic.getStartPosition = function () {
  return this.startPosition;
};

function Child(x, y) {
  this.position = { x, y };
}

// 正确地创建继承关系!
Object.setPrototypeOf(Child.prototype, ParentWithStatic.prototype);
Object.setPrototypeOf(Child, ParentWithStatic);

Child.prototype.getOffsetByInitialPosition = function () {
  const position = this.position;
  const startPosition = this.constructor.getStartPosition();

  return {
    offsetX: startPosition.x - position.x,
    offsetY: startPosition.y - position.y,
  };
};

console.log(new Child(1, 1).getOffsetByInitialPosition()); // { offsetX: -1, offsetY: -1 }

再次强调,使用 Object.setPrototypeOf() 可能会对性能产生不利影响,因此请确保它仅在必要时使用,并在构造函数声明后立即使用,并在创建任何实例之前使用,以避免对象被“污染”。

备注:设置或更新构造函数可能会导致结果不同且令人困惑的结果。为了防止它,只需在特定情况下定义 constructor。多数情况,不使用 constructor,并且不需要重新对其赋值。

规范

Specification
ECMAScript Language Specification
# sec-object.prototype.constructor

浏览器兼容性

BCD tables only load in the browser

参见