【问题标题】:How were "classes" extended prior to class syntax and Object.create?在类语法和 Object.create 之前,“类”是如何扩展的?
【发布时间】:2021-10-14 03:13:52
【问题描述】:

我还没有找到答案。 JavaScript 中的对象有一个继承链;任何函数的链都是Function => ObjectTypeError 实例的链是TypeError => Error => Object,而TypeError 的链奇怪的是Function => Function => Object

我查看了如何使构造的对象除了直接构造函数之外还从另一个函数继承属性,期望得到的继承链是object => constructor => second function,并且这是一个构造函数扩展另一个构造函数的方式。我找到的解决方案是在构造函数的主体内调用<second function>.<call or apply>(this[, optional arguments...]),但object instanceof <second function> 最终返回false

进一步的研究揭示了大多数使用类语法或 Object.create 的答案,但这些都是新的,并且自 JavaScript 语言创建以来,一个“类”在 JavaScript 中扩展另一个“类”就已经存在,所以还有其他一些方法可以做到这一点.这些信息应该与 JavaScript 构造函数的基本解释一起提到,但事实并非如此。扩展“类”(不是实际的类语法)导致更深的继承链的主要方法是什么?

示例结果:

// Square is the subclass
// Rectangle is the superclass

var rectangle = new Rectangle(1, 1);
var square = new Square(1);

rectangle instanceof Rectangle; // true
rectangle instanceof Square; // false
square instanceof Rectangle; // true
square instanceof Square; // true
Square instanceof Rectangle; // true

错误解决方案:

function F () {
    this.value = 0;
}

function G () {
    F.apply(this);
}

var f = new F();
var g = new G();

// g gets the same properties from F that f gets.
"value" in f; // true
"value" in g; // true

// But neither g nor G are instances of F.
g instanceof G; // true
g instanceof F; // false
G instanceof F; // false

【问题讨论】:

  • "我找到的解决方案" - 你能分享一下你找到了什么以及在哪里找到的吗?请注意,Parent.call(this, ...arguments) 仅等效于 super() 调用,而不是 extends 子句。
  • Object.create 并不新鲜。它从 ES5 开始就存在了,比 class 语法长得多。
  • Object.createnew was used to create the prototype object 之前,但是it's never been an exact replacement 和正确的操作方式很复杂。此外,使用复杂的类层次结构从未流行过。
  • "TypeError 实例的链是 TypeError => Error => Object" - 不完全是。我很困惑你的帖子怎么从来没有在任何地方提到 .prototype 对象。属性不是从函数继承的。你了解类实例的原型链是如何工作的,new 关键字与它有什么关系吗?
  • @RBarryYoung 当A 是一个对象/函数并且BC 是不同的构造函数时,A instanceof BA instanceof CB instanceof C 都可以返回true。我认为这在 ES5 之前是可能的。我说B 扩展了C,因为我不知道还能怎么称呼它。那么,这是如何实现的呢?

标签: javascript prototype prototypal-inheritance extending-classes ecmascript-3


【解决方案1】:

一个“类”在 JavaScript 中扩展另一个“类”自语言创建以来就一直存在

不,它没有。 JavaScript 从来都不是(现在仍然不是)基于类的语言。您拥有的唯一工具是 .prototypenew

Object.create 之前的“类”是如何扩展的?

基本上使用相同的方法。建立原型链的关键是

Subclass.prototype = Object.create(Superclass.prototype);

如果没有Object.create,人们只是使用

创建了该对象
Subclass.prototype = new Superclass;

