原型链继承
构造函数、原型与实例之间的关系
-
每创建一个函数,该函数就会自动带有一个 prototype 属性。该属性是个指针,指向了一个对象,我们称之为 原型对象。什么是指针?指针就好比学生的学号,原型对象则是那个学生。我们通过学号找到唯一的那个学生。假设突然,指针设置 null, 学号重置空了,不要慌,对象还存在,学生也没消失。只是不好找了。
-
原型对象上默认有一个属性 constructor,该属性也是一个指针,指向其相关联的构造函数。
-
通过调用构造函数产生的实例,都有一个内部属性,指向了原型对象。所以实例能够访问原型对象上的所有属性和方法。
- 所以三者的关系是,每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。通俗点说就是,实例通过内部指针可以访问到原型对象,原型对象通过constructor指针,又可以找到构造函数。
下面看一个例子:
function Dog (name) {
this.name = name;
this.type = 'Dog';
}
Dog.prototype.speak = function () {
alert('wang');
}
var doggie = new Dog('jiwawa');
doggie.speak(); //wang
以上代码定义了一个构造函数 Dog(), Dog.prototype 指向的原型对象,其自带的属性construtor又指回了 Dog,即 Dog.prototype.constructor == Dog.
实例doggie由于其内部指针指向了该原型对象,所以可以访问到 speak方法。
原型链
前面我们说到,所有的实例有一个内部指针,指向它的原型对象,并且可以访问原型对象上的所有属性和方法。doggie实例指向了Dog的原型对象,可以访问Dog原型对象上的所有属性和方法;如果Dog原型对象变成了某一个类的实例 aaa,这个实例又会指向一个新的原型对象 AAA,那么 doggie 此时就能访问 aaa 的实例属性和 AA A原型对象上的所有属性和方法了。同理,新的原型对象AAA碰巧又是另外一个对象的实例bbb,这个实例bbb又会指向新的原型对象 BBB,那么doggie此时就能访问 bbb 的实例属性和 BBB 原型对象上的所有属性和方法了。
这就是JS通过原型链实现继承的方法了。看下面一个例子:
//定义一个 Animal 构造函数,作为 Dog 的父类
function Animal () {
this.superType = 'Animal';
}
Animal.prototype.superSpeak = function () {
alert(this.superType);
}
function Dog (name) {
this.name = name;
this.type = 'Dog';
}
//改变Dog的prototype指针,指向一个 Animal 实例
Dog.prototype = new Animal();
//上面那行就相当于这么写
//var animal = new Animal();
//Dog.prototype = animal;
Dog.prototype.speak = function () {
alert(this.type);
}
var doggie = new Dog('jiwawa');
doggie.superSpeak(); //Animal
以上代码,首先定义了一个 Animal 构造函数,通过new Animal()得到实例,会包含一个实例属性 superType 和一个原型属性 superSpeak。另外又定义了一个Dog构造函数。然后情况发生变化,代码中加粗那一行,将Dog的原型对象覆盖成了 animal 实例。当 doggie 去访问superSpeak属性时,js会先在doggie的实例属性中查找,发现找不到,然后,js就会去doggie 的原型对象上去找,doggie的原型对象已经被我们改成了一个animal实例,那就是去animal实例上去找。先找animal的实例属性,发现还是没有 superSpeack, 最后去 animal 的原型对象上去找才找到。
这就说明,我们可以通过原型链的方式,实现 Dog 继承 Animal 的所有属性和方法。
总结来说:就是当重写了Dog.prototype指向的原型对象后,实例的内部指针也发生了改变,指向了新的原型对象,然后就能实现类与类之间的继承了。(但是如果在重写原型对象之前,产生的实例,其内部指针指向的还是最初的原型对象。这个我下次再发篇文章讲。js 继承之借用构造函数继承)
共享属性迁移到原型
代码:
<script>
function Shape() {
Shape.prototype.name='shape';
Shape.prototype.toString=function () {
return this.name;
}
}
function TwoDShape() {}
TwoDShape.prototype=new Shape();
TwoDShape.prototype.constructor=TwoDShape;//重置
TwoDShape.prototype.name='2D Shape';
function Triangle(side,height){
this.side=side;
this.height=height;
}//side和height不一定固定所以不适合放在原型中
Triangle.prototype=new TwoDShape();
Triangle.prototype.constructor=Triangle;
Triangle.prototype.name='triangle';
Triangle.prototype.getArea=function () {
return this.side*this.height/2;
}//因为得到面积的方式是固定的
var myTriangle=new Triangle(5,10);
console.log(myTriangle.getArea());
console.log(myTriangle.toString());
console.log(myTriangle.hasOwnProperty('side'));//检测自身属性
console.log(myTriangle.hasOwnProperty('name'));
console.log(myTriangle instanceof Triangle);
console.log(myTriangle instanceof TwoDShape);
console.log(myTriangle instanceof Shape);
</script>
通过uber访问父类中的方法
<script>
function Shape() {};
Shape.prototype.name='Shape';
Shape.prototype.toString=function () {
var result=[];
if(this.constructor.uber){
result[result.length]=this.constructor.uber.toString();//检查该属性是否有uber属性
}
result[result.length]=this.name;
return result.join(',');
};
function TwoDShape() {};
var F=function () {};//临时构造器
F.prototype=Shape.prototype;
TwoDShape.prototype=new F();
TwoDShape.prototype.constructor=TwoDShape;
TwoDShape.uber=Shape.prototype;//子对象对父对象的访问
TwoDShape.prototype.name='2d';
function Triangle(side,height) {
this.side=side;
this.height=height;
}
var F=function(){};
F.prototype=TwoDShape.prototype;
Triangle.prototype=new F();
Triangle.prototype.constructor=Triangle;
Triangle.uber=TwoDShape.prototype;//父级对象的一个原型引用
Triangle.prototype.name='triangle';
Triangle.prototype.getArea=function () {
return this.side*this.height/2;
}
var my=new Triangle(5,10);
console.log(my.getArea());
console.log(my.toString());
var td=new TwoDShape();
console.log(td.toString());
</script>
我们可以选择封装 减少代码重复
function extend(Child,Parent){
var F=function () {};
F.prototype=Parent.prototype;
Child.prototype=new F();
Child.prototype.constructor=Child;
Child.uber=Parent.prototype;
}//封装
function Shape() {};
Shape.prototype.name='Shape';
Shape.prototype.toString=function () {
var result=[];
if(this.constructor.uber){
result[result.length]=this.constructor.uber.toString();//检查该属性是否有uber属性
}
result[result.length]=this.name;
return result.join(',');
};
function TwoDShape() {};
extend(TwoDShape,Shape);
TwoDShape.prototype.name='2d';
function Triangle(side,height) {
this.side=side;
this.height=height;
}
extend(Triangle,TwoDShape);
Triangle.prototype.name='Triangle';
Triangle.prototype.getArea=function () {
return this.side*this.height/2;
}
var my=new Triangle(5,10);
console.log(my.getArea());
这样效果也是一样的
拷贝父类对象属性
<script>
function extend(Child,Parent){
var F=function () {};
F.prototype=Parent.prototype;
Child.prototype=new F();
Child.prototype.constructor=Child;
Child.uber=Parent.prototype;
}//封装继承
function extend1(Child,Parent) {
var p=Parent.prototype;
var c=Child.prototype;
for(var i in p ){
c[i]=p[i];//父级拷贝给子级
}
c.uber=p;//子类中的uber指向父类原型
}
var Shape=function () {};
var TwoShape=function () {};
Shape.prototype.name='shape';
Shape.prototype.toString=function () {
return this.name;
}
extend(TwoShape,Shape);
var td=new TwoShape();
console.log(td.name);
console.log(TwoShape.prototype.name);//继承的方式访问到
console.log(td.__proto__.name);
console.log(td.hasOwnProperty('name'));//是不是td对象的属性
console.log(td.__proto__.hasOwnProperty('name'));//不是原型的属性
extend1(TwoShape,Shape);
var td= new TwoShape();
console.log(td.__proto__.hasOwnProperty('name'));
console.log(td.__proto__.hasOwnProperty('toString'));
console.log(td.__proto__.toString==Shape.prototype.toString);
//对象是被当作引用来使用的
var A=function () {};
var B=function () {};
A.prototype.test=[1,2,3];
A.prototype.test1='this is a test';
extend(B,A);//拷贝
console.log(B.prototype.hasOwnProperty('test'));
console.log(B.prototype.hasOwnProperty('test1'));
console.log(B.prototype.test);
console.log(B.prototype.test===A.prototype.test);
B.prototype.test1='hello';//不影响A
console.log(A.prototype.test1);
B.prototype.test.push(4,5,6);
console.log(B.prototype.test);//会变
console.log(A.prototype.test);//会变 数组里多了4 5 6
//重写test属性
B.prototype.test=['aa','bb','cc','dd'];//相当于将指针移动到新对象
console.log(A.prototype.test);//A仍不变
console.log(B.prototype.test);//B指针指向新对象
</script>
结果:
深拷贝
<script>
var o={};
function extendCopy(p) {
var c={};
for(var i in p){
c[i]=p[i];
}
c.uber=p;
return c;
}//简单的拷贝
var Shape={
name:'shape',
toString:function () {
return this.name;
}
};
var TwoDShape=extendCopy(Shape);
TwoDShape.name='2D';//扩展
TwoDShape.toString=function () {
return this.uber.toString()+"--"+this.name;
}//重写方法
var Triangle=extendCopy(TwoDShape);//继承TwoDShape
Triangle.name='Triangle';
Triangle.getArea=function () {
return this.side*this.height/2;
}
Triangle.side=5;
Triangle.height=10;
console.log(Triangle.getArea());
//深拷贝可以等同于修改了函数
function deepCopy(p,c){
var c=c||{};
for(var i in p){
if(typeof p[i]==='object'){
c[i]=(p[i].constructor===Array)?[]:{};//判断p里的i是否为数组,是则以数组显示否则就是个对象赋给c里的i
deepCopy(p[i],c[i]);//然后递归调用
}//如果是对象就进行深度拷贝
else{
c[i]=p[i];
}//否则将p的属性拷贝给c
}
return c;
}
var parent={
numbers:[1,2,3]
};
var mydeep=deepCopy(parent);
var myshallow=extendCopy(parent);
mydeep.numbers.push(4,5,6);
console.log(mydeep.numbers);//深拷贝
console.log(parent.numbers);//深
myshallow.numbers.push(7,8,9);
console.log(myshallow.numbers);//浅拷贝
console.log(parent.numbers);
多重继承
单继承
function object(o) {
function F() {};
F.prototype=o;
return new F();
}//object函数接收父级对象
//如果想访问uber属性
function object(o) {
function F() {};
F.prototype=o;
var n=new F();
n.uber=o;
return n;
}//只需将某个对象传给它 就会创建一个新的对象
var Shape={
name:'shape',
toString:function () {
return this.name;
}
};
var TwoDShape=object(Shape);
TwoDShape.name='2D shape';
TwoDShape.toString=function () {
return this.uber.toString()+'---'+this.name;
}
var Triangle=object(TwoDShape);
Triangle.name='triangle';
Triangle.getArea=function () {
return this.side*this.height/2;
}
console.log(Triangle.toString());//相当于原型的继承
单继承:
function objectPlus(o,stuff){
function F() {}
F.prototype=o;
var n=new F();
n.uber=o;
for(var i in stuff){
n[i]=stuff[i];
}
return n;
}//o用于继承 stuff用于拷贝属性和方法
var Shape={
name:'shape',
toString: function () {
return this.name;
}
};
var TwoDShape=objectPlus(Shape,{
name:'2d',
toString:function () {
return this.uber.toString()+'---'+this.name;
}
});
var Triangle=objectPlus(TwoDShape,{
name:'triangle',
getArea:function () {
return this.side*this.height/2;
},
side:0,
height:0
});
var my=objectPlus(Triangle,{
side:5,
height:10
});
console.log(my.getArea());
console.log(my.toString());//Triangle出现2次 它的name属性被重复执行2次
//为Triangle赋一个新的name属性就不会出现2次
var my=objectPlus(Triangle,{
name:'this is ',
side:10,
height:20
});
console.log(my.toString());
多继承:可以说是 子对象不止有一个父对象