javascript 面向对象全新理练之原型继承

  首先创建一个父类的实例化对象,然后将该对象赋给子类的 prototype 属性。

  这样,父类中的所有公有实例成员都会被子类继承。并且用 instanceof 运算符判断时,子类的实例化对象既属于子类,也属于父类。

  然后将子类本身赋值给它的 prototype 的 constructor 属性。(注意:这里赋值的时候是没有 () 的!)

  这一步是为了保证在查看子类的实例化对象的 constructor 属性时,看到的是子类的定义,而不是其父类的定义。

  接下来,通过对 o.method1() 调用的结果我们会看到,子类继承来的公有实例方法中,如果调用了私有实例字段或者私有实例方法,则所调用的这些私有实例成员是属于父类的。

  同样,通过对 o.method2() 调用的结果我们看到,子类中定义的实例方法,如果调用了私有实例字段或者私有实例方法,则所调用的这些私有实例成员是属于子类的。

  通过对 o.method() 调用的结果我们看到,定义在父类原型上的方法,会被子类继承。

  通过对 o.method3() 调用的结果我们看到,子类中定义的实例方法是不能访问父类中定义的私有实例成员的。

  最后,通过对 subClass.staticMethod() 调用的结果我们看到,静态成员是不会被继承的。

  2.4 调用继承法

  调用继承的本质是,在子类的构造器中,让父类的构造器方法在子类的执行上下文上执行,父类构造器方法上所有通过 this 方式操作的内容实际上都都是操作的子类的实例化对象上的内容。因此,这种做法仅仅为了减少重复代码的编写。

  

复制代码 代码如下:

  function parentClass() {

  // private field

  var x = "I'm a parentClass field!";

  // private method

  function method1() {

  alert(x);

  alert("I'm a parentClass method!");

  }

  // public field

  this.x = "I'm a parentClass object field!";

  // public method

  this.method1 = function() {

  alert(x);

  alert(this.x);

  method1();

  }

  }

  parentClass.prototype.method = function () {

  alert("I'm a parentClass prototype method!");

  }

  parentClass.staticMethod = function () {

  alert("I'm a parentClass static method!");

  }

  function subClass() {

  // inherit

  parentClass.call(this);

  // private field

  var x = "I'm a subClass field!";

  // private method

  function method2() {

  alert(x);

  alert("I'm a subClass method!");

  }

  // public field

  this.x = "I'm a subClass object field!";

  // public method

  this.method2 = function() {

  alert(x);

  alert(this.x);

  method2();

  }

  this.method3 = function() {

  method1();

  }

  }

  // test

  var o = new subClass();

  alert(o instanceof parentClass); // false

  alert(o instanceof subClass); // true

  alert(o.constructor); // function subClass() {...}

  o.method1(); // I'm a parentClass field!

  // I'm a subClass object field!

  // I'm a parentClass field!

  // I'm a parentClass method!

  o.method2(); // I'm a subClass field!

  // I'm a subClass object field!

  // I'm a subClass field!

  // I'm a subClass method!

  o.method(); // Error!!!

  o.method3(); // Error!!!

  subClass.staticMethod(); // Error!!!

  上面这个例子很好的反映出了如何利用调用继承法来实现继承。

  利用调用继承的关键只有一步操作:

  就是在子类定义时,通过父类的 call 方法,将子类的 this 指针传入。使父类方法在子类上下文中执行。

  这样,父类中的所有在父类内部通过 this 方式定义的公有实例成员都会被子类继承。

  用 instanceof 运算符判断时,子类的实例化对象只属于子类,不属于父类。

  查看子类的实例化对象的 constructor 属性时,看到的是子类的定义,不是其父类的定义。

  接下来,通过对 o.method1() 和 o.method2() 调用的结果跟原型继承法的调用结果是相同的,所说明的问题也是一样的,这里不再重复。

  通过对 o.method() 调用的结果我们看到,定义在父类原型上的方法,不会被子类继承。

  通过对 o.method3() 调用的结果我们看到,子类中定义的实例方法同样不能访问父类中定义的私有实例成员的。

  最后,通过对 subClass.staticMethod() 调用的结果我们看到,静态成员同样不会被继承的。

  最后,还有一点,在这个例子中没有体现出来,就是通过调用继承法,可以实现多继承。也就是说,一个子类可以从多个父类中继承通过 this 方式定义在父类内部的所有公有实例成员。

  作为一种弱类型语言,javascript 提供了丰富的多态性,javascript 的多态性是其它强类型面向对象语言所不能比的。

  多态

  重载和覆盖

  先来说明一下重载和覆盖的区别。重载的英文是 overload,覆盖的英文是 override。发现网上大多数人把 override 当成了重载,这个是不对的。重载和覆盖是有区别的。

  重载的意思是,同一个名字的函数(注意这里包括函数)或方法可以有多个实现,他们依靠参数的类型和(或)参数的个数来区分识别。

  而覆盖的意思是,子类中可以定义与父类中同名,并且参数类型和个数也相同的方法,这些方法的定义后,在子类的实例化对象中,父类中继承的这些同名方法将被隐藏。

  重载

  javascript 中函数的参数是没有类型的,并且参数个数也是任意的,例如,尽管你可以定义一个:

  

