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 相关文章推荐
用javascript实现兼容IE7的类库 IE7_0_9.zip提供下载
Aug 08 Javascript
原生javascript获取元素样式属性值的方法
Dec 25 Javascript
基于JavaScript实现继承机制之调用call()与apply()的方法详解
May 07 Javascript
JavaScript获取当前页面上的指定对象示例代码
Feb 28 Javascript
JavaScript中的值类型转换介绍
Dec 31 Javascript
轻松实现js弹框显示选项
Sep 13 Javascript
JavaScript判断浏览器对CSS3属性是否支持的多种方法
Nov 13 Javascript
jQuery UI仿淘宝搜索下拉列表功能
Jan 10 Javascript
js读取json文件片段中的数据实例
Mar 09 Javascript
Vue.js实战之组件之间的数据传递
Apr 01 Javascript
详解Angular2表单-模板驱动的表单(Template-Driven Forms)
Aug 04 Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
Jan 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数组函数序列之array_pop() - 删除数组中的最后一个元素
2011/11/07 PHP
php中json_encode UTF-8中文乱码的更好解决方法
2014/09/28 PHP
使用PHP编写发红包程序
2015/07/22 PHP
让你的PHP7更快之Hugepage用法分析
2016/05/31 PHP
php检测mysql表是否存在的方法小结
2017/07/20 PHP
如何判断图片地址是否失效
2007/02/02 Javascript
niceTitle 基于jquery的超链接提示插件
2010/05/31 Javascript
详解参数传递四种形式
2015/07/21 Javascript
jQuery右侧选项卡焦点图片轮播特效代码分享
2015/09/05 Javascript
AngularJS实现全选反选功能
2015/12/08 Javascript
AngularJS使用ngOption实现下拉列表的实例代码
2016/01/23 Javascript
Nodejs 搭建简单的Web服务器详解及实例
2016/11/30 NodeJs
简单理解vue中实例属性vm.$els
2016/12/01 Javascript
详解bootstrap的modal-remote两种加载方式【强化】
2017/01/27 Javascript
mpvue中配置vuex并持久化到本地Storage图文教程解析
2018/03/15 Javascript
小程序登录态管理的方法示例
2018/11/13 Javascript
js实现带有动画的返回顶部
2020/08/09 Javascript
[49:20]2014 DOTA2国际邀请赛中国区预选赛5.21 CIS VS TongFu
2014/05/22 DOTA
[01:04:48]VGJ.S vs TNC Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
[02:05:03]完美世界DOTA2联赛循环赛 LBZS VS Matador BO2 10.28
2020/10/28 DOTA
Python爬虫实例扒取2345天气预报
2018/03/04 Python
在Python中分别打印列表中的每一个元素方法
2018/11/07 Python
使用Python创建简单的HTTP服务器的方法步骤
2019/04/26 Python
django的model操作汇整详解
2019/07/26 Python
Matplotlib中%matplotlib inline如何使用
2020/07/28 Python
俄罗斯玩具、儿童用品、儿童服装和鞋子网上商店:MyToys.ru
2019/10/14 全球购物
Vector, ArrayList, HashTable, HashMap哪些是线程安全的,哪些不是
2015/10/12 面试题
淘宝活动策划方案
2014/02/06 职场文书
营销总监岗位职责
2014/09/16 职场文书
机关党总支领导班子整改方案
2014/09/20 职场文书
2015领导干部廉洁自律工作总结
2015/07/23 职场文书
一年级语文教学随笔
2015/08/14 职场文书
使用react+redux实现计数器功能及遇到问题
2021/06/02 Javascript
css中z-index: 0和z-index: auto的区别
2021/08/23 HTML / CSS
Python编程源码报错解决方法总结经验分享
2021/10/05 Python
实操Python爬取觅知网素材图片示例
2021/11/27 Python