继承

在面向对象的语言中, 大多语言都支持两种继承方式: 接口继承实现继承, 接口继承 只继承方法签名, 实现继承 才继承实际的方法, ECMAScript 值支持 实现继承, 今天我们来谈谈实现继承的几种方式

原型链

关于原型链的知识我们前面已经介绍过了, 详情请见 原型链, 在 js 中原型链是实现继承的主要方法, 实现原理是利用原型链让一个引用类型继承另一个引用类型的属性和方法, 在阅读此章节需要对前面的原型有较深的理解, 而且最好能够清晰的描述出 js 中几大引用类型构造器之间的原型链, 现在来看个 Demo

let Car = function(brand) {
this.brand = brand;
}; Car.prototype.getBrand = function() {
console.log(this.brand);
}; let Ferrari = function() {
this.brand = 'ferrari';
} // 实现继承
Ferrari.prototype = new Car('rover');
let inst = new Ferrari();
inst.getBrand(); //ferrari
console.log(inst instanceof Car); // true

这里需要说明一下, instanceof 只要实例的原型链中出现的构造器都会返回 true, 使用字面量和使用拓展的形式 Ferrari.prototype = new Car('rover') VS Ferrari.prototype.getBrand = new Car('rover') 两种方式存在一些差别, 前者是重写了子类的原型, 也就是说子类原型的内存地址已经发生了变化, 这有会导致原来的实例全部丢失与现在的原型的联系, 后者是拓展子类的原型, 并不会导致切断原来的实例与现在的原型的联系, 因为子类的原型还是原来内存中的那个对象, 并不只是原型链继承才会出现这样的差别, 这是 js 语言导致的, 堆内存 本身就具有这样的特性. 其实原型继承还存在一个缺点, 那就是当原型链里有引用类型的值的时候会出现一些问题, 请看 Demo

let Car = function(brand) {
this.options = {
color: 'red'
};
}; let Ferrari = function() {}; // 实现继承
Ferrari.prototype = new Car(); let ferrari1 = new Ferrari();
ferrari1.options.price = '$100'; let ferrari2 = new Ferrari();
console.log(ferrari2.options.price); // $100

原型链中有引用类型的值时修改该值时会影响到其他实例, 这不是我们希望看到的

借用构造函数

实现思路: 在子类里借用父类的构造器来实现, Demo 如下


let Car = function(brand) {
this.options = {
color: 'red'
};
}; let Ferrari = function() {
Car.call(this);
}; // 实现继承
Ferrari.prototype = new Car(); let ferrari1 = new Ferrari();
ferrari1.options.price = '$100'; let ferrari2 = new Ferrari();
console.log(ferrari2.options.price); // undefined

构造函数继承也存在一些问题, 比如 当继承方法时, 我们希望这些实例全部共享一个方法, 但是借用构造函数这种继承方式, 所有的继承都发生在构造函数内部, 那么每次创建一个实例都会重新创建一个方法(内存地址不同), 这样就导致了代码复用率降低

组合继承

实现思路, 使用原型链继承实现原型属性和方法的继承, 借用构造函数实现实例属性的继承

let Car = function(brand) {
this.options = {
color: 'red'
};
}; Car.prototype.getOptions = function() {
console.log(Object.keys(this.options));
}; // 实现继承
let Ferrari = function() {
Car.call(this);
}; Ferrari.prototype = new Car(); let ferrari1 = new Ferrari();
ferrari1.options.price = '$100';
ferrari1.getOptions() // ['color', 'price'] let ferrari2 = new Ferrari();
ferrari2.getOptions() // ['color']

这种继承方式避免了原型链继承和借用构造函数的缺点, 融合了它们的优点, 是最常用的继承方式

原型式继承

如果只是在两个对象之间实现继承, 我们可以考虑使用该方法

let object = (o) {
function F() {};
F.prototype = o;
return new F();
}; // 本质上 `object` 只是对传入的对象进行了一次浅复制
let ferrari = {
color: 'red'
}; let myCar = object(ferrari);
console.log(myCar.color); // red

ES5 中有一个方法叫 Object.create() 实现了相似的行为

寄生式继承

这种继承是和原型式继承紧密相关的一种继承方式, 也是运用于对象之间的继承

let copy = function(o) {
let clone = Object.create(o);
clone.getColor = function() {
console.log(this.color);
};
}; let ferrari = {
color: 'red'
}; let myCar = copy(ferrari);
myCar.getColor(); // red

寄生组合式继承

前面谈到的 组合式继承 已经相当完美, 但是还有一点瑕疵, 就是父函数会有一次没必要的调用

let Car = function(brand) {
this.options = {
color: 'red'
};
}; Car.prototype.getOptions = function() {
console.log(Object.keys(this.options));
}; // 实现继承
let Ferrari = function() {
Car.call(this); // 第2次调用
}; Ferrari.prototype = new Car(); 第1次调用 let ferrari1 = new Ferrari();
ferrari1.options.price = '$100';
ferrari1.getOptions() // ['color', 'price']

我们对其进行一次改造, 减少一次调用, Demo如下

let extendsSuper = function(sub, superFunc) {
let proto = Object.create(superFunc.prototype);
proto.constructor = sub;
sub.prototype = proto;
}; let Car = function(brand) {
this.options = {
color: 'red'
};
}; Car.prototype.getOptions = function() {
console.log(Object.keys(this.options));
}; // 实现继承
let Ferrari = function() {
Car.call(this);
}; extendsSuper(Ferrari, Car); // 在此减少了调用父函数次数 let ferrari1 = new Ferrari();
ferrari1.options.price = '$100';
ferrari1.getOptions() // ['color', 'price']

小结

  1. 在实现两个构造器之间的继承时我们推荐使用 组合继承寄生式组合继承
  2. 在实现两个对象之间的继承我们推荐使用 原型式继承(可以使用 ES5Object.create()) 和 寄生式继承

最新文章

  1. Spring源代码解析
  2. 错误 1 error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
  3. 编写Delphi控件属性Stored和Default的理解及应用
  4. golang level
  5. ODI中显示us7ascii字符集的测试
  6. 在cshtml页面中,以‘@’开始的表达式 表示C#语句,会被编译执行
  7. oracle包详解(二)【weber出品】
  8. 新手可以学习cocos2dx 3.0 组态(两)
  9. vue路由表(简单)
  10. (转载)oracle 在一个存储过程中调用另一个返回游标的存储过程
  11. 【D3】transition API
  12. ES6这些就够了
  13. Error updating database. Cause: java.sql.BatchUpdateException: Field 'id' doesn't have a default value
  14. CentOS6.4下安装Nginx1.12.2
  15. Linux成为云计算平台的主流操作系统
  16. final可以修饰类、属性、方法。
  17. ATS metric query
  18. iOS开发-适配器和外观模式
  19. 使用Python中的config配置
  20. [work]Spring_Jdbc

热门文章

  1. 正则获取a标签和a标签中的href地址
  2. javascript基本知识图解
  3. 对JavaScript 引擎基础:原型优化的研究 -----------------------引用
  4. Fiddler的工作原理与主菜单介绍(一)
  5. php将base64字符串转换为图片
  6. 访问SpringBoot中的Swagger的方法
  7. Maven Waring : GroupId is duplicate of parent groupId 和 Version is duplicate of parent version
  8. windows10 gcc编译C程序(简单编译)
  9. 字符串处理工具StringUtils
  10. Codechef TRIPS Children Trips (分块、倍增)