不用构造函数(Constructor)new关键字也能实现JavaScript的面向对象


Posted in Javascript onJanuary 11, 2013

JavaScript中的对象模型(object model)并不广为人知。我曾写过一篇关于他们的博客。之所以不被人所熟知,原因之一就是JavaScript是这些被人广泛使用的语言中唯一一个通过原型(prototype)来实现继承的。但是,我认为另一个原因就是这种对象模型非常复杂,难于解释。它为什么这么复杂并且又令人困惑呢?那是因为JavaScript试图去隐藏它传统的面向对象的特性——最终导致了它的双重人格(译者注:作者意思是JavaScript既有面向过程的特征,又有面向对象的特征)。

我认为正是由于JavaScript对象模型的难以理解和使用,才出现了一些像CoffeeScript,Dart和TypeScript这些通过编译可以生成JS代码的语言。

JavaScript的前辈们和那些顽固派相信JavaScript有更好的对象模型,并且为其将被大家所遗忘感到惋惜。即使JavaScript的专家Nicholas Zakas也欢迎在ECMAScript 6中加入的新的class语法——只不过是对原型风格(prototypal style)的语法做了一些修饰。换句话说,传统的OOP赢了。

一个大胆的设想
但是,让我们以玩笑的方式做一个设想:我们假想穿越到过去,那时候传统的面向对象的程序设计还没有像现在这样被大家广泛的接受,相反的,基于原型的继承模型得到了大家的广泛接受。这样的话会发生什么?我们最终又会得到些什么样的设计模式呢?

我们再设想:假设JavaScript没有构造函数或者没有new关键字会怎样?事情又会变成什么样的呢?让我们推到以前的重来。:)

首先,第一件事情,在JavaScript中,我们可以使用对象字面量的来创建一个新对象。如下所示:

var felix = { 
name: 'Felix', 
greet: function(){ 
console.log('Hello, I am ' + this.name + '.'); 
} 
};

接下来,假设我们想要将greet函数一般化,将其提取出来,放到一个一般的位置,这样一来,我们就可以创建多个对象来共享同一个greet方法。该怎么实现呢?
我们有好几种选择,先以mixin开始吧。

1、混入(对象扩张)Mixin(Augmentation)
在JavaScript语言中,混入属性非常简单。你只需要将混入对象的属性复制到要混入的对象中去即可。我们将使用一个“augment”函数来实现它,看代码就明白了:

var Dude = { 
greet: function(){ 
console.log('Hello, I am ' + this.name + '.') 
} 
}; 
var felix = { name: 'Felix' }; 
augment(felix, Dude);//将Dude中的属性复制一份到felix中,即混入(mixin)

在上面的代码中,augment函数将Dude对象的属性混入到了felix当中。在很多的JS库中,augment函数被叫做extend。我不喜欢用extend,因为一些语言用extend表示继承,以至于是我很困惑。我更喜欢用“augment”表示,因为实际上这种做法并不是继承,并且语法augment(felix, Dude)已经很清楚的表明你是用Dude中的属性对felix进行了扩充,而不是继承。

也许你早就猜到了augment的代码实现了,没错,非常简单。如下所示:

function augment(obj, properties){ 
for (var key in properties){ 
obj[key] = properties[key]; 
} 
}

2、对象克隆(Cloning)
mixin的一个替代的办法就是先克隆Dude对象,然后再给克隆的对象设置name属性。如下所示:
var Dude = { 
greet: function(){ 
console.log('Hello, I am ' + this.name + '.'); 
} 
} 
var felix = clone(Dude);//克隆Dude对象 
felix.name = 'Felix';

这两种方法之间的唯一不同就是添加属性的顺序。如果你想覆写克隆的对象中的某些方法,你可以考虑使用这种手法。
var felix = clone(Dude); 
felix.name = 'Felix'; 
felix.greet = function(){ 
console.log('Yo dawg!'); 
};//覆写greet方法

如果想要调用父类的方法也很简单——使用apply函数即可,如下所示
felix.greet = function(){ 
Dude.greet.apply(this); 
this.greetingCount++; 
}

这比原型风格的代码要好很多,因为你不必去使用构造函数的.prototype属性——我们不会使用任何构造函数。
以下是clone函数的实现:
function clone(obj){ 
var retval = {};//创建一个空对象 
augment(retval, obj);//复制属性 
return retval; 
}

3、继承(Inheritance)
最后,就是继承了。在我看来,继承被高估了,但是继承在“实例对象”之间共享属性方面确实要比对象扩张有一些优势。让我们编写一个inherit函数,这个函数接收一个对象作为参数,并且返回一个继承自该对象的新对象。
var felix = inherit(Dude); 
felix.name = 'Felix';

使用继承,你可以创建多个继承自同一个对象的子对象,这些子对象可以实时的继承父对象的属性。如下面的代码所示,
var garfield = inherit(Dude);//garfield继承自Dude 
Dude.walk = function(){//给Dude添加新的方法walk 
console.log('Step, step'); 
}; 
garfield.walk(); // prints "Step, step" 
felix.walk(); // also prints "Step, step"

在inherit函数中使用了基于原型对象的继承
function inherit(proto){ 
if (Object.create){ 
// 使用ES5中的Object.create方法 
return Object.create(proto); 
}else if({}.__proto__){ 
//使用非标准属性__proto__ 
var ret = {}; 
ret.__proto__ = proto; 
return ret; 
}else{ 
//如果两种都不支持,使用构造函数继承 
var f = function(){}; 
f.prototype = proto; 
return new f(); 
} 
}

