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

时间:2013-11-13 15:19:35

标签: javascript oop language-design prototype-programming

拥有Java背景,当我切换到Javascript时,我(懒惰地)试图坚持我对oop的了解,即经典的继承。我正在开发一个web-app(我制作),并使用了这种继承。但是,我正在考虑更改我的代码并重写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's article到Eloquent Javascript的部分内容阅读了很多相关内容。在Aadit M Shahcomment中,他提出了一个看起来像这样的结构:

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();

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

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

1 个答案:

答案 0 :(得分:1)

  
    

我可以声明变量(data,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,因此是特定于实例的成员。

可以使用构建器,但是当你很自然地声明构造函数,原型,混合ins和静态时,你只需要一个辅助函数来继承和混合。

可以找到原型简介here。它还包括继承,混合输入,覆盖,调用超级和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创建实例时,必须使用新关键字:

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的实例,因此他们共享walk成员(bob.walk === ben.walk)。

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

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

尽管bob,ben和所有其他创建的Person实例共享walk,但每个实例的函数行为都不同,因为在walk函数中它使用thisthis的值将是调用对象;现在让我们说它是当前的实例,所以bob.walk()“这个”将是bob。 (更多关于“this”和稍后调用的对象)。

如果本正在等待红灯而且鲍勃处于绿灯状态;然后你会在ben和bob上调用walk(),显然ben和bob会发生不同的事情。

当我们执行类似ben.walk=22之类的操作时会发生隐藏成员,即使bob和ben共享walk 22的赋值到ben.walk也不会影响bob.walk。这是因为该语句将直接在ben上创建一个名为walk的成员,并为其赋值22.将有2个不同的walk成员:ben.walk和Person.prototype.walk。

当要求bob.walk时,你将获得Person.prototype.walk函数,因为在bob上找不到walk。然后要求ben.walk将获得值22,因为成员行走已在ben上创建,因为JavaScript发现在Ben上行走它不会在Person.prototype中查找。

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

The next part(有关原型的更多信息)将使用示例代码解释这一点并演示如何继承。