大纲:
- 原型链
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
1、原型链:
- 什么是原型链?
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
function Father(){
this.hair = "black";
}
Father.prototype.getHair = function(){
return this.hair
}
function Son(){
this.eyes = "blue";
}
//Son继承了Father
//没有使用Son的默认原型,而是换了一个新原型。本质是重写原型
Son.prototype = new Father();
Son.prototype.getEyes = function(){
return this.eyes;
}
//Son的实例对象,并不是函数
var Gson = new Son();
console.log(Gson.getHair()); //black
console.log(Gson.getEyes()); //blue
console.log(Son.prototype.getHair()); //black
- 继承的实现:
1、通过创建Father的实例赋值给Son.prototype实现的继承。
2、实现的本质是重写原型对象,以一个新类型的实例取代。
3、即存在Father实例中的所有属性和方法,现在也存在Son.prototype中了
- 捋一下Object,Father和Son,Gson之间的关系:
1、所有的引用类型都默认继承了Object,继承的方式也是原型链
2、Gson是Son的实例对象,通过var Gson = new Son()。此时Gson里的__ proto__指向Son的prototype。
console.log(Gson.__proto__ == Son.prototype); //true
3、Son继承了Father,通过Son.prototype = new Father()。此时Son的prototype作为实例对象,他的__ proto__属性指向Father的prototype
console.log(Son.prototype.__proto__ == Father.prototype); //true
4、由于Son实现继承的本质是重写原型对象。Son.prototye的constructor属性已经不再指向Son。而是指向Father
console.log(Son.prototype.constructor == Son); //false
console.log(Son.prototype.constructor == Father); //true
5、所有函数的默认原型都是Object的实例,所以默认的原型都有__ proto__属性指向Object.prototye。这也是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因
console.log(Father.prototype.__proto__ == Object.prototype); //true
console.log(Son.prototype.__proto__ == Object.prototype); //true
6、Son继承了Father,Father继承了Object。而Gson是Son的实例对象。
-
简单图示:
- 注意:
不能使用对象字面量创建原型方法。因为会改变constructor的指向,会导致原型链被切断
- 问题:
1、包含引用类型值的原型。
2、在创建子类型的实例时,不能向超类型的构造函数中传递参数
function Father(){
this.colors = ["black","blue","red"]
}
function Son(){}
Son.prototype = new Father();
var Gson1 = new Son();
Gson1.colors.push("yellow");
console.log(Gson1.colors); //["black", "blue", "red", "yellow"]
var Gson2 = new Son();
console.log(Gson2.colors); //["black", "blue", "red", "yellow"]
//这是因为Father的实例对象是Son.prototye,而prototype属性上的方法和属性都会共享
2、借用构造函数:(伪造对象或经典继承)
- 基本思想:
在子类型构造函数的内部调用超类型构造函数。
- 例子:
实际上是在Son的要创建的新实例(Gson1,Gson2)环境下调用了Father。
1、使用Father.call()方法,可以解决原型链包含引用类型值的原型的问题
function Father(){
this.colors = ["black","blue","red"]
}
function Son(){
Father.call(this) //this指向Son的新实例对象
}
var Gson1 = new Son();
Gson1.colors.push("yellow");
console.log(Gson1.colors); //["black", "blue", "red", "yellow"]
var Gson2 = new Son();
console.log(Gson2.colors); //["black", "blue", "red"]
2、解决原型链传递参数问题
function Father(name){
this.name = name
}
function Son(){
//继承了Father,并传递参数
Father.call(this,"Tom");
//实例属性,要在继承之后添加
this.age = 36;
}
var Gson = new Son();
console.log(Gson.name); //Tom
console.log(Gson.age); //36
3、组合继承:(伪经典继承)
是将原型链和借用构造函数相结合。
1、使用原型链实现对原型属性和方法的继承
2、通过借用构造函数来实现对实例属性的继承
- 优点:
1、通过在原型上定义方法实现了函数复用
2、还能保证每个实例都有它自己的属性
- 例子:
function Father(){
this.colors= ["black","blue","red"];
}
function Son(name,age){
//继承属性
Father.call(this);
this.name = name;
this.age = age;
}
//继承方法
Son.prototype = new Father();
Son.prototype.constructor = Son;
Son.prototype.isAge = function(){
console.log(this.age);
}
Son.prototype.isName = function(){
console.log(this.name);
}
var Gson1 = new Son("lili",11);
Gson1.colors.push("yellow")
console.log(Gson1.colors); //["black", "blue", "red", "yellow"]
console.log(Gson1.name); //lili
console.log(Gson1.age); //11
var Gson2 = new Son("bree",20);
console.log(Gson2.colors); //["black", "blue", "red"]
console.log(Gson2.name); //bree
console.log(Gson2.age); //20
- 问题:
1、无论什么情况下,都会调用两次超类型构造函数。
2、一次是在创建子类型原型的适合
3、另一次是在子类型构造函数内部
4、原型式继承
- 思想:
借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。
function obj(o){ //本质上obj()对传入的对象执行了一次浅复制
function F(){}
F.prototype = o;
return new F();
}
var lion = {
name:"lion",
friends:["giraffe","elephant","rabbit"]
}
//将lion传入obj中,返回一个新对象。
//这个新对象的原型指向lion
//即lion上的属性被加到新对象上的原型上了
var fox = obj(lion);
fox.name = "fox";
fox.friends.push("cat"); //friends属性被共享了
var wolf = obj(lion);
wolf.name = "wolf";
wolf.friends.push("tiger");
console.log(lion.friends); //["giraffe", "elephant", "rabbit", "cat", "tiger"]
- Object.create方法:
1、只有一个参数时,用法和obj()一样
2、有两个参数时,用法如下:
var lion = {
name:"lion",
friends:["giraffe","elephant","rabbit"]
}
//类似描述符修改属性
var fox = Object.create(lion,{
name:{
value:"fox"
}
});
console.log(fox.name);
- 优点:
当没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
- 缺点:
包含引用类型值的属性始终会共享相应的值。
5、寄生式继承:
- 本质:
1、创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再想真的是他做了所有工作一样返回对象
2、与寄生构造函数和工厂模式类似
function obj(o){
function F(){}
F.prototype = o;
return new F();
}
function clo(z){ //封装继承过程
var clone = obj(z); //继承函数
clone.sayHi = function(){
alert('hi')
};
return clone //返回
}
var lion = {
name:"lion",
friends:["giraffe","elephant","rabbit"]
}
var zoo = clo(lion);
zoo.sayHi() //hi
console.log(zoo.name); //lion
console.log(zoo.friends); //["giraffe", "elephant", "rabbit"]
- 注意:
obj()不是必需的,任何能够返回新对象的函数都适用于此模式
- 缺点:
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,与构造函数模式类似。
6、寄生组合式继承
- 概念:
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
- 基本思路:
不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已
- 本质:
使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
- 例子:
function obj(o){
function F(){}
F.prototype = o;
return new F();
}
function inher(son,father){
var pro = obj(father.prototype); //创建对象
pro.constructor = son; //增强对象
son.prototype = pro; //指定对象
}
function Father(name){
this.name = name;
this.colors = ["black","blue","yellow"]
}
Father.prototype.isName = function(){
alert(this.name)
}
function Son(name,age){
Father.call(this,name); //只调用一次Father
this.age=age;
}
inher(Son,Father);
Son.prototype.isAge=function(){
alert(this.age)
}
- 优点:
1、只调用一次Father构造函数,也避免了再Son.prototype上面创建不必要的属性。
2、原型链能保持不变
3、是引用类型最理想的继承方式