最近一直在研究js面向对象,原型链继承是一个难点,下面是我对继承的理解
如下文章借鉴自CSDN季诗筱的博客数组
ES中描述了原型链的概念,并将原型链做为实现继承的主要方法;
基本思想:利用一个引用类型继承另外一个引用类型的属性和方法:
简单回顾下: 构造函数 -- 原型 -- 实例 三者之间的关系
构造函数:function Person(){}
每一个构造函数都有一个原型对象(Person.prototype),
原型对象都包含一个指向构造函数的指针(constructor),
(其实原型对象也是一个对象,也有一个 __proto__
指针,指向他所继承的对象)
而实例都包含着一个指向圆形对象的内部指针([[prototye]] 又称__proto__);
每一个实例也有一个constructor属性默认调用原型对象的constructor属性(!!!)函数
让原型对象等于另外一个构造函数的实例, 显然,此时的原型对象将包含一个指向另外一个原型对象的指针([[prototype]]),从而拥有该原型对象的属性和方法this
另外一个原型对象中也包含着指向另外一个构造函数的指针。那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念!spa
如上如所示,这种关系直到 当某个原型对象的 contructor属性指向 Object 为止.net
function SuperType(){ this.prototype = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue()); //true
以上代码定义了两个类型:SuperType 和 SubType.
每一个类型分别有一个属性和方法。
他们的主要区别是SubType继承了SuperType,而继承是经过建立SuperType的实例,并将该实例赋值给SubType.prototype实现的。
实现的本质是重写原型对象,代之以一个新类型的实例。
换句话说,原来存在于SuberType的实例中的全部属性和方法,如今存在于SubType.prototype中了。
在确立了继承关系以后,咱们给SubType.prototype添加了一个方法,,这样就在继承了SuperType的属性和方法的基础上又添加了一个新方法。
这个例子的实例以及构造函数和原型之间的关系以下图所示
图:prototype
在上面的代码中,咱们没有使用SubType默认提供的原型,而是给他换了一个新原型;
这个原型就是SuperType的实例。
因而,新原型不只有做为一个SuperType的实例所拥有的所有属性和方法,并且内部还有一个指针,指向了SuperType的原型。
最终结果是这样的:instace指向SubType的原型,SubType的原型又指向SuperType的原型。指针
在经过原型链继承的状况下,搜索过程就得以沿着原型链继续向上。就拿上面的例子来讲,调用instance.getSuperValue()会经历三个步骤:
1.搜索实例
2.搜索SubType.prototype
3.搜索SuperType.prototype
最后一步才会找到该方法,再找不到该属性或方法的状况下,搜索过程老是要一环一环地前行到原型链末端才会停下来.
别忘记默认原型Object
事实上前面例子中展现的原型链还少一环,咱们知道,全部引用类型都默认继承了Object,而这个继承也是经过原型链实现的。
你们记住,全部函数的默认原型都是Object的实例,所以默认原型内部都会包含一个指针,指向Object.prototype。这也正是全部自定义类型都会继承toString()等默认方法的根本缘由.code
原型链虽然很强大,能够用它实现继承,但也存在一些问题。对象
其中,最主要的问题来自包含引用类型值的原型。
想必你们还记得,咱们前面介绍过包含引用类型值的属性会被全部实例共享;而这也正是为何要在构造函数中,而不是在原型对象中定义属性的缘由。blog
在经过原型来实现继承时,原型实际上会变成另外一个类型的实例。因而原先的实例属性也就瓜熟蒂落地变成了如今的原型属性了.
例子说明问题:
function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ } SubType.prototype = new SuperType(); // 继承了SuperType var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); // red,blue,green,black var instance2 = new SubType(); alert(instance2.colors); // red,blue,green,black
这个例子中的SuperType构造函数定义了一个colors属性,该属性包括一个数组(引用类型值)。SuperType的每一个实例都会有各自包含本身数组的colors属性。当SubType经过原型链继承了SuperType以后,SubType.prototype就变成了SuperType的一个实例,所以他也拥有了一个他本身的colors属性-----就跟专门建立了一个SubType.prototype.colors属性同样。
但结果是什么呢? 全部实例都会共享这个colors属性!!!
问题1:结果是SubType的全部实例都会共享这一个colors属性。而咱们对instance1.colors的修改可以经过instance2.colors反映出来,就已经充分证实这一点了
问题2:在建立自定义类型的时候,不能向超类型的构造函数中传递参数。
实际上,应该说是没有办法在不影响全部实例的状况下,给超类型构造函数传递参数。
有鉴于此,在加上前面刚刚讨论的因为原型中所包含引用类型值所带来的问题,实践中中不多单独使用原型链
1.借用构造函数2.组合式继承
3.原型式继承
4.寄生式继承5.寄生组合式继承
这里来谈一下最经常使用的组合式继承和寄生组合式继承
组合继承,有时候也叫作经典继承,指的是将原型链和借用构造函数的技术组合到一块儿,从而发挥两者之长的一种继承模式。
其背后的思路是使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例的属性的继承
请看例子:
function SuperType(name){ this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ SuperType.call(this,name); //继承属性, 第二次调用SuperType this.age = age; } //继承方法 SubType.prototype = new SuperType(); //第一次调用SuperType SubType.prototype.constructor = SubType(); // 至关重要,此处 SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("leo",29); instance1.colors.push("black"); alert(instance1.colors); // r,b,g,b instance1.sayName(); // leo instance1.sayAge(); // 29 var instance2 = new SubType("lck",34); alert(instance2.colors); // r,b,g instance2.sayName(); // lck instance2.sayAge(); // 34
在例子中,SuperType构造函数定义了两个属性:name 和 colors。SuperType的原型定义了一个方法sayName()。
SubType构造函数在调用SuperType构造函数传入了name参数,紧接着又定义了他本身的属性age,而后,将SuperType的实例赋值给SubType的原型,而后又在该新原型上定义了方法sayAge()方法,
这样一来,就可让两个不一样的SubType实例既分别拥有本身属性和公共的colors属性,又可使用相同的方法
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优势,成为js中最经常使用的继承模式。
并且 instanceof和isPrototypeOf()也可以用于识别基于组合继承建立的对象
**另外说一下:
1.任何一个Prototype对象都有一个constructor指针,指向它的构造函数;
2.每一个实例中也会有一个constructor指针,这个指针默认调用Prototype对象的constructor属性。
结果:当替换了子类的原型以后,即 SubType.prototype = new SuperType()以后,
SubType.prototype.constructor 就指向了SuperType(),
SubType的实例的constructor也指向了SuperType(),这就出现问题了。
由于这形成了继承链的紊乱,由于SubType的实例是由SubType构造函数建立的,如今其constructor属性却指向了SuperType,为了不这一现象,就必须在替换prototype对象以后,为新的prototype对象加上constructor属性,使其指向原来的构造函数。
组合式继承的缺点
组合继承最大的问题就是不管在什么状况下,都会两次调用超类型构造函数;
一次是在建立子类型的原型的时候,
另外一次是在子类型构造函数内部。
没错子类型最终会包含超类型对象的所有实例属性,但咱们不得不在调用子类构造函数时重写这些属性
此种模式解决了组合式继承的缺点
原理:经过借用构造函数来继承属性,经过原型链的混成形式来继承方法。
思路:没必要为了指定子类的原型而调用超类型的构造函数,咱们所须要的无非就是超类型原型的一个副本而已。
寄生式组合继承的基本模式以下所示:
function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); // 建立对象 prototype.constructor = subType; // 加强对象 subType.prototype = prototype; // 指定对象 }
这个示例中的inheritPrototype()函数实现了寄生式组合继承的最简单形式。
这个函数接收两个参数:子类型构造函数和超类型构造函数。
在函数内部:
第一步:是建立超类型原型的一个副本
第二步:为建立的副本添加constructor属性,从而弥补因失去原型而失去的默认的constructor属性
第三步:将建立的对象(即副本)赋值给子类型的原型。
这样,咱们就能够调用inherit-Protoype()函数的语句,去替换前面例中为了子类型原型赋值的语句了
实例以下:
//借助原型能够基于已有对象建立新对象 function object(o){ var F = function(){}; // 建立一个空对象 F.prototype = o; return new F(); //返回出一个实例对象 } //寄生式组合继承 function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //建立父构造函数实例对象副本 prototype.constructor = subType; // 重写将子类原型对象的constructor属性 subType.prototype = prototype; // 父类实例赋值给子类原型 }; function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } //调用此方法代替前面赋值语句,可解决两次调用超类型构造函数的问题 inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("lck",29); console.log(instance1.name,instance1.age,instance1.colors); // lck,29,r,b,g instance1.sayName(); // lck instance1.sayAge() // 29
优势:
这个例子的高效率体如今他只调用了一次SuperType构造函数,
而且所以避免了在SubType.prototype上面建立没必要要的,多余的属性。
与此同时,原型链还能保持不变;
所以,还能正常使用instanceof 和 isPrototypeOf()。
开发人员广泛认为寄生式组合式继承是引用类型最理想的继承范式