关于JavaScript定义类和对象的几种方式


Posted in Javascript onNovember 09, 2010

可以看看这个例子:

var a = 'global'; 
(function () { 
alert(a); 
var a = 'local'; 
})();

大家第一眼看到这个例子觉得输出结果是什么?‘global'?还是‘local'?其实都不是,输出的是undefined,不用迷惑,我的题外话就是为了讲这个东西的。
其实很简单,看一看JavaScript运行机制就会明白。我们可以把这种现象看做“预声明”。但是如果稍微深究一下,会明白得更透彻。

这里其实涉及到对象属性绑定机制。因为所有JavaScript函数都是一个对象。在函数里声明的变量可以看做这个对象的“类似属性”。对象属性的绑定在语言里是有分“早绑定”和“晚绑定”之分的。

【早绑定】
是指在实例化对象之前定义其属性和方法。解析程序时可以提前转换为机器代码。通常的强类型语言如C++,java等,都是早绑定机制的。而JavaScript不是强类型语言。它使用的是“晚绑定”机制。

【晚绑定】
是指在程序运行前,无需检查对象类型,只要检查对象是否支持特性和方法即可。可以在绑定前对对象执行大量操作而不受任何惩罚。
上面代码出现的“预声明”现象,我们大可用“晚绑定”机制来解释。在函数的作用域中,所有变量都是“晚绑定”的。 即声明是顶级的。所以上面的代码和下面的一致:

var a = 'global'; 
(function () { 
var a; 
alert(a); 
a = 'local'; 
})();

在alert(a)之前只对a作了声明而没有赋值。所以结果可想而知。

<!-- 题外话到此结束 -->

RT:本文要说的是,在JavaScript里,我所知道的几种定义类和对象的方式:<! -- 声明:以下内容大部分来自《JavaScript高级程序设计》,只是个人叙述方式不同而已 -- >

【直接量方式】

使用直接量构建对象是最基础的方式,但也有很多弊端。

var Obj = new Object; 
Obj.name = 'sun'; 
Obj.showName = function() { 
alert('this.name'); 
}

我们构建了一个对象Obj,它有一个属性name,一个方法showName。但是如果我们要再构建一个类似的对象呢?难道还要再重复一遍?
NO!,我们可以用一个返回特定类型对象的工厂函数来实现。就像工厂一样,流水线的输出我们要的特定类型结果。

【工厂方式】

function createObj(name) { 
var tempObj = new Object; 
tempObj.name = name; 
tempObj.showName = function () { 
alert(this.name); 
}; 
return tempObj; 
} 
var obj1 = createObj('obj_one'); 
var obj2 = createObj('obj_two');

这种工厂函数很多人是不把他当做构建对象的一种形式的。一部分原因是语义:即它并不像使用了运算符new来构建的那么正规。还有一个更大的原因,是因为这个工厂每次产出一个对象都会创建一个新函数showName(),即每个对象拥有不同的版本,但实际上他们共享的是同一个函数。
有些人把showName在工厂函数外定义,然后通过属性指向该方法,可以避开这个问题:
function showName () { 
alert(this.name); 
} 
function createObj(name) { 
var tempObj = new Object; 
tempObj.name = name; 
tempObj.showName = showName; 
return tempObj; 
} 
var obj1 = createObj('obj_one'); 
var obj2 = createObj('obj_two');

可惜的是,这种方式让showName()这个函数看起来不像对象的一个方法。

【构造函数方式】
这种方式是为了解决上面工厂函数的第一个问题,即没有new运算符的问题。可是第二个问题它依然不能解决。我们来看看。

function Obj(name) { 
this.name = name; 
this.showName = function () { 
alert(this.name); 
} 
} 
var obj1 = new Obj('obj_one'); 
var obj2 = new Obj('obj_two');

