关于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 相关文章推荐
javascript 操作文件 实现方法小结
Jul 02 Javascript
来自国外的页面JavaScript文件优化
Dec 08 Javascript
jQuery EasyUI API 中文文档 - EasyLoader 加载器
Sep 29 Javascript
Jquery异步请求数据实例代码
Dec 28 Javascript
如何使用Jquery获取Form表单中被选中的radio值
Aug 09 Javascript
Jquery 返回json数据在IE浏览器中提示下载的问题
May 18 Javascript
JavaScript与jQuery实现的闪烁输入效果
Feb 18 Javascript
angular过滤器实现排序功能
Jun 27 Javascript
vue滚动固定顶部及修改样式的实例代码
May 30 Javascript
JavaScript变量基本使用方法实例分析
Nov 15 Javascript
Ajax获取node服务器数据的完整步骤
Sep 20 Javascript
JavaScript中reduce()的用法
May 11 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
require(),include(),require_once()和include_once()区别
2008/03/27 PHP
php中根据某年第几天计算出日期年月日的代码
2011/02/24 PHP
php利用scws实现mysql全文搜索功能的方法
2014/12/25 PHP
php输出指定时间以前时间格式的方法
2015/03/21 PHP
深入浅析安装PhpStorm并激活的步骤详解
2020/09/17 PHP
动态表格Table类的实现
2009/08/26 Javascript
JavaScript 学习笔记(五)
2009/12/31 Javascript
js获取当前select 元素值的代码
2010/04/19 Javascript
JavaScript初学者需要了解10个小技巧
2010/08/25 Javascript
使用pjax实现无刷新更改页面url
2015/02/05 Javascript
jQuery 如何实现一个滑动按钮开关
2016/12/01 Javascript
初识 Vue.js 中的 *.Vue文件
2017/11/22 Javascript
详解如何在React组件“外”使用父组件的Props
2018/01/12 Javascript
Vue 重置组件到初始状态的方法示例
2018/10/10 Javascript
JavaScript字符和ASCII实现互相转换
2020/06/03 Javascript
Vue-cli打包后如何本地查看的操作
2020/09/02 Javascript
python中List的sort方法指南
2014/09/01 Python
Python使用dis模块把Python反编译为字节码的用法详解
2016/06/14 Python
python3使用PyMysql连接mysql数据库实例
2017/02/07 Python
python使用opencv读取图片的实例
2017/08/17 Python
Python实现Kmeans聚类算法
2020/06/10 Python
python3 判断列表是一个空列表的方法
2018/05/04 Python
Python反爬虫技术之防止IP地址被封杀的讲解
2019/01/09 Python
Python 使用Numpy对矩阵进行转置的方法
2019/01/28 Python
dataframe 按条件替换某一列中的值方法
2019/01/29 Python
浅谈Django前端后端值传递问题
2020/07/15 Python
Python类绑定方法及非绑定方法实例解析
2020/10/09 Python
Html5应用程序缓存(Cache manifest)
2018/06/04 HTML / CSS
德购商城:德国进口直邮商城
2017/06/13 全球购物
运动服饰每月订阅盒:Ellie
2018/04/29 全球购物
什么是Assembly(程序集)
2014/09/14 面试题
linux面试题参考答案(4)
2013/01/28 面试题
北京鼎普科技股份有限公司软件测试面试题
2012/04/07 面试题
如何写出高质量、高性能的MySQL查询
2014/11/17 面试题
暑期培训班策划方案
2014/08/26 职场文书
2016高考寄语集锦
2015/12/04 职场文书