Object.prototype.constructor
**constructor
** 的 **数据属性** Object
实例返回对创建实例对象的构造函数的引用。请注意,此属性的值是对**函数本身**的引用,而不是包含函数名称的字符串。
**注意:**这是 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(); // Inheritance: TypeError -> Error -> Object
const proto = Object.getPrototypeOf;
Object.hasOwn(o, "constructor"); // false
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 is ${theTree.constructor}`);
此示例显示以下输出
theTree.constructor is function Tree(name) { this.name = name; }
将构造函数属性分配给对象
可以将非基本类型的 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
// etc.
这不会覆盖旧的 constructor
属性——它最初存在于实例的 [[Prototype]]
上,而不是作为它自己的属性。
const arr = [];
Object.hasOwn(arr, "constructor"); // false
Object.hasOwn(Object.getPrototypeOf(arr), "constructor"); // true
arr.constructor = String;
Object.hasOwn(arr, "constructor"); // true — the instance property shadows the one on its prototype
但即使 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
用于基本类型。
更改构造函数的原型的构造函数
每个构造函数都有一个 prototype
属性,该属性在通过 new
运算符调用时将成为实例的 [[Prototype]]
。因此,ConstructorFunction.prototype.constructor
将成为实例的 [[Prototype]]
上的属性,如前所述。
但是,如果重新分配 ConstructorFunction.prototype
,则会丢失 constructor
属性。例如,以下是一种常见的方式来创建继承模式
function Parent() {
// …
}
Parent.prototype.parentMethod = function () {};
function Child() {
Parent.call(this); // Make sure everything is initialized properly
}
// Pointing the [[Prototype]] of Child.prototype to Parent.prototype
Child.prototype = Object.create(Parent.prototype);
Child
实例的 constructor
将是 Parent
,因为重新分配了 Child.prototype
。
这通常不是什么大问题——语言几乎从不读取对象的 constructor
属性。唯一的例外是当使用 [Symbol.species]
创建类的新的实例时,但这种情况很少见,您应该使用 extends
语法来对内置类进行子类化。
但是,确保 Child.prototype.constructor
始终指向 Child
本身对于某些调用者使用 constructor
从实例访问原始类至关重要。以以下情况为例:该对象具有 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, since constructor === Parent
在上面的示例中,会抛出一个异常,因为 constructor
链接到 Parent
。为了避免这种情况,只需分配您要使用的必要的构造函数即可。
function Parent() {
// …
}
function CreatedConstructor() {
// …
}
CreatedConstructor.prototype = Object.create(Parent.prototype, {
// Return original constructor to Child
constructor: {
value: CreatedConstructor,
enumerable: false, // Make it non-enumerable, so it won't appear in `for...in` loop
writable: true,
configurable: true,
},
});
CreatedConstructor.prototype.create = function () {
return new this.constructor();
};
new CreatedConstructor().create().create(); // it's pretty fine
请注意,当手动添加 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(); // still works without re-creating constructor property
Object.setPrototypeOf()
存在其潜在的性能问题,因为涉及原型链中所有先前创建的对象都必须重新编译;但如果上面的初始化代码发生在构造 Parent
或 CreatedConstructor
之前,则效果应该很小。
让我们考虑一个更复杂的情况。
function ParentWithStatic() {}
ParentWithStatic.startPosition = { x: 0, y: 0 }; // Static member property
ParentWithStatic.getStartPosition = function () {
return this.startPosition;
};
function Child(x, y) {
this.position = { x, y };
}
Child.prototype = Object.create(ParentWithStatic.prototype, {
// Return original constructor to Child
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true,
},
});
Child.prototype.getOffsetByInitialPosition = function () {
const position = this.position;
// Using this.constructor, in hope that getStartPosition exists as a static method
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, since the
// constructor is Child, which doesn't have the getStartPosition static method
为了使此示例正常工作,我们可以将 Parent
的静态属性重新分配给 Child
// …
Object.assign(Child, ParentWithStatic); // Notice that we assign it before we create() a prototype below
Child.prototype = Object.create(ParentWithStatic.prototype, {
// Return original constructor to Child
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true,
},
});
// …
但更好的是,我们可以使构造函数本身彼此扩展,就像类 extends
一样。
function ParentWithStatic() {}
ParentWithStatic.startPosition = { x: 0, y: 0 }; // Static member property
ParentWithStatic.getStartPosition = function () {
return this.startPosition;
};
function Child(x, y) {
this.position = { x, y };
}
// Properly create inheritance!
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
没有被使用,重新分配它也没有必要。
规范
规范 |
---|
ECMAScript 语言规范 # sec-object.prototype.constructor |
浏览器兼容性
BCD 表格只在启用了 JavaScript 的浏览器中加载。