JavaScript-继承
一、概念
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
二、原型链继承
【1】基本概念:
1、超类型:被继承的函数叫做超类型(又叫父类、基类)。
2、子类型:继承的函数叫做子类型(又叫子类、派生类)。
3、通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性。子类型的原型,得到的是超类型的构造+原型里的信息
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>原型链继承</title>
</head>
<body>
<script>
function Student(){ //被继承的函数叫做超类型(父类、基类)
this.name = 'pangtong';
}
function Score(){ //继承的函数叫做子类型(子类、派生类)
this.result = 100;
}
Score.prototype = new Student();
var score = new Score();
alert(score.result); //100
alert(score.name); //pangtong
</script>
</body>
</html>
通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性。
子类型的原型,得到的是超类型的构造+原型里的信息。
【2】如果实例和原型中都包含相同属性,结果是什么?
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>原型链继承</title>
</head>
<body>
<script>
function Student(){
this.name = 'pangtong';
}
Student.prototype.result = 150;
function Score(){
this.result = 100;
}
Score.prototype = new Student();
var score = new Score();
alert(score.result); //100
alert(score.name); //pangtong
</script>
</body>
</html>
结论:就近原则,实例里有,就返回,没有就去原型中查找。
【3】从属关系
所有的构造函数都继承自Obejct。而继承Object是自动完成的,并不需要手动继承。
经过继承后的实例的从属关系:子类型从属于自己或者他的超类型。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>原型链继承</title>
</head>
<body>
<script>
function Student(){
this.name = 'pangtong';
}
Student.prototype.result = 150;
function Score(){
this.result = 100;
}
Score.prototype = new Student();
var score = new Score();
var student = new Student();
alert(score.result); //100
alert(score.name); //pangtong
//从属关系
alert(score instanceof Object); //true
alert(score instanceof Score); //true
alert(score instanceof Student); //true
alert(student instanceof Score); //false
</script>
</body>
</html>
【4】问题
继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。(引用共享和超类型无法传参的问题)
三、借用构造函数的技术
为了解决引用共享和超类型无法传参的问题,采用一种叫借用构造函数的技术,或者称为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
【1】实现原理:
在子类的构造函数中,通过apply( ) 或call( )的形式,调用父类构造函数,以实现继承。
【2】call() 方法
1、用法:call()方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。
语法:
fun.call(thisArg, arg1, arg2, …)
参数:
1)thisArg:在fun函数运行时指定的this值。
需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
2)arg1, arg2, …:指定的参数列表。
2、返回值
返回值是调用的方法的返回值,若该方法没有返回值,则返回undefined。
3、描述
可以让call()中的对象调用当前对象所拥有的function。可以使用call()来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
4、注意:该方法的作用和 apply() 方法类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。
【3】借用构造函数继承用法
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>借用构造函数继承</title>
</head>
<body>
<script>
function Student(age){
this.name = ['pangtong','xiaotong']; //引用类型,放在构造里就不会被共享
this.age = age;
}
function People(age){
Student.call(this,age); //对象冒充,给超类型传值
}
var student = new Student();
var people = new People(18);
alert(people.age); //18
people.name.push('tongtong'); //添加的新的数据,只给子类型
alert(people.name); //pangtong,xiaotong,tongtong
alert(student.name); //pangtong,xiaotong
</script>
</body>
</html>
【5】借用构造函数继承缺点
借用构造函数虽然解决了引用共享和超类型无法传参的问题,但没有原型,复用则无从谈起。
这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,放在构造里,每次实例化,都会分配一个内存地址,浪费,所以最好放在原型里,保证多次实例化只有一个地址。
方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新,只有后面的实例才能访问到新方法。
四、组合继承(原型链+构造函数)
【1】缺点:
超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。
【2】组合继承用法:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>组合继承</title>
</head>
<body>
<script>
function Student(name,age){
this.name = name;
this.age = age;
}
Student.prototype.run = function(){
return this.name+ ' is running';
}
function People(name,age){
Student.call(this,name,age);
}
People.prototype = new Student();
var people = new People('pangtong',18);
var people1 = new People('pangtong',18);
alert(people.run()); //pangtong is running
</script>
</body>
</html>
五、原型式继承
【1】概念:
这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
【2】原型式继承用法
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>原型式继承</title>
</head>
<body>
<script type="text/javascript">
function obj(o){ //传递一个字面量函数
function F(){} //创建一个构造函数(F构造是一个临时新建的对象,用来存储传递过来的对象)
F.prototype=o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回实例化的构造函数
}
var a = {
name:'pangtong',
age:18,
family:['gege','jiejie','didi']
}
var b = obj(a);
alert(b.name); //pangtong
b.name = 'tongtong';
alert(b.name); //tongtong
alert(b.family); //gege,jiejie,didi
b.family.push('meimei');
alert(b.family); //gege,jiejie,didi,meimei
var c = obj(a);
alert(c.name); //pangtong
alert(c.family); //gege,jiejie,didi,meimei
//引用类型共享了
</script>
</body>
</html>
【3】缺点
引用类型的值被共享了,原型式继承一般不单独用。
六、寄生式继承
【1】概念:
把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。
【2】寄生式继承用法:
function create(o) { //封装创建过程
var f= obj(o);
return f;
}
//同样也会共享引用类型
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>寄生式继承</title>
</head>
<body>
<script type="text/javascript">
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
function create(o){
var f = obj(o);
f.sayHello = function(){
return this.name+this.age;
}
return f;
}
var a = {
name:'pangtong',
age:18,
family:['gege','jiejie','didi']
}
var b = create(a);
alert(b.name);
alert(b.sayHello());
</script>
</body>
</html>
七、寄生组合式继承
【1】产生背景:
在javascript中,使用最多的是组合继承,但是组合继承中超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。
【2】寄生组合式继承用法:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>寄生组合式继承</title>
</head>
<body>
<script type="text/javascript">
//临时中转函数
function obj(o) {
function F() {}
F.prototype = o;
return new F();
}
//寄生函数
function create(box, desk) {
var f = obj(box.prototype);
f.constructor = desk; //调整原型构造指针
desk.prototype = f;
}
function Box(name, age) {
this.name = name;
this.age = age;
this.family = ['gege','jiejie'];
}
Box.prototype.run = function () {
return this.name + this.age + '运行中...'
}
function Desk(name, age) {
Box.call(this, name, age); //对象冒充
}
//通过寄生组合继承来实现继承
create(Box, Desk); //这句话用来替代Desk.prototype = new Box();
var desk = new Desk('tong', 100);
alert(desk.run()); //tong100运行中
alert(desk.constructor); //function Desk(){}
desk.family.push('didi'); //gege,jiejie,didi
alert(desk.family);
var desk1 = new Desk('pangtong',50);
alert(desk1.run()); //pangtong50运行中
alert(desk1.constructor); //function Desk(){}
alert(desk1.family); //gege,jiejie 只共享了方法,没有共享引用
</script>
</body>
</html>