【问题标题】:Is the following explanation of `prototypes` in Javascript valid?以下对 Javascript 中“原型”的解释是否有效?
【发布时间】:2022-01-01 13:06:23
【问题描述】:

我一直在尝试了解原型,因为我需要对它们有一些了解。我一直在使用thisMDN文章及其相关文章供参考。

在阅读了一些之后,我对原型有了一些清晰的认识,并尝试用我自己的话来表达它们,并想知道它是否正确,并且希望喜欢知道我是否以及在哪里错了

开始解释

每个对象都是使用某种构造函数创建的。比如说,我们创建一个对象如下。

let obj = new Object();

这里,Object 是构造函数。函数的问题在于,所有函数(包括非构造函数)都有一个名为 prototype 的属性。这个prototype 属性定义了使用new 关键字和构造函数创建的any 对象的原型。您可以按如下方式检查原型属性:

console.log(Object.prototype);

上述代码将返回一个对象,其中包含使用new Object() 创建的任何对象都可以使用的方法。

在上面的示例中,如果措辞过于混乱,您可以将所有出现的Object 替换为any 其他构造函数,例如ArrayDate 甚至自定义构造函数例如Person 或您可能已定义的其他内容。

解释结束

我的理解对吗?如果没有,你能指出我哪里出错了吗?

【问题讨论】:

  • 这个问题很清楚了,不需要多关注。
  • “我的理解对吗?如果不是,你能指出我哪里出错了吗?” 从大局来看,是的,你的理解大部分是正确的(但不完整) .有一些细节是不正确的。 “每个对象都是使用某种构造函数创建的。比如说,我们创建一个对象如下。” 这不太正确,JavaScript 也有文字形式({} [object],@987654334 @ [array] 和 // [regular expression]),它们在不使用构造函数的情况下创建对象。这些表单将Object.prototypeArray.prototypeRegExp.prototype 分配给对象(续)
  • (continuing) 他们创建,即使构造函数本身没有被调用。 “关于函数的事情是,它们(包括非构造函数)都有一个名为prototype 的属性。” 这是不正确的,箭头函数和类方法没有@987654340 @属性。
  • 与上面分开,请注意您已经专注于使用构造函数,但这并不是对象获取原型的唯一方式。您也可以使用Object.create 创建对象并直接指定原型:const x = Object.create(y); 创建一个使用y 作为其原型的对象。 (你也可以使用Object.setPrototypeOf,但是......不要...... :-))
  • 然后就是整个问题:是的,但是...为什么?原型有什么用?他们在做什么?例如,您需要解释属性查找等。

标签: javascript ecmascript-6 prototype


【解决方案1】:

我的理解对吗?如果没有,你能指出我哪里出错了吗?

从大局来看,是的,你的理解大多是正确的,但解释不完整,有些细节是不正确的。

每个对象都是使用某种构造函数创建的。

这不太正确,JavaScript 也有字面形式({} [object]、[] [array] 和 // [regular expression]),它们无需使用构造函数即可创建对象。这些表单将Object.prototypeArray.prototypeRegExp.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 函数可以做任何作者想要这样做。)

【讨论】:

  • 我试图跟踪对象的创建到它的根源进入内部方法。最常见的方法之一是OrdinaryObjectCreate,它将原型作为参数。对于对象字面量,尚不清楚原型值来自何处。类似地,原型被传递给创建内置对象的内部方法。 proto 参数的值是在解释/编译时创建的吗?
  • @Teemu - 我不确定我是否正确地阅读了这个问题,但是:当 JavaScript 引擎创建一个新的 realm(全局环境和其中的东西)时,它会创建基础函数和其他对象,包括成为Object.prototype 的初始值(希望是唯一值)的对象,规范称之为%Object.prototype%(续)
  • (继续)evaluating an object initializer 时,该对象用作OrdinaryCreateObjectproto 参数。同样,[] 使用 %Array.prototype%,尽管OrdinaryCreateObject (details) 不使用。
  • 非常感谢您的回答。我很困惑,因为 %prototype% 不在 [[Intrinsicts]](tc39.es/ecma262/multipage/…) 的列表中,但当然不是,因为它是 %Object.prototype%,而 %Object% 在列表中。
  • @Teemu - 是的。它曾经是%ObjectPrototype%had its own entry on that list,但他们出于某种原因更改了它。
猜你喜欢
  • 1970-01-01
  • 2011-11-01
  • 1970-01-01
  • 2011-12-17
  • 1970-01-01
  • 2017-08-21
  • 2016-06-19
  • 2016-06-15
  • 1970-01-01
相关资源
最近更新 更多