JavaScript 常见的继承方式汇总


Posted in Javascript onSeptember 17, 2020

原型链机制:

在ECMAscript中描述了原型链的概念,并将原型链作为实现继承的主要方法,其基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。

构造函数和原型还有实例之间的关系:

每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针 ( __propto__ ) 。关系图如下图所示:

JavaScript 常见的继承方式汇总

每一个Function都是Object基类的一个实例,所以每一个Function上都有一个__proto__指向了Object.prototype

当查找一个实例的属性时,会先从这个实例的自定义属性上找,如果没有的话通过__proto__去实例所属类的原型上去找,如果还没有的话再通过原型(原型也是对象,只要是对象就有__proto__属性)的__proto__到Object的原型上去找,一级一级的找,如果没有就undefined。

所以引用类型之间的继承就是通过原型链机制实现的。

一.原型继承

原型继承:把父类的私有+公有的属性和方法,都作为子类公有的属性。

核心:不是把父类私有+公有的属性克隆一份一模一样的给子类的公有。他是通过__proto__建立和子类之间的原型链,当子类的实例需要使用父类的属性和方法的时候,可以通过__proto__一级级找上去使用。 

function Parent(){
  this.x = 199;
  this.y = 299;
}
Parent.prototype.say = function(){
  console.log('say')
}
function Child(){
  this.g = 90;
}
Child.prototype = new Parent();
var p = new Parent();
var c = new Child();
console.dir(c)

实现的本质是重写了原型对象 ,通过将子类的原型指向了父类的实例,所以子类的实例就可以通过__proto__访问到 Child.prototype 也就是 Parent的实例,这样就可以访问到父类的私有方法。然后再通过__proto__指向父类的prototype就可以获得到父类原型上的方法。

这样就做到了将父类的私有、公有方法和属性都当做子类的公有属性。这样就通过原型链实现了继承。

但是别忘了默认的原型,因为所有引用类型都是继承了Object的,所有说子类也可以访问到Object上的方法如toString() 、valueOf() 等。

结果如下图所示:

JavaScript 常见的继承方式汇总

有的时候我们需要在子类中添加新的方法或者是重写父类的方法时候,切记一定要放到替换原型的语句之后

function Parent(){
  this.x = 199;
  this.y = 299;
}
Parent.prototype.say = function(){
  console.log('say')
}
function Child(){
  this.g = 90;
}
/*Child.prototype.Bs = function(){
  console.log('Bs')
}*/在这里写子类的原型方法和属性是没用的因为会改变原型的指向,所以应该放到重新指定之后
Child.prototype = new Parent();
Child.prototype.constructor=Child//由于重新修改了Child的原型导致默认原型上的constructor丢失,我们需要自己添加上,其实没啥用,加不加都一样
Child.prototype.Bs = function(){
  console.log('Bs')
}
Child.prototype.say = function(){
  console.log('之后改的')
}
var p = new Parent();
var c = new Child();
console.dir(c)
c.Bs() //Bs
c.say()  // 之后改的
p.say() //say 不影响父类实例访问父类的方法

存在的问题:

1. 子类继承父类的属性和方法是将父类的私有属性和公有方法都作为自己的公有属性和方法,我们要清楚一件事情就是我们操作基本数据类型的时候操作的是值,在操作应用数据类型的时候操作的是地址,如果说父类的私有属性中引用类型的属性,那他被子类继承的时候会作为公有属性,这样子类一操作这个属性的时候,会影响到子类二。

2. 在创建子类的实例时,不能向父类型的构造函数中传递参数。应该说是没有办法在不影响所有对象实例的情况下,给父类的构造函数传递参数。

所以在实际中很少单独使用原型继承

二.call继承

改变方法的this指向,同时执行方法。 在子类构造函数中父类.call(this) 可以将父类的私有变成子类的私有。

function Parent() {
  this.x = 100;
  this.y = 199;
}
Parent.prototype.fn = function() {}
 
function Child() {
  this.d = 100;
  Parent.call(this); //构造函数中的this就是当前实例
}
var p = new Parent();
var c = new Child();
console.log(p) //Parent {x: 100, y: 199}
console.log(c) //Child {d: 100, x: 100, y: 199}

在子类的构造函数中,改变父类的this指向,改变为子类的实例,同时运行父类方法,这样父类中的this.x就变成了子类的实例.x ,通过这种方法就可以继承了父类的私有属性,且只能继承父类的私有属性和方法。

三.冒充对象继承

