原型的来源
原始创建类
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