原型与继承学习笔记4

经过前三节的研究,我们终于可以对js这门无法实现接口继承的语言进行实现继承,为了更好的面向对象。。。。。

    原型链继承

这个原型链我们上节引用了一下,用它实现继承的基本思想是利用原型让一个引用类型引用另一个引用类型的属性和方法。即把一个函数(狭义上来举例)的所有属性和方法(这个函数的实例)赋值给另一个函数的prototype,使一个函数的实例可以通过__proto__原型一层层的调用另一个函数的所有属性。

有点绕,简单的说就是把超类的实例赋值给子类的prototype。看个例子:

 1 function SuperType () {
 2     this.name = ‘super‘;
 3 };
 4 SuperType.prototype.getName = function() {
 5     console.log(this.name);
 6 };
 7
 8 function SubType () {
 9     this.age = 10;
10 };
11 SubType.prototype=new SuperType();
12 SuperType.prototype.getAge = function() {
13     return this.age;
14 };
15
16 var sub = new SubType();
17 sub.getName();      //super

如上第11行为将SuperType的实例赋值给SubType的prototype,那么SubType的实例化对象sub便可以访问父类的方法getName(),因为这个方法已经存在于sub的原型上。

但是原型链也不是没自己的问题,最主要的问题来自包含引用类型值的原型属性会被所有实例共享。原型实际上会变成另一个类型的实例,于是,原先的实例属性就变成了现在的原型属性了。

 1 function SuperType () {
 2     this.family=[‘brother‘,‘sister‘];
 3 };
 4
 5 function SubType () {};
 6 SubType.prototype = new SuperType();
 7
 8 var obj1=new SubType();
 9 obj1.family.push(‘cousin‘)
10 console.log(obj1.family);       //["brother", "sister", "cousin"]
11
12 var obj2=new SubType();
13 console.log(obj2.family);       //["brother", "sister", "cousin"]

第6行将SuperType的实例化赋值给SubType的原型后,就相当于创建了哟个SubType.prototype.family属性一样,结果就是SubType所有的实例有共享这个属性,造成了通过obj1修改这个属性在obj2上也反应出来。

    借用构造函数

这种方式的思想是在子类构造函数的内部调用超类构造函数,也就是说通过call()或者apply()方法在新创建的SubType实例的环境下调用了SuperType构造函数,再通俗点这个新创建的对象是在借胎生子,看似是SubType的孩子,实质上是SuperType的孩子,当然可以继承两个人所有可继承的属性和方法。

 1 function SuperType () {
 2     this.family = [‘brother‘,‘sister‘];
 3 };
 4 function SubType () {
 5     SuperType.call(this);
 6 };
 7 var obj1 = new SubType();
 8 obj1.family.push(‘cousin‘);
 9 console.log(obj1.family);       //["brother", "sister", "cousin"]
10
11 var obj2 = new SubType();
12 console.log(obj2.family);        //["brother", "sister"]

如上在第7行实例化obj1的时候,调用SubType的构造函数,而SubType的内在实际上又执行了Supertype的构造函数,所以实现了继承,而obj2会在实例化的时候再次调用SuperType构造函数,重新得到一个SuperType.prototype.family,同时call()和apply()函数都是可以传参的,完美解决上述原型链继承的问题。在此就不演示传参了。

但是借用构造器就没问题吗,当然不,其实和创建对象使用的构造函数模式的问题是一样的——所有的方法必须写在构造函数里,那么这样每次都会实例化一次方法,这样做的后果就是函数无法实现复用,不符合面向对象的思想。

    组合继承

这种方式其实是让上述两种方式取长补短,使用原型链实现对原型属性和方法的继承,使用构造函数实现实例属性的继承,这样既保证函数的复用,又可以保证每个实例都有自己独有的属性。

 1 function SuperType (name) {
 2     this.name=name;
 3     this.color=[‘red‘,‘blue‘]
 4 };
 5 SuperType.prototype.sayName=function  () {
 6     console.log(this.name);
 7 };
 8 function SubType (name) {
 9     SuperType.call(this,name);
10 };
11 SubType.prototype=new SuperType();
12 SubType.prototype.constructor = SubType;
13
14 var obj1 = new SubType(‘James‘);
15 obj1.color.push(‘black‘);
16 console.log(obj1.color);        //[‘red‘,‘blue‘,‘black‘]
17 obj1.sayName();     //James
18
19 var obj2 = new SubType(‘Wade‘);
20 console.log(obj2.color);        //[‘red‘,‘blue‘]
21 obj2.sayName();     //Wade

