JavaScript高级 面向对象的程序设计 (二)

二、继承

OO是面向对象语言最为有魅力的概念。一般的OO语言都实现了两种继承,接口继承和实现继承。接口继承只继承方法签名,而实际继承继承了实际的方法。

而在JS中,函数没有签名,所以无法实现接口继承。只能依靠原型链--实现继承。

2.1原型链

JS中描述了原型链的概念,并利用原型链作为实现继承的主要方法。

其基本思想:利用原型链让一个引用类型继承另一个引用类型的属性和方法。

functionSuperF(){

this.superPropty =‘B‘;

}

SuperF.prototype.getSuperPropty =function(){

alert(this.superPropty);

}

functionSubF(){

this.subPropty =‘S‘;

}

SubF.prototype =newSuperF();

SubF.prototype.constructor =SubF

var p =newSubF();

p.getSuperPropty();

重写子类的原型对象,代之以父类的实例。那么这个实例拥有一个指向父类构造函数原型的指针。

2.1.1别忘记默认的原型

完整的原型链

2.1.2确定原型和实例的关系

确定引用类型是否在原型链中出现过

2.1.3谨慎的定义方法

在子类中定义新方法属性或者重写父类中的方法属性,必须在子类原型被替换之后。

ps:需要注意的一定是再通过原型链实现继承时,不能使用对象字面量添加或重写方法或属性。

这样会使原型再次替换成字面量而导致前面的操作无效。

2.1.4原型链的问题

原型链虽然强大,但是它也带来一些问题:其中最主要的问题来自包含引用类型值的原型。因为包含的引用类型值的原型,其属性会被所有实例共享。所以我们通常把属性写在构造函数中,来避免这个问题。

于是,原型实际上会变成另一个类型的实例,那么原型实例的属性就成了原型属性了。这就会带来问题。

第二个问题是:在创建子类实例的时候,不能向超类型的构造函数中传递参数。这样会影响所有对象实例。

所以,结合上面两个问题,我们通常很少单独使用原型链。

2.2借用构造函数

