JavaScript定义类或函数的几种方式小结


Posted in Javascript onJanuary 09, 2011

提起面向对象我们就能想到类,对象,封装,继承,多态。在《javaScript高级程序设计》(人民邮电出版社,曹力、张欣译。英文名字是:Professional JavaScript for Web Developers)这本书中描述的还算比较详细。我们看看JavaScript中定义类的各种方法。
1.工厂方式
javaScript中创建自己的类和对象,我们应该是必须掌握的,我们都知道javaScript中对象的属性可以在对象创建后动态定义,比如下面的代码:

<script type="text/javascript"> 
//定义 
var oCar = new Object(); 
oCar.color = "red"; 
oCar.doors = 4; 
oCar.showColor = function() { 
alert(this.color); 
} 
//调用 
oCar.showColor(); 
</script>

我们很容易使用oCar对象,但是我们创就是想创建多个Car实例。我们可以使用一个函数来封装上面的代码来实现:<script type="text/javascript">
//定义 
function createCar() { 
var oCar = new Object(); 
oCar.color = "red"; 
oCar.doors = 4; 
oCar.showColor = function() { 
alert(this.color); 
} 
return oCar; 
} 
//调用 
var ocar1 = createCar(); 
var ocar2 = createCar(); 
ocar1.color = "black"; 
ocar1.showColor(); 
ocar2.showColor(); 
</script>

顺便说一下,javaScript对象默认成员属性都是public 的。这种方式我们称为工厂方式,我们创造了能创建并返回特定类型的对象的工厂。
这样做有点意思了,但是在面向对象中我们经常使用创建对象的方法是:
Car car=new Car();
使用new 关键字已经深入人心,因此我们使用上面的方法去定义总感觉别扭,并且每次调用时都去创建新的属性以及函数,功能上也不实际。下来我们看看构造函数的形式定义类。
2.构造函数
这种方式看起来有点象工厂函数。具体表现如下:
<script type="text/javascript"> 
//定义 
function Car(color, doors) { 
this.color = color; 
this.doors = doors; 
this.showColor = function() { 
alert(this.color); 
}; 
} 
//调用 
var car1 = new Car("red", 4); 
var car2 = new Car("blue", 4); 
car1.showColor(); 
car2.showColor(); 
</script>

看起来效果很明显,有差别了吧。感觉有点意思了。在构造函数内部创造对象使用this 关键字,使用new 运算符创建对象感觉非常亲切。但是也有点问题:每次new 对象时都会创建所有的属性,包括函数的创建,也就是说多个对象完全独立,我们定义类的目的就是为了共享方法以及数据,但是car1对象与car2对象都是各自独立的属性与函数,最起码我们应该共享方法。这就是原形方式的优势所在。
3.原型方式
利用对象的prototype属性,可把它看出创建新对象所依赖的原型。方法如下:
<script type="text/javascript"> 
//定义 
function Car() { 
}; 
Car.prototype.color = "red"; 
Car.prototype.doors = 4; 
Car.prototype.drivers = new Array("Tom", "Jerry"); 
Car.prototype.showColor = function() { 
alert(this.color); 
} 
//调用: 
var car1 = new Car(); 
var car2 = new Car(); 
car1.showColor(); 
car2.showColor(); 
alert(car1.drivers); 
car1.drivers.push("stephen"); 
alert(car1.drivers); //结果:Tom,Jerry,stephen 
alert(car2.drivers); //结果:Tom,Jerry,stephen 
//可以用json方式简化prototype的定义: 
Car.prototype = 
{ 
color: "red", 
doors: 4, 
drivers: ["Tom", "Jerry",'safdad'], 
showColor: function() { 
alert(this.color); 
} 
}</script>

首先这段代码的构造函数,其中没有任何代码,接下来通过对象的prototype属性添加属性定义Car对象的属性。这种方法很好,但是问题是Car的对象指向的是Array指针,Car的两个对象都指向同一个Array数组,其中一个对象car1改变属性对象的引用(数组Array)时,另一个对象car2也同时改变,这是不允许的。
同时该问题也表现在原型不能带任何初始化参数,导致构造函数无法正常初始化。这需要另一种方式来解决:那就是混合的构造函数/原型模式。
4. 混合的构造函数/原型模式
联合使用构造函数和原型方式,定义类就非常方便。
<script type="text/javascript"> 
//定义 
function Car(color,doors) 
{ 
this.color=color; 
this.doors=doors; 
this.drivers=new Array("Tom","Jerry"); 
} 
Car.prototype.showColor=function(){ 
alert(this.color); 
} 
//调用: 
var car1=new Car('red',4); 
var car2=new Car('blue',4); 
car1.showColor(); 
car2.showColor(); 
alert(car1.drivers); 
car1.drivers.push("stephen"); 
alert(car1.drivers); //结果:Tom,Jerry,stephen 
alert(car2.drivers); //结果:Tom,Jerry 
alert(car1 instanceof Car); 
</script>

