试图理解JavaScript继承性

试图理解JavaScript继承性,javascript,inheritance,Javascript,Inheritance,我对JavaScript非常陌生,并且来自C++/Java背景,因此我更习惯使用“经典”语言 我如何在JavaScript中实现以下内容,因为它没有类 假设我有一个基类a,它定义了派生类B和C应该实现的一些函数,例如(Java ish psuedo代码): 然后我想创建一个B或C并使用它,因为我知道它将拥有a中定义的所有函数 e、 g 我甚至不知道在用JS编程时是否应该这样想?我读过一些Douglas Crockfords的网页,但我仍然感到困惑 function A() { }; A.pr

我对JavaScript非常陌生,并且来自C++/Java背景,因此我更习惯使用“经典”语言

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

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

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

e、 g

我甚至不知道在用JS编程时是否应该这样想?我读过一些Douglas Crockfords的网页,但我仍然感到困惑

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将保持不变

这是一把小提琴,你可以用它来做实验

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

在StackOverflow上,您可以查看

  • 有关JS类的详细概述
  • 更全面地了解JS中的对象创建

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

在ES6规范中,
\uuuuuu proto\uuuuuu
的实现仍然是可选的,但是标准将包括一个
对象。setPrototypeOf()
来进行对象链接

下面是您的问题的解决方案,无需使用new()运算符。查看此问题,了解为什么不鼓励使用
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()

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

如何实施: 继承的方法有很多种,这是我使用的一种方法,不一定是最好的,但却是最容易理解的:

//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() { };
实际上,您正在创建一个函数并将其存储在中,然后将一个方法放入原型中(记住,函数是对象,所以它们有原型)。执行此操作时:

var a= new A(arg1,arg2,arg3...);
您正在创建一个实例,“新”操作符是一种特殊类型的操作符,基本上是这样做的:

a= A.apply(a, [arg1,arg2,arg3,...]);
a.prototype= A.prototype;
下面是函数的说明。应用方法:

下面是对传递给它的“arguments”数组的解释:

当你这么说的时候:

C.prototype.doSomething= function() {
   A.doSomething.apply(this, arguments);
};
请注意,在本例中,A.doSomething类似于Java中的静态函数,您不是调用A实例的方法,而是调用构造函数A上的方法(该方法实际上在一个.prototype上,但由于函数是对象,解释器将自己在prototype上查找它)

你可以这么做,因为构造器是函数,函数是对象,对象有原型,原型是对象,对象里面可以有函数。疯狂吧?但是如果你停下来想一想前面解释的原型链,那就没那么难了。只要把那句话再读几遍就行了

为什么这么胡说八道? 你现在可能有点困惑,我鼓励你在网上寻找更多的例子。但很容易说这是一个复杂的问题,虽然有点复杂(在javascript中),但它也非常强大。你可以在运行时修改对象的行为:

如果您在运行时的任何时候执行这段代码

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

事实上,您可以修改Javascript内置类(如字符串或数组)的行为: 如果在程序中的某个位置运行此代码:

String.prototype.alert= function() {
   alert(this);
}
您现在可以执行以下操作: “text.alert(); 然后会出现一个弹出框,里面有“文本”。但是不要像那样修改内置类,这是一个糟糕的做法

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

私有方法呢? 它们不存在于javascript中,但您可以通过闭包使其他函数只能看到的函数。如果需要创建私有方法,我鼓励您阅读闭包的信息。

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