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 相关文章推荐
prototype 中文参数乱码解决方案
Nov 09 Javascript
JavaScript 模拟用户单击事件
Dec 31 Javascript
js函数排序的实例代码
Jul 01 Javascript
jQuery 2.0.3 源码分析之core(一)整体架构
May 27 Javascript
5款JavaScript代码压缩工具推荐
Jul 07 Javascript
node.js中的fs.mkdir方法使用说明
Dec 17 Javascript
jquery使用slideDown实现模块缓慢拉出效果的方法
Mar 27 Javascript
每天一篇javascript学习小结(RegExp对象)
Nov 17 Javascript
javascript 继承学习心得总结
Mar 17 Javascript
Node.JS段点续传:Nginx配置文件分段下载功能的实现方法
Mar 12 Javascript
vue下canvas裁剪图片实例讲解
Apr 16 Javascript
谈谈JavaScript中的垃圾回收机制
Sep 17 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英文单词统计器
2016/06/23 PHP
Avengerls vs Newbee BO3 第三场2.18
2021/03/10 DOTA
基于Web标准的UI组件 — 树状菜单(2)
2006/09/18 Javascript
JavaScript中对象属性的添加和删除示例
2014/05/12 Javascript
nodeJS代码实现计算交社保是否合适
2015/03/09 NodeJs
jQuery position() 函数详解以及jQuery中position函数的应用
2015/12/14 Javascript
jQuery绑定事件-多种实现方式总结
2016/05/09 Javascript
微信小程序 122100版本更新问题解决方案
2016/12/22 Javascript
深究AngularJS中ng-drag、ng-drop的用法
2017/06/12 Javascript
使用jquery的jsonp如何发起跨域请求及其原理详解
2017/08/17 jQuery
详解EasyUi控件中的Datagrid
2017/08/23 Javascript
使用命令行工具npm新创建一个vue项目的方法
2017/12/27 Javascript
JavaScript的console命令使用实例
2019/12/03 Javascript
跟老齐学Python之私有函数和专有方法
2014/10/24 Python
Python操作Access数据库基本步骤分析
2016/09/19 Python
python数据清洗系列之字符串处理详解
2017/02/12 Python
Python网络爬虫中的同步与异步示例详解
2018/02/03 Python
把django中admin后台界面的英文修改为中文显示的方法
2019/07/26 Python
通过Turtle库在Python中绘制一个鼠年福鼠
2020/02/03 Python
Python SMTP发送电子邮件的示例
2020/09/23 Python
css3 transform属性详解
2014/09/30 HTML / CSS
香蕉共和国Banana Republic官网:美国GAP旗下偏贵族风格服饰品牌
2016/11/21 全球购物
物业管理毕业生个人的求职信
2013/11/30 职场文书
写给女生的道歉信
2014/01/14 职场文书
初中教师业务学习材料
2014/05/12 职场文书
公司晚会策划方案
2014/05/17 职场文书
计算机软件专业求职信
2014/06/10 职场文书
重大事项社会稳定风险评估方案
2014/06/15 职场文书
医德医风自我评价
2014/09/19 职场文书
2014教师评职称工作总结
2014/11/10 职场文书
2015年学生会纪检部工作总结
2015/03/31 职场文书
2015年学生会部门工作总结
2015/04/21 职场文书
学校通报表扬范文
2015/05/04 职场文书
论语读书笔记
2015/06/26 职场文书
SQL SERVER存储过程用法详解
2022/02/24 SQL Server
JavaScript中reduce()的用法
2022/05/11 Javascript