它的好处是不用在构造函数内新建一个对象了,因为new运算符执行的时候会自动创建一个对象,并且只有通过this才能访问这个对象。所以我们可以直接通过this来对这个对象进行赋值。而且不用再return,因为this指向默认为构造函数的返回值。
同时,用了new关键字来创建我们想要的对象是不是感觉更“正式”了。
可惜,它仍然不能解决会重复生成方法函数的问题,这个情况和工厂函数一样。

【原型方式】
这种方式对比以上方式,有个很大的优势,就是它解决了方法函数会被生成多次的问题。它利用了对象的prototype属性。我们依赖原型可以重写对象实例。

var Obj = function () {} 
Obj.prototype.name = 'me'; 
Obj.prototype.showName = function () { 
alert(this.name); 
} 
var obj1 = new Obj(); 
var obj2 = new Obj();

我们依赖原型对构造函数进行重写,无论是属性还是方法都是通过原型引用的方式给新建的对象,因此都只会被创建一次。可惜的是,这种方式存在两个致命的问题:
1。没办法在构建对象的时候就写入想要的属性,因为原型在构造函数作用域外边,没办法通过传递参数的方式在对象创建的时候就写入属性值。只能在对象创建完毕后对值进行重写。
2。致命问题在于当属性指向对象时,这个对象会被多个实例所共享。考虑下面的代码:
var Obj = function () {} 
Obj.prototype.name = 'me'; 
Obj.prototype.flag = new Array('A', 'B'); 
Obj.prototype.showName = function () { 
alert(this.name); 
} 
var obj1 = new Obj(); 
var obj2 = new Obj(); 
obj1.flag.push('C'); 
alert(obj1.flag); // A,B,C 
alert(obj2.flag); //A,B,C

是的,当flag属性指向对象时,那么实例obj1和obj2都共享它,哪怕我们仅仅改变了obj1的flag属性,但是它的改变在实例obj2中任然可见。
面对这个问题,让我们不得不想是否应该把【构造函数方式】和【原型方式】结合起来,让他们互补。。。

【构造函数和原型混合方式】
我们让属性用构造函数方式创建,方法用原型方式创建即可:

var Obj = function (name) { 
this.name = name; 
this.flag = new Array('A', 'B'); 
} 
Obj.prototype = { 
showName : function () { 
alert(this.name); 
} 
} 
var obj1 = new Obj(); 
var obj2 = new Obj(); 
obj1.flag.push('C'); 
alert(obj1.flag); // A,B,C 
alert(obj2.flag); //A,B

这种方式有效地结合了原型和构造函数的优势,是目前用的最多,也是副作用最少的方式。
不过,有些追求完美的家伙还不满足,因为在视觉上还没达到他们的要求,因为通过原型来创建方法的过程在视觉上还是会让人觉得它不太像实例的方法(尤其对于传统OOP语言的开发者来说。)
所以,我们可以让原型活动起来,让他也加入到构造函数里面去,好让这个构造函数在视觉上更为统一。而这一系列的过程只需用一个判断即可完成。
var Obj = function (name) { 
this.name = name; 
this.flag = new Array('A', 'B'); 
if (typeof Obj._init == 'undefined') { 
Obj.prototype = { 
showName : function () { 
alert(this.name); 
} 
}; 
Obj._init = true; 
} 
}

如上,用_init作为一个标志来判断是否已经给原型创建了方法。如果是那么就不再执行。这样其实在本质上是没有任何变化的,方法仍是通过原型创建,唯一的区别在于这个构造函数看起来“江山统一”了。
但是这种动态原型的方式是有问题的,《JavaScript高级程序设计》里并没有深究。创建第一个对象的时候会因为prototype在对象实例化之前没来的及建起来,是根本无法访问的。所以第一个对象是无法访问原型方法的。同时这种方式在子类继承中也会有问题。
关于解决方案,我会在下一文中说明。

其实就使用方便来说的话,个人觉得是没必要做这个判断的。。。呵呵 ^_^

