JavaScript中的面向对象

2016-10-21 07:03贾红健
电子技术与软件工程 2016年5期
关键词:多态继承面向对象

贾红健

摘 要 在很多编程者的心目中,JavaScript作为一种函数式脚本语言长期行走在面向对象语言的边缘,对于它是否面向对象模棱两可,本文通过简单的示例,回归面向对象本意,从语法角度阐述JavaScript是一种彻底的面向对象语言以及如何应用这种特性。

【关键词】JavaScript 面向对象 封装 继承 多态

面向对象程序设计(OOP)是一种程序设计范型,同时也是一种程序开发方法。对象是指类的实例,它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。那么,JavaScript(以下简称JS)是否是面向对象语言?答案是:从语法角度来说,是。但是在实践中相当多的开发者并不严格遵从面向对象。面向对象三个要素:封装,继承,多态。通常JS开发中这三个要素并不会被完全遵守,请看以下例子。

//定义个Person类

function Person(id, name)

{

this.id = id; //身份证号

this.name = name;//姓名

}

//实例化一个Person对象

var user = new Person("321321xxxxxx", "Jack");

实现封装了吗?实现了,但是不很严格。尽管user变量包含了id, name两个成员,但这两个成员都可以被任意更改,比如代码:user.name = “Rose”,没有Java、c++中类似private的关键字来控制访问权限。继承呢?JS中没有显式关键字来表示继承,像Java中有extends、implements,C++中有”:”。至于多态,是基于继承的,没有继承哪来多态。所以看起来JS对面向对象的支持不好啊,那为什么还要说它是面向对象的语言呢?下面就从面向对象三个要素:封装,继承,多态逐条讲解JS对它们的支持。

1 封装

封装是说,不只是让你能用简化的视图来看复杂的概念,同时还不能让你看到复杂概念的任何细节,你能看得到的就是你能全部得到的“代码大全”。将一组变量放到一个对象中并不是完全的封装,所以前文所说示例中的封装不严格,因为不想暴露的成员变量还是暴露了。一般OO语言中成员变量、函数都至少有三个访问级别:public所有对象可见;protected自身、子类可见;private自身可见。JS无法支持到如此详细,仅仅支持public、private。以下示例是在JS类中定义private变量,public方法。

function Person(id, name)

{

varmId; //身份证号

varmName; //姓名

mId = id;

mName = name;

//私有函数,通过身份照Id来取得生日

functiongetBirthday(){}

//读取姓名

this.getName = function(){returnmName;}

//修改姓名,人是可以改名字的

this.setName = function(name){mName = name;}

//id没有set方法,身份证号码是不能改的

this.getId = function(){returnmId;}

this.print = function(){console.log("name:" + mName + " id: " + id);}

}

var user = new Person("P1", "Jack");

private成員变量使用var关键字声明放在Person函数内部,可以防止对象外部的函数直接访问,而成员函数可以访问,从而实现了private成员变量。private成员函数getBirthday也只有成员函数才能访问,类外面是访问不了的。这个实现方法的原理是使用闭包,篇幅原因不对闭包进行展开讲解。实现了private就是完成了封装了吗?没有。

当实例化一个Person对象之后,外部尽管访问不了private变量,但是却可以恶意或不小心扩展、篡改这个对象,进而导致软件缺陷。

比如这样的代码,getName函数将无法返回正确的结果:

var user = new Person("P1", "Jack");

user.getName = function() {return "foo";}

尽管这种情况比较少,但是当软件变得复杂,人员规模变大后,很可能出问题,这是墨菲定律所决定的(墨菲定律:如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导致灾难,则必定有人会做出这种选择。)。

为了解决这个问题,则要使用函数Object.freeze

var user = new Person("P1", "Jack");

Object.freeze(user); //冻结对象

user.getName = function() {return "foo";}

这个函数调用之后,后面的修改user的代码将不起作用。不过很可惜,这个函数在IE8,或者更低的IE版本下不支持

2 继承

继承是OO设计中支持复用的基石,可以很方便的复用、扩展已有功能。JS中没有显式支持继承的关键字,但可把子类的prototye定义为父类的实例来实现。接上面的Person例子,定义一个子类Programmer。

function Programmer(id, name, skill)

{ //id, name 的意义和Person一样

Person.call(this, id, name); //调用父类构造函数

varmSkill = skill; //数组,表示技能

this.getSkill = function() {returnmSkill;}

this.addSkill = function(s) {mSkill.push(s);}

this.useSkill = function(){console.log(mSkill);}

}

//将子类的prototype指向父类的实例,否则instanceof操作将出错

Programmer.prototype = newPerson();

//设置constructor,否则子类的constructor将是父类的构造函数

Programmer.prototype.constructor = Programmer;

var nerd = newProgrammer("321321aaaa", "Linus",[ "c++", "JS" ]);

这样的操作就实现了继承。但由于无法实现protected权限,导致子类无法访问父类的private变量。对于父类成员的访问,子类和其他的类并没有更多的权限。所以将父类的成员设置为public还是private,要视情况决定了。

3 多态

面向对象中多态即意味着子类的某一功能可以有区别于父类的实现,并且同一父类的不同子类的实现也可以不一样。多態在JS中实现很简单,直接在子类中用同名函数重写父类函数即可。如下所示,在Programmer类重写print函数,将skill也打印出来:

this.print = function(){console.log("name: " + this.getName() + " id: " + this.getId() + " skill: " + JSON.stringify(this.getSkill()));}

4 其他元素

面向对象中还有一些其他元素,如重载、静态变量、多继承/接口继承以及弱类型语言中的鸭式辩型,此处仅做简要介绍。

重载可在JS函数内部判断参数个数、类型来执行不同功能,以此实现重载,代码如下:

functionfoo(v)

{

if(typeof(v) == "number"){console.log(v + " is a number");}

else if (typeof(v) == "string"){console.log(v + " is a string");}

else if (typeof(v) == "boolean"){console.log(v + " is a boolean");}

}

静态变量可通过在类的prototype上面定义变量来实现,代码如下:

Person.prototype.staticVar= "test";

多继承/接口继承是指子类有多个父类,兼有多个父类的功能。JS中没有很好的办法来实现,但由于JS是弱类型语言,只要在一个对象中添加某一个类型的方法就可以冒充该类型,即鸭式辩型。

5 小结

以上讨论了面向对象三要素在JS中的实现。我们讨论JS面向对象的目的并非鼓励大家编写OO的JS代码,而是当你认真考虑发现OO更加适合当下的需求之后,用本文提供的方法可以写出更健壮的JS代码。

作者单位

中国邮政集团公司南京分公司 江苏省南京市 210029

猜你喜欢
多态继承面向对象
分层多态加权k/n系统的可用性建模与设计优化
参差多态而功不唐捐
面向对象的计算机网络设计软件系统的开发
面向对象的数据交换协议研究与应用
浅谈杜审言、杜甫的祖孙关系:推崇、继承、发展
论电影《暮光之城》的哥特文化
面向对象Web开发编程语言的的评估方法
面向对象信息提取中影像分割参数的选择
烟碱型乙酰胆碱受体基因多态与早发性精神分裂症的关联研究