【问题标题】:Switch from classical to prototypal inheritance in Javascript: Change of pattern在 Javascript 中从经典继承切换到原型继承:模式的改变
【发布时间】:2013-11-13 15:19:35
【问题描述】:

拥有 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'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();

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

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

【问题讨论】:

  • 在 svgBuilder 中,成员 xCoord 建议这应该是一个实例成员(对于您创建的每个实例都是唯一的),但它会将其添加到原型上以便共享。如果此成员是原始成员(数字、字符串、布尔值),那么它是不可变的,您只能通过重新分配来更改它,因此它基本上会创建一个额外的无价值变量。如果这个成员是一个数组,那么你就有麻烦了aInstance.someArray.push("fromA"); bInstance.someArray 将是["fromA"]
  • 那里有一些构建器和辅助函数,因此您可以在创建实例时混合继承。这种方式违背了 OOP 的观点,因为当某些事情发生变化时,您的代码中有很多点需要在您的对象发生变化时进行查看。如果您了解构造函数的基础知识,那么使用工厂方法可能比让创建实例的代码决定对象是什么更好。我已经更新了我的答案,以解决您关于 svgBuilder 中实例变量的问题。

标签: javascript oop language-design prototype-programming


【解决方案1】:

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

可以使用构建器,但是当您可以轻松地声明构造函数、原型、混入和静态时,您只需要一个用于继承和混入的辅助函数。

原型介绍见here。它还涉及继承、混合、覆盖、调用 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 的实例,因此它们共享 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(),“this”将是 bob。 (稍后会详细介绍“this”和调用对象)。

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

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

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

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

The next part(有关原型的更多信息)将通过示例代码对此进行解释并演示如何继承。

【讨论】:

    猜你喜欢
    • 2013-11-07
    • 1970-01-01
    • 2015-10-12
    • 2011-02-17
    • 2016-07-30
    • 1970-01-01
    • 2011-03-02
    • 2010-09-28
    • 2018-02-21
    相关资源
    最近更新 更多