Javascript 相关文章推荐
扩展jquery实现客户端表格的分页、排序功能代码
Mar 16 Javascript
基于js disabled=&quot;false&quot;不起作用的解决办法
Jun 26 Javascript
div浮层,滚动条移动,位置保持不变的4种方法汇总
Dec 11 Javascript
Javascript堆排序算法详解
Dec 03 Javascript
jQuery中clearQueue()方法用法实例
Dec 29 Javascript
javascript浏览器窗口之间传递数据的方法
Jan 20 Javascript
js表单提交和submit提交的区别实例分析
Dec 10 Javascript
AngularJS仿苹果滑屏删除控件
Jan 18 Javascript
教你如何终止JQUERY的$.AJAX请求
Feb 23 Javascript
vue watch深度监听对象实现数据联动效果
Aug 16 Javascript
vue地址栏直接输入路由无效问题的解决
Nov 15 Javascript
PHP读取远程txt文档到数组并实现遍历
Aug 25 Javascript
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
Nov 09 #Javascript
一个javascript图片阅览组件
Nov 09 #Javascript
js中格式化日期时间型数据函数代码
Nov 08 #Javascript
window.location.hash 使用说明
Nov 08 #Javascript
JavaScript游戏之是男人就下100层代码打包
Nov 08 #Javascript
JavaScript游戏之优化篇
Nov 08 #Javascript
javascript开发中因空格引发的错误
Nov 08 #Javascript
You might like
Ajax PHP 边学边练 之三 数据库
2009/11/26 PHP
php 文件上传实例代码
2012/04/19 PHP
php实现可用于mysql,mssql,pg数据库操作类
2014/12/13 PHP
PHP验证信用卡卡号是否正确函数
2015/05/27 PHP
举例详解Python中smtplib模块处理电子邮件的使用
2015/06/24 Javascript
jquery SweetAlert插件实现响应式提示框
2015/08/18 Javascript
JS数组合并push与concat区别分析
2015/12/17 Javascript
基于JavaScript实现轮播图原理及示例
2020/04/10 Javascript
Vue 2.X的状态管理vuex记录详解
2017/03/23 Javascript
微信小程序日历组件calendar详解及实例
2017/06/08 Javascript
JavaScript实现职责链模式概述
2018/01/25 Javascript
微信小程序checkbox组件使用详解
2018/01/31 Javascript
jQuery滚动条美化插件nicescroll简单用法示例
2018/04/18 jQuery
vue项目环境变量配置的实现方法
2018/10/12 Javascript
JS获取当前时间的实例代码(昨天、今天、明天)
2018/11/13 Javascript
深入了解JavaScript代码覆盖
2019/06/13 Javascript
如何解决日期函数new Date()浏览器兼容性问题
2019/09/11 Javascript
Python的Twisted框架上手前所必须了解的异步编程思想
2016/05/25 Python
python实现八大排序算法(2)
2017/09/14 Python
python topN 取最大的N个数或最小的N个数方法
2018/06/04 Python
python3中替换python2中cmp函数的实现
2019/08/20 Python
Python获取二维数组的行列数的2种方法
2020/02/11 Python
django xadmin action兼容自定义model权限教程
2020/03/30 Python
HTML5中外部浏览器唤起微信分享功能的代码
2020/09/15 HTML / CSS
美国最大的电子宠物训练产品制造商:PetSafe
2018/10/12 全球购物
家长给幼儿园的表扬信
2014/01/09 职场文书
大学生演讲稿
2014/04/25 职场文书
工商企业管理应届生求职信
2014/05/04 职场文书
服务型党组织建设典型材料
2014/05/07 职场文书
放弃继承权公证书
2015/01/23 职场文书
2015年度员工自我评价范文
2015/03/11 职场文书
消费者理赔投诉书
2015/07/02 职场文书
mysql 数据插入优化方法之concurrent_insert
2021/07/01 MySQL
oracle索引总结
2021/09/25 Oracle
Lakehouse数据湖并发控制陷阱分析
2022/03/31 Oracle
分享五个Node.js开发的优秀实践 
2022/04/07 NodeJs