javascript不支持传统类的继承的模式,是基于原型的继承,也就是通过prototype设置来实现继承
下面我们考虑下面的这个问题
function Person() { this.name = "haha"; this.sayName = function(){ console.log(this.name); } } var p1 = new Person(); var p2 = new Person(); p1.sayName(); console.log(p1.sayName === p2.sayName); //false
当我们使用构造函数模式(通过new调用函数)的时候 ,发现构造函数内的方法在每一个实例中都有创建一遍,也就是实现同样的一个功能要创建两个函数(函数也是对象,也就是当我们创建更多实例的时候,要不可避免的创建更多的相同功能的对象,这并不符合复用的思路)
为了实现对一个函数(对象)引用的问题 我们可以将上面的sayName方法移动到Person构造的外部,在通过this在实例化的时候绑定到特定的对象上面,这就实现了不同的对象引用的同一个函数(对象)
function Person() { this.name = "haha"; this.sayName = sayName; } function sayName() { console.log(this.name); } var p1 = new Person(); var p2 = new Person(); p1.sayName()//haha console.log(p1.sayName === p2.sayName);//true
这样解决问题的方式也存在着很大的问题 ,就是1)全局作用的函数仅仅被某个对象所调用,就应该考虑这个全局的函数位置有些不合理 (位置有点大)2)当我们要为Person定义很多类似的函数的时候,那这个person也就不存在我们所说的封装性,它定义的方法大部分都暴露了在外边
这样也就引出了原型模式
原型:每个函数都有一个prototype(原型)属性,这个属性是一个指针,它指向一个对象,这个对象的用途包含可以由特定的类型的所有实例共享的属性和方法 也就是prototype就是你创建实例的原型对象
原型模式
下面是一个简单的例子
var animal = function(){ this.type = "animal"; this.testtype = "haha"; } animal.prototype.sayType = function(){ console.log(this.type); };//为animal的原型添加方法 var person = function() { this.type = "person";//在person中添加新的type,屏蔽了animal中的type }//为Person添加属性 person.prototype = new animal();//设置person的原型指向(同一个)animal实例 var a = new person(); a.sayType(); console.log(a.testtype); a[person实例] {type:"person"} person.prototype[animal实例] {type:"animal" testtype:"haah"} animal.prototype {function:sayType} object.prototype//默认的原型 {}
a对象的原型是animal的一个实例,所以a能访问animal实例中的属性或方法,并且通过animal的实例访问到animal原型中的属性或方法,我们可以通过对象实例访问保存在原型中的值,但是却不能通过对象实例重写原型的值,如果我们在实例中添加了一个与原型中一样的属性或者方法,那我们就是在这个实例中创建了这个属性或方法,这个属性或方法屏蔽了原型中的属性或方法
这是因为属性查找的过程是从实例开始沿着原型链一步一步向上查找的,当找到相应的属性,就返回,如果查找到object.prototype都没有,就返回undefined
所以当我们只想获得实例中的属性,而不是从原型的获得属性的时候,就要通过hasOwnProperty()这个方法了
缺点: 上面通过将实例的原型属性指向另一个实例的模式,会导致原型中的所有属性被实例所共享(指向同一个实例)并且我们也不能向原型中传递参数(也会导致所有实例同样属性的问题) 当这个实例中出现引用类型的属性的时候,就有可能出现我们意想不到的结果
function SuperType() { this.colors = ["red","green"]; } function SubType() { } SubType.prototype = new SuperType(); var a = new SubType(); var b = new SubType(); a.colors.push("black"); console.log(b.colors);//red green black
console.log(a instanceof SubType);//true
console.log(a instanceof SuperType);//true
console.log(a instanceof Object);//true 反映了实例与原型的关系 由于原型链的关系,可以说a实例是原型链中出现的类型的实例
上面的例子就反映出当原型对对象中存在引用类型的时候,修改一个实例中的值,第二个实例也会受到影响(是因为这个属性是存在原型链中通过查找获得,并不存在自己的实例中,所以会产生这样的结果)
基于上面的原因,我们很少使用单一的原型链实现继承 (继承的实现机制就是重写原型对象)
为了解决超类原型中存在引用类型的问题,提出了一种借用构造函数的模式(经典继承)实现的思路是在子类型的环境中调用超类型的构造函数,这样每个子类型都有自己本身的一个超类型的副本,同时这种模式还有一个优点是可以向超类型的构造函数传递参数(也就是实现不同的子类型的定义)
function SuperType(name) { this.name = name; this.colors = ["red","green"]; } function SubType(name) { SuperType.call(this,name); //可以在这里添加自己的属性或者方法 //call apply的区别是apply传递参数的形式必须是一个数组 call以多个参数的形式传递 } var a = new SubType("haha"); var b = new SubType("hao"); console.log(a.name);//haha console.log(b.name);//hao a.colors.push("black"); console.log(a.colors);//["red","green","black"] console.log(b.colors);//["red","green"]
但是借用构造函数模式也存在着一定的问题 1)在构造函数中定义方法,存在复用的问题 2)超类型原型中定义的方法对子类型是不可见的
SuperType.prototype.sayHi = function() { console.log("hi"); }
当我们通过生成的a实例去调用sayHi方法的时候,会报sayHi undefined (因为我们没有通过prototype去继承,所以无法通过原型链访问SuperType的属性)
这就提出了另外一种模式,也就是组合继承(伪经典继承),它是将借用构造模式与原型链技术整合到一起的一种模式,也就是通过借用构造模式实现对属性的继承,原型链实现对原型的属性和方法的继承
function SuperType(name) { this.name = name; this.colors = ["red","green"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name) { SuperType.call(this,name); } SubType.prototype = new SuperType(); var a = new SubType("haha"); var b = new SuperType("hao"); a.sayName();//haha b.sayName();//hao a.colors.push("black"); console.log(a.colors);//["red","green","black"] console.log(b.colors);//["red","green",]
这样的模式下 实例既有自己本身原型属性的副本,同时还能通过原型链访问到从原型继承来的属性 也是最常用的一种继承的模式
参考 javascript高级程序设计
汤姆大叔的blog 强大的原型与原型链 :http://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html