冒充对象继承的原理是循环遍历父类实例,然后父类实例的私有方法全部拿过来添加给子类实例。

function Parent(){
  this.x = 100;
}
Parent.prototype.getX = function(){
  console.log('getX')
}
function Child(){
  var p = new Parent();
  for(var attr in p){//for in 可以遍历到原型上的公有自定义属性
    this[attr] = p[attr]
  }
  //以下代码是只获得到私有方法和属性,如果不加这个的话就可以遍历到所有方法和属性
  /*if(e.hasOwnProperty(attr)){
    this[attr] = e[attr]
  }
  e.propertyIsEnumerable()*///可枚举属性==> 可以拿出来一一列举的属性
}
var p = new Parent();
var c = new Child();
console.dir(c)

for in 可以遍历到原型上的公有自定义属性 ,所以他可以拿到私有和公有的属性和方法,这个你可以遍历私有和公有的,需要你加限制条件。但是如果不做hasOwnProperty判断那么就是把父类的公有的和私有的都拿过来当私有的。

四.混合继承

就是将call继承和原型继承集合在一起,无论是私有的还是公有的都拿过来了。但是有个问题就是子类的原型上的多了一套父类私有属性,但是不会产生问题。因为子类的私有属性也有一套相同的通过call继承拿过来的。