这是一种解决原型中包含引用类型值所带来问题的技术。(又叫做伪造对象

这种技术的基本思想:在子类型构造函数的内部调用超类构造函数。(使用apply()/call()方法)

functionSuperF(){

this.superPropty =‘B‘;

this.colors =[‘red‘,‘blue‘];

}

functionSubF(){

SuperF.call(this);

this.subPropty =‘S‘;

}

var p =newSubF();

p.colors.push(‘black‘);

p.superPropty =‘C‘

alert(p.superPropty);//C

alert(p.colors);//‘red‘,‘blue‘,‘black‘

var pp =newSubF();

alert(pp.superPropty);//B

alert(pp.colors);//‘red‘,‘blue‘

2.2.1传递参数

functionSuperF(name){

this.name = name;

this.colors =[‘red‘,‘blue‘];

}

functionSubF(){

SuperF.call(this,‘zjh‘);

this.subPropty =‘S‘;

}

var p =newSubF();

p.colors.push(‘black‘);

alert(p.name);//zjh

alert(p.colors);//‘red‘,‘blue‘,‘black‘

var pp =newSubF();

alert(pp.name);//zjh

alert(pp.colors);//‘red‘,‘blue‘

这是借用构造函数模式的一个很大的优势。

ps:为了防止超类构造函数不会重写类型的属性,可以在调用超类型构造函数之后,再添加子类型构造函数的属性。

2.2.2借用构造函数模式的问题

如果仅仅是借用构造函数,那么也无法避免构造函数模式存在的问题----方法都在构造函数中定义,因此函数复用就不可能了。在超类构造函数的原型中定义方法,对子类而言是不可见的,结果所有的类型都只能使用构造函数模式。

所以这种技术也是很少单独使用的。

2.3组合继承

组合继承有时也叫作经典继承。指的是:利用原型模式和借用构造函数模式相结合,发挥两者之长的继承模式。

/**

* 组合继承

*/

functionSuperF(name){

this.name = name;

this.colors =[‘red‘,‘blue‘];

}

SuperF.prototype.getName =function(){

alert(this.name+‘+function‘);

}

functionSubF(){

SuperF.call(this,‘zjh‘);

this.subPropty =‘S‘;

}

SubF.prototype =newSuperF();

var p =newSubF();

p.colors.push(‘black‘);

alert(p.name);//zjh

alert(p.colors);//‘red‘,‘blue‘,‘black‘

p.getName();//zjh+function

var pp =newSubF();

pp.name=‘zzz‘

alert(pp.name);//zjh

alert(pp.colors);//‘red‘,‘blue‘

pp.getName();//zzz+function

组合继承避免了原型链和借用构造函数的缺陷,成为一种JS最常用的继承模式。同时支持instanceof和isPrototypeOf()

2.4原型式继承

这种实现继承的方法没有使用严格意义上的构造函数,而是借用原型,基于已有对象创建新的对象,同时还不必因此创建自定义类型。

function getObject(o){

function F(){};

F.prototype = o;

return new F();

}

var o = {

name : ‘zzz‘,

age : ‘15‘,

colors: [‘red‘,‘blue‘]

}

var o1 = getObject(o);

o1.colors.push(‘black‘);

alert(o1.colors);

var o2 = getObject(o);

alert(o2.colors);

从何本质上来讲,getObject()对传入其中的对象执行了一次浅复制。

ps:JS也提供了一个函数create(),当它只有一个参数的时候,实现了和上面getObject()方法相同的效果。

var o3 = Object.create(o);

alert(o3.name)

第二个参数:一个为新对象定义的额外属性的对象

var o4 = Object.create(o,{

name : {value:‘zsda‘}

});

alert(o4.name)

但是无论用getObject()还是create() ,原型继承所带来的负面影响一定会有(引用类型值在所有创建的对象中共享),但是如果只想让一个对象与另一个对象保持类似的情况下,这种原型集成模式是完全可以胜任的。

2.5寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似。即 创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

function getObject(o){

function F(){};

F.prototype = o;

return new F();

}

var o = {

name : ‘zzz‘,

age : ‘15‘,

colors: [‘red‘,‘blue‘]

}

function getAnother(o){

var clone = getObject(o);  //通过调用一个函数 返回复制的对象

clone.getName = function(){ //对对象增强

alert(this.name);

}

return clone;

}

var o1 = getAnother(o);

o1.getName();

这种方式主要利用于不考虑自定义类型和构造函数的情况下。

它的缺陷:和构造函数模式一样,每创建一次对象,那么它的方法都无法复用,必须创建一个新的方法,效率相对差一些。

2.6寄生组合式继承

上面说过 组合继承是JS中最常见的实用的继承模式,不过它也有自身的不足。

组合继承最大的问题就是:在任何情况下都会调用两次超类型构造函数,一次是在创建子类的原型时候,第二次是在子类型构造函数内部。子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

function SuperF(){

this.name = ‘zjh‘;

this.age = 15

}

SuperF.prototype.getName = function(){

alert(this.name);

}

function SubF(){

SuperF.call(this); //第二次调用

this.colors = [‘red‘,‘blue‘];

}

SubF.prototype = new SuperF(); //第一次调用

SubF.prototype.constructor = SubF;

var o = new SubF();

o.getName();

从上面的代码可以看出name和age属性被两次得到,一次在实例上,一次在SubF原型上。这就是组合继承的弊端

解决它的方法就是----寄生组合继承

所谓寄生组合继承,就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。主要思路是:

不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function object(o){

function F(){};

F.prototype = o;

return new F();

}

function inheritPrototype(subF,superF){

var prototype = object(superF.prototype); //创建超类对象原型的副本

prototype.constructor = subF;//增强对象

subF.prototype = prototype;//指定对象

}

function SuperF(){

this.name = ‘zjh‘;

this.age = 16;

this.colors = [‘red‘,‘blue‘];

}

SuperF.prototype.getName = function(){

alert(this.name);

}

function SubF(){

SuperF.call(this);

this.sex = ‘男‘;

}

inheritPrototype(SubF,SuperF);

var o = new SubF();

o.colors.push(‘black‘);

o.getName();

alert(o.colors);

var o1 = new SubF();

alert(o1.colors);

这种方式的高效体现在只调用一次SupeF构造函数,并且因此避免了在SubType.prototype上面创建不必要的多余的属性,与此同时,原型链还能保持不变,因此能够正常使用instanceof 和 isPrototypeOf()。这种寄生组合式继承是引用类型最理想的继承范式。

ps:YUI中的YAHOO.lang.extend()方法就采用了这种方式。

三、小结

JS支持面向对象(OO)的编程,但不使用类或者接口来实现。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。

3.1几种创建对象的方法:

工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回一个对象。这个模式因为无法被识别类型,只能被识别为object,而被构造函数模式所取代。

构造函数模式:可以创建自定义的引用类型,可以像创建内置对象

实例一样使用new操作符。不过,它的缺点就是它的每个成员都没办法复用,特别是函数,一方面效率低,一方面函数可以不局限于任何对象,因此没有理由不在多个对象间共享函数。

原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式。一般使用构造模式定义实例属性,使用原型模式定义共享的属性和方法。

3.2几种继承方式:

JS主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型来实现的。

原型链继承导致引用类型值会被所有创建的对象共享。

借用构造函数继承(call/apply)导致方法都必须在构造函数内部定义,这样方法的复用就无从谈起了。

组合继承:利用上面两种继承的有点组合起来。缺陷是:会调用两次超类型构造器,从而重复定义属性。

原型式继承(create):它实际上也是原型链继承,问题也是引用类型值会被所有创建的对象共享。(可以不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制,得到复制后的副本可以进一步改造)

寄生式继承:在原型式继承的基础上,基于某个对象创建一个新对象,然后增强对象,返回对象。缺陷和借用构造函数继承一样,方法无法复用共享。

寄生组合式继承:结合寄生式继承和组合继承,解决了两种继承的缺陷,是最有效的方式。

 

时间: 2024-12-30 10:59:57

JavaScript高级 面向对象的程序设计 (二)的相关文章

JavaScript高级 面向对象的程序设计 (一)

创建对象 继承 面向对象的语言都有一个表示---类.通过类我们可以创建多个具有相同属性的对象.但是,在JS中并没有类的概念,所以JS的对象也和其他语言的对象不同. 对象的定义:无序的属性集合,其属性可以包含基本值,对象,函数.(所以我们又可以把JS对象看成散列表,一组键值对.) 一.创建对象 1.1工厂模式 function createPerson(name,age){ var o =newObject(); o.name = name; o.age= age; o.sayName =func

JavaScript基础——面向对象的程序设计(二)继承

继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.如前所述,由于函数没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且实现继承主要是依靠原型链来实现的. 原型链 ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法.其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.简单回顾以下构造函数.原型和实例的关系:每个构造函数都有

JavaScript基础——面向对象的程序设计(一)创建对象的几种方式总结

简介 面向对象(Object-Oriented, OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象.前面提到过,ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同. ECMA-262把对象定义为:"无序属性的集合,其属性可以包含基本值.对象或者函数."严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每个属性或方法都有一个名字,而每个名字都映射到一个值.正因为这样(以及其他将要讨论的原因),我们可以把E

JavaScript高级 面向对象(13)--构造函数的执行过程

说明(2017-4-2 21:50:45) 一.构造函数是干什么用的: 1. 初始化数据的. 2. 在js给对象添加属性用的,初始化属性值用. 二.创建对象的过程: 1. 代码:var p = new Person(); 2. 首先运算符new创建了一个对象,它类似于{},是一个"没有任何成员"的对象. * 使用new创建对象,对象的类型就是创建它的构造函数名(如,Person类型). * 使用{}无论如何都是Object类型,相当于"new Object()".

JavaScript高级 面向对象(1)--添加一个div标签

说明(2017.3.28): 1. JavaScript是一种基于对象的多范式编程语言,不是面向对象,但离开对象不能活. 范式编程是指编程习惯.方式,分为过程式.对象式和函数式编程. 2. 面向对象是指,使用对象进行开发,面向对象是对面向过程的封装. 3. JavaScript面向对象的三个特点,抽象性.继承性和封装性. 抽象性是指,抽取出核心属性和方法,不在特定条件下不能确定对象的具体意义. 继承性是指,把我没有的属性和方法拿来使用,并变成自己的属性和方法. 封装性是指,把方法和属性打包成一个

轻松学习JavaScript十二:JavaScript基于面向对象之创建对象(二)

四原型方式 我们创建的每个函数都有一个通过prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型 的所有实例共享的属性和方法.逻辑上可以这么理解:prototypt通过条用构造函数而创建的那个对象的原型对象.使 用原型的好处就是可以让所有对象实例共享它所包含的属性和方法.也就是说,不必在构造函数中定义对象信息,而 是直接将这些信息添加到原型中. 原型方式利用了对象的prototype 属性,可以把它看成创建新对象所依赖的原型.这里,首先用空构造函数来设置 函数名.然后所

JavaScript高级 面向对象(12)--引用类型值类型作为参数传递的特性

说明(2017-4-2 18:27:11): 1. 作为函数的参数,就是将函数的数据拷贝一份,传递给函数的定义中的参数. 函数foo()在调用的时候,做了两件事: (1)函数在调用的时候,首先需要将参数中的数据拷贝一份,即数字123拷贝一份. (2)跳转到函数的定义中(函数体),在此之前完成了函数的赋值,即num=123. (3)正式的进入函数内,准备执行函数的每一句话. 1 function foo(num){} 2 var a = 123; 3 foo(a); 2. 值类型作为函数参数传递的

JavaScript高级 面向对象(10)--onload与jq中ready的区别

说明(2017.4.2): 1. 在body中放一个img标签,src链接一张图片,那么页面会先读取html的document文档,然后再读取外部资源(这里没加onload其实就是从上往下顺序读取). 外部资源包括导入的js,css,图片,音乐,视频等等. onload会将所有的资源包括外部资源全部加载完成. 而jquery的ready只要加载完dom树就完成了,里面的图片等资源有没有加载无所谓. 1 <body> 2 <img src="1.jpg"> 3 &

JavaScript高级 面向对象(5)--最简单的继承方式,混入mix

说明(2017.3.30): 1. 最简单的继承方式,混入mix 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 </head> 7 <body> 8 <script type="text/javascrip