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
对象和对象字面量创建的普通对象。
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
属性。
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
属性。
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
属性赋值。
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]]
中,而不是作为其自有属性。
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
控制:
const arr = [];
arr.constructor = String;
arr instanceof String; // false
arr instanceof Array; // true
constructor
属性没有受到保护,可以被重新赋值或被覆盖,因此在检测变量类型时,通常应避免使用它,而应该使用更不易出错的方法,如对于对象使用 instanceof
和 Symbol.toStringTag
,对于基本类型使用 typeof
。
更改构造函数原型对象的 constructor 属性
每个构造函数都有一个 prototype
属性,当通过 new
运算符调用时,该属性将成为实例的 [[Prototype]]
。因此,ConstructorFunction.prototype.constructor
将成为实例的 [[Prototype]]
上的属性,如前面所述。
然而,如果对 ConstructorFunction.prototype
重新赋值,constructor
属性将丢失。例如,以下是创建继承模式的常见方式:
function Parent() {
// …
}
Parent.prototype.parentMethod = function () {};
function Child() {
Parent.call(this); // 确保所有内容都已正确初始化
}
// 将 Child.prototype 的 [[Prototype]] 指向 Parent.prototype
Child.prototype = Object.create(Parent.prototype);
由于重新赋值了 Child.prototype
,Child
实例的 constructor
将是 Parent
。
通常情况下,这不是什么大问题——JavaScript 几乎从不读取对象的 constructor
属性。唯一的例外是在使用 [Symbol.species]
创建类的新实例时,但这种情况很少见,并且你应该使用 extends
语法来子类化内置对象。
然而,在某些调用使用 constructor
从实例中访问原始类时,确保 Child.prototype.constructor
总是指向 Child
本身非常重要。考虑这种情况:对象具有 create()
方法来创建自身。
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
。为了避免这种情况,只需将其赋值为你将要使用的必要构造函数即可。
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()
来操作原型链。
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()
存在潜在的性能缺陷,因为所有先前创建的涉及该原型链的对象都必须重新编译;但是,如果上述初始化代码发生在 Parent
或 CreatedConstructor
构造之前,其影响应该是很小的。
接下来,看另外一个相关示例。
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
的构造分配静态属性:
// …
Object.assign(Child, ParentWithStatic); // 注意,在创建 Child 的原型前我们先分配它的值
Child.prototype = Object.create(ParentWithStatic.prototype, {
// 将原始构造函数返回给 Child
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true,
},
});
// …
但更好的方法是,我们可以使构造函数本身相互继承,就像类的 extends
一样。
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