继承和原型链

来源:互联网 发布:360软件管家字体模糊 编辑:程序博客网 时间:2024/05/17 06:25

继承和原型链
语言是用来表达的工具。当我们需要代指某个东西的时候,通常称其为一个对象。在编程语言中,对象并不像真实世界中那样随处可见,随口可以指代。通常我们只有少数的原生对象,剩下的,需要我们自己去创建。在Java语言中,创建一只会“咯咯咯”叫的鸡时,我们是这么做的。

public class Chicken{  public void makeSound(){  System.out.println("咯咯咯");  }} Chicken littleChicken = new Chicken();

先定义一个Chicken类,在Chicken中对其诸多特质进行一番描述,然后根据这些描述new一个littleChicken出来。

这是在以类为中心的编程语言中实现产生对象的方法,就像是先制造模具,然后根据模具浇铸模件。还有一种思想,是将已经存在的最原始最基本的对象复制一遍,然后在新对象身上修修补补,这个原始对象,就是原型,这种思想,就是原型编程的思想,其代表语言,便是Javascript。

var Chicken = function(){
this.sound = “咯咯咯”;
};
Chicken.prototype = {
bark:function(){alert(this.sound)}
};
var littleChicken = new Chicken();

是和刚才定义类的方法看起来非常相似,同样是先定义类再根据类new一个对象。实际上,其原理是很不一样的。从原型讲起。

在Javascript中,一切对象都继承自Object.prototype,看起来Object确实比较像一个祖先级的东西,那么为什么不是继承自Object而是Object.prototype?因为Object其实是一个构造器,构造器,是一个类似于Java中类的存在。它像一个高科技机器,用来完成复制原型的动作。而高科技机器里的模版呢?它早已被放入机器,它就是机器的原型,也就是Object.prototype。当我们在说Object的时候,实际是在说那个高科技机器,Object.prototype,才是代指机器中的原型。

上面代码中的Chicken就是一个自定义的构造器。当我们用这个构造器new一个对象的时候,实际上是使用这个构造器克隆放在里面的模版,也就是prototype。构造器实际上就是一个普通的函数,这是在其中我们用this来进行了一些配置。this在这里指代的是即将要创建的对象,而prototype就是放在机器中的模版。当我们用Chicken这个构造器new 一个新的对象,就等于通过复制Chicken.prototype来创建一个新对象,然后给这个新对象增加一个sound属性,给其赋值“咯咯咯”。

简单地说:

所有的对象都是由另一个对象复制而来,被复制的那个对象就被称为新对象的“原型”

完成复制动作的工具,叫做构造器。构造器是一个普通的函数。通过给构造器定义prototype属性,来配置“原型”

通过调用Object.getPrototypeOf()或者object.proto可以得到此对象的原型,根据 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指派 someObject 的原型

所有的原型都继承自Object.prototype

Object.prototype继承自null,也就是说,原型链的尽头,是null

proto和prototype
首先,要明确几个点:

在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。
即:对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向该对象构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

方法(Function)
方法这个特殊的对象,除了和其他对象一样有上述proto属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

每个函数都是Function函数创建的对象,所以每个函数也有一个proto属性指向Function函数的原型。这里需要指出的是,真正形成原型链的是每个对象的proto属性,而不是函数的prototype属性,这是很重要的。

1.构造函数Foo()

构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。

2.原型对象Foo.prototype

Foo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。

3.实例

f1和f2是Foo这个对象的两个实例,这两个对象也有属性proto,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法啦。

另外:
构造函数Foo()除了是方法,也是对象啊,它也有proto属性,指向谁呢?

指向它的构造函数的原型对象呗。函数的构造函数不就是Function嘛,因此这里的proto指向了Function.prototype。

其实除了Foo(),Function(), Object()也是一样的道理。

原型对象也是对象啊,它的proto属性,又指向谁呢?
同理,指向它的构造函数的原型对象呗。这里是Object.prototype.

最后,Object.prototype的proto属性指向null。

总结:

对象有属性proto,指向该对象的构造函数的原型对象。
方法除了有属性proto,还有属性prototype,prototype指向该方法的原型对象。
实现继承的几种方法
1.原型链
基本思想:

利用原型让一个引用类型继承另外一个引用类型的属性和方法。

构造函数,原型,实例之间的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

原型链实现继承例子:

function SuperType() {    this.property = true;}SuperType.prototype.getSuperValue = function() {    return this.property;}function SubType() {    this.property = false;}//继承了SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){    return this.property;}var instance = new SubType();console.log(instance.getSuperValue());//true

这种是最简单实现原型继承的方法,直接把父类的对象赋值给子类构造函数的原型,这样子类的对象就可以访问到父类以及父类构造函数的prototype中的属性。

这种方法的优点很明显,实现十分简单,不需要任何特殊的操作;同时缺点也很明显,如果子类需要做跟父类构造函数中相同的初始化动作,那么就得在子类构造函数中再重复一遍父类中的操作。

2.借用构造函数
基本思想:
在子类型构造函数的内部调用超类构造函数,通过使用call()和apply()方法可以在新创建的对象上执行构造函数。

例子:

function SuperType() {    this.colors = ["red","blue","green"];}function SubType() {    SuperType.call(this);//继承了SuperType}var instance1 = new SubType();instance1.colors.push("black");console.log(instance1.colors);//"red","blue","green","black"var instance2 = new SubType();console.log(instance2.colors);//"red","blue","green"

3.组合继承
基本思想:
将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式。

例子:

var Parent = function(name){    this.name = name || 'parent' ;} ;Parent.prototype.getName = function(){    return this.name ;} ;Parent.prototype.obj = {a : 1} ;var Child = function(name){    Parent.apply(this,arguments) ;} ;Child.prototype = new Parent() ;var parent = new Parent('myParent') ;var child = new Child('myChild') ;console.log(parent.getName()) ; //myParentconsole.log(child.getName()) ; //myChild

上面这种方法在子类构造函数中通过apply调用父类的构造函数来进行相同的初始化工作,这样不管父类中做了多少初始化工作,子类也可以执行同样的初始化工作。但是上面这种实现还存在一个问题,父类构造函数被执行了两次,一次是在子类构造函数中,一次在赋值子类原型时Child.prototype = new Parent() ;,这是很多余的,所以我们还需要做一个改进。所以引入原型式继承。

4.原型式继承
基本想法:
借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
原型式继承的思想可用以下函数来说明:

function object(o) {    function F(){}    F.prototype = o;    return new F();}

上文提到的两次调用构造函数的问题,可以通过原型式继承解决。

  1. 寄生组合继承
function inheritPprototype(subType, superType) {    var prototype = object(superType.prototype);    prototype.constructor = subType;    subType.prototype = superType;}function SuperType() {    this.colors = ["red","blue","green"];}SuperType.prototype.sayName = function (){    alert(this.name);}function SubType() {    SuperType.call(this);//继承了SuperType    this.age = age;}inheritPprototype( subType, superType);SubType.prototype.sayAge = function (){    alert(this.age);}

该方法的原型继承方式很容易可以看出,通过在父类原型和子类原型之间加入一个临时的构造函数F,切断了子类原型和父类原型之间的联系,这样当子类原型做修改时就不会影响到父类原型。

同时只调用一次supertype构造函数,还能够正常使用instanceof 和isPrototypeOf()

原创粉丝点击