上面的代码看起来不怎么好,那是因为我们使用了特性监测来判断到底使用3种方式中的哪一种。

但是,怎么使用构造方法呢(也就是,初始化方法)?你该怎么在实例对象之间共享初始化代码呢?在一些情况下,如果你只需要为对象设置一些属性,这时候,初始化函数不是必须的,就像我们上面的例子中那样。但是如果你有更多的初始化代码呢,你也许会制定一个约定,例如:使用一个叫initialize的初始化方法。我们假设在Dude中定义了一个叫initialize的方法,如下

var Dude = { 
initialize: function(){ 
this.greetingCount = 0; 
}, 
greet: function(){ 
console.log('Hello, I am ' + this.name + '.'); 
this.greetingCount++; 
} 
}

然后,你可以这样来初始化对象
var felix = clone(Dude); 
felix.name = 'Felix'; 
felix.initialize();或者也可以 
var felix = { name: 'Felix' }; 
felix.name = 'Felix'; 
augment(felix, Dude); 
felix.initialize();还可以 
var felix = inherit(Dude); 
felix.name = 'Felix'; 
felix.initialize();结语

我表示通过上面定义的三个函数——augment,clone和inherit,你可以对JavaScript中的对象做任何你想做的事,而不必使用构造函数和new关键字。我认为这三个函数所体现的语义更简单并且更接近于JavaScript底层的对象系统。(完)^_^
Javascript 相关文章推荐
自适应图片大小的弹出窗口
Jul 27 Javascript
分享十五个最佳jQuery 幻灯插件和教程
Mar 27 Javascript
JS打开新窗口的2种方式
Apr 18 Javascript
如何阻止复制剪切和粘贴事件为了表单内容的安全
May 23 Javascript
[JSF]使用DataModel处理表行事件的实例代码
Aug 05 Javascript
【JS+CSS3】实现带预览图幻灯片效果的示例代码
Mar 17 Javascript
JS简单生成两个数字之间随机数的方法
Aug 03 Javascript
炫酷的js手风琴效果
Oct 13 Javascript
简单的js计算器实现
Oct 26 Javascript
Bootstrap表单控件学习使用
Mar 07 Javascript
mui上拉加载更多下拉刷新数据的封装过程
Nov 03 Javascript
详解Vue、element-ui、axios实现省市区三级联动
May 07 Javascript
javascript使用中为什么10..toString()正常而10.toString()出错呢
Jan 11 #Javascript
javascript将数组插入到另一个数组中的代码
Jan 10 #Javascript
jquery实现点击TreeView文本父节点展开/折叠子节点
Jan 10 #Javascript
javascript 中String.match()与RegExp.exec()的区别说明
Jan 10 #Javascript
防止文件缓存的js代码
Jan 10 #Javascript
js修改table中Td的值(定义td的单击事件)
Jan 10 #Javascript
js修改table中Td的值(定义td的双击事件)
Jan 10 #Javascript
You might like
php设计模式小结
2013/02/15 PHP
php加密之discuz内容经典加密方式实例详解
2017/02/04 PHP
原生JS实现Ajax通过POST方式与PHP进行交互的方法示例
2018/05/12 PHP
tp5(thinkPHP5框架)captcha验证码配置及验证操作示例
2019/05/28 PHP
toString()一个会自动调用的方法
2010/02/08 Javascript
Javascript string 扩展库代码
2010/04/09 Javascript
DIV始终居中的js代码
2014/02/17 Javascript
jQuery中ajax的load()与post()方法实例详解
2016/01/05 Javascript
jQuery 中的 DOM 操作
2016/04/26 Javascript
jQuery获取与设置iframe高度的方法
2016/08/01 Javascript
JS实现搜索关键词的智能提示功能
2017/07/07 Javascript
video.js 一个页面同时播放多个视频的实例代码
2018/11/27 Javascript
基于vue框架手写一个notify插件实现通知功能的方法
2019/03/31 Javascript
JS关闭子窗口并且刷新上一个窗口的实现示例
2020/03/10 Javascript
本地文件上传到七牛云服务器示例(七牛云存储)
2014/01/11 Python
Python yield 小结和实例
2014/04/25 Python
Python中除法使用的注意事项
2014/08/21 Python
Python3如何解决字符编码问题详解
2017/04/23 Python
对Python中的条件判断、循环以及循环的终止方法详解
2019/02/08 Python
python将pandas datarame保存为txt文件的实例
2019/02/12 Python
python pandas cumsum求累计次数的用法
2019/07/29 Python
Python-接口开发入门解析
2019/08/01 Python
HTML5声音录制/播放功能的实现代码
2018/05/03 HTML / CSS
鼠标滚轮事件和Mac触控板双指事件
2019/12/23 HTML / CSS
美国特价机票专家:Airfarewatchdog
2018/01/24 全球购物
H&M旗下高端女装品牌:& Other Stories
2018/05/07 全球购物
软件生产职位结构化面试主要考察要素及面试题库
2015/06/12 面试题
三年级音乐教学反思
2014/01/28 职场文书
电工工作职责范本
2014/02/22 职场文书
设备管理实施方案
2014/05/31 职场文书
2014教师教育实践活动对照检查材料思想汇报
2014/09/21 职场文书
师德师风个人整改措施
2014/10/27 职场文书
写景作文评语集锦
2014/12/25 职场文书
经理岗位职责范本
2015/04/15 职场文书
幼儿园班级管理心得体会
2016/01/07 职场文书
解决python存数据库速度太慢的问题
2021/04/23 Python