JavaScript 内幕:更多关于原型和继承

2025-06-09

JavaScript 内幕:更多关于原型和继承

我差点就选了“ JavaScript 的万物工厂”这个标题,但读完我之前的帖子后就改变了主意。我只是想把我的上一篇帖子发到这里。写这篇文章我感觉不太自在,不是因为我不懂这些东西,而是因为它伪装成某种东西,其实它根本不是。抗议原型继承根本不是继承,这根本改变不了什么。或许,如果不是为了方便面向对象编程人员,它更应该被称为原型连接原型委托。如果你是 Java 程序员,你会如何回答“为什么 Java 不支持多类继承? ”这个问题?嗯,你会指出钻石问题,对吧?既然 JavaScript 不知道这个问题,你怎么解释它不支持这种东西?附言:支持多类继承是可以的,但它也有自己的问题。尽管 JavaScript 看起来与经典继承很相似,但它有自己的(不能)做某些事情的理由。并不是要求你忘记经典继承,而是为了理解 JavaScript 处理原型继承的方式,你需要至少暂时放弃那些​​直观的假设。

忘掉它吧



我不想回忆起我开始在控制台中记录对象只是为了检查它们里面到底有什么的时候。我的意思是我知道对象有它们的属性,但我还发现了诸如 __proto__、构造函数、原型、 __proto__ 之类的东西。它不会停止。我继续挖掘,它继续抛出更多,然后我意识到我心甘情愿地陷入了引用循环。让我借助一个比喻来向你解释这一点。假设你在 JavaScript 中创建的任何函数都是一栋要出租的房子。房子配有一串钥匙(原型)。这串钥匙 里面还有另一串小主钥匙(__proto__ )并且有一个带有房子名称的标签(构造函数)。这串钥匙交给租户(从函数创建的对象),然后租户将它随身携带,他们喜欢称之为__proto__。哎呀!令人困惑。在这里建立类比并不容易。看看我提出的图表。

JavaScript 中的原型

查看 Ppt

您所看到的就是我现在要写下的内容。考虑一个类A或者只是一个函数A。函数在创建时,默认获得两个属性,即prototype__proto__。__ proto__JS 中所有内容(无论是原始类型还是对象)上可用的属性。来吧!在控制台中尝试一下。它包含一些来自负责首先创建原始类型/对象的函数的信息。由于函数只不过是一个Function 对象,因此每个函数上的__proto__属性都从Function.prototype获取其值。那么prototype属性呢?与JS 中所有内容上可用的__proto__相比, prototype仅适用于 JavaScript 函数。prototype属性是一个对象(唯一的例外是 Function.prototype,它是一个本机函数),具有两个默认属性,即构造函数(指原型所属的 Function/Class 本身)和__proto__ 。 A.prototype上的__proto__的用途与函数A本身的用途没有什么不同。A.prototype.__proto__包含负责创建A.prototype的函数的信息。由于此对象 (A.prototype) 是自动创建的,因此负责创建它的函数/类是Object。难怪每个SomeFunction.prototype.__proto__都会获得Object.prototype的默认值。为了验证这一点,请尝试使用 object lietral 语法创建一个对象,如下所示。

let randomObj = {};
console.log(randomObj.__proto__ === Object.prototype); // true
Enter fullscreen mode Exit fullscreen mode
在 Codepen 上尝试




接下来实例化构造函数,让我们执行let objA = new A();来从函数A创建一个对象。objA获得一个__proto__。我们刚刚讨论了 JS 中的所有内容如何获得这个默认属性,其值为SomeFunction.prototypeSomeFunction是负责创建它的函数/类。在这种情况下猜测它的值没有任何意义。它是A.prototype
let objA = new A();
console.log(objA.__proto__ === A.prototype); // true 
console.log(objA.prototype); // undefined
Enter fullscreen mode Exit fullscreen mode
.prototype 仅存在于函数中。在 Codepen 上尝试一下


原型继承

一直以来,我一直试图告诉您的是,__proto__只是原型的笔名构造函数的原型成为其对象的 __proto__。这有什么用呢?好吧,由于它不是副本,而是对函数原型的引用,该引用在使用该函数创建的对象之间共享,因此函数原型上的任何新函数/属性都可以在对象的__proto__上轻松获得。虽然在构造函数的原型上修补属性并不是一个好习惯。请在此处阅读更多相关信息。有趣的是,您甚至不需要通过__proto__访问修补的属性。您只需在对象上访问它,如objA.somePatchedFunction() ,它会从链中的__proto__解析。这听起来很有趣,但是当某些对象开始修补其__proto__属性上的函数/属性,从而导致原型命名空间污染时,很快就会让人感到不安

