JavaScript 常见的继承方式汇总

原型链机制:

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

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

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

  每一个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() 等。

  结果如下图所示:

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

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 继承方式的资料请关注我们其它相关文章!

(0)

相关推荐

  • 6种JavaScript继承方式及优缺点(小结)

    温馨提示:想要更好的理解JS继承方式,须了解构造函数.原型对象.实例化对象.原型链等概念 第一种:原型链继承 利用原型链的特点进行继承 function Parent(){ this.name = 'web前端'; this.type = ['JS','HTML','CSS']; } Parent.prototype.Say=function(){ console.log(this.name); } function Son(){}; Son.prototype = new Parent();

  • 重新理解JavaScript的六种继承方式

    类式继承(构造函数) JS中其实是没有类的概念的,所谓的类也是模拟出来的.特别是当我们是用new 关键字的时候,就使得"类"的概念就越像其他语言中的类了.类式继承是在函数对象内调用父类的构造函数,使得自身获得父类的方法和属性.call和apply方法为类式继承提供了支持.通过改变this的作用环境,使得子类本身具有父类的各种属性. var father = function() { this.age = 52; this.say = function() { alert('hello

  • JavaScript中的继承方式详解

    js继承的概念 js里常用的如下两种继承方式: 原型链继承(对象间的继承) 类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念.所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现 在面向对象的语言中,我们使用类来创建一个自定义对象.然而js中所有事物都是对象,那么用什么办法来创建自定义对象呢?这就需要用到js的原型: 我们可以简单的把prototype看做是一个模版,新创建的自定义对象都是这个模

  • Javascript编程中几种继承方式比较分析

    本文实例分析了Javascript编程中几种继承方式比较.分享给大家供大家参考,具体如下: 开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究

  • js的三种继承方式详解

    1.js原型(prototype)实现继承 代码如下 <body> <script type="text/javascript"> function Parent(name,age){ this.name=name; this.age=age; this.sayHi=function(){ alert("Hi, my name is "+this.name+", my age is "+this.age); } } //C

  • js的2种继承方式详解

    js中继承可以分为两种:对象冒充和原型链方式 一.对象冒充包括三种:临时属性方式.call()及apply()方式1.临时属性方式 复制代码 代码如下: function Person(name){     this.name = name;     this.say = function(){          alert('My name is '+this.name);     }}function F2E(name,id){     this.temp = Person;     thi

  • Javascript中的几种继承方式对比分析

    开篇 从'严格'意义上说,javascript并不是一门真正的面向对象语言.这种说法原因一般都是觉得javascript作为一门弱类型语言与类似java或c#之类的强型语言的继承方式有很大的区别,因而默认它就是非主流的面向对象方式,甚至竟有很多书将其描述为'非完全面向对象'语言.其实个人觉得,什么方式并不重要,重要的是是否具有面向对象的思想,说javascript不是面向对象语言的,往往都可能没有深入研究过javascript的继承方式,故特撰此文以供交流. 为何需要利用javascript实现

  • javascript学习笔记(九)javascript中的原型(prototype)及原型链的继承方式

    在使用面向对象编程时,对象间的继承关系自然少不了!而原型正是实现javascript继承的很重要的一种方法! 我们首先来看以下代码: 复制代码 代码如下: function person(name, age) { this.name = name; this.age = age; } person.prototype.getInfo = function() { alert("My name is "+this.name+", and I have "+this.a

  • JavaScript的六种继承方式(推荐)

    继承是面向对象编程中又一非常重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的. 原型链 首先得要明白什么是原型链,在一篇文章看懂proto和prototype的关系及区别中讲得非常详细 原型链继承基本思想就是让一个原型对象指向另一个类型的实例 function SuperType() { this.property = true } SuperType.prototype.getSuperValue = function () { return thi

  • 浅谈js中的三种继承方式及其优缺点

    第一种,prototype的方式: //父类 function person(){ this.hair = 'black'; this.eye = 'black'; this.skin = 'yellow'; this.view = function(){ return this.hair + ',' + this.eye + ',' + this.skin; } } //子类 function man(){ this.feature = ['beard','strong']; } man.pr

  • 浅析2种JavaScript继承方式

    js继承方法最主要的是2种,一种是通过原型的方式,一种是通过借用call&apply的构造函数方式. 1.原型(prototype): function Body(name,age){// 创建一个Body类 this.name = name;// 赋予基础属性name.age this.age = age; } Body.prototype.sayName =function() {// 给原型定义一个sayName的方法 console.log(this.name); } var a = n

随机推荐