该方法是把属性放在内部定义,把方法放在外边利用prototype进行定义。解决了第三种方法的问题。
这种方法其实应该来说非常友好了,但是比起java的语法来,应该有一些不和谐,感觉比较凌乱,对C++来说,我们就没有那么麻烦的感觉了,可是开发C++的研发人员一般情况下很少涉及javaScript,而对J2EE的研发人员来说,这种方式总有一些别扭。总感觉不是友好的封装,其实只不过是视觉上封装效果不是很好而已,要想达到视觉封装效果而又能达到这种方法的效果的也可以以,个人认为其实比较麻烦。那就是动态原型法。
5.动态原型
对于习惯使用其他语言的开发者来说,使用混合的构造函数/原型方式感觉不那么和谐。毕竟,定义类时,大多数面向对象语言都对属性和方法进行了视觉上的封装。考虑下面的C#类:
class Car //class 
{ 
public string color = "red"; 
public int doors = 4; 
public int mpg = 23; 
public Car(string color, int doors, int mpg) //constructor 
{ 
this.color = color; 
this.doors = doors; 
this.mpg = mpg; 
} 
public void showColor() //method 
{ 
Console.WriteLine(this.color); 
} 
}

C#很好的打包了Car类的所有属性和方法,因此看见这段代码就知道它要实现什么功能,它定义了一个对象的信息。批评混合的构造函数/原型方式的人认为,在构造函数内存找属性,在其外部找方法的做法不合逻辑。因此,他们设计了动态原型方法,以提供更友好的编码风格。
动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置。下面是用动态原型方法重写的Car类:
<script type="text/javascript"> 
//定义 
function Car() { 
this.color = "red"; 
this.doors = 4; 
this.drivers = new Array("Tom", "Jerry"); 
if (typeof Car._initialized == "undefined") { 
Car.prototype.showColor = function() { 
alert(this.color); 
} 
//............ 
} 
//最后定义 
Car._initialized = true; 
} 
</script>

直到检查typeof Car._initialized是否等于"undefined"之前,这个构造函数都未发生变化。这行代码是动态原型方法中最重要的部分。如果这个值未定义,构造函数将用原型方式继续定义对象的方法,然后把Car._initialized设置为true。如果这个值定义了(它的值为true时,typeof的值为Boolean),那么就不再创建该方法。简而言之,该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次,为取悦传统的OOP开发者,这段代码看起来更像其他语言中的类定义了。
6 混合工厂方式

这种方式通常是在不能应用前一种方式时的变通方法。它的目的是创建假构造函数,只返回另一种对象的新实例。这段代码看来与工厂函数非常相似:
function Car() { 
var oTempCar = new Object(); 
oTempCar.color="red"; 
oTempCar.doors=4; 
oTempCar.mpg=23; 
oTempCar.showColor = function() { 
alert(this.color); 
} 
return oTempCar; 
}

与经典方式不同,这种方式使用new运算符,使它看起来像真正的构造函数:
var oCar = new Car();

由于在Car()构造函数内部调用了new运算符,所以将忽略第二个new运算符(位于构造函数之外)。在构造函数内部创建的对象被传递回变量var。这种方式在对象方法的内部管理方面与经典方式有着相同的问题。强烈建议:除非万不得已(请参阅第15章),还是避免使用这种方式。
总结:(采用哪种方式)
目前使用最广泛的是混合的构造函数/原型方式。此外,动态原型方法也很流行,在功能上与构造函数/原型方式等价。可以采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。

