在JavaScript中实现类的方式探讨


Posted in Javascript onAugust 28, 2013

在 javascript 中有很多方式来创建对象,所以创建对象的方式使用起来非常灵活。那么,到底哪一种方式是最恰当的对象创建方式呢?构造模式,原型模式还是对象原意模式(Object literal)呢?

但这些模式具体是怎么回事呢?

在开始讲解之前,让我们先清楚地介绍一下关于 javascript 基本知识。

有没有可能在 javascript 中实现面向对象编程的方式呢?

答案是可能的,javascript 是可以创建对象的!这种对象可以包含数据及能够操作数据的方法,甚至可以包含其他对象。它没有类但拥有构造函数;它没有类继承机制,但是可以通过原型(prototype)实现继承。

现在看起来,我们已经了解了在 javascript 中创建对象及实现基于对象编程时所必须的组成部分。

我们都知道 javascript 拥有私有变量。一个通过“var”关键字定义的变量,只能在函数体中被访问,而不能在函数外被访问。那么,如果我们不通过使用“var”关键字来定义变量会怎样呢?我们现在不对这个问题进行深入探讨,可能是通过“this”进行访问的,我会在另外的时间来详细讲述这个问题。

现在回到之前的问题。到底哪一种方式是最恰当的对象创建方式呢?
让我们用已经知晓的知识,通过创建Person的对象是来试验一下。

var Person = { 
firstName : 'John', 
lastName : 'Cody', 
fullName : '', 
message : '', createFullName : function () { 
fullName = this.firstName + ' ' + this.lastName; 
}, 
changeMessage : function (msg) { 
this.message = msg; 
}, 
getMessage : function () { 
this.createFullName(); 
return this.message + ' ' + fullName; 
} 
} 
Person.firstName = 'Eli'; 
Person.lastName = 'Flowers' 
Person.changeMessage('welcome'); 
var message = Person.getMessage(); // welcome Eli Flowers 
alert(message);

这是对象原意模式(literal pattern)。这非常接近我们常创建对象的方式。如果你不需要关心私有/包装的成员,并且你知道不将创建这个对象的实例。那么,这种方式将会很适合你。公有的成员可以做所有私有成员的事情,不是吗?但是,这不是一个类,而是一个对象而已,不能被创建实例并且不能被继承。

让我们尝试下其他的方面:

var Person = { 
firstName : 'John', 
lastName : 'Cody', 
fullName : '', 
message : '', createFullName : function () { 
fullName = this.firstName + ' ' + this.lastName; 
}, 
changeMessage : function (msg) { 
this.message = msg; 
}, 
getMessage : function () { 
this.createFullName(); 
return this.message + ' ' + fullName; 
} 
} 
Person.firstName = 'Eli'; 
Person.lastName = 'Flowers' 
Person.changeMessage('welcome'); 
var message = Person.getMessage(); // welcome Eli Flowers 
alert(message);

这是一种构造模式的实例(Constructor Pattern)。那么,这是类还是对象呢?应该 两种都算是吧。我们能够在当请求时把它当做对象Person来使用。它毕竟也只是一个函数而已。然而,它可以通过使用“new”关键字来实现创建新的实例功能。

在使用这种方式时,我们需要时刻记住如下要点:

1. 无论什么时候这个函数被调用时,它拥有一个特别的变量叫做“this”并且可以在全局范围内使用。全局范围依赖于这个函数自身的作用范围。

2. 无论什么时候通过“new”关键字创建这个函数的实例,“this”变量指向这个函数本身,并且这个“new”操作将会影响到函数体中的代码被执行。这也正是构造模式。

3. 任何附加到“this”变量下的变量都会成为公有属性并且任何通过“var”关键字定义的变量都将是属于私有属性。

4. 一个附加到“this”下的函数叫做特权函数,它可以访问所有的私有变量以及被附加到“this”下的函数及变量。

5. 私有函数可以访问到其他私有变量及私有函数。

6. 私有函数不能直接访问被附加到“this”变量和函数。我们可以通过创建一个私有变量“_that”并且将它赋值为“this”的方式实现。

7. 任何私有变量及函数对于其他私有函数及其他被附加到“this”的函数是可用的。这完全是可能的再javascript的作用范围下。

8. 一个变量:不是通过“var”关键字,也不是附加到“this”变量上以获得全局作用范围的。例如,对于一个自定义函数的作用范围。需要再一次地了解作用域及集群的知识。

这已经实现了我们想要的大部分要求了,但是,有时候“this”和“that”这两个入口变量很容易造成给人们带来疑惑。尤其对于那些一直坚持要求纯粹私有的人来说,更容易迷惑。

让我们再稍微修改下试试吧。

