类: JS是没有类的概念的,把JS中的构造函数看做的类
所以说类式继承也是一种针对构造函数来做继承的一种方式
<script>
function Aaa() { // 父类
this.name = '小明';
}
Aaa.prototype.showName = function() {
alert( this.name );
};
function Bbb() { // 子类
}
// 将父类创建出来的对象赋值给子类的原型,就是类式继承
Bbb.prototype = new Aaa();
var b1 = new Bbb();
b1.showName(); //小明
alert(b1.name); //小明
</script>
下面我们分析一下Bbb.prototype = new Aaa();执行了哪些事情
原型链示意图
我们把new Aaa();创建的对象起个名字叫做a1;
那么a1和Aaa.prototype的可以通过原型链链接起来;
this.name在a1下面;
showName在Aaa.prototype的原型下面;
接下来,我们把a1的对象赋值给Bbb.prototype的原型,建立引用关系;
那么b1可以通过原型链查找到Bbb.prototype;
所以说这样就把它们链接到一起了;
这时候我们可以分析
首先b1找showName,b1下面没有找到;
这时候再顺着原型链找到Bbb.prototype,这时候我们发现Bbb.prototype也没有showName
Bbb.prototype没有showName没有没关系,a1和Bbb.prototype之间是引用关系,
所以b1找Bbb.prototype的原型和找a1对象是一回事;
但是a1下面也没有showName;
这时候它可以继续顺着原型链找到Aaa.prototype,最终在Aaa.prototype下面找到showName。
关于类式继承面试题中的那些坑?
通常面试题中会出如何用一句话写出面向对象的继承,实际上考的就是类式继承
将父类创建的对象赋值给子类原型,但是这还是有坑,有必要向面试官解释一下
我们看一下坑
<script>
function Aaa() { // 父类
this.name = '小明';
}
Aaa.prototype.showName = function() {
alert( this.name );
};
function Bbb() { // 子类
}
// 将父类创建出来的对象赋值给子类的原型,就是类式继承
Bbb.prototype = new Aaa();
var b1 = new Bbb();
//这时候我们查一下b1的构造函数
alert(b1.constructor); //弹出来的是 function Aaa() { // 父类 this.name = '小明';}
// 这就是我们提到的坑
/*
* 这就是因为直接把new Aaa()赋值给Bbb.prototype,
* 他就会把他原先自动生产的还是我们自己添加的原型下面的属性都覆盖掉了
* 所有就把construtor指向给修改了
* 所以还需要修正constructor指向 Bbb.prototype.constructor = Bbb;
* /
</script>
因为我们我们应该和面试官提一下,我们还应该修改一下constructor的指向问题
<script>
function Aaa() { // 父类
this.name = '小明';
}
Aaa.prototype.showName = function() {
alert( this.name );
};
function Bbb() { // 子类
}
// 将父类创建出来的对象赋值给子类的原型,就是类式继承
Bbb.prototype = new Aaa();
Bbb.prototype.constructor = Bbb; //修正constructor指向问题
var b1 = new Bbb();
b1.showName();
alert(b1.constructor); // function Bbb() { // 子类 }
</script>
还有哪些坑?我们再看一个例子
比如说现在我们把‘小明’改成一个数组,如下代码
function Aaa() { // 父类
this.name = [1,2,3];
}
Aaa.prototype.showName = function() {
alert( this.name );
};
function Bbb() { // 子类
}
Bbb.prototype = new Aaa();
Bbb.prototype.constructor = Bbb; //修正指向问题
var b1 = new Bbb();
//当我创建出来b1这个对象之后
// b1是不是就可以找到name了,现在是个数组,既然是数组,我们就可以给他push个元素进去
b1.name.push(4);//这时name就变成[1,2,3,4]了
alert(b2.name); //[1,2,3,4] 这说明b1,b2其实是两个不同的对象,但是这两个对象之间的name是互相会影响的
我们从原型链上解释,不管是b1还是b1,其中找的都是a1下面的name,都是同一个,所以一个修改了,另一个也改变了
面向对象的特点是复用性很强,既然复用性很强那么对象之间应该是不影响的
所以我们想对象之间不影响,那么在做属性和方法的时候,要分开继承
也就是说属性有属性的继承方式,对象有对象的继承方式,属性的修改不会影响到另外的对象
function Aaa() { // 父类
this.name = [1,2,3];
}
Aaa.prototype.showName = function() {
alert( this.name );
};
function Bbb() { // 子类
Aaa.call(this);
}
var F = function () { };
F.prototype = Aaa.prototype;
Bbb.prototype = new F();
Bbb.prototype.constructor = Bbb; //修正指向问题
var b1 = new Bbb();
b1.name.push(4);
var b2 = new Bbb();
alert(b2.name);
我们依然采用原型链的方式来解释
首先把Aaa.prototype赋值给F.prototype;
接下来把new F()创建的对象起个名字叫做f1;
然后把f1对象赋值给Bbb.prototype对象;
然后b1可以通过原型链找
Aaa.prototype的下面有showName;
然后b2再去找name
b1找name,b1下面没有;
再找到Bbb.prototype下面也没有;
由于f1和Bbb.prototype是引用关系;
再去f1下找;
f1的构造函数实际是F,F这个构造函数下面没有name这个属性;
但是它通过原型链能找到shouName;
当把子类的原型赋值给F.prototype的时候,这时候它只会把showName赋值过去
而属性name是不会赋过去的;
而这个F当中没有属性所以Bbb.prototype接收原型的时候,它只能接收方法,接收不了属性
所以我们就用这种方式把它的属性给去掉了
所以只把方法继承过去了
所以这时候再去找b2.name就找不到了
属性继续使用call构造函数方式继承
F的作用是避免属性的继承,而只做方法的继承