第9行为借用构造函数,保证了实例化的时候每次都会取一个新的name和color,而原型上的方法可以实现该方法的共享,保证复用性,第12行属于对原型构造的重定向,因为11行的时候SubType的所有属性均来自SuperType,其中自然也包括这个constructor。一句话总结:原型继承太热衷于共享,把应该共享的(方法)和不应该共享的(有个性的实例属性)通通共享,构造函数法太热衷于个性,把应该共享的都独自分配了。这种组合继承方法融合了他们优点,已经成为最常用的继承模式。

    原型式继承

其实就是把上边的组合式继承的构造函数过程进行了封装,先看看代码:

1 function object (o) {
2     function F(){};
3     F.prototype=o;
4     return new F();
5 }

首先说这个个临时中转函数,在object函数的内部,传入了一个对象,将这个对象作为这个构造函数的原型,最后返回这个临时函数的新实例,这个新的实例具有对象o的所有的属性和方法。

但是这个F.prototype=o;语句会共享这个对象o,和原型链继承遇到的问题是一样的,这时候就得说一下我们以前的一个老朋友Object.create()方法,这个方法接受两个参数,第一个参数用作新对象原型的对象和一个为新对象定义额外属性的对象(可选)。在只传递一个参数的情况下,这个方法和上述object是一样的。所以也没有克服包含引用类型值的属性共享的问题,但可以提供一个完美的方案,往下看。

    寄生式继承

这种方式会创建一个用于封装继承过程的临时中转函数,在该函数的内部做了某些操作后返回这个对象,如下代码:

    寄生组合式继承

前边谈过,组合式继承是最常用的一种继承模式,但也有自己的问题,他在任何情况下都会调用两次构造函数,回到上边,第一次在第11行创建子类型的原型时new了一下,第二次在第9行子类型构造函数的内部,所以目前最完美的解决方案就出来了,就是这个寄生组合式继承:

这种方式实质上是用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型,相当于对Subtype.prototype=new Super();的另一种操作。首先又是一个临时中转函数:

1 function inheritProtoType (subType,superType) {
2     var prototype = Object.create(superType).prototype;
3     prototype.constructor = subType;
4     subType.prototype=prototype;
5 };

用这个函数的调用代替组合继承中的Subtype.prototype=new Super();,则可以得到:

 1 function inheritProtoType (subType,superType) {
 2      var prototype = Object.create(superType).prototype;
 3      prototype.constructor = subType;
 4      subType.prototype=prototype;
 5  };
 6
 7 function SuperType (name) {
 8     this.name = name;
 9     this.color = [‘blue‘,‘red‘];
10 };
11 SuperType.prototype.sayName=function  () {
12     console.log(this.name);
13 };
14 function SubType(name){
15     SuperType.call(this,name);
16 };
17 inheritProtoType(SubType,SuperType);
18 var obj = new SubType(‘success‘);
19 obj.sayName();      //success
20 obj.color.push(‘black‘);
21 console.log(obj.color)      //["blue", "red", "black"]
22
23 var obj = new SubType(‘thanks‘);
24 obj.sayName();      //thanks
25 console.log(obj.color)       //["blue", "red"]

到这基本也就完了,主要的思想在于认识到prototype的动态性以及与constructor的关系,其实上边代码是可以再简化一下的,就是把这个中转函数拆开来写,那怎么改呢,其实以前是说过得:

 1 function SuperType (name) {
 2     this.name = name;
 3     this.color = [‘blue‘,‘red‘];
 4 };
 5 SuperType.prototype.sayName=function  () {
 6     console.log(this.name);
 7 };
 8 function SubType(name){
 9     SuperType.call(this,name);
10 };
11 SubType.prototype =Object.create(SuperType).prototype;
12 SubType.prototype.constructor = SubType;
13 var obj = new SubType(‘success‘);
14 obj.sayName();      //success
15 obj.color.push(‘black‘);
16 console.log(obj.color)      //["blue", "red", "black"]
17
18 var obj = new SubType(‘thanks‘);
19 obj.sayName();      //thanks
20 console.log(obj.color)       //["blue", "red"]

归根结底,这个关键点在于这个Object.create()函数,关于这个函数在原型式继承里边说到了。