var Person = function () { //private 
var firstName = 'John'; 
var lastName = 'Cody'; 
var fullName = ''; 
var message = ''; 

var createFullName = function () { 
fullName = firstName + ' ' + lastName; 
} 
//public setters 
var setMessage = function (msg) { 
message = msg; 
} 
var setFirstName = function (fName) { 
firstName = fName; 
} 
var setLastName = function (lName) { 
lastName = lName; 
} 
var getMessage = function () { 
createFullName(); 
return message + ' ' + fullName; 
} 
//functions exposed public 
return { 
setFirstName: setFirstName, 
setLastName: setLastName, 
setMessage: setMessage, 
getMessage: getMessage 
}; 
}; 
var person1 = new Person(); 
person1.setFirstName('Eli'); 
person1.setLastName('Flowers'); 
person1.setMessage('welcome'); 
var message = person1.getMessage(); // welcome Eli Flowers 
alert(message);

这是一个显示模式(Revealing Pattern)。非常感谢 Christian Heilmann。使用这种模式的方式就是把请求的"getters" 和 "setters" 当作属性使用。我们很多都是从传统的Java编程中找到这样的身影并且很明显地知道实现它其实并不复杂。这同样是一种类似于当类继承自一个接口的情况。

这种模式大部分方面都实现得很好,仅仅只有一个很微小的问题。每一次当一个类的实例被创建时。这个新创建的对象获得了一份变量和函数的拷贝。现在,拷贝变量是没有问题的,我们希望对于每一个对象的数据都是属于对象自身的,那么,成员函数呢?他们仅仅是操作数据而已。那么,为什么需要拷贝他们呢?

这正是原型模式(Prototype)的优势所在。在所有实例中,所有东西都是被创建成一个原型,并且能够相互分享。我们仅仅需要做的就是依据原型创建共有函数。

var Person = function () { //private 
var welcomeMessage = 'welcome'; 
var fullName = ''; 
var firstName = ''; 
var lastName = ""; 
var createFullName = function () { 
Person.prototype.setFirstName('asdsad'); 
fullName = firstName + ' ' + lastName; 
}; 
//constructor 
var Person = function () { }; //will be created evrytime 
//public 
Person.prototype = { 
getFullName: function () { 
createFullName(); 
return welcomeMessage + ' ' + fullName; 
}, 
setFirstName: function (fName) { 
firstName = fName; 
}, 
setLastName: function (lName) { 
lastName = lName; 
}, 
ChangeMessage: function (mesg) { 
welcomeMessage = mesg; 
} 
} 
return new Person(); // Person; //new Person(); 
}; 

var person1 = new Person(); 
person1.setFirstName ('Eli'); 
person1.setLastName('Flowers'); 
person1.ChangeMessage('welcome'); 
var message = person1.getFullName(); // welcome asdsad Flowers 
alert(message);

原型模式存在的一个问题是它不能访问私有变量及私有函数,正因为这个问题,我们才会介绍闭包以及始终组织好创建类中存在的代码以使得它在全局范围内不会变得很混乱。所有都是属于 Person 类的作用范围内。

另外一个问题是每一次实例被创建时,全部的代码都被执行一遍,包括原型的绑定。对于我们中的一部分人来说,这仅仅只是一个效率问题。处理好这个问题的一种方式是仅仅在期望共有函数不可用的情况下绑定这个原型。

这样将会使得绑定原型操作只会在第一个实例被创建时执行,并且在那之后所有其他的实例都将只会进行检查操作。不幸的是,这样还是不能解决我们在上面例子中提到的问题,因为我们只有重新再来一次创建的函数用于生成一个闭包来达到这个类的效果。这样的话,至少我们减少了一部分内存的使用。

等等,还有另外一个问题是私有函数不能直接访问原型函数。

为什么你们一定得需要私有函数和私有变量呢?我知道你一定是想实现类的封装性,想确保类中的属性或者内部的数据不会被突然地修改了或者被内部的其他程序所修改,或者任何其他的操作……

你应该记住你是不能将 javascript 代码编译成二进制的,对于这种情况,你在一定程度上很恼火吧,这样代码始终都是可用的。所以,如果任何人想搅乱代码的话,不管你真正实现私有或者没有实现私有,不管你将代码给团队中的其他成员或者卖出去,他们都可以搅乱代码。实现私有化可能有那么一点点帮助吧。

另一个其他编程者使用的技术是使用约定命名,使用下划线 “_”给所有你想设成私有任何的东西加上前缀以规定它成为私有。

(function () { 
var Person = function () { 
this._fullName = ''; 
this.welcomeMessage = ''; 
this.firstName = ''; 
this.lastName = ""; 
_that = this; this._createFullName = function () { 
this.ChangeMessage('Namaste'); 
this._fullName = this.firstName + ' ' + this.lastName; 
}; 
} 
//Shared Functions for Code optimization 
Person.prototype = { 
constructor: Person, 
getFullName: function () { 
this._createFullName(); 
return this.welcomeMessage + ' ' + this._fullName; 
}, 
ChangeMessage: function (mesg) { 
this.welcomeMessage = mesg; 
} 
} 
this.Person = Person; 
})(); 
var person1 = new Person(); 
person1.firstName = 'Eli'; 
person1.lastName = 'Flowers'; 
person1.ChangeMessage('Welcome'); 
var message = person1.getFullName(); // Namaste Eli Flowers 
alert(message);