//ps 
//static class (1:function) 
var CarCollection = new function() { 
var _carCollection = new Array(); //global,private 
this.Add = function(objCar) { 
alert('Add'); 
} 
this.Get = function(carid) { 
alert('Get'); 
} 
} 
//static class (2:json) var Car = { 
color: 'red', 
doors: 4, 
showColor: function() { alert(this.color); } 
} 
Car.showColor();
Javascript 相关文章推荐
JS 跳转页面延迟2种方法
Mar 29 Javascript
Javascript中的五种数据类型详解
Dec 26 Javascript
jQuery的几个我们必须了解的特点
May 03 Javascript
MVVM模式中ViewModel和View、Model有什么区别?
Jun 19 Javascript
JavaScript html5 canvas绘制时钟效果(二)
Mar 27 Javascript
jQuery Pagination分页插件使用方法详解
Feb 28 Javascript
koa上传excel文件并解析的实现方法
Aug 09 Javascript
angular2组件中定时刷新并清除定时器的实例讲解
Aug 31 Javascript
js实现文字头像的生成代码
Mar 07 Javascript
vue fetch中的.then()的正确使用方法
Apr 17 Javascript
js禁止查看源文件屏蔽Ctrl+u/s、F12、右键等兼容IE火狐chrome
Oct 01 Javascript
一篇文章看懂JavaScript中的回调
Jan 05 Javascript
javascript中用星号表示预录入内容的实现代码
Jan 08 #Javascript
js中访问html中iframe的文档对象的代码[IE6,IE7,IE8,FF]
Jan 08 #Javascript
js中top、clientTop、scrollTop、offsetTop的区别 文字详细说明版
Jan 08 #Javascript
Jquery Ajax请求代码(2)
Jan 07 #Javascript
JQuery 绑定select标签的onchange事件,弹出选择的值,并实现跳转、传参
Jan 06 #Javascript
神奇的7个jQuery 3D插件整理
Jan 06 #Javascript
jquery下为Event handler传递动态参数的代码
Jan 06 #Javascript
You might like
PHP 内存缓存加速功能memcached安装与用法
2009/09/03 PHP
php处理抢购类功能的高并发请求
2018/02/08 PHP
简单的无缝滚动程序-仅几行代码
2007/05/08 Javascript
jQuery实现等比例缩放大图片让大图片自适应页面布局
2013/10/16 Javascript
node.js中的socket.io的广播消息
2014/12/15 Javascript
javascript的switch用法注意事项分析
2015/02/02 Javascript
jquery简单实现图片切换效果的方法
2015/05/12 Javascript
BootStrap点击下拉菜单项后显示一个新的输入框实现代码
2016/05/16 Javascript
Javascript 基础---Ajax入门必看
2016/07/06 Javascript
Ubuntu 16.04 64位中搭建Node.js开发环境教程
2016/10/19 Javascript
微信小程序 textarea 详解及简单使用方法
2016/12/05 Javascript
ES6新特性八:async函数用法实例详解
2017/04/21 Javascript
React Native实现进度条弹框的示例代码
2017/07/17 Javascript
Vue中定义全局变量与常量的各种方式详解
2017/08/23 Javascript
详解Vue组件实现tips的总结
2017/11/01 Javascript
JQuery元素快速查找与操作
2018/04/22 jQuery
angularjs结合html5实现拖拽功能
2018/06/25 Javascript
angular6 利用 ngContentOutlet 实现组件位置交换(重排)
2018/11/02 Javascript
vue-cli3全面配置详解
2018/11/14 Javascript
layui按条件隐藏表格列的实例
2019/09/19 Javascript
vue 中url 链接左边的小图标更改问题
2019/12/30 Javascript
详细分析vue响应式原理
2020/06/22 Javascript
[16:27]DOTA2 HEROS教学视频教你分分钟做大人-艾欧
2014/06/11 DOTA
python字符串,元组,列表,字典互转代码实例详解
2020/02/14 Python
python实现凯撒密码、凯撒加解密算法
2020/06/11 Python
Python中实现一行拆多行和多行并一行的示例代码
2020/09/06 Python
python调用jenkinsAPI构建jenkins,并传递参数的示例
2020/12/09 Python
基于CSS3实现图片模糊过滤效果
2015/11/19 HTML / CSS
使用HTML5 Canvas API控制字体的显示与渲染的方法
2016/03/24 HTML / CSS
Sneaker Studio乌克兰:购买运动鞋
2018/03/26 全球购物
莫斯科珠宝厂官方网站:Miuz
2020/09/19 全球购物
应聘文员自荐信范文
2014/03/11 职场文书
意外事故赔偿协议书
2016/03/22 职场文书
介绍信应该怎么开?
2019/04/03 职场文书
七年级写作指导之游记作文
2019/10/07 职场文书
《时代广场的蟋蟀》读后感:真挚友情,温暖世界!
2020/01/08 职场文书