无论如何,你有没有想过这个?当你手动更改objA上的__proto__属性(例如objA.__proto__ = { random : 10 })时会发生什么?显然,来自函数A的链接断开了,你无法再访问A.prototype上修补的函数,然后你可以访问新设置的对象 ( { random : 10 } ) 的属性,例如objA.random 。除了直接为objA.__proto__赋值之外, JavaScript 中还存在合法的函数(Object.setPrototypeOfObject.create)来帮助你执行此操作,其中一些濒临被弃用,但这不是我在这篇博文中关心的问题。这似乎也没什么帮助。我们为什么不尝试更改A.prototype上的__proto__属性呢?嗯,这听起来像是一个好主意。

function Parent() {
  this.p = 50;
}
Parent.prototype.patchedP = 100;

function Child() {
  Parent.call(this);
  this.c = 200;
}

//  Object.create sets (Child.prototype).__proto__ = Parent.prototype
Child.prototype = Object.create(Parent.prototype);
// Resetting the child constructor may/may not be needed
Child.prototype.constructor = Child;

Child.prototype.patchedC = 400;
console.log(new Child().p); // 50  //undefined if no Parent.call(this)
console.log(new Child().patchedP); //100
Enter fullscreen mode Exit fullscreen mode
在 Codepen 上尝试

为了便于理解,让我们尝试改变Child.prototype。让我告诉你我想做什么。当我使用 Child 构造函数创建一个新对象时,我可以说类似new Child().c 的东西并得到预期值,即200 。我想要的是在执行new Child().patchedP时获得一个有效值,100。你认为我应该简单地进行这样的分配Child.prototype = Parent.prototype 吗?嗯,不是,因为当你想修补Child.prototype上的某些功能时,你最终会修补原始的Parent.prototype。 Child 上的更改不应影响 Parent ,否则您不能称之为继承。我最好使用中间对象来设置child 的原型。这就是我们这样做的原因Child.prototype = Object.create(Parent.prototype)。现在,当你修补 Child 的原型时,它不会影响 Parent(你只修补中间对象)。

您是否注意到 Child 函数中对 Parent 函数的调用(如果您来自 Java,则有点像 super)?尝试在pen中将其注释掉。这将使您无法访问 Parent 的实例属性,即此处的p 。当您使用“ this ”调用 Parent 时this在您说new Child()时指的是新创建的对象),Parent 函数将执行以在new Child()添加属性p。现在,在您从 Child 函数创建的每个新实例中,您都可以访问 Parent 和 Child 的实例属性以及Parent.prototypeChild.prototype的修补属性。此外,现在修补Child.prototype不会影响 Parent。现在我们可以称之为继承。仅谈及原型链的概念,不用说,如果您尝试在给定aChild = new Child() 的情况下访问aChild.randomProperty ; ,则首先在aChild本身的属性列表中查找,如果找不到,则在aChild.__proto__(我们之前讨论过的中间对象)中搜索,接下来在aChild.__proto__.__proto__中搜索,直到搜索到原型链中的最后一个Object.prototype 。

看一下 new Child()

看一下 new Child()


外卖

  1. 除了 Function 函数之外,每个函数的 .prototype 属性都是 object 类型。(Function 函数的 .prototype 属性是 function 类型)

  2. 每个函数的 .__proto__ 属性始终等于 Function.prototype  ,因此类型为 - Function。

  3. 对象没有 .prototype 属性。

  4. 每个对象的 .__proto__ 属性都是对象类型。

  5. 对象的 .__proto__属性 从创建它的函数的.prototype属性 中获取其值 。

  6. 如果对象不是使用任何特定函数创建的(使用对象文字或使用 Object.create(Object.prototype) 创建),则其 .__proto__ 属性的值将为 Object.prototype

  7. 从类A 或函数 A创建对象  :let  objA = Object.create(A.prototype); 或 let objA = new A();

  8. 在 ES5 中,继承如下所示:  let anObjectFromParent = Object.create(Parent.prototype);  Child.prototype = anObjectFromParent;

  9. 在 ES6 中,extends关键字扮演Object.create(Parent.prototype)的角色super关键字调用父级的构造函数。

  10. 直接在对象上访问__proto__并不像使用new关键字、Object.create(设置)和Object.getPrototypeOf(获取)那样最佳。

  11. __proto__只是一种以编程方式访问对象的[[Prototype]]内部槽的方法,否则无法通过代码访问。

最初发布于此处 -

https://mayankav.webflow.io/blog/javascript-prototypal-inheritance

鏂囩珷鏉ユ簮锛�https://dev.to/mayankav/javascript-inside-story-more-about-prototypes-and-inheritance-3a9l
PREV
将 React Components 包添加到 Monorepo
NEXT
JSX!