有关示例,请参阅 How to inherit from a class in javascript? 中 2010 年的答案。
是的,this is a bad idea,但它激增了。设计了不会执行超类构造函数的更好的解决方案,这就是 how Object.create came into existence,由 Douglas Crockford 推广(另见 What is happening in Crockford's object creation technique?)。

【讨论】:

  • 我记得Subclass.prototype = new Superclass; 后面连Subclass.prototype.constructor = Subclass 都没有吗?
  • @PeterSeliger 是的,但是it was not essential,并且经常被省略。
  • 谢谢,不必再处理那种胶水代码是我对 JavaScript 的 class 语法感到高兴的众多原因之一。
【解决方案2】:

所以我想我会在我的旧项目中的 javascript 中包含几个“扩展”示例,以防万一这是正在寻找的内容。

这是一个通过修改valueOf 函数来“扩展”所有对象的示例。首先它将内置的.valueOf 定义复制到一个新的函数/属性-> .originalvalueOf,然后在内置的上添加我的自定义.valueOf。我这样做是为了让 JS 数字对诸如 NaN 或除以零之类的东西抛出错误。可以看到是通过修改Object.prototype完成的,而我新的.valueOf实际上是通过.originalvalueOf调用内置版本的。

/*  ****    Object extensions    ****    

    Check for NaN and DivZeros
    (adapted from: https://stackoverflow.com/a/20535480/109122)
*/
 Object.prototype.originalValueOf = Object.prototype.valueOf;

 Object.prototype.valueOf = function() {

     if (typeof this == 'number') {
        if (!isFinite(this)) {throw new Error('Number is NaN or not Finite! (RBY)');}
     }

     return this.originalValueOf();
 }

// var a = 1 + 2; // -> works
// console.log(a); // -> 3

// var b = {};
// var c = b + 2; // -> will throw an Error

这是我的“类”的构造函数示例,名为 Pe2dSpatialState。请注意,我已在此处添加了所有对象的字段:

// Constructor for Dynamic Spatial State
//( physical properties that change according to Newtonian Mechanics )
var Pe2dSpatialState = function(point2dPosition, 
                                point2dVelocity, 
                                point2dAcceleration, 
                                interval,
                                rotationDegrees,
                                rotationDegreesPerSec) {
    this.position = point2dPosition;            // position of this state
    this.velocity = point2dVelocity;            // velocity of this state
    this.acceleration = point2dAcceleration;    // acceleration to be applied
    
    this.interval = interval;                   // time to the next state

    this.rotationD = (rotationDegrees ? rotationDegrees : 0);
                                                // degrees rotated (because SVG uses degrees)
    this.rotationDPerSec = (rotationDegreesPerSec ? rotationDegreesPerSec : 0);
                                                // degrees per sec (because SVG uses degrees)
}

我通过原型给 Pe2dSpatialState 对象添加了函数:

Pe2dSpatialState.prototype.clone = function() {
    var tmp = new Pe2dSpatialState( this.position.clone(),
                                this.velocity.clone(),
                                (this.acceleration ? this.acceleration.clone() : undefined),
                                this.interval,
                                this.rotationD,
                                this.rotationDPerSec);
    return tmp;
}

然后我使用 Object.defineProperty 添加了 get/set 类型“属性”:

Object.defineProperty(Pe2dSpatialState.prototype, "rotationR", {
    get()  {return this.rotationD * Math.PI * 2 / 360;},
    set(v) {this.rotationD = v * 360 / (Math.PI * 2);}
});
Object.defineProperty(Pe2dSpatialState.prototype, "rotations", {
    get()  {return this.rotationD / 360;},
    set(v) {this.rotationD = v * 360;}
});

查看我的列表,我总是按以下顺序定义我的“类”:使用字段的构造函数,然后向原型添加函数/方法,最后使用Object.defineProperty 添加属性。只有在那之后我才能在任何地方使用它。

我不记得为什么我以这三种不同的方式做所有事情,除了我经历了很多不同的尝试和迭代,这就是我最终得到的为我工作。 (这在 ES6 下可能要容易得多)。

我还发现了一个非常复杂的函数,它可以列出任何对象的对象-函数-原型树,这对我弄清楚真正发生了什么以及什么会起作用非常有帮助。篇幅太长,我就不放了,如果你想看的话,我会放在这里。

(我不能保证这是最好的方法,也不能保证这段代码没有任何错误。事实上,现在看,我怀疑我的一些字段应该是属性。 ..)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    • 2018-02-22
    • 2017-09-29
    • 2011-03-26
    • 1970-01-01
    • 2016-01-15
    相关资源
    最近更新 更多