继承
简介:继承是面向对象的三个基本特征之一,继承是实现复用性的一个重要手段,可以在不重复编写已实现的功能的前提下,对功能进行复用和拓展。许多OO(Object Oriented,面向对象)语言都支持两种继承方式:
- 接口继承-只继承方法签名,属性,但是子类必须提供实现的能力;
- 实现继承-继承实际方法,直接使用基类的属性和方法而无需额外编码;
JS中函数没有签名,所以无法实现接口继承,只支持实现继承,且实现继承主要依靠原型链来实现的。
原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性或方法。
构造函数、原型与实例的关系:
- 每个构造函数都有一个原型对象(prototype)
- 原型对象都包含一个指向构造函数的指针(constructor)
- 实例包含一个指向原型对象的内部指针(__proto__)
基本概念:
__proto__
属性,它是对象所独有的,可以看到__proto__
属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象),那么这个属性的作用是什么呢?它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__
属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__
属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找….直到原型链顶端null(可以理解为原始人。。。),此时若还没找到,则返回undefined
(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此为止),由以上这种通过__proto__
属性来连接对象直到null
的一条链即为我们所谓的原型链。
不懂的先看这篇文章 《帮你彻底搞懂JS中的prototype、__proto__与constructor》,每天上班读一遍,周一到周五,基本就明白了,不会再迷糊了。
实现原型链
可以在《JavaScript高级程序设计》P162-6.3继承和《JavaScript设计模式》P19-2.3传宗接代-继承 中可以看到类似的代码。在设计模式一书中称其为类式继承,我沿用这个称呼也这么叫了,并对原书中的代码进行了小修改。
实现本质:重写原型对象,代之以一个新类型的实例。
代码:
/**
类式继承
类式继承需要将父类的实例赋值给子类的的原型对象prototype
类的原型对象作用是就是为了类的原型添加共有属性和方法,但是类本身不能不直接访问这些属性和方法,必须通过原型prototype来访问。
实例化父类时,new关键字将父类的构造函数,属性和方法复制给了实例化对象,并且将实例化对象的__proto__指向了父类的原型对象。
这样实例对象就拥有了父类原型对象上的属性和方法。
并且,实例对象可以直接访问父类原型对象上的属性和方法。
*/
//声明父类
function SuperClass() {
this.superValue = "我是父类";
}
//增加父类公共方法--类的原型对象作用是就是为了类的原型添加共有属性和方法
SuperClass.prototype.getSuperValue = function () {
//为父类原型对象上添加一个获取值的方法
console.log(this.superValue)
}
//声明子类
function SubClass() {
this.subValue = "我是子类";
}
//继承父类--父类的实例赋值给子类的的原型对象prototype
//new关键字将父类的构造函数,属性和方法复制给了实例化对象,并且将实例化对象的__proto__指向了父类的原型对象
SubClass.prototype = new SuperClass();//在此处时重写原型对象
//增加子类公共方法
SubClass.prototype.getSubValue = function () {
//为子类原型对象上添加一个获取值的方法
console.log(this.subValue);
}
//实例化
var instance = new SubClass();
//使用
instance.getSubValue(); //我是子类
instance.getSuperValue(); //我是父类--实例对象可以直接访问父类原型对象上的属性和方法
确定原型与实例的关系:使用instanceof操作符。instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。
代码:
//通过instanceof检测某个对象是否是某个类的实例
//instanceof通过判断对象的prototype链来判断某个对象是否是某个类的实例
console.log(instance instanceof SuperClass); //true
console.log(instance instanceof SubClass); //true
//instanceof是检测某个对象是否是某个类的实例,它并不表示两者的继承关系
console.log(SubClass instanceof SuperClass); //false
//实际继承SuperClass 是 SubClass.prototype ,所以
console.log(SubClass.prototype instanceof SuperClass) //true
//所有对象的都是Object 的实例
console.log(instance instanceof Object) //true
谨慎的定义方法:子类有时需要覆盖父类中的某个方法,或者添加父类中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在继承父类(重写原型对象)语句之后。同时,使用原型链实现继承时,不能使用对象字面量给子类创建原型方法。因为会重写原型链。
代码:
//声明父类
function SuperClass() {
this.superValue = "我是父类";
}
SuperClass.prototype.getSuperValue = function () {
console.log(this.superValue)
}
//声明子类
function SubClass() {
this.subValue = "我是子类";
}
//继承父类
SubClass.prototype = new SuperClass();
//使用字面量添加方法,会导致上一行无效
SubClass.prototype={
getSubValue : function () {
console.log(this.subValue);
},
otherMethod:function(){
//.....
}
}
//实例化
var instance = new SubClass();
console.log(instance.getSuperValue());//error
类式继承两大缺点:
- 引用类型值的误修改--原型属性中的引用类型属性会被所有实例共享-子类实例更改从父类原型继承来的引用类型共有属性会影响其他子类。
- 无法传递参数--由于子类的继承是靠其prototype对父类的实例化实现的,所以无法传递参数。
代码:
function Father() {
this.base='base';
this.books = ['1', 'js', 'php'];
}
function Son() {};
Son.prototype = new Father();
var instance1 = new Son();
var instance2 = new Son();
console.log(instance1.books)
console.log(instance2.books)
instance1.books.push("设计")
console.log(instance2.books);
//可以尝试修改实例属性base 查看另一个实例是否受影响
总结:如果对JS中的继承概念比较模糊,思维比较混乱,心态比较惧怕,那么推荐你沉下心来仔细看看《JavaScript高级程序设计》第6章 面向对象的程序设计。有道是“书读百遍,其义自见”,其实用不了,这章看十来遍就够够的了,就明白了,那种明白的感觉像什么呢?用烟民的感觉给你形容一下:候机3个小时,坐了6个小时,出关1个小时,到机场大厅外,点着了烟,抽的第三口那种感觉。
再次推荐这篇文章《帮你彻底搞懂JS中的prototype、__proto__与constructor》