JavaScript实现继承的多种方式
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一,那么它是怎么实现继承的呢!
创建一个父类
function People (name, age) {
// 属性
this.name = name || '无名氏';
this.age = age || 18;
// 实例方法
this.sleep = function () {
console.log('有事就来梦里找我吧...');
}
}
// 原型方法
// 原型方法和实例方法的区别是原型方法是共有的,实例方法在创建实例的时候也会一起创建
People.prototype.eat = function (food) {
console.log (this.name + '正在吃' + food);
}
1. 原型实现继承
核心:将父类的实例作为子类的原型
function Man (name, age) {
this.sex = 'man';
this.duty = function () {
console.log('get money and take care of family');
}
}
Man.prototype = new People(); // 原型继承
Man.prototype.constructor = Man; // 修复构造函数
Man.prototype.name = 'man'; // 如果要扩展属性,必须在new People()之后
var xiaoMing = new Man('xiaoMing', 24); // 原型继承无法向父类构造函数传参
console.log(xiaoMing.name); // man
console.log(xiaoMing.sex); // Man构造函数中的私有变量
xiaoMing.eat('肉'); // man正在吃肉 继承People的方法
xiaoMing.duty(); // get money and take care of family, Man构造函数的方法
console.log(xiaoMing instanceof Man); // true
console.log(xiaoMing instanceof People); // true
从上面可以看出,该方法有一些有点,也有一些缺点
优点
1. 纯粹的继承关系,即是父类的实例,又是子类的实例
2. 父类新增原型方法/原型属性,子类都能访问到
3. 简单,易于实现
缺点
1. 要想为子类新增属性和方法,必须要在new People()这样的语句之后执行,不能放到构造器中
2. 无法实现多继承
3. 来自原型对象的引用属性是所有实例共有的
4. 创建子类实例时无法向父类传参
2. 构造函数继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Man (name, age) {
People.apply(this, arguments);
this.sex = 'man';
this.duty = function () {
console.log('get money and take care of family');
}
}
var xiaoMing = new Man('xiaoMing', 24); //
console.log(xiaoMing.name); // xiaoMing
console.log(xiaoMing.sex); // Man
xiaoMing.eat('肉'); // VM99:25 Uncaught TypeError: xiaoMing.eat is not a function at <anonymous>:25:10 不能继承原型的方法和属性
xiaoMing.duty(); // get money and take care of family
console.log(xiaoMing instanceof Man); // true
console.log(xiaoMing instanceof People); // false
特点:
1. 解决了1中,子类实例共享父类引用属性的问题
2. 创建子类实例时,可以向父类传递参数
3. 可以实现多继承(call多个父类对象)
缺点:
1. 实例并不是父类的实例,只是子类的实例
2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3. 组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Man (name, age) {
People.apply(this, arguments);
this.sex = 'man';
this.duty = function () {
console.log('get money and take care of family');
}
}
Man.prototype = new People();
Man.prototype.constructor = Man;
var xiaoMing = new Man('xiaoMing', 24);
console.log(xiaoMing.name); // xiaoMing
console.log(xiaoMing.sex); // Man
xiaoMing.eat('肉'); // xiaoMing正在吃肉
xiaoMing.duty(); // get money and take care of family
console.log(xiaoMing instanceof Man); // true
console.log(xiaoMing instanceof People); // true
优点:
1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
2. 既是子类的实例,也是父类的实例
3. 不存在引用属性共享问题
4. 可传参
5. 函数可复用
缺点:
1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
4.寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Man (name, age) {
People.apply(this, arguments);
this.sex = 'man';
this.duty = function () {
console.log('get money and take care of family');
}
}
//通过一个没有空的方法类,将方法类的显示原型指向,父类的显示原型。将实例作为子类的原型
(function(){
var Super = function () {}; //不能用super,super是关键字
Super.prototype = People.prototype;
Man.prototype = new Super();
Man.prototype.constructor = Man;
})();
var xiaoMing = new Man('xiaoMing', 24);
console.log(xiaoMing.name); // xiaoMing
console.log(xiaoMing.sex); // Man
xiaoMing.eat('肉'); // xiaoMing正在吃肉
xiaoMing.duty(); // get money and take care of family
console.log(xiaoMing instanceof Man); // true
console.log(xiaoMing instanceof People); // true
优点
所有优点都有
缺点
理解稍微有点困难
js实现继承还有一些其他的方式,具体请参考,博客JS实现继承的几种方式