总结JS中的继承

ECMAScript没有明确的继承机制,而是通过模仿实现的,而且ECMAScript只支持实现继承(即继承实际的方法,还有接口继承),实现继承主要是依靠原型链来实现的。

昨天总结了创建对象的几种方法:JavaScript创建对象的几种方式,既然创建了对象,那么我们就要给它实际用起来,这一节就好好总结一下JS中的继承方法,这也是一道重要的面试题目!

实现继承的4种方法

1.原型链(JS中的实现继承主要是依靠原型链来实现的)

即让原型对象等于另一个类型的实例,即重写原型对象,代之以一个新类型的实例,像下面这样:

function Super(){
    this.supProperty = true;
}
Super.prototype.getSuperValue = function(){
    return this.supProperty;
}
function Sub(){
    this.subProperty = false;
}
Sub.prototype = new Super();// 继承
Sub.prototype.getSubValue = function(){
    return this.subProperty;
}
var sub = new Sub();
alert(sub.getSuperValue()); // true 
alert(sub.getSubValue()); // false

原型链的构建是通过将一个类型的实例复制给另一个构造函数的原型实现的。这样,子类型就能够访问超类型中的所有属性和方法。但是问题是对象实例共享所有继承的属性和方法,所以不适合单独使用。

另外提一点,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()valueOf()等默认方法的根本原因。

子类型有时候需要覆盖父类型中的某个方法,或者需要添加父类中不存在的方法,给原型添加新方法的代码一定要放在替换原型的语句之后,像上面的Sub.prototype.getSubValue那样,放在替换原型语句之后。

另外,在通过原型链实现继承时,不能使用对象字面量创建原型方法,前面已经提到过,这样会切断原型链。

注意:

  • 前面已经提到过,当原型中包含引用类型值时,会被所有实例共享,所以应该要在构造函数中定义属性而非原型对象中定义属性,回顾一下:
function Super(){
    this.colors = ["red","green","pink"];
}
function Sub(){}
Sub.prototype = new Super();
var sub1 = new Sub();
sub1.colors.push("black");
alert(sub1.colors); // red,green,pink,black

var sub2 = new Sub();
alert(sub2.colors); // red,green,pink,black

这里的colors属性是在父类构造函数中定义的,当通过原型链被子类继承后,即相当于是在子类的原型对象中定义了colors属性,所以会被所有子类实例所共享,这是不应该的,我们可以借用构造函数来解决这个问题。

2.借助构造函数

它的思想是在子类型构造函数内部调用父类型构造函数,而函数只不过是在特定环境中执行代码的对象,所以可以通过使用apply(),call()方法可以在新创建的对象上执行构造函数。像下面这样:

function Super(){
    this.colors = ["red","green","pink"];
}
function Sub(){
    Super.call(this);//这里的this,代表Sub
}
Sub.prototype = new Super();
var sub1 = new Sub();
sub1.colors.push("black");
alert(sub1.colors); // red,green,pink,black

var sub2 = new Sub();
alert(sub2.colors); // red,green,pink

使用call()方法,实际上是在将要创建的Sub实例的环境下调用了Super构造函数,这样一来,就会在新的Sub对象上执行Super()函数中定义的所有对象初始化代码。结果就是每个Sub的实例都会自己的colors属性了。

  • 传递参数
    通过借用构造函数,还解决了在子类型构造函数中向父类型构造函数传参数的问题,像下面这样:
function Super(name){
    this.name = name;
}
function Sub(age){
    Super.call(this,"Stan");//这里的this,代表Sub
    this.age = age;
}
Sub.prototype = new Super();
var sub = new Sub(0000);

alert(sub.name); //继承来的 Stan
alert(sub.age); //实例属性 0

单独使用构造函数,不使用原型链话,一是方法都在构造函数中定义,因此函数复用就无从谈起了,而且在父类型的原型中定义的方法,在子类型中是不可见的。所以就有了下面的组合继承。

3.组合继承

就是结合原型链和借用构造函数,使用原型链阶乘共享的属性和方法,而通过借用构造函数继承实例属性。这种模式是目前用最多的,先看下面的代码:

function Super(name){
    this.name = name;
    this.colors = ["red","green","pink"];
}
Super.prototype.sayName = function(){
    alert(this.name);
}
function Sub(name,age){
    this.age = age;
    Super.call(this,name); // 这里不要忘记传参数name
}

Sub.prototype = new Super(); 
// 这里如果传参数会怎么样? new Super("haha")其实是不会影响的,会被传进来的属性屏蔽掉
Sub.prototype.sayAge = function(){
    alert(this.age);
}
var sub1 = new Sub("Stan",11);
sub1.sayName(); // Stan
sub1.sayAge(); // 11
sub1.colors.push("black");
alert(sub1.colors); // red,green,pink,black

var sub2 = new Sub("Joe",22);
alert(sub2.colors); // red,green,pink

以上是借用构造函数和原型链一起做了优化,解决了引用类型属性的问题。这是目前最常用的模式,但它不是最完美的,分析一下:

function Super(name){
    this.name = name;
    this.colors = ["red","green","pink"];
}
Super.prototype.sayName = function(){
    alert(this.name);
};
function Sub(name,age){
    this.age = age;
    Super.call(this.name); // 第二次调用 Super()构造函数
}
Sub.prototype = new Super(); // 第一次调用Super()构造函数
Sub.prototype.sayAge = function(){
    alert(this.age);
}

所以可以看到,上面是两次调用了Super()构造函数,其实这样是没有必要的,我们所需要的无非是父类中的所有属性,即父类型原型的一个副本而已,所以寄生组合式继承就可以解决这个问题。

4.寄生组合式继承(改善组合式继承的方法)

是实现基于类型继承的最有效的方法。看下面代码:

function create(obj){
    function fun(){}; // 先定义一个函数
    fun.prototype =obj;
    return new fun(); 
}
function inheritPrototype(sup,sub){
    sub.prototype = create(sup.prototype);
}
function Super(name){
    this.name = name;
    this.colors = ["red","green","pink"];
}
Super.prototype.sayName = function(){
    alert(this.name);
};
function Sub(name,age){
    this.age = age;
    Super.call(this,name); // 只调用一次Super()构造函数
}
inheritPrototype(Super,Sub)

Sub.prototype.sayAge = function(){
    alert(this.age);
}

var sub = new Sub("Stan",11);
sub.sayName(); // Stan
sub.sayAge(); // 11

这个模式的高效率体现在它只调用了一次Super()构造函数,并且因此避免了在Sub.prototype上面创建不必要的、多余的属性,同时,原型链还能保持不变,所以这种模式普遍认为是引用类型最理想的继承范式。

再推荐一篇:JS实现继承的几种方式详述 (着重介绍原型链,对照相应的图,一目了然)

猜你喜欢

转载自blog.csdn.net/chenjuan1993/article/details/82291126