试图理解JavaScript继承

时间:2013-06-11 14:45:55

标签: javascript inheritance

我是JavaScript新手,来自C ++ / Java背景,所以我更习惯于'经典'语言。

我如何在JavaScript中实现以下内容,因为它没有类!?

假设我有一个基类A,它定义了派生类B和C应该实现的一些函数,例如(Java ish psuedo-code):

public class A {
  public void doSomething();
  public void doSomethingElse();
};

public class B extends A {
  public void doSomething() { 
    // does B sort of something 
  };

  public void doSomethingElse() { 
    // does B sort of something else };
  };
};

public class C extends A {
  public void doSomething() { 
    // does C sort of something 
  };

  public void doSomethingElse() { 
    // does C sort of something else };
  };
};

然后我想创建一个B或C并使用它,因为它知道它将具有在A中定义的所有函数。

e.g。

A a = new B();
b.doSomething();

我甚至不知道在JS编程时这是不是我应该怎么想?我已经阅读了一些道格拉斯Crockfords网页,但我仍然感到困惑!!!。

3 个答案:

答案 0 :(得分:2)

function A() {

};

A.prototype.doSomething = function() {
    console.log("hi")
}

A.prototype.doSomethingElse = function() {}

function B() { 
   A.apply(this,arguments) ) 
};

function C() { 
   A.apply(this,arguments) ) 
};

B.prototype = Object.create(A.prototype);
C.prototype = Object.create(A.prototype);

var b = new B();
var c = new C();

b.doSomething();
c.doSomethingElse();

您可以通过在函数的原型链上设置这些对象来继承其他对象。

由于你可能想要覆盖这些基本功能,你可以这样做

B.prototype.doSomething = function() {
   console.log("goodbye");
}

当你打电话给doSomething时,b类型的对象会说再见而不是你好,c会保持不变。

这是一个可以用来试验http://jsfiddle.net/Yp3UR/

的小提琴

在一些地方有很好的外部资源

在StackOverflow上,您可以查看

答案 1 :(得分:1)

如何思考: 在Javascript和类似语言中,每个对象都有一个原型,它本身也是一个对象。如果你试图访问一个方法或属性(因为函数是javascript中的第一类,方法和属性之间的区别是误导的),如果解释器无法找到对象本身,它将查看原型。由于原型也是一个对象,它也有自己的原型(对象原型原型)。所以有这个原型链,每个级别的继承跟踪一直到基类Object,如果它仍然没有找到你试图访问解释器的属性将抛出一个未定义的属性错误。

如何实施: 有许多方法可以进行继承,这是我使用的方法,它不是最好的,但最容易理解的是:

//A constructor
var A= function() {};

A.prototype.doSomething= function() { };

A.prototype.doSomethingElse= function() { };

//B constructor
var B= function () {
   A.apply(this, arguments); //calls A constructor on this instance of B
};

B.prototype= new A(); //this makes B "extend" A. Basically it sets B prototype to be an instance of A.

B.prototype.doSomething= function() {
   A.doSomething.apply(this, arguments); //calling "super"
};

B.prototype.doSomethingElse= function() {
   A.doSomethingElse.apply(this, arguments); //calling "super"
};



//C constructor
var C= function () {
   A.apply(this, arguments);
};

C.prototype= new A();

C.prototype.doSomething= function() {
   A.doSomething.apply(this, arguments); //calling "super"
};

C.prototype.doSomethingElse= function() {
   A.doSomethingElse.apply(this, arguments); //calling "super"
};

因此,如果说C 没有方法doSomethingElse,你会做类似的事情:

c= new C();
c.doSomethingElse();

它将在c实例上调用A.doSomethingElse方法。

关于.apply函数的一点解释: javascript中的函数"扩展"对象,因此它们是对象本身。实际上你可以这样做:

var myFunc= new Function("alert('myFunc');");
myFunc();

由于函数是对象,因此它们也有自己的属性和方法。 "应用"是其中之一。

当你这样说时:

//A constructor
var A= function() {};

A.prototype.doSomething= function() { };

你实际上正在创建一个函数并将其存储在A中,然后你将一个方法放在一个原型中(记住,函数是对象,所以它们有原型)。当你这样做时:

var a= new A(arg1,arg2,arg3...);

您正在创建A的实例," new"运算符是一种特殊的运算符,基本上是这样的:

a= A.apply(a, [arg1,arg2,arg3,...]);
a.prototype= A.prototype;

以下是Function.apply方法的说明: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FFunction%2Fapply

以下是"论证的解释"传递给它的数组: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments

当你这样说时:

C.prototype.doSomething= function() {
   A.doSomething.apply(this, arguments);
};

请注意,在这种情况下,A.doSomething是种类类似于Java中的静态函数,您不是调用A实例的方法,而是实际上构造函数中存在的方法函数A(该方法实际上是在A.prototype上,但由于函数是对象,解释器将自己在原型上查找它。)

你可以做到这一切,因为构造函数是一个函数,函数是对象,对象有原型,原型是对象,对象可以在其中有函数。疯了吧?但是,如果你停下来考虑早先解释的原型链,那就不那么难了。只是重读那句话几次。

为什么所有这些废话? 你现在可能有点困惑,我鼓励你在网上寻找更多的例子。但很容易说这是错综复杂的,过于复杂,有点像(在javascript中),但它也非常强大。您可以在运行时修改对象的行为:

如果你在运行时的任何一点执行这一点代码

A.prototype.doSomething= function() {
    A.doSomethingElse.(apply(this, arguments));
}

您实际上正在修改A的所有实例的行为以及从其继承的任何其他类的行为(B和C的所有实例也被修改)。在这种情况下,你的A.doSomething现在的行为与A.doSomethingElse完全相同。 想想没有所有疯狂代码的Java反思。

实际上,您可以修改内置的Javascript类(如String或Array)的行为: 如果您在程序中的某个位置运行此代码:

String.prototype.alert= function() {
   alert(this);
}

您现在可以执行以下操作:     "文本" .alert(); 弹出框将显示" text"在里面。但是不要修改这样的内置类,这是一种不好的做法。

这只是使用基于原型的面向对象的众多优势之一。还有很多其他的。

私有方法怎么样? 它们不存在于javascript中,但您可以通过闭包创建仅对另一个函数可见的函数。如果你需要制作私人方法,我鼓励你阅读关闭。

答案 2 :(得分:0)

您需要在类之间进行原型链接。 B.proto应该指向A,以及C.proto。进行原型链接的方法是通过__proto__属性。在过去,关于__proto__存在很大的争议,因为并非所有JS引擎都实现它,尽管现在它们都有。

__proto__的实现在ES6规范中仍然是可选的,但标准将包含Object.setPrototypeOf()来进行对象链接。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf

这是您的问题的解决方案,不使用new()运算符。查看此问题"How to properly create a custom object in javascript",了解为什么不鼓励使用new()运算符来实例化对象。

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

function A() {
    var a = {};
    a.doSomething = function() {
        console.log("A.doSomething()");
    }
    return a;
}

function B() {
    var b = {};
    Object.setPrototypeOf(b, A());
    b.doSomethingElse = function() {
        console.log("B.doSomethingElse()");
    }
    return b;
}

function C() {
    var c = {};
    Object.setPrototypeOf(c, A());
    c.doSomething = function() {
        this.__proto__.doSomething();
        console.log("C.doSomething()");
    }
    return c;
}

var a = A();
var b = B();
var c = C();

a.doSomething();
b.doSomething();
b.doSomethingElse();
c.doSomething();

输出:

A.doSomething()
A.doSomething()
B.doSomethingElse()
A.doSomething()
C.doSomething()