js面向对象编程—继承

正统的面向对象语言都会提供extend之类的方法用于处理类的继承,但javascript并不提供extend方法,在js中继承需要用点技巧。

在js中实例的属性和行为是由构造函数和原型两部分组成的,我们定义两个类Animal和Bird,他们在内存中有自己的构造函数和原型。如果想让Bird继承自Animal,那么我们需要把Animal构造函数和原型中的属性和行为全部传给Bird的构造函数和原型。理清思路以后,我们分步来完成Animal和Bird的继承功能。首先我们需要一个Animal类:

1
2
3
4
5
6
7
8
9
10
11
12
13
 <body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  </script>
 </body>

接下来我们定义一个Bird类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(){
  }
  Bird.prototype={
  }
  </script>
 </body>

Bird类有自己特有的属性和行为,但是它大部分属性和行为来自Animal,所以我们先让Bird继承Animal的构造函数中的属性和行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(name){
    this.name=name;
    this.type="animal";
  }
  Bird.prototype={
  }
  var myBird=new Bird("feimos");
  alert(myBird.type);
  </script>
 </body>

上述方案只是将Animal的属性复制了一份,而不是真正意义上的继承,如果Animal类的构造函数有变动,我们也要修改Bird类的构造函数,这违反DRY原则,降低了代码可维护性。我们对它进行一下改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(name){
    Animal(name);
  }
  Bird.prototype={
  }
  var myBird=new Bird("feimos");
  alert(myBird.type);
  </script>
 </body>

结果弹出undefined。我们在Bird类构造函数里面调用Animal()函数,希望它内部的this可以在Bird类的构造函数里面执行一遍,但是实际情况却不是这样。在js中,function有两种调用方式,作为函数时,直接被()调用,此时的this指向window对象;另一种作为类得构造函数,被new调用,此时的this指向实例对象。上面代码框中的代码,Bird类构造函数中的Animal是通过函数的方式调用的,它内部的this对象指向的是window,其效果等同于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(name){
    window.name=name;
    window.type="animal";
  }
  Bird.prototype={
  }
  var myBird=new Bird("feimos");
  alert(myBird.type);
  alert(type);      //window.type可以省略写成type
  </script>
 </body>

如果想让Animal内部的this指向Bird类得实例,可以通过call或apply方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(name){
    Animal.call(this,name);
  }
  Bird.prototype={
  }
  var myBird=new Bird("feimos");
  alert(myBird.type);      //animal
  </script>
 </body>

构造函数的属性和行为已经成功实现了继承,接下来我们要实现原型中属性和行为的继承。能否将Animal类的原型直接传给Bird类的原型呢?先试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[cc lang="javascript"]<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(name){
    Animal.call(this,name);
  }
  Bird.prototype=Animal.prototype;
  var myBird=new Bird("feimos");
  myBird.say();    
  </script>
 </body>

上述代码视乎是能够解决问题,Bird类成功获得了say行为。但是如果我们要给Bird类添加fly行为,又会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
  <script type="text/javascript">
  function Animal (name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+", my name is "+this.name);
    }
  }
  function Bird(name){
    Animal.call(this,name);
  }
  Bird.prototype=Animal.prototype;
  Bird.prototype.fly=function (){
    alert("I'm flying");
  }
  var myBird=new Bird("feimos");
  myBird.say();     //I'm a(an) animal , my name is feimos
  myBird.fly();     //I'm flying
  var myDog=new Animal("aHuang");
  myDog.fly();      //I'm flying
  </script>
 </body>

我们只想给Bird类添加fly行为,为什么Animal类也获得了fly行为呢?在js中,赋值语句会用传址和传值两种不同的方式进行赋值,如果是数值型、布尔型、字符型等基本数据类型,在进行赋值时会将数据赋值一份,将复制的数据进行赋值,也就是传值,如果是数组、hash对象等复杂数据类型,在进行赋值时会直接用内存地址赋值,而不是将数据复制一份,也就是所说的传址。可以参考下列代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
  <script type="text/javascript">
  var a=10;
  var b=a;
  var c=[1,2,3];
  var d=c;
  b++;
  d.push(4);
  alert(a);   //10
  alert(b);   //11
  alert(c);   //1,2,3,4 变量c和d指向同一份数据,数据变更会互相影响
  alert(d);   //1,2,3,4
  </script>
 </body>

在原生js中,选择传值还是传址是根据数据类型自动判定的,我们需要对复杂数据的赋值进行控制,让复杂数据也可以进行传值。最简单的方法是遍历数组或hash对象,将数组或者hash对象这种复杂数据拆分成一个个简单数据,然后分别赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
  <script type="text/javascript">
  var a=[1,2,3];
  var b={name:"feimos",sex:"male",tel:"1234567"};
  var c=[],d={};
  for(var p in a){
    c[p]=a[p];
  }
  for(var p in b){
    d[p]=b[p];
  }
  c.push(4);
  d.email="xxx@gmail.com";
  alert(a);   //1,2,3
  alert(c);   //1,2,3,4
  alert(b.email);  //undefined
  alert(d.email);  //xxx@gmail.com
  </script>
 </body>

对于数组的传值,我们还可以使用数组类的slice或concat方法实现:

1
2
3
4
5
6
7
8
9
  <script type="text/javascript">
  var a=[1,2,3];
  var b=a.slice(),c=a.concat();
  b.pop();
  c.push(4);
  alert(a);  //1,2,3
  alert(b);  //1,2
  alert(c);  //1,2,3,4
  </script>

prototype本质上也是一个hash对象,所以直接用它赋值时会进行传址,这也是为什么myDog会fly的原因。我们可以用for in 来遍历prototype,从而实现prototype的传值。但是因为prototype和function的关系,我们还有另一种方法实现prototype的传值——new SomeFunction():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  <script type="text/javascript">
  function Animal(anme){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+" , my name is "+this.name);
    }
  }
  function Bird(name){
    Animal.call(this,name);
  }
  Bird.prototype=new Animal();
  Bird.prototype.constructor=Bird;
  Bird.prototype.fly=function(){
    alert("I'm flying");
  }
  var myBird=new Bird("feimos");
  myBird.say(); //I'm a(an) animal , my name is feimos
  myBird.fly(); //I'm flying
  var myDog=new Animal("aHuang");
  myDog.fly();  //报错
  </script>

我们之所以要加入Bird.prototype.constructor=Bird;语句是因为Bird.prototype=new Animal();时,Bird.prototype.constructor指向了Animal,我们需要纠正它,让它指向Bird。这样的方式可以顺利的实现js的继承,我们接下来将它进一步的封装,定义一个extend函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<script type="text/javascript">
  function extend(subClass,superClass){
    var F=function(){};
    F.prototype=superClass.prototype;
    subClass.prototype=new F();
    subClass.prototype.constructor=subClass;
    subClass.superClass=superClass.prototype;
    if(superClass.prototype.constructor==Object.prototype.constructor){
      superClass.prototype.constructor=superClass;
    }
  }
  function Animal(name){
    this.name=name;
    this.type="animal";
  }
  Animal.prototype={
    say:function(){
      alert("I'm a(an) "+this.type+" , my name is "+this.name);
    }
  }
  function Bird(name){
    this.constructor.superClass.constructor.apply(this,arguments);
    this.type="bird"
  }
  extend(Bird,Animal);
  Bird.prototype.fly=function(){
    alert("I'm flying");
  }
  var canary=new Bird("feimos");
  canary.say(); //I'm a(an) bird , my name is ??????
  canary.fly(); //I'm flying
  </script>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值