复制代码 代码如下:

  function add(a, b) {

  return a + b;

  }

  这样的函数,但是你仍然可以再调用它是带入任意多个参数,当然,参数类型也是任意的。至于是否出错,那是这个函数中所执行的内容来决定的,javascript 并不根据你指定的参数个数和参数类型来判断你调用的是哪个函数。

  因此,要定义重载方法,就不能像强类型语言中那样做了。但是你仍然可以实现重载。就是通过函数的 arguments 属性。例如:

  

复制代码 代码如下:

  function add() {

  var sum = 0;

  for (var i = 0; i < arguments.length; i++) {

  sum += arguments[i];

  }

  return sum;

  }

  这样你就实现了任意多个参数加法函数的重载了。

  当然,你还可以在函数中通过 instanceof 或者 constructor 来判断每个参数的类型,来决定后面执行什么操作,实现更为复杂的函数或方法重载。总之,javascript 的重载,是在函数中由用户自己通过操作 arguments 这个属性来实现的。

  覆盖

  实现覆盖也很容易,例如:

  

复制代码 代码如下:

  function parentClass() {

  this.method = function() {

  alert("parentClass method");

  }

  }

  function subClass() {

  this.method = function() {

  alert("subClass method");

  }

  }

  subClass.prototype = new parentClass();

  subClass.prototype.constructor = subClass;

  var o = new subClass();

  o.method();

  这样,子类中定义的 method 就覆盖了从父类中继承来的 method 方法了。

  你可能会说,这样子覆盖是不错,但 java 中,覆盖的方法里面可以调用被覆盖的方法(父类的方法),在这里怎么实现呢?也很容易,而且比 java 中还要灵活,java 中限制,你只能在覆盖被覆盖方法的方法中才能使用 super 来调用次被覆盖的方法。我们不但可以实现这点,而且还可以让子类中所有的方法中都可以调用父类中被覆盖的方法。看下面的例子:

  

复制代码 代码如下:

  function parentClass() {

  this.method = function() {

  alert("parentClass method");

  }

  }

  function subClass() {

  var method = this.method;

  this.method = function() {

  method.call(this);

  alert("subClass method");

  }

  }

  subClass.prototype = new parentClass();

  subClass.prototype.constructor = subClass;

  var o = new subClass();

  o.method();

  你会发现,原来这么简单,只要在定义覆盖方法前,定义一个私有变量,然后把父类中定义的将要被覆盖的方法赋给它,然后我们就可以在后面继续调用它了,而且这个是这个方法是私有的,对于子类的对象是不可见的。这样跟其它高级语言实现的覆盖就一致了。

  最后需要注意,我们在覆盖方法中调用这个方法时,需要用 call 方法来改变执行上下文为 this(虽然在这个例子中没有必要),如果直接调用这个方法,执行上下文就会变成全局对象了。