首页 > 代码库 > JavaScript定义类和对象以及实现继承

JavaScript定义类和对象以及实现继承

定义类和对象

工厂模式

function createCar(color, doors, mpg){
  var car = {};
  car.color = color,
  car.doors = doors,
  car.mpg = mpg;
  car.showColor = function(){alert(this.color);};
  return car;
}

工厂模式比较简单,所以只适用于创建比较简单的对象。

构造函数方式

function Car(color, doors, mpg){
  this.color = color,
  this.doors = doors,
  this.mpg = mpg,
  this.showColor = function(){alert(this.color);};
}

var car1 = new Car(‘red‘, 4, 23),
  car2 = new Car(‘blue‘, 2, 25);

这种方式与工厂模式类似,都会重复地为每个对象创建独立的函数版本。

原型方式

function Car(){}

Car.prototype.color = ‘red‘,
Car.prototype.doors = 4,
Car.prototype.mpg = 23,
Car.prototype.drivers = [‘kobe‘, ‘t-mac‘, ‘james‘],
Car.prototype.showColor = function(){alert(this.color);};

var car1 = new Car(),
  car2 = new Car();

这个方式的缺点是首先不能传参,只能创建后再一个个改;而另外一个重要的问题是属性里的对象一般不会共享的,但这种方式的对象属性如drivers是每个实例所共享的,所以这种方式下会出现问题是一个实例的对象属性被改,会影响到另外的一个实例的相应属性。

car1.drivers.push(‘yao‘);

alert(car1.drivers);    // ‘kobe, t-mac, james, yao‘
alert(car2.drivers);    // ‘kobe, t-mac, james, yao‘

混合的构造函数/原型方式

这种方式用构造函数来定义对象的所有非函数的属性,用原型方式定义对象的函数属性(方法)。

function Car(color, doors, mpg){
  this.color = color,
  this.doors = doors,
  this.mpg = mpg;
  this.drivers = [‘kobe‘, ‘t-mac‘, ‘james‘];
}

Car.prototype.showColor = function(){alert(this.color);};

这种方式具有其他方式的特性,却没有它们的副作用。不过,有些开发者觉得这种方式不够完美。

动态原型方式

function Car(color, doors, mpg){
  this.color = color,
  this.doors = doors,
  this.mpg = mpg;
  this.drivers = [‘kobe‘, ‘t-mac‘, ‘james‘];

  if(‘undefined‘ === typeof Car._initialized){
    Car.prototype.showColor = function(){alert(this.color);};

    Car._initialized = true;
  }
}

该方法利用标志_initialized判断是否已经给原型赋予了任何方法,该方法只创建并赋值一次。这种方式使得代码样子更像其他语言中的类定义了。

混合工厂方式

function Car(){
  var car = {};
  car.color = ‘red‘,
  car.doors = 4,
  car.mpg = 23;
  car.drivers = [‘kobe‘, ‘t-mac‘, ‘james‘];
  car.showColor = function(){alert(this.color);};
  return car;
}

与经典的工厂方式不同,这个使用new运算符,使它更像构造函数。

var car1 = new Car(), car2 = new Car();
car1.drivers === car2.drivers;     // false
car1.showColor === car2.showColor; // false

这种方式也是独立拥有属性,不能共享方法。但不推荐用这种方式。

总结

目前使用最广泛的是混合的构造函数/原型方式,此外,动态原型方式也很流行,这俩种方式是等价的。推荐使用这俩种方式的任何一种。

对象继承

混合方式

与创建类的最好方式类似,继承也是采用 混合构造函数和原型链 的方式,用对象冒充继承构造函数的属性,用原型链继承prototype对象:

function ClassA(color){this.color = color;}

ClassA.prototype.sayColor = function(){alert(this.color);};

/*ClassB继承ClassA*/
function ClassB(color, name){ClassA.call(this, color); this.name = name;}

ClassB.prototype = new ClassA(); // 注意是空参数。这是原型链中最标准的做法,确保构造函数没有任何参数。
ClassB.prototype.sayName = function(){alert(this.name);};

动态原型方式

作为创建类和对象的第二种最好的方式,动态原型方式适合对象继承吗?答案是否定的。

function Polygon(sides){
  this.sides = sides;

  if(‘undefined‘ === typeof Polygon._initialized){

    Polygon.prototype.getArea = function(){return 0;};

    Polygon._initialized = true;
  }
}


function Triangle(base, height){
  Polygon.call(this, 3),
  this.base = base,
  this.height = height;

  if(‘undefined‘ === typeof Triangle._initialized){
    // 注意下面这行代码
    Triangle.prototype = new Polygon(),

    Triangle.prototype.getArea = function(){
      return .5 * this.base * this.height;
    };

    Triangle._initialized = true;
  }
}

分析上面代码,错误在于需要注意的那行代码。从逻辑上来说,它的位置是正确的,但从功能上来说是无效的。从技术上来说,在代码运行前,对象已被实例化,并与原始的prototype对象联系在一起。虽然可以用 极晚绑定 (即实例化对象后再修改其原型链,实例仍会拥有新修改的属性/方法)可使对原型对象的修改可以反映出来,但替换prototype对象却不会对该对象产生任何影响。因此只有未来的对象实例才会反映出这种改变,而第一个实例对象就变得不正确了。

要正确使用动态原型实现继承,必须在构造函数外赋予新的prototype对象:

function Triangle(base, height){
  Polygon.call(this, 3),
  this.base = base,
  this.height = height;

  if(‘undefined‘ === typeof Triangle._initialized){

    Triangle.prototype.getArea = function(){
      return .5 * this.base * this.height;
    };

    Triangle._initialized = true;
  }
}

Triangle.prototype = new Polygon();

可这种方式又不能把代码完全封装在构造函数中了,违反了动态原型的主旨,还不如用混合方式。

zInherit继承插件

利用zInherit库,未重写prototype对象,支持多重继承。

xbObjects

xbObjects目的是为JavaScript提供更强的面向对象范型,不止支持继承,还支持方法的重载和调用超类方法的能力。

JavaScript定义类和对象以及实现继承