[JavaScript]继承的真真假假

文章结构

继承的判断标准
真真假假的继承实现方式

  1. 构造函数绑定
  2. prototype的拷贝
  3. 直接继承prototype
  4. prototype模式
  5. 利用空对象


JavaScript的继承实现设计得有点遮遮掩掩,对于从强类型语言转向来学习JavaScript的新手来说,是件很费脑瓜子的事情。
Sodino作为从Java转向JavaScript的新学员,尝试用这篇文章来理清‘继承’这点事。


继承的判断标准

考虑到JavaScript已经实现了’instanceof’这个运算符,所以本文中约定如下判断标准:

1

2

3

4

5

6

7

8

9

10

11
function Parent() {}

function Child()   {}

// -------start------

继承的各种实现方式

// -------end------ 

var parent = new Parent();

var child = new Child();

chlid instancof Parent  == true

chlid instancof Parent值为true时,才判定Child继承自Parent

在此判断标准下,来看看以下各种“百花齐放”的继承实现方式吧…操家伙,割韭菜。


真真假假的继承实现方式

在各种实现方式分为两种思路:

  • 增加Child的属性、方法

    1. 构造函数绑定
  • 操作prototype实现继承关系
    1. prototype拷贝
    2. 直接继承prototype
    3. prototype模式
    4. 利用空对象

下面逐一细说各种方式的实现与结论判断。


构造函数绑定

可以使用Functionapply()call()bind()来绑定构造函数,实现所谓的’继承’效果。
如下代码,child可以执行在Parent类中定义的play()方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20
代码一:

function Parent() {

this.play = function() {

console.log(‘play ...‘);

};

}

function Child(){

Parent.apply(this);

}

var parent = new Parent();

var child = new Child();

// true false

console.log(parent instanceof Parent, parent instanceof Child);

// false  true

console.log(child instanceof Parent, child instanceof Child);

child.play();    // print ‘play ...‘

代码运行如下:

使用构造函数绑定的方式,对于Chlid()构造函数来说,相当于借用了Parent()函数内的内容来对Child进行属性或方法的定义,在本例中是新增加了play()方法。
与下面的代码是等价的。

1

2

3

4

5

6
function Child() {

// 借用了Parent()中的代码内容

this.play = function() {

console.log(‘play ...‘);

};

}

应该知道instancof的运算原理是和对象的原型链相关的,所以构造函数绑定的方式并没有将ParentChild在原型链上建立关系。代码运行后child instancof Parent值是false!!!
所以这种方式只是代码复用的一种技巧,看起来是’继承‘,是假’继承‘。


prototype的拷贝

这种实现方式是将Parent.prototpye中的属性、方法全部复制到Child中去。
实现如下:

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

33
代码二:   

function extendByCopy(Child, Parent) {

  var p = Parent.prototype;

  var c = Child.prototype;

  for (var i in p) {

    c[i] = p[i];

  }

}

function Parent() {

this.play = function() {

console.log(‘play ...‘);

};

}

function Child(){} 

extendByCopy(Child, Parent);

var parent = new Parent();

var child = new Child();

// true  false

console.log(parent instanceof Parent, parent instanceof Child);

// false true

console.log(child instanceof Parent, child instanceof Child);

// child.play()  exception..

// 因为extendByCopy()只是修改prototype

// 并没有将Parent私有的方法也复制给Chlid   //sodion.com

child.play();

代码运行如下:

很明显,由于extendByCopy()只是将两个类的prototype经复制后看起来一模一样,但并没有真正在Child的原型链建立与Parent的关系,所以child instanceof Parent值仍为false,所以这也是一种假的’继承‘实现方法。


直接继承prototype

直接继承prototype的方法是将Parent.prototype赋值到Child.prototype,使两者的prototype是一致的。

