继承

在面向对象的语言中, 大多语言都支持两种继承方式: 接口继承实现继承, 接口继承 只继承方法签名, 实现继承 才继承实际的方法, 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()) 和 寄生式继承

ES5中的继承的更多相关文章

  1. ES6中的类继承和ES5中的继承模式详解

    1.ES5中的继承模式 我们先看ES5中的继承. 既然要实现继承,首先我们得要有一个父类. Animal.prototype.eat = function(food) { console.log(th ...

  2. 《前端之路》- TypeScript (三) ES5 中实现继承、类以及原理

    目录 一.先讲讲 ES5 中构造函数(类)静态方法和多态 1-1 JS 中原型以及原型链 例子一 1-2 JS 中原型以及原型链中,我们常见的 constructor.prototype.**prot ...

  3. 彻底理解什么是原型链,prototype和__proto__的区别以及es5中的继承

    再讲一遍好了( 参考https://blog.csdn.net/cc18868876837/article/details/81211729 https://blog.csdn.net/lc23742 ...

  4. ES5和ES6中的继承 图解

    Javascript中的继承一直是个比较麻烦的问题,prototype.constructor.__proto__在构造函数,实例和原型之间有的 复杂的关系,不仔细捋下很难记得牢固.ES6中又新增了c ...

  5. ES5和ES6中的继承

    看到一篇写的非常好的关于js继承的文章,其中对构造函数.原型.实例之间的关系的描述十分透彻,故转载作者文章以随时学习,并供大家共同进步! ES5 ES5中的继承,看图: function Super( ...

  6. 浅谈ES5和ES6继承和区别

    最近想在重新学下ES6,所以就把自己学到的,记录下加强下自己的理解 首先先简单的聊下ES5和ES6中的继承 1.在es5中的继承: function parent(a,b){ this a = a; ...

  7. ES5和ES6中对于继承的实现方法

    在ES5继承的实现非常有趣的,由于没有传统面向对象类的概念,Javascript利用原型链的特性来实现继承,这其中有很多的属性指向和需要注意的地方. 原型链的特点和实现已经在之前的一篇整理说过了,就是 ...

  8. Es5中的类和静态方法 继承

    Es5中的类和静态方法 继承(原型链继承.对象冒充继承.原型链+对象冒充组合继承) // es5里面的类 //1.最简单的类 // function Person(){ // this.name='张 ...

  9. ES5与ES6中的继承

    ES5继承在ES5中没有类的概念,所以一般都是基于原型链继承,具体的继承方法有以下几种: 父类: function Father (name) { this.name = name || 'sam' ...

随机推荐

  1. docker安装各类软件

    安装Docker Docker 要求 CentOS 系统的内核版本高于 3.10 , uname -r 命令查看你当前的内核版本 1 安装一些必要的系统工具: sudo yum install -y ...

  2. python接口自动化七(重定向-禁止重定向Location)

    前言 某屌丝男A鼓起勇气向女神B打电话表白,女神B是个心机婊觉得屌丝男A是好人,不想直接拒绝于是设置呼叫转移给闺蜜C了,最终屌丝男A和女神闺蜜C表白成功了,这种场景其实就是重定向了. 一.重定向 1. ...

  3. Java面试之持久层(10)

    91,什么是ORM?         对象关系映射(Object-Relational Mapping,简称ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术: 简单的说,O ...

  4. CondaHTTPError问题的解决

    我是在配置pytorch时遇到的这个错误,截图如下: 这是某个网址访问失败导致的,我们可以通过添加其他路径解决这个问题,分别添加如下4个镜像路径,解决问题: 1)conda config --add ...

  5. python中的类,对象,实例,继承,多态

    ------------恢复内容开始------------ 类 (通俗来讲是 属性和方法的集合) 用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法. 对象,即为类 ...

  6. 安装SQL2012出现[HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)设置为 1

    本人安装SQL2012出现这个错误,找了三天三夜,终于把问题找出来,共享给有需要的人们,不用重新换系统 错误如下: 1,此问题是系统.net Framework版本冲突,首先下载.net Framew ...

  7. AJAX 实例解析

    AJAX 实例 为了帮助您理解 AJAX 的工作原理,我们创建了一个小型的 AJAX 应用程序: 实例 AJAX 不是新的编程语言,而是一种使用现有标准的新方法.深圳dd马达 AJAX 是与服务器交换 ...

  8. ZooKeeper 原生API操作

    zookeeper客户端和服务器会话的建立是一个异步的过程,也就是说在程序中,程序方法在处理完客户端初始化后立即返回(即程序继续往下执行代码,这样,在大多数情况下并没有真正的构建好一个可用会话,在会话 ...

  9. AVLTree的实现以及左右旋转维持自平衡

    AVL(Adelson-Velskii and Landis)树是带有平衡条件的二叉查找树.这个平衡条件必须要容易保持,而且它保证树的深度须是o(logN).最简单的想法是要求左右子树具有相同的高度, ...

  10. .net framework4.6项目的dll升级后,未找到方法“System.String.GetPathsOfAllDirectoriesAbove”解决

    https://stackoverflow.com/questions/59276192/getpathsofalldirectoriesabove-cannot-be-evaluated-after ...