javascirpt实现继承
前言
在面试的过程中,被问到最多的问题的javascript的继承,我之前也有了解过一些,但是总是理解得不够透彻,在实际的应用中没有很好的利用。这一次,我拿着《javascirtpt高级程序设计》这本书,将面向对象设计讲的继承反复的看了好几遍,跟着书本代码来敲,也按照自己的理解去实现继承,旨在能够充分理解熟练掌握js继承的思想。下面是我的读书笔记,记下来供以后翻看复习。
原型是什么?
每一个对象实例内部都一个指针,指向一个普通的对象(原型)。js中一切都是对象,函数也是对象,所以函数也有一个原型指针。
对象实例与它的构造器函数拥有同一个原型,这个原型指向的是构造器的父类的一个实例。
原型链
每一个对象都有原型,而对象的原型也是一个普通的对象,那么就可以形成一个链,例如String对象的原型是Object类的一个实例,而object对象的原型是一个空对象,空对象的原型是null.
当我们访问对象的属性时,会从对象自身找,如果自身没有,就会沿着原型链往上找,直到找到为止。如果最后都没有找到,就会返回undefined.通过修改原型的指向,对象可以获得相应原型上的属性,js就是这样实现了继承。
继承
原型继承
原型继承实际就是利用原型链来继承属性和方法,通过重写原型对象,以一个新的实例代替原型对象,这样就是继承到该新实例的属性和方法。
function SuperType() {
this.property = true;
}
superType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType(); // 重写SubType的原型对象,让它指向SuperType实例
SubType.prototype.getSubValue = function() {
return this.subProperty;
}
var instance = new SubType();
instance.getSuperValue(); // true
instance.getSubValue(); // false
使用原型方法可以很好的继承到父类型的属性和方法,只要通过更改子类型的原型对象为父类性的实例,这样子就形成了一个原型链,但返回子类型的实例的某一个方法的时候,首先在实例中查找该方法,没有就会往实例的原型对象查找,原型对象本来就是一个实例,找不到集继续找该原型对象的原型对象,直到原型链的最末端为止。
我们可以通过intanceof 和 isPrototypeOf来确认实例和原型的关系
alert(instance intanceof Object) // true
alert(instance instanceof SubType) // true
alert(instance instanceof SuperType) // true
alert(Object.prototype.isPrototypeOf(instance)) // true
alert(SubTYpe.prototype.isPrototypeOf(instance)) // true
alert(SuperType.prototype.isPrototypeOf(instacne)) // true
要注意的地方:
- 要谨慎的定义方法
子类型有时候会覆盖父类型中的某个方法,或者是添加父类型中没有的方法,但是无论怎么样,都是要放在替换原型的语句之后 - 通过原型链实现继承的时候,不能使用对象字面量创建原型和方法。否则会重写原型链
不过原型继承也存在一些问题:
- 包含引用类型的原型被所有实例共享的问题,
如果包含引用类型的原型,会导致该引用类型会被所有的实例共享,这样就会出现一个操作一个实例的引用类型会影响到另外一个实例的引用类型,这是我们不愿意看到的。 所以我们平时在定义的属性的时候都定义在构造函数中,定义方法就在原型对象中。 - 原型继承中子类型不能够向超类型传递参数。
所以我们在平常的使用中很少会单独使用原型继承。
构造函数继承
构造函数继承是通过在子类型中执行父类型的函数,让父类型的作用域子类型自己本身,这样就可以访问到父类型的属性和方法。
function Super(name) {
this.name = name
this.color = ['red','green','blue'];
}
function Sub(name,age) {
Super.call(this, name);
this.age = age
}
var instance = new Sub("lili", 12);
instance.name // lili
instance.color.push('white');
alert(instance.color) // red,green,blue,white
var instance new Sub('zhangsan',18);
alert(instance.name) // zhangsan
alert(intance.color) // red,green,blue
从上面的例子可以看到,我们利用构造函数继承,在子类型中调用Super.call(this,name)可以继承到父类型的name,color,并且可以在子类型中传递参数给父类型,同时我们在操作各自的实例属性或者引用类型的时候,并没有影响到另外的实例。
但是,如果所以的方法都要在子类型定义,这样就没有复用的说法了,而且,我们在父类型的原型对象中定义的方法,对子类型是不可见的,这样没有达到真正的继承,所以在平时中单独使用构造函数继承也是很少的。
组合继承(原型时继承)
上面我们我们谈到了两种继承方法都有自己的优点和缺点,无法达到真正继承的效果,我们可以结合两者的优点,相互祢补不足,发挥两者之长。
中心思想:
- 使用原型链实现对原型属性和方法的继承
- 使用构造函数实现对实例属性的继承
function Super(name) {
this.name = name;
}
Super.prototype.sayName = function() {
alert(this.name);
}
function Sub(name,age) {
Super.call(this,name);
this.age = age;
}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayName = function() {
alert(this.name + "and I'm " + this.age)
}
var instance = new Sub("Lucy", 35);
instance.sayName();
总结
javascript主要是通过原型链来实现继承,原型链的构建是通过将一个类型的实例对象赋值给另外一个构造函数的原型实现的。这样,子类型就可以访问超类型的所有属性和方法。
原型链继承的问题是对象实例共享所有的实例和方法,并且不能在子类型中传递参数给超类型,不适合单独使用。解决这个问题的技术就是借助构造函数,即在子类型构造函数内部调用超类型的构造函数,这样子可以做到每一个实例都有自己的属性,同时还保证只用构造函数模式定义类型。使用最多的是组合继承,这样方法是使用构造函数来定义实例属性,使用原型链来是实现原型的属性和方法。
同时,还有以下供选择的继承方式
- 原型式继承
- 寄生式继承
- 寄生式组合继承