Javascript 继承机制的设计思想(原型链)


原型的来源

原始创建类
new 后面跟的不是类,而是构造函数

function Dog (name) {
  this.name = name;
}

var dogA = new Dog('大毛');
alert(dogA.name); //大毛

new 运算的缺点
用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法
例如:

function Dog (name) {
  this.name = name;
  this.species = '犬科';
}

然后生成两个实例对象:

var dogA = new Dog('大毛');
var dogB = new Dog('二毛');

这两个对象的species属性是独立的,修改其中一个,不会影响到另外一个。

dogA.species = '猫科';
alert(dogB.species); //显示"犬科",不受dogA的影响

缺点:每一个实例对象,都有自己的属性和方法的副本。这无法做到数据共享,极大的浪费了资源

prototype属性的引入
这个属性包含一个对象(检查prototype对象),所以实例对象需要共享的属性和方法,都放在这个对象里面,那些不需要共享的属性和方法,就放在构造函数里面。

实例对象一旦创建,将自动引入prototype对象的属性和方法。也就是说,实例对象的属性和方法,分为2种,一种是本地的,另一种是引用的。

  function Dog(name){
    this.name = name;
  }
  Dog.prototype = { species : '犬科' };

  var dogA = new DOG('大毛');
  var dogB = new DOG('二毛');
  alert(dogA.species); // 犬科
  alert(dogB.species); // 犬科

  Dog.prototype.species = '猫科'; //只要修改了prototype对象,就会同时影响2个实例对象
  alert(dogA.species); // 猫科
  alert(dogB.species); // 猫科

由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。

原型的详解

原型的使用方式
第一种:通过字面量的方式设定对象的原型

var Calculator = function (decimalDigits, tax) {
  this.decimalDigits = decimalDigits;
  this.tax = tax;
};

Calculator.prototype = {
  add: function (x, y) {
    return x + y;
  },
  subtract: function (x, y) {
    return x - y;
  }
};
//alert((new Calculator()).add(1, 3));

第二种:通过function立即执行的表达式来赋值

Calculator.prototype = function () {
  add = function (x, y) {
    return x + y;
  },
  subtract = function (x, y) {
    return x - y;
  }
  return {
    add: add,
    subtract: subtract
  }
} ();
//alert((new Calculator()).add(11, 3));

这种方式的好处:可以封装私有的function, 通过return的形式暴露出简单的使用名称,以达到public/private的效果。

第三种: 分步声明

var BaseCalculator = function () {
  //为每个实例都声明一个小数位数 
  this.decimalDigits = 2;
};

//使用原型给BaseCalculator扩展2个对象方法BaseCalculator.prototype.add = function (x, y) {
  return x + y;
};
BaseCalculator.prototype.subtract = function (x, y) {
  return x - y;
};

使用方式讲完了,接下来让我们开始(承接上面第三种方式的代码):

var Calculator = function () {
  //为每个实例都声明一个税收数字 
  this.tax = 5;
};
Calculator.prototype = new BaseCalculator();

我们可以看到Calculator的原型是指向到BaseCalculator的一个实例上,目的是让Calculator集成它的add(x,y)和subtract(x,y)这2个function,还有一点要说的是,由于它的原型是BaseCalculator的一个实例,所以不管你创建多少个Calculator对象实例,他们的原型指向的都是同一个实例。

var calc = new Calculator();alert(calc.add(1, 1));
//BaseCalculator 里声明的decimalDigits属性,在 Calculator里是可以访问到
alert(calc.decimalDigits);

上面的代码,运行后,我们可以看到因为Calculator的原型是指向BaseCalculator的实例上的,所以可以访问他的decimalDigits属性值,那如果我不想让Calculator访问BaseCalculator的构造函数里声明的属性值,那怎么办呢?这么办:

var Calculator = function () {this.tax= 5;};
Calculator.prototype = BaseCalculator.prototype;

通过将BaseCalculator的原型赋给Calculator的原型,这样你在Calculator的实例上就访问不到那个decimalDigits值了,如果你访问如下代码,那将会提升出错。

var calc = new Calculator();
alert(calc.add(1, 1));
alert(calc.decimalDigits);

重写原型
在使用第三方JS类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的add代码的形式来达到覆盖重写前面的add功能,代码如下:

//覆盖前面Calculator的add() function 
Calculator.prototype.add = function (x, y) {
  return x + y + this.tax;
};
var calc = new Calculator();alert(calc.add(1, 1));

原型链

JavaScript对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻(优先搜对象),还会搜寻该对象的原型,以及该对象的原型的原型,以此层层向上搜,直到找到一个名字匹配的属性或到达原型链的末尾.

我们来看个例子:

function foo() {
  this.add = function (x, y) {
    return x + y;
  }
}

foo.prototype.add = function (x, y) {
  return x + y + 10;
}
Object.prototype.subtract = function (x, y) {
  return x - y;
}

var f = new foo();
alert(f.add(1, 2)); //结果是3,而不是13 
alert(f.subtract(1, 2)); //结果是-1

参考文献:
http://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

你可能感兴趣的:(Javascript 继承机制的设计思想(原型链))