在 Javascript 中从经典继承切换到原型继承:模式的改变

Switch from classical to prototypal inheritance in Javascript: Change of pattern

本文关键字:继承 原型 模式 改变 Javascript 经典      更新时间:2023-09-26

有了Java背景,当我切换到Javascript时,我(懒惰地)试图坚持我所知道的关于oop的东西,即经典继承。我正在开发一个网络应用程序(我制作的),并使用了这种继承。但是,我正在考虑更改我的代码并重写 OOP 部分以进行原型继承(两个原因:我读了很多它更好,其次,我需要尝试另一种方式以便更好地理解它)。

我的应用创建数据可视化(使用 D3js,但这不是主题),并按以下方式组织我的代码:

function SVGBuilder( dataset ) {
  this.data = dataset;
  this.xCoord;
  this.startDisplaying = function() {
    // stuff
    this.displayElement();
  }
}

displayElement()方法在继承自SVGBuilder(或多或少是一个抽象类)的"类"中定义。然后我有:

function SpiralBuilder( dataset ) {
  SVGBuilder.call( this, dataset );
  this.displayElement = function() {
    // stuff
  };
}
SpiralBuilder.inheritsFrom( SVGBuilder );

我还有其他几个基于相同结构的"构建器"。

调用构建器的脚本如下所示(它有点复杂,因为它根据用户输入选择正确的构造函数):

var builder = new SpiralBuilder( data );
builder.startDisplaying();

现在来了"转换部分"。我读了很多关于这方面的文章,从 Douglas Crockford 的文章,到 Eloquent Javascript 的部分内容。在Aadit M Shah的评论中,他提出了一个看起来像这样的结构:

var svgBuilder = {
  data: [],
  xCoord: 0,  // ?
  create: function( dataset ) {
    var svgBuilder= Object.create(this);
    svgBuilder.data = dataset;
    return svgBuilder;
  },
  startDisplaying: function() {
    // stuff
  }
}

然而,在这一点上,我被困住了。首先(技术问题),我可以声明变量(数据,xCoord)而不在此模式中初始化它们吗?就像this.data;一样?其次,我应该如何创建遗产?我只是手动将相应的功能放在原型中?像这样:

var spiralBuilder = builder.create( dataset );
spiralBuilder.prototype.displayElements = function() {
  // Code to display the elements of a spiral
};
spiralBuilder.displayElements();

如果我是对的,这意味着在调用构建器的脚本中,我将不得不在单个构建器实例的原型中添加/修改方法,而不是选择正确的构造函数(这将不再存在)。事情应该这样做吗?

还是我应该尝试以完全不同的方式设计我的代码?如果是这样的话,你能给我一些建议/参考吗?

我可以声明变量(数据,xCoord)而不在此模式中初始化它们吗?

var svgBuilder = {
  //removed data here as it's only going to shadowed
  // on creation, defaults on prototype can be done
  // if value is immutable and it's usually not shadowed later
  create: function( dataset, xcoord ) {
    var svgBuilder= Object.create(this);
    svgBuilder.data = dataset;//instance variable
    svgBuilder.xcoord = xcoord;//instance variable
    return svgBuilder;
  },
  startDisplaying: function() {
    // stuff
  },
  constructor : svgBuilder.create
};

我知道我很少在我的示例中这样做,但在创建实例或调用函数时,通常最好传递参数对象。

在某些时间点,您可能会在这里或那里更改内容,并且您不想更改代码中的许多位置。

在前几个示例中,您根本没有使用原型。每个成员都声明为构造函数中的this.something,因此特定于实例的成员也是如此。

可以使用构建器,但是当您习惯于声明构造函数,原型,mixins和静态时,您所需要的只是一个用于继承和mixin的辅助函数。

可以在此处找到原型的介绍。它还进入继承、混合、覆盖、调用 super 和 this 变量。引言副本如下:

构造函数介绍

可以使用函数作为构造函数来创建对象,如果构造函数名为 Person,则使用该构造函数创建的对象是 Person 的实例。

var Person = function(name){
  this.name = name;
};
Person.prototype.walk=function(){
  this.step().step().step();
};
var bob = new Person("Bob");
Person

是构造函数,因为它是一个对象(就像 JavaScript 中的大多数其他任何东西一样),你也可以给它属性,比如: Person.static="something"这适用于与 Person 相关的静态成员,例如:

 Person.HOMETOWN=22;
 var ben = new Person("Ben");
 ben.set(Person.HOMETOWN,"NY");
 ben.get(Person.HOMETOWN);//generic get function what do you thing it'll get for ben?
 ben.get(22);//maybe gets the same thing but difficult to guess

当您使用 Person 创建实例时,您必须使用 new 关键字:

var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(bob.name);//=Ben

属性/成员name是特定于实例的,对于 bob 和 ben 是不同的

所有实例共享成员walk Bob 和 ben 是 Person 的实例,因此它们共享步行成员 (bob.walk===ben.walk)。

bob.walk();ben.walk();

因为 walk() 在 bob 上找不到,JavaScript 会在 Person.prototype 中查找它,因为这是 bob 的构造函数。如果在那里找不到,它将在 Function.prototype 上查找,因为 Person 的构造函数是 Function。函数的构造函数是 Object,所以它最后看到的是 Object.prototype。这称为原型链。

即使 bob、ben 和所有其他创建的 Person 实例共享 walk,该函数在每个实例的行为也会有所不同,因为在 walk 函数中它使用 this .this的值将是调用对象;现在,假设它是当前实例,因此对于bob.walk()"this"将是Bob。(稍后将详细介绍"this"和调用对象)。

如果 ben 正在等待红灯,而 bob 处于绿灯状态;那么你将在 Ben 和 Bob 上调用 walk(),显然 Ben 和 Bob 会发生一些不同的事情。

当我们做类似 ben.walk=22 的事情时,也会发生影子成员,即使 bob 和 ben 共享walk将 22 分配给 ben.walk 不会影响 bob.walk。这是因为该语句将直接在 ben 上创建一个名为 walk 的成员,并为其赋值 22。将有2个不同的步行成员:ben.walk和Person.prototype.walk。

当请求 bob.walk 时,你会得到 Person.prototype.walk 函数,因为在 bob 上找不到walk。然而,请求 ben.walk 会得到值 22,因为成员步行是在 ben 上创建的,并且由于 JavaScript 在 ben 上找到了步行,它不会在 Person.prototype 中查找。

因此,成员的赋值将导致 JavaScript 无法在原型链中查找它并为其赋值。相反,它会将值分配给对象实例的现有成员,或者创建它,然后将值分配给它。

下一部分(有关原型的更多信息)将通过示例代码对此进行解释,并演示如何继承。