1.原型链继承
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),而每个实例都包含一个指向原型对象的内部指针(proto).
实现原型链继承的基本模式:
//定义第一个类型
function FirstFun() {
this.num = 1
}
//第一个类型定义方法
FirstFun.prototype.getFirstNum = function() {
console.log(this.num);
}
//定义第二个类型
function SecondFun() {
this.numb = 2
}
//继承第一个类型
SecondFun.prototype = new FirstFun()
//第二个类型定义方法
SecondFun.prototype.getSecondNum = function() {
console.log(this.numb)
}
//第二个类型添加实例
var numbers = new SecondFun()
//调用实例继承来的方法
numbers.getFirstNum()
//结果打印为:1
原型链继承需注意:
(1)别忘记默认原型:
所有函数的默认原型都是object的实例。因此所有默认原型都会包含一个内部指针,指向object.prototype,这也是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。
(2)确定原型和实例的方法
第一个:instanceof
alert(numbers instanceof Object) //true
alert(numbers instanceof FirstFun) //true
alert(numbers instanceof SecondFun) //true
第二个:isPrototypeOf()
alert(Object.prototype.isPrototype(numbers)) //true
alert(FirstFun.prototype.isPrototype(numbers)) //true
alert(SecondFun.prototype.isPrototype(numbers)) //true
(3)谨慎定义方法:
给原型添加方法的代码一定要在替换原型的语句之后。
还有,再通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链。
(4)原型链存在的问题:
第一个问题:
引用类型值的原型属性,会被所有实例共享,这正是为什么要在构造函数中,而不是在原型上定义属性的原因。
function Obj1() {
this.colors = ["red","blue","yellow"]
}
function Obj2() {
}
Obj2.prototype = new Obj1()
var test1 = new Obj2()
test1.colors.push("green")
console.log(test1.colors) //["red","blue","yellow","green"]
var test2 = new Obj2
console.log(test2.colors) //["red","blue","yellow","green"]
第二个问题:
没办法在不影响所有对象实例的情况下,向超类型的构造函数中传递参数。
2.借用构造函数(有时候也叫伪造对象或者经典继承)
基本思想:在子类型构造函数内部调用超类型构造函数。
函数只不过是在特定环境中执行代码的对象,因此通过使用apply()或者call()方法也可以在(将来)新创建的对象上执行构造函数。
function Obj1() {
this.colors = ["red","blue","yellow"]
}
function Obj2() {
Obj1.apply(this)
}
var test1 = new Obj2()
test1.colors.push("green")
console.log(test1.colors) //["red","blue","yellow","green"]
var test2 = new Obj2
console.log(test2.colors) //["red","blue","yellow"]
借用构造函数继承优缺点:
(1)优点:可以在子类型构造函数中向超类型构造函数传递参数。
function Obj1(name) {
this.name = name
}
function Obj2() {
//继承了Obj1,同时传递了参数
Obj1.call(this, "Peter")
this.age = 28
}
var test1 = new Obj2()
console.log(test1.name) //Peter
console.log(test1.age) //28
(2)缺点:方法都在构造函数中定义,因此函数复用就无从谈起。
3.组合继承(也叫伪经典继承)
思路:
通过使用原型链实现対原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
function Obj1(name) {
this.name = name
this.colors = ['aaa','bbb','ccc']
}
Obj1.prototype.sayName = function() {
console.log(this.name);
}
function Obj2(name, age) {
//继承属性
Obj1.call(this, name)
// 添加新的属性
this.age = age
}
//继承方法
Obj2.prototype = new Obj1()
//弥补因重写原型失去的constructor属性
Obj2.prototype.constructor = Obj2
//添加新的方法
Obj2.prototype.sayAge = function() {
console.log(this.age);
}
var test1 = new Obj2('Peter', 28)
test1.colors.push('ddd')
console.log(test1.colors)
test1.sayName()
test1.sayAge()
var test2 = new Obj2('Greo', 33)
console.log(test2.colors)
test2.sayName()
test2.sayAge()
优点:组合继承避免了原型链与借用构造函数继承的缺陷,融合入了他们的优点。
缺点:无论在什么情况下,都会调用两次超类型构造函数。一次在创建子类型原型的时候,一次在子类型构造函数内部。
4.寄生式继承
基本思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
寄生式继承模式:
function createAnother(original) {
var clone = Object.create(original)
clone.sayHi = function() {
alert("hi")
}
return clone
}
var person = {
name:"Daiv",
friends:["one","two","three"]
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi()
5.寄生组合式继承
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
基本思路:
不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
基本模式如下:
//obj1是子类型结构,obj2是超类型结构
function inheritPrototype(obj1, obj2){
//第一步:创建超类型原型副本
var prototype = object(obj2.prototype)
//第二步:为创建的副本添加constructor属性,弥补因为重写原型而失去的默认的constructor属性
prototype.constructor = obj1
//第三步:将新创建的对象(即副本),赋值给子类型的原型
obj1.prototype = prototype
}
function SuperType(name){
this.name = name
this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function(){
alert(this.name)
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function(){
alaert(this.age)
}