js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念。所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现。
在面向对象的语言中,我们使用类来创建一个自定义对象。然而js中所有事物都是对象,那么用什么办法来创建自定义对象呢?这就需要用到js的原型(prototype)。
我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模版(prototype)的一个拷贝 (实际上不是拷贝而是链接,只不过这种链接是不可见,新实例化的对象内部有一个看不见的__Proto__指针,指向原型对象)。
js可以通过构造函数和原型的方式模拟实现类的功能。 另外,js类式继承的实现也是依靠原型链来实现的。
既然要实现继承,那么首先我们得有一个父类,代码如下:
// 定义一个Parent
function Parent(name){
this.name = name || "parent";
}
// 原型方法
Parent.prototype.getName = function(){
return this.name;
};
一、简单原型链
具体实现
这种是最简单实现原型继承的方法,直接把父类的对象赋值给子类构造函数的原型,这样子类的对象就可以访问到父类以及父类构造函数的prototype中的属性。
var Child= function(name){
this.name = name ||'child';
};
// 继承Parent
Child.prototype = new Parent();
var pp = new Parent();
var cc = new Child();
console.log(pp.name); // parent
console.log(cc.name); // child
console.log(pp.getName()); // parent
console.log(cc.getName()); // Uncaught TypeError: cc.getName is not a function
console.log(cc instanceof Parent); // true
console.log(cc instanceof Child); // true
- 原型继承图
- 优点 - 1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例; 2.父类新增原型方法/原型属性,子类都能访问到; 3.简单,易于实现。
- 缺点 - 1.来自原型对象的引用属性是所有实例共享的; 2.创建子类实例时,无法向父类构造函数传参;3.无法实现多继承;
二、构造函数绑定(类式继承)
具体实现
借父类的构造函数来增强子类实例,等于是把父类的实例属性复制了一份给子类实例装上了(完全没有用到原型)。
function Child(name,age){
// 继承Parent
Parent.call(this, name); // 或 Parent.apply(this,[a1,a2,a3...])(又叫对象冒充)
this.age= age;
}
Child.prototype = new Parent();
var pp = new Parent();
var cc = new Child("小明","18");
console.log(pp.getName()); // parent
console.log(cc.getName()); // 小明
console.log(cc instanceof Parent); // false
console.log(cc instanceof Child); // true
- 原型继承图
- 优点 - 1.解决了子类实例共享父类引用属性的问题; 2.创建子类实例时,可以向父类构造函数传参。
- 缺点 - 1.无法实现函数复用,影响内存
三、组合继承(伪经典继承)(最常用)
具体实现
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现在实例属性的继承;这样既保障了原型上方法的复用,有保障了每个实例都有它自己的属性。
function Child(name,age){
// 继承属性
Parent.call(this, name); // 第二次调用Parent()
this.age = age;
}
// 继承方法
Child.prototype = new Parent(); // 第一次调用Parent()
Child.prototype.getAge = function(){
return this.age;
}
var pp = new Parent();
var cc = new Child("小明","18");
console.log(pp.getName()); // parent
console.log(cc.getName()); // 小明
console.log(pp.getAge()); // Uncaught TypeError: pp.getAge is not a function
console.log(cc.getAge()); // 18
console.log(cc instanceof Parent); // true
console.log(cc instanceof Child); // true
- 原型继承图
- 优点 - 1.不存在引用属性共享问题;2.可传参;3.函数可复用。
- 缺点 - 子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,生成了两份,而子类实例上的那一份屏蔽了子类原型上的,浪费内存。
四、原型式继承
具体实现
用生孩子函数得到得到一个“纯洁”的新对象(“纯洁”是因为没有实例属性),再逐步增强之(填充实例属性)。
function beget(o){// 生孩子函数 beget:龙beget龙,凤beget凤。
function F(){}
F.prototype = o;
return new F();
}
var parent = new Parent();
var child = new beget(parent);
child.age = '18';
child.sex = 'male';
console.log(child.name); // parent
console.log(child instanceof Parent); // true
console.log(child instanceof beget); // false
- 优点 - 从已有对象衍生新对象,不需要创建自定义类型(对象的浅复制)
- 缺点 - 1.原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象;2.无法实现代码复用。
五、 寄生式继承
具体实现
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是他做了所有工作一样返回对象。
function createAnother(original){
var clone = Object.create(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ // 以某种方式来增强这个对象
alert("Hi");
};
return clone; //返回这个对象
}
var parent = new Parent();
var anotherChild = createAnother(parent); // 相当于复制
console(anotherChild.getName()); // parent
- 优点 - 从已有对象衍生新对象,不需要创建自定义类型(对象的浅复制)
- 缺点 - 1.原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象; 2.无法实现代码复用。
六、寄生组合继承(最佳方式)
具体实现
1.通过借用构造函数来继承属性; 2.通过原型链的混和方式来继承方法。
function beget(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。
var F = function(){};
F.prototype = obj;
return new F();
}
function Child(name, age){
Parent.call(this,name);
this.age = 18;
}
var proto = beget(Parent.prototype); // 创建对象
proto.constructor = Child; // 增强对象
Child.prototype = proto; // 指定对象
Child.prototype.getAge = function(){
return this.age;
}
var pp = new Parent();
var cc = new Child("小明","18");
console.log(pp.getName()); // parent
console.log(cc.getName()); // 小明
console.log(pp.getAge()); // Uncaught TypeError: pp.getAge is not a function
console.log(cc.getAge()); // 18
console.log(cc instanceof Parent); // true
console.log(cc instanceof Child); // true
- 原型继承图
目前来说,寄生组合式是最好的一种继承的方法。
以上如有不对之处请大家指正。