javascript 的七种继承方式(一)原型链

原型链的概念

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简述一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,反过来原型对象又都包含一个指向构造函数的指针,而实例又都包含一个指向原型对象的内部指针。如下图所示。

那么,如果我们让原型对象等于另一个类型的实例,结果会怎样呢,显然,此时的原型对象将包含一个指向另一个原型的指针,相应的另一个原型对象也包含着指向另一个构造函数的指针。假如另一个原型对象又是另另一个类型的实例,上述关系依然成立,以此类推,就构成了实例与原型的链条,这就是所谓的原型链。

原型链实现继承

function Father(){
    this.surname = 'li'
}

Father.prototype.getSurname = function(){
    return this.surname;
}

function Son(){
    this.name = 'alvin'
}

//这里Son的原型对象指向了Father的实例,也就是继承了Father
Son.prototype = new Father();

Son.prototype.getName = function(){
    return this.name;
}

var son = new Son();
console.log(son.getSurname()); //输出 li

以上代码我们定义了两个类型:Father和Son,每个类型都分别有自己的属性和方法。两者唯一的区别就是Son继承了Father,而继承是通过创建Father的实例,并将该实例赋值给Son.prototype实现。实现的本质就是重写了Son的原型对象。那么原来存在于Father中的属性和方法现在也都存在于Son.prototype中了。同时我们又给Son的prototype添加了新的方法,这样Son就继承了Father的属性和方法,同时又有了自己的新方法。这样就实现了简单的继承。如下图描述了实例以及构造函数和原型之间的关系

在上图中我们不难发现,在Son Prototype中并没有标记contructor,实际上我们没有使用Son默认提供的原型,而是用新原型(Father的实例)重写了默认的原型,因此新原型不仅具有作为Father实例拥有的所有属性和方法,其内部还有个指向Father原型的指针。最终就是:instance指向Son的原型,Son的原型又指向Father的原型。getSurname依然存在于Father.prototype中,但name则位于Son.prototype中了,这是因为name是实例属性,getSurnam是原型方法。既然Son.prototype是Father的实例,那么name属性自然就位于Father实例中了。此外instance.constructor现在指向的是Father,因为Son.prototype中的constructor被Father的实例重写了并且没有手动指定constructor。如果想让Son.prototype中的constructor重新指向Son的话,可以手动指定,如下所示

Son.prototype.constructor = Son

原型链本质上扩展了原型的搜索机制,当访问一个实例属性时,首先会在实例中搜索,如果没有找到则到实例的原型中继续搜索,如果依然没有找到则会继续到父类的原型中搜索,直到原型链的末端为止。

默认原型

在上面的示例的原型链中还少一个默认的原型,我们知道,所有的引用类型都继承了Object,而这个继承也是通过原型链实现的,所有函数的默认原型都是Object的实例,因此默认原型都包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toSting(), valueOf()等默认方法的根本原因。结合上面的示例的完整图如下:

原型链的2个问题 

原型链虽然可以实现继承,但也存在一些问题。其中最主要的包含引用类型的原型。包含引用类型的原型属性将被所有实例共享,这也正是为什么在构造函数中定义属性而不是在原型中定义的原因。

function Father(){
    this.colors = ['red','pink','green']
}


function Son(){

}

//这里Son的原型对象指向了Father的实例,也就是继承了Father
Son.prototype = new Father();

var son1 = new Son();
son1.colors.push('yellow');
console.log(son1.colors);//输出:red,pink,green,yellow

var son2 = new Son();
console.log(son2.colors);//输出:red,pink,green,yellow

 以上代码很好的诠释了这个问题,我们只是给实例1的colors属性中添加了yellow颜色,当我们打印实例 2的colors属性时,发现值也随着改变了。

第二个问题就是在创建子类的实例时,不能向父类的构造函数中传递参数。

以上就是原型链及原型链继承的介绍。如有问题欢迎留言。

猜你喜欢

转载自blog.csdn.net/lixiaosenlin/article/details/108081241