javascript 继承学习心得总结
作者:jingxian 时间:2024-05-13 09:36:47
看了不少js继承的东西也该总结总结了。
先说一下大概的理解,有不对的还望指正,也好更正一下三观。另外说明下,下面的例子并非原创基本就是改了个变量名啥的,有的甚至直接拿过来用的。
js继承是用来干啥的:
首先说js没有真正的跟其他面向对象的语言一样概念的继承,js里边所说的继承是指模拟继承。
具体js继承是干啥呢,刚开始做前端的时候我是用来面试的(最早写些小效果的时候基本用不到,为啥要看呢,因为面试官很爱问这个问题啊),所以就看看大概的,面试时候能说个大概,在这个问题上算是面试党了。后来跟着慢慢的实际上虽然概念不是很明确也用到一些。
真正是用来干啥的呢,主要是用来复用我们之前写过的代码。比如写过一个功能,一个对象,或者用别人写的东西,我们要自己加点儿什么,总不能改人家的东西吧,直接继承过来用一下就好了,这才是继承的真正用途。
js继承怎么实现:
先不上代码,先说说想法。其实继承呢就是想办法把其他对象(js里边一切皆对象哈)的属性或者方法搞到我们自己的对象上,让我们自己的这个对象可以用。这也就达到复用的目的了。
目的搞明白了,下面就是实现手段了。
根据js的特性,实现无非就是以下几种方法的其中一种或者组合使用。
1、构造函数,js好像没有严格的构造函数的定义,但是可以用new来创建新的对象。构造函数据说也是严格的面向对象的语言实现继承的方法,那么js当然可以模拟一下了,所以学过oop语言的人们会最先想到这个。
2、利用函数原型,利用原型链把两个对象链接起来,因为js原型链是比较独特所以想到这个也是很容易的。
原型也分几种,就是把什么作为继承对象的原型,被继承对象的原型或者被继承对象的实例,或者直接被继承者。这几种作为继承对象的原型得到的继承效果是不一样的。
3、复制属性和方法,把被继承对象的属性或者方法统统复制克隆过来变成我们自己对象的属性和方法就好了啊,我们就可以无比顺畅的用了。当然这个还分浅复制和深度复制两种情况。
4、利用call和apply这两个方法,这两个方法比较神奇,可以改变函数执行的上下文(this),所以利用这两个方法也可以实现对被继承对象的方法的继承复用。
总的来js实现继承的途径大概就是这些,千变万化的实现方法都是从这几种方法的基础上组合升级完善出来的,为毛大多数要组合使用呢,当然是因为使用单个方法实现的效果不太理想啊。当然可以根据自己项目中实际的需求选择使用哪种方式,只要满足自己的需求并没有说必须使用哪种方法去实现。就像说从北京去石家庄,最快当然是飞机啦。但是如果离机场远,算上到机场,从机场去市里,整体算下来还不如高铁快,那就可以做高铁。又比如自己有车可以开车,想要挑战一下还可以骑自行车,这个根据自己的实际情况来选就可以。
代码实现,下面结合代码说说上面的几种实现方法,有些是从其他地方摘来的,加点儿注释啥的。
看了不少js继承的东西也该总结总结了。
先说一下大概的理解,有不对的还望指正,也好更正一下三观。另外说明下,下面的例子并非原创基本就是改了个变量名啥的,有的甚至直接拿过来用的。
js继承是用来干啥的:
首先说js没有真正的跟其他面向对象的语言一样概念的继承,js里边所说的继承是指模拟继承。
具体js继承是干啥呢,刚开始做前端的时候我是用来面试的(最早写些小效果的时候基本用不到,为啥要看呢,因为面试官很爱问这个问题啊),所以就看看大概的,面试时候能说个大概,在这个问题上算是面试党了。后来跟着慢慢的实际上虽然概念不是很明确也用到一些。
真正是用来干啥的呢,主要是用来复用我们之前写过的代码。比如写过一个功能,一个对象,或者用别人写的东西,我们要自己加点儿什么,总不能改人家的东西吧,直接继承过来用一下就好了,这才是继承的真正用途。
js继承怎么实现:
先不上代码,先说说想法。其实继承呢就是想办法把其他对象(js里边一切皆对象哈)的属性或者方法搞到我们自己的对象上,让我们自己的这个对象可以用。这也就达到复用的目的了。
目的搞明白了,下面就是实现手段了。
根据js的特性,实现无非就是以下几种方法的其中一种或者组合使用。
1、构造函数,js好像没有严格的构造函数的定义,但是可以用new来创建新的对象。构造函数据说也是严格的面向对象的语言实现继承的方法,那么js当然可以模拟一下了,所以学过oop语言的人们会最先想到这个。
2、利用函数原型,利用原型链把两个对象链接起来,因为js原型链是比较独特所以想到这个也是很容易的。
原型也分几种,就是把什么作为继承对象的原型,被继承对象的原型或者被继承对象的实例,或者直接被继承者。这几种作为继承对象的原型得到的继承效果是不一样的。
3、复制属性和方法,把被继承对象的属性或者方法统统复制克隆过来变成我们自己对象的属性和方法就好了啊,我们就可以无比顺畅的用了。当然这个还分浅复制和深度复制两种情况。
4、利用call和apply这两个方法,这两个方法比较神奇,可以改变函数执行的上下文(this),所以利用这两个方法也可以实现对被继承对象的方法的继承复用。
总的来js实现继承的途径大概就是这些,千变万化的实现方法都是从这几种方法的基础上组合升级完善出来的,为毛大多数要组合使用呢,当然是因为使用单个方法实现的效果不太理想啊。当然可以根据自己项目中实际的需求选择使用哪种方式,只要满足自己的需求并没有说必须使用哪种方法去实现。就像说从北京去石家庄,最快当然是飞机啦。但是如果离机场远,算上到机场,从机场去市里,整体算下来还不如高铁快,那就可以做高铁。又比如自己有车可以开车,想要挑战一下还可以骑自行车,这个根据自己的实际情况来选就可以。
代码实现,下面结合代码说说上面的几种实现方法,有些是从其他地方摘来的,加点儿注释啥的。
一、构造函数实现(借用构造函数):
function Super(arg){
this.arr1 = "I'm super "+arg;
this.show = function(){
alert(this.arr1);
}
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){
Super.apply(this, arguments); //在suber的上下文中运行super
}
var sub =new suber("suber");
var sub2 = new suber("suber1");
console.log(sub.arr1); //I'm super suber
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //undefined
console.log(sub.show === sub2.show); //false哎呀,发现sub.say是undefined,这说明它没有被继承过来啊,下边两个对象sub,sub2的show不相等,说明两个函数指向了两个不同的对象,也就是说被复制了两份出来。
所以这个方法实现继承的话原型对象上的属性和方法没有被继承过来,Super上的属性和方法为每个new出来的对象分别复制一份。
所以单单使用这个方法来实现继承还是不妥啊,因为原型上的方法都没有被继承过来啊。于是大神们就想到了原型继承二、原型继承:
function Super(arg){
this.arr1 = "I'm super "+arg;
this.show = function(){
alert(this.arr1);
}
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){}
suber.prototype = new Super();
var sub = new suber("suber1");
var sub2 = new suber("suber2");
console.log(sub.arr1); //I'm super undefined
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show); //true;
console.log(sub.say === sub2.say); //true;
这次是arr1继承过来了,但是参数没有添加进来,是undefined,所以这个方法子类声明时候这个参数传进来付类继承过来的这个属性没法收到。其他的都还算正常。show和say都继承过来了。但是有一点儿需要注意,say是通过super的原型对象继承过来的,而show是新建super对象实例时实例的属性。
那么怎么实现参数传输又能把原型里边的东东继承过来呢,当然上边两种方法组合一下就好了啊,于是前辈们又发明了下面这种方法
三、组合继承(借用构造函数并设置原型):
function Super(arg){
this.arr1 = "I'm super "+arg;
this.show = function(){
alert(this.arr1);
}
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){
Super.apply(this, arguments);
}
suber.prototype = new Super();
var sub = new suber("suber1");
var sub2 = new suber("suber2");
console.log(sub.arr1); //I'm super suber1
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show); //false;
console.log(sub.say === sub2.say); //true;
这次几乎完美了,但是可以发现sub.show 和sub2.show并不相等啊,这是为啥呢,因为apply这个地方使得show成为了suber的自己的属性,那么就吧suber原型里的show(Super的当做suber原型对象的实例对象的show)给覆盖了,所以又变成每次复制一个,当然这个地方没有办法避免啊。为了不产生这种多余的开消这种可以共用的函数可以多放到原型对象里边。
因为suber的构造里边的调用,和给suber原型对象赋值时候的调用导致Super被调用了两次,那么每次new suber对象时候就调用了两次Super,调用两次就会产生两个实例对象,需要消耗多余的资源了。
于是前辈们为了解决这个问题又开了开脑洞,开发出来下面这种方法。
四、寄生组合继承:
该方法跟方法三最主要的不同就是把父类原型赋给了子类原型而不是父类示例,看例子
function Super(arg){
this.arr1 = "I'm super "+arg;
}
Super.prototype.show = function(){ //这个方法放到了原型对象上。
alert(this.arr1);
}
Super.prototype.say = function(){
alert(this.arr1);
}
function suber(arg){
Super.apply(this, arguments);
}
/*inherit函数的作用,使用一个新的空函数,来切断父类对象的原型对象与子类原型对象的直接联系,而是通过这个空构造的实例对象实现继承,这样可以避免更改子类原型的属性或者方法而影响了父类原型对象的属性或者方法。*/
function inherit(obj){
function F(){}
F.prototype = obj;
return new F();
}
suber.prototype = inherit(Super.prototype);
var sub = new suber("suber1");
var sub2 = new suber("suber2");
console.log(sub.arr1); //I'm super suber1
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show); //true;
console.log(sub.say === sub2.say); //true;