如下代码中,Child.prototype指向一个新对象,但由于每个prototype都有一个constructor属性,指向它的构造函数,当执行了Child.prototype = Parent.prototype后,
Child.prototype.constructor将会等于Parent,会导致后续通过Child()构造函数初始化的对象的constructor都会是Parent(),这显然会是继承链的紊乱。
所以必须手动纠正,将Child.prototype.constructor赋值为Child本身,以此解决。
这也是JavaScript中务必要遵守的一点,如果替换了prototype对象,则下一步必然是为新的prototype对象加上constructor属性并指回原来的构造函数。

代码实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23
function Parent() {

this.play = function() {

console.log(‘play ...‘);

};

}

function Child(){

}

Child.prototype = Parent.prototype;  // Child.prototype指向新对象      // sodino.com

Child.prototype.constructor = Child;  // 必须恢复Child.prototype.constructor为Child本身,构造函数不能变

var parent = new Parent();

var child = new Child();

// true true 

console.log(parent instanceof Parent, parent instanceof Child);

// true true 

console.log(child instanceof Parent, child instanceof Child);

child.play();    // exception...

运行输出如下图:

这种方式看似符合文章开头对’继承的判断标准’。但真的是‘继承’吗?很明显该方式有以下缺点:
第一继承关系紊乱了。

child instanceof Parent值为true是正常的,但parent instanceof Child值也为true,这…‘乱伦’的画面感不敢看。

第二,由于示例代码中 play()方法并没有声明在Parent.prototype中,所以Child的对象也无法直接调用该方法。

第三,两者的prototype一致了,会导致对任一prototype的改动都会同时反馈在ChlidParent上,而这是不严谨的编程思想。(虽然严谨也不是JavaScript的风格,JavaScript一直都是随随便便的)

第四,在debug界面查看Child的原型链,发现其不完整,缺少了Parent这一环了;而且Parent也被指向了Child,会导致后续调bug时干扰分析思路。

所以’直接继承prototype’方式,虽然满足child instanceof Parent == true,但这种代码技巧更像是一种‘变脸易容’而已,Sodino也把该方式归为假继承。


prototype模式

prototype模式是对上文直接继承prototype的改进,指将子类的prototype对象指向一个父类的实例。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22
代码三:

function Parent() {

this.play = function() {

console.log(‘play ...‘);

};

}

function Child(){}

Child.prototype = new Parent();             // 子类的prototype对象指向一个父类的实例。

Child.prototype.constructor = Child;      // 修正Child的构造函数

var parent = new Parent();

var child = new Child();

// true  false

console.log(parent instanceof Parent, parent instanceof Child);

// true  true

console.log(child instanceof Parent, child instanceof Child);

child.play();    // print play...

运行后代码如下所示:

终于child instanceof Parent值为true了。这是一种真正的继承实现方式。
可以在debug界面上观察该child对象的原型链如下图所示:

相比上文的直接继承prototypeParent的原型链并没有被改变,而且子类的原型链从Child指向Parent再指向Object!很完美!


利用空对象

上文prototype模式已经完美实现继承了。但从代码设计层面上来看,JavaScript中,prototype中声明的属性、方法是共用、共享的,这部分数据被子类是继承是没有问题的。
但父类也有一些自己定义的私有属性、方法,如代码中的play()方法,在JavaScript语言层面上,它并没有定义在Parent.prototype中,所以能不能在实现继承的同时保留该方法仍是父类的私有方法,子类不可访问吗?
答案是可以的。上文prototype模式使用了Parent的一个实例对象,由于该实例对象中有play()方法,所以JavaScript解释器在执行chlid.play()时,发现child本身并没有定义,会顺着原型链逐级向上查找直至找到或找不到抛出异常。在本文示例中,很方便就在Child.prototype,即new Parent()的这个对象中找到了该方法并执行。
所以做出的改进要保留不变的是Child.prototype仍然通过一个对象间接指向Parent.prototype,需要做出改变的是该对象是个空对象即可。
具体实现为Child.prototype指向一个空的构造函数,但该空的构造函数原型指向Parent.prototype即可。

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
function extend(Child, Parent) {

  var F = function(){};

  F.prototype = Parent.prototype;

  Child.prototype = new F();

  Child.prototype.constructor = Child;

}