认识到函数是对象,对象的__proto__原型,函数的prototype属性,他们之间的规律后,理解起来相对来说会简单很多,感谢陪伴,写出来比想象中难多了,本来以为1天的工作量实际写起来必须把前后串联起来,考虑的也比自己单纯理解要多一点,希望能给大家带来一点想得到的东西。

时间: 2025-02-01 17:00:29

原型与继承学习笔记4的相关文章

原型与继承学习笔记1

浅谈对象 面向对象原型链继承这块,应该算是javascript中最难理解的部分了,小弟脑子比较难转弯,也是看了好久视频,博文,慢慢的才有了自己的理解,现在记录一下学习的内容和总结.首先第一节应该说说对象这个东西了,js中对象和其他语言还是有所不同的,现在切入正题,开始浅谈对象. 什么是对象 定义(ECMA-262):无序属性的集合,其属性可以包含基本值.对象或者函数. 通过定义可以看出来,对象是属性的集合,这些属性又会是一个基本值,一个函数或者又是一个新的对象.记住,函数也是对象,了解这点以后原

原型与继承学习笔记2

上节我们讨论了对象的定义和对象的创建,知道了函数也是对象,知道了对象都是由函数创建的,知道了对象的原型和函数的原型属性的关系.这节说一下关于对象属性的操作,下节就可以切入正题了. 属性删除 1 var person = {age : 28, title : 'fe'}; 2 delete person.age; // true 3 delete person['title']; // true 4 person.age; // undefined 5 delete person.age; //

C#中面向对象编程机制之继承学习笔记

继承反应了类和类之间的关系. 世界上很多事物都是有共性的,共性的那一部分我们就抽象为基类,用于派生其它类,这样提高了代码的复用性,使得代码的结构清晰易读,而且易于代码的扩展和维护. C#的继承只能继承自一个基类,这一点不同于C++的继承. C#的继承具有传递性,即B继承自A,C继承自B,则C具有A的所有特性. C#的继承隐式为public的. 假如不在派生类构造器中显示调用一个基类构造器,编译器会自动插入对基类的默认构造器的一个调用,然后才会执行派生类构造器中的代码, 如果基类没有默认的构造器,

【转载】Javascript原型继承-学习笔记

阮一峰这篇文章写的很好 http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html 笔记如下: 一直很难理解Javascript语言的继承机制. 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(p

JavaScript继承学习笔记

JavaScript作为一个面向对象语言(JS是基于对象的),可以实现继承是必不可少的,但是由于本身并没有类的概念,所以不会像真正的面向对象编程语言通过类实现继承,但可以通过其他方法实现继承.(javascript中的继承是通过原型链来体现的http://www.cnblogs.com/amumustyle/p/5435653.html)实现继承的方法很多,下面就只是其中的几种. 一.原型链继承 1 function Person() { //被继承的函数叫做超类型(父类,基类) 2 this.

《C++ Primer Plus》14.2 私有继承 学习笔记

C++(除了成员变量之外)还有另一种实现has-a关系的途径——私有继承.使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员.(如果使用保护继承,基类的公有成员和保护成员都将称为派生类的保护成员.)这意味着基类方法将不会称为派生类对象共有接口的一部分,但可以在派生类的成员函数中使用它们.14.2.1 Student类示例(新版本)Student类应从两个类派生而来,因此声明将列出这两个类:class Student : private std::string, private std

《C#高级编程》【第四章】继承 -- 学习笔记

计算机程序,在很大的程度上是为了描述和解决现实问题.在面向对象语言中的类很好的采用了人类思维中抽象和分类的方法,类和对象的关系很好的反映了个体与同类群体的共同特征的关系.但是在诸多共同点之下还是存在着些许差异.于是面向对象语言中设计了继承机制,允许我们在保持原有类特性的基础上,进行拓展.由于类的继承和派生机制的引入,使得代码的重用性和可扩充性大大提高.利用这个机制我们还可以站在巨人的肩膀上就行开发---利用别人写好的类进行扩充,这样又可以提高我们的开发效率.在派生新类的过程一般来说有三个步骤:吸

继承 学习笔记

package ctgu.java.java; public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this

《javascript高级程序设计(第二版)》学习(4)原型与继承

声明:这类属于学习笔记,主要是摘录书中内容,比较少的整理.内容经常是跳跃的,建议您阅读书本,收益更大. function Person(){} Person.prototype.name="tom"; //这里等于重写了原型对象 //切断了与构造函数之间的联系 Person.prototype={ name:"mike", age:22 }; //得到的不是Person了,而是Object console.log(Person.prototype.construct