function Parent(){
  this.x=100;
}
Parent.prototype.getX = function(){}
function Child(){
  Parent.call(this);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var p = new Parent();
var c = new Child();
console.log(c)//Child {x: 100}

存在的问题:

无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,没错,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。

还有一种就是call+拷贝继承

//混合继承:call继承+拷贝继承
  function extend(newEle,oldEle){
    for(var attr in oldEle){
      newEle[attr]=oldEle[attr];
    }
  }
  function F(){
    this.x=100;
    this.showX=function(){}
  }
  F.prototype.getX=function(){};
  F.prototype.getX1=function(){};
  var f1=new F;
  console.dir(f1)
  function S(){
    F.call(this)//call继承
  }
  extend(S.prototype, F.prototype);//拷贝继承
  S.prototype.cc=function(){ }
  var p1=new S;
  console.dir(p1);

这种方式使用call继承将父类的私有方法继承过来,使用for in 拷贝将父类的公有属性和方法继承过来,比较实用。

五.中间件继承

中间件继承就是通过原型链的机制,子类的prototype.__proto__本来应该是直接指向Object.prototype。

从父类的原型上的__proto__也可以到Object.prototype,在父类.prototype上停留了下,父类.prototype就是一个中间件,所以子类可以继承到父类的公有方法当做自己的公有方法。

function Parent(){
  this.x = 100;
}
Parent.prototype.getX = function(){}
function Child(){
  
}
Child.prototype.__proto__ = Parent.prototype;
var p = new Parent();
var c = new Child()
console.log(c)

六.寄生组合式继承

   寄生式组合: call继承+Object.create();

所谓寄生组合式继承就是通过借用构造函数来继承属性,通过原型链的混合形式来继承方法

基本思路是不必为了指定子类的原型而调用父类的构造函数,我们所需要的就是父类型原型的一个副本。

本质上,就是使用寄生式继承父类的原型,然后再将结果指定给子类的原型。

function F(){
  this.x=100;
}
F.prototype.showX=function(){};
function S(){
  this.y = 200
  F.call(this)//只继承了私有的;
}
function inheritPrototype(subType,superType){
  var prototype = Object.create(superType.prototype);//创建对象
  prototype.constructor = subType;//增强对象
  subType.prototype = prototype;//指定对象
}
inheritPrototype(S,F)
var p1=new S;
console.dir(p1)

1、第一步是创建父类型原型的一个副本。

2、第二步是为创建的副本增加constructor属性,从而弥补了因为重写原型而失去的默认的constructor属性。

3、第三步是将创建的对象赋值给子类型的原型。

这个例子的高效率体现在他只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时原型链还能保持不变,所以可以正常使用instanceof 和 isPrototypeOf() ,所以寄生组合继承是引用类型最理想的继承方法。

七.class继承

class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

class Father{
  constructor(x, y) {
     this.x = x;
     this.y = y;
  }

  toString() {
     return '(' + this.x + ', ' + this.y + ')';
  }
}
class Son extends Father{
  constructor(x,y,color){
     super(x,y); // 调用父类的constructor(x, y)
     this.color = color;
  }
  toString() {
        console.log( super.toString()+this.color); // 调用父类的toString()
  }
}
let son = new Son(3,4,'red');
son.toString();//结果为(3,4)red

上面代码定义了一个Son类,该类通过extends关键字,继承了Father类的所有属性和方法。

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

以上就是JavaScript 常见的继承方式汇总的详细内容,更多关于JavaScript 继承方式的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
js DataSet数据源处理代码
Mar 29 Javascript
jQuery cdn使用介绍
May 08 Javascript
使用javascript实现ListBox左右全选,单选,多选,全请
Nov 07 Javascript
javascript利用apply和arguments复用方法
Nov 25 Javascript
javascript获取xml节点的最大值(实现代码)
Dec 11 Javascript
JavaScript数据类型检测代码分享
Jan 26 Javascript
基于javascript数组实现图片轮播
May 02 Javascript
100多个基础常用JS函数和语法集合大全
Feb 16 Javascript
angular bootstrap timepicker TypeError提示怎么办
Jun 13 Javascript
详解Vue用自定义指令完成一个下拉菜单(select组件)
Oct 31 Javascript
使用vux实现上拉刷新功能遇到的坑
Feb 08 Javascript
js实现数字滚动特效
Dec 16 Javascript
JavaScript 闭包的使用场景
Sep 17 #Javascript
javascript贪吃蛇游戏设计与实现
Sep 17 #Javascript
js实现简单的随机点名器
Sep 17 #Javascript
谈谈JavaScript中的垃圾回收机制
Sep 17 #Javascript
js对象属性名驼峰式转下划线的实例代码
Sep 17 #Javascript
详细分析JavaScript中的深浅拷贝
Sep 17 #Javascript
js实现鼠标滑动到某个div禁止滚动
Sep 17 #Javascript
You might like
php adodb连接带密码access数据库实例,测试成功
2008/05/14 PHP
php中的一些数组排序方法分享
2012/07/20 PHP
浅析PHP中的UNICODE 编码与解码
2013/06/29 PHP
PHP处理Json字符串解码返回NULL的解决方法
2014/09/01 PHP
yii2中使用Active Record模式的方法
2016/01/09 PHP
PHP实现bitmap位图排序与求交集的方法
2016/07/28 PHP
PHP-FPM和Nginx的通信机制详解
2019/02/01 PHP
JavaScript进阶教程(第四课第一部分)
2007/04/05 Javascript
js 兼容多浏览器的回车和鼠标焦点事件代码(IE6/7/8,firefox,chrome)
2010/04/14 Javascript
JavaScript高级程序设计 XML、Ajax 学习笔记
2011/09/10 Javascript
Jquery颜色选择器ColorPicker实现代码
2012/11/14 Javascript
js 弹出框只弹一次(二次修改之后的)
2013/11/26 Javascript
js中继承的几种用法总结(apply,call,prototype)
2013/12/26 Javascript
基于NodeJS的前后端分离的思考与实践(三)轻量级的接口配置建模框架
2014/09/26 NodeJs
jQuery在ul中显示某个li索引号的方法
2015/03/17 Javascript
javascript实现获取字符串hash值
2015/05/10 Javascript
javascript基础知识分享之类与函数化
2016/02/13 Javascript
理解javascript中的Function.prototype.bind的方法
2017/02/03 Javascript
vue组件父子间通信详解(三)
2017/11/07 Javascript
Taro集成Redux快速上手的方法示例
2018/06/21 Javascript
从零开始学习搭建React脚手架项目
2018/08/23 Javascript
原生JS实现手动轮播图效果实例代码
2018/11/22 Javascript
Node.js实现用户评论社区功能(体验前后端开发的乐趣)
2019/05/09 Javascript
JavaScript进阶(二)词法作用域与作用域链实例分析
2020/05/09 Javascript
python+pygame简单画板实现代码实例
2017/12/13 Python
python 模拟创建seafile 目录操作示例
2019/09/26 Python
详解使用Python下载文件的几种方法
2019/10/13 Python
使用Dajngo 通过代码添加xadmin用户和权限(组)
2020/07/03 Python
用CSS3实现瀑布流布局的示例代码
2017/11/10 HTML / CSS
lululemon美国官网:瑜伽服+跑步装备
2018/11/16 全球购物
英国名牌服装购物网站:OD’s Designer
2019/09/02 全球购物
英国最受欢迎的平价女士时装零售商:Roman Originals
2019/11/02 全球购物
应届生求职推荐信
2013/10/28 职场文书
一次性工伤赔偿协议书范本
2014/11/25 职场文书
开学第一天的感想
2015/08/10 职场文书
MySql开发之自动同步表结构
2021/05/28 MySQL