我不是说你不应该考虑 “private” 或者类似的知识。你是代码的设计者,所以你将知道怎么来管理并且知道怎么做才是最好的。根据你的需求,你可以使用任何一种设计模式或者多个设计模式组合一起使用。

无论你决定采用哪种设计模式,始终记住做尽量少的事情,不要在全局作用范围内实现闭包,尽量减少内存泄露,以及优化代码,并且组织好代码。所以,尽量多了解些作用域,闭包以及 “this” 的表现行为。

最后,祝编程愉快!

译后感

经常使用 javascript,对于它的印象一直都是直接拷贝过来就可以用的。最近使用 extjs,它的类框架非常好用。从这样文章也明白在 javascript 中实现类的各种方式,以及在文章最后讨论了类中私有成员的实现情况。

Javascript 相关文章推荐
javascript学习笔记(七) js函数介绍
Jun 19 Javascript
Javascript的数组与字典用法与遍历对象的属性技巧
Nov 07 Javascript
jquery 提交值不为空的元素示例代码
May 10 Javascript
CKEditor无法验证的解决方案(js验证+jQuery Validate验证)
May 09 Javascript
关于微信中a链接无法跳转问题
Aug 02 Javascript
JS实现屏蔽网页右键复制及ctrl+c复制的方法【2种方法】
Sep 04 Javascript
javascript读取文本节点方法小结
Dec 15 Javascript
Javascript实现运算符重载详解
Apr 07 Javascript
使用electron实现百度网盘悬浮窗口功能的示例代码
Oct 24 Javascript
vue路由对不同界面进行传参及跳转的总结
Apr 20 Javascript
解决layui追加或者动态修改的表单元素“没效果”的问题
Sep 18 Javascript
js实现简单的秒表
Jan 16 Javascript
实用的Jquery选项卡TAB示例代码
Aug 28 #Javascript
jQuery实现图片信息的浮动显示实例代码
Aug 28 #Javascript
JQuery筛选器全系列介绍
Aug 27 #Javascript
HTML Color Picker(js拾色器效果)
Aug 27 #Javascript
Js中的onblur和onfocus事件应用介绍
Aug 27 #Javascript
javascript:void(0)使用探讨
Aug 27 #Javascript
顶部缓冲下拉菜单导航特效的JS代码
Aug 27 #Javascript
You might like
PHP及Zend Engine的线程安全模型分析
2011/11/10 PHP
php.ini 配置文件的深入解析
2013/06/17 PHP
php中http与https跨域共享session的解决方法
2014/12/20 PHP
什么是PEAR?什么是PECL?PHP中两个容易混淆的概念解释
2015/07/01 PHP
PHP实现笛卡尔积算法的实例讲解
2019/12/22 PHP
用于table内容排序
2006/07/21 Javascript
用js小类库获取浏览器的高度和宽度信息
2012/01/15 Javascript
浅析jquery某一元素重复绑定的问题
2014/01/03 Javascript
如何设置一定时间内只能发送一次请求
2014/02/28 Javascript
Nodejs Post请求报socket hang up错误的解决办法
2014/09/25 NodeJs
jQuery ztree实现动态树形多选菜单
2016/08/12 Javascript
在百度搜索结果中去除掉一些网站的资料(通过js控制不让显示)
2017/05/02 Javascript
浅谈sass在vue注意的地方
2017/08/10 Javascript
深入浅析JSONAPI在PHP中的应用
2017/12/24 Javascript
微信小程序订阅消息(java后端实现)开发
2020/06/01 Javascript
vue中echarts引入中国地图的案例
2020/07/28 Javascript
[10:54]Team Spirit vs Navi
2018/06/07 DOTA
python求众数问题实例
2014/09/26 Python
python 第三方库的安装及pip的使用详解
2017/05/11 Python
对python中Librosa的mfcc步骤详解
2019/01/09 Python
Python列表对象实现原理详解
2019/07/01 Python
计算pytorch标准化(Normalize)所需要数据集的均值和方差实例
2020/01/15 Python
python中turtle库的简单使用教程
2020/11/11 Python
Python实现Appium端口检测与释放的实现
2020/12/31 Python
一款纯css3实现的动画加载导航
2014/10/08 HTML / CSS
荷兰和比利时时尚鞋店:Van Dalen
2018/04/23 全球购物
办公室驾驶员岗位职责
2013/11/15 职场文书
村长贪污检举信
2014/04/04 职场文书
教师产假请假条范文
2014/04/10 职场文书
党员倡议书
2015/01/19 职场文书
教师师德表现自我评价
2015/03/05 职场文书
学校党支部公开承诺书
2015/04/30 职场文书
毕业设计致谢词
2015/05/14 职场文书
2015小学新教师个人工作总结
2015/10/14 职场文书
高二化学教学反思
2016/02/22 职场文书
新手初学Java List 接口
2021/07/07 Java/Android