我的理解对吗?如果没有,你能指出我哪里出错了吗?
从大局来看,是的,你的理解大多是正确的,但解释不完整,有些细节是不正确的。
每个对象都是使用某种构造函数创建的。
这不太正确,JavaScript 也有字面形式({} [object]、[] [array] 和 // [regular expression]),它们无需使用构造函数即可创建对象。这些表单将Object.prototype、Array.prototype 和RegExp.prototype(分别)分配给它们创建的对象,即使没有调用构造函数本身。
还有其他创建对象的方法根本不通过构造函数。例如,Object.create,它创建一个对象并将您提供的原型分配给它:
const p = {};
const obj = Object.create(p);
console.log(Object.getPrototypeOf(obj) === p); // true
(通过隐式转换创建对象的方式也比较隐晦。)您还可以使用Object.setPrototypeOf更改现有对象的原型。
关于函数的一点是,它们(包括非构造函数)都有一个名为prototype 的属性。
不完全是,箭头函数和类方法没有prototype 属性,不能用作构造函数:
const arrow = () => {};
class X {
method() {
}
static staticMethod() {
}
}
console.log("prototype" in arrow); // false
console.log("prototype" in X.prototype.method); // false
console.log("prototype" in X.staticMethod); // false
这个prototype 属性定义了使用new 关键字和构造函数创建的任何对象的原型。
正确。 (构造函数可能会弄乱它们返回的内容,但这是通常的标准行为。)
在解释的这一点上,我可能会指出函数上的prototype 属性和对象原型之间的区别。初学者有时会认为在对象上设置prototype 属性会改变其原型;它没有,该名称仅在函数上很重要,并且它 不是 函数的原型,它只是一个属性(如您所说)将用于分配使用 @ 创建的对象的原型987654345@ 具有该功能。对象的原型保存在对象的内部字段[[Prototype]] 中。该字段不能直接访问,但您可以通过Object.getPrototypeOf 访问它并通过Object.setPrototypeOf 更改它(您也可以使用已弃用的__proto__ 访问器属性,它只是一个包装器那些函数——但不要使用__proto__,直接使用函数)。
但除此之外,在您的解释中还有一个很大的悬而未决的问题:用于的原型是什么?他们在做什么?为什么有它们?
答案是它们提供了 JavaScript 的继承机制。当你获取一个对象的属性值并且该对象没有给定键的自己的属性时,JavaScript 引擎会查看对象的原型以查看它是否具有该属性(以及原型的原型,依此类推):
const parent = {
a: "a property on base",
};
const child = Object.create(parent);
child.b = "a property on child";
const grandChild = Object.create(child);
grandChild.c = "a property on grandChild";
console.log(grandChild.a); // "a property on base"
console.log(grandChild.b); // "a property on child"
console.log(grandChild.c); // "a property on grandChild"
const hasOwn =
Object.hasOwn || // Fairly new, ES2022
Function.prototype.call.bind(Object.prototype.hasOwnProperty);
console.log(`hasOwn(grandChild, "a")? ${hasOwn(grandChild, "a")}`); // false
console.log(`hasOwn(grandChild, "b")? ${hasOwn(grandChild, "b")}`); // false
console.log(`hasOwn(grandChild, "c")? ${hasOwn(grandChild, "c")}`); // true
这些示例属性值是字符串,但这在属性值是函数的情况下被广泛使用,提供了一种从父对象继承方法的方法。
不过,属性访问过程是不对称的;它仅适用于上述获取属性值。如果您在对象上设置属性的值,它总是在对象本身上设置它,而不是在其原型上:
const parent = {
prop: "parent",
};
const child = Object.create(parent);
const hasOwn =
Object.hasOwn || // Fairly new, ES2022
Function.prototype.call.bind(Object.prototype.hasOwnProperty);
console.log(`[Before] child.prop: ${child.prop}`);
// => "[Before] child.prop: parent"
console.log(`[Before] hasOwn(child, "prop")? ${hasOwn(child, "prop")}`);
// => "[Before] hasOwn(child, "prop")? false"
child.prop = "child";
console.log(`[After] child.prop: ${child.prop}`);
// => "child.prop: child"
console.log(`[After] hasOwn(child, "prop")? ${hasOwn(child, "prop")}`);
// => "[After] hasOwn(child, "prop")? true"
(获取和设置属性值之间的差异适用于数据属性[我们主要创建的那种];访问器属性的工作方式不同,因为获取和设置属性会导致函数调用,而访问器的 setter 函数可以做任何作者想要这样做。)