function Parent() {

this.play = function() {

console.log(‘play ...‘);

};

}

function Child(){}   // sodino.com

extend(Child, Parent);

var parent = new Parent();

var child = new Child();

// true false

console.log(parent instanceof Parent, parent instanceof Child);

// true true

console.log(child instanceof Parent, child instanceof Child);

// exception....

child.play();

运行后效果如下图。

查看childparent的原型链,仍旧很完美。

所以这是一种更严格的继承实现方式。



About Sodino

时间: 2024-11-13 06:39:30

[JavaScript]继承的真真假假的相关文章

闲聊javascript继承和原型

javascript继承已经是被说烂的话题了,我就随便聊一点~ 一.javascript的复制继承 javascript的继承有复制继承和原型继承,基于复制继承用的不太多,而且无法通过instanceof的验证 //拷贝继承,prototype.js的extend=> function extend(destination,source){ for(var property in source) destination[property]=source[properyt]; return des

javascript继承的三种方法

javascript并不是纯粹的面向对象的语言,因此也没有明确的继承方式,但可以通过一些方式来模拟继承.本文总结常见的javascript继承模拟方式 1,对象继承 //父类 function Person(name,age){ this.name = name; this.age = age; }; Person.prototype.height = "170cm"; //子类 function Boy(){ this.speak = function(){ alert("

javascript继承

原型链继承 1 <script> 2 function Parent(){ 3 this.name = 'mike'; 4 } 5 6 function Child(){ 7 this.age = 12; 8 } 9 Child.prototype = new Parent();//Child继承Parent,通过原型,形成链条 10 11 var test = new Child(); 12 alert(test.age); 13 alert(test.name);//得到被继承的属性 14

JavaScript继承基础讲解,原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承

说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象JS基础讲解,工厂模式.构造函数模式.原型模式.混合模式.动态原型模式>,接下来讲一般通过那些方法完成JavaScript的继承. 原型链 JavaScript中实现继承最简单的方式就是使用原型链,将子类型的原型指向父类型的实例即可,即“子类型.prototype = new 父类型();”,实现方法如下

javascript继承—prototype属性介绍(2)

js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向自身.这会形成一个非常有意思的链式结构.举例如下: function Person(){ this.name =12; } console.log(Person.prototype); console.log(Person.prototype.constructor);//输出Person,指向自身

一种基于ES5的JavaScript继承

关于JavaScript继承,方式很多,包括compile-to-javascript的语言TypeScript, CoffeeScript以及网站MDN, GitHub, Modernizr各种polyfill都给出了稳妥的实现方案. 从ES5的角度看,这其中一些方案在功能上OK,但在语义上却不尽如人意. 本人从这些方案中采取一些比较潮的思路,整理出一份方案,可实现与原生DOM类继承的风格一致,达到功能和语义兼得的效果(当然,就别再老想着99后ES3了). 如果你的WebApp是基于ES5运行

面向面试编程——javascript继承的6种方法

javascript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script type="text/javascript"> function Person(name,sex) { this.name=name; this.sex=sex; this.friends=['李四']; this.getName=function(){ alert(this.name

javascript继承的实现方式介绍

javascript继承的实现方式介绍:作为面向对象的一门语言,继承自然是javascript所比不可少的特性,下面就简单介绍一下javascript实现继承的几种方式,希望能够对需要的朋友带来一定的帮助,下面进入正题.一.对象冒充: function A() { this.name="蚂蚁部落"; this.address="青岛市南区"; } function B() { this.target="提供免费的教程"; this.newA=A;

重温Javascript继承机制

原文:http://mozilla.com.cn/post/21667/ =========================== 上段时间,团队内部有过好几次给力的分享,这里对西风师傅分享的继承机制稍作整理一下,适当加了些口语化的描述,留作备案. 一.讲个故事吧 澄清在先,Java和Javascript是雷锋和雷峰塔的关系.Javascript原名Mocha,当时还叫做LiveScript,创造者是Brendan Eich,现任Mozilla公司首席技术官. 1994年,历史上第一个比较成熟的网