不用构造函数(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 相关文章推荐
js下关于onmouseout、事件冒泡的问题经验小结
Dec 09 Javascript
Jquery下:nth-child(an+b)的使用注意
May 28 Javascript
简单实现js间歇或无缝滚动效果
Jun 29 Javascript
js如何判断是否在iframe中及防止网页被别站用iframe嵌套
Jan 11 Javascript
JQuery页面随滚动条动态加载效果的简单实现(推荐)
Feb 08 Javascript
js Canvas绘制圆形时钟效果
Feb 17 Javascript
jQuery实现ajax无刷新分页页码控件
Feb 28 Javascript
详解用vue.js和laravel实现微信授权登陆
Jun 23 Javascript
ES6中字符串string常用的新增方法小结
Nov 07 Javascript
微信小程序全局变量改变监听的实现方法
Jul 15 Javascript
用JS实现一个简单的打砖块游戏
Dec 11 Javascript
Vue CLI3移动端适配(px2rem或postcss-plugin-px2rem)
Apr 27 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
dedecms后台验证码总提示错误的解决方法
2007/03/21 PHP
PHP判断远程url是否有效的几种方法小结
2011/10/08 PHP
php判断当前操作系统类型
2015/10/28 PHP
Packer 3.0 JS压缩及混淆工具 下载
2007/05/03 Javascript
JavaScript自定义事件介绍
2013/08/29 Javascript
WEB前端开发都应知道的jquery小技巧及jquery三个简写
2015/11/15 Javascript
jQuery增加和删除表格项目及实现表格项目排序的方法
2016/05/30 Javascript
星期几的不同脚本写法(推荐)
2016/06/01 Javascript
Javascript 6里的4个新语法
2016/08/25 Javascript
对Angular.js Controller如何进行单元测试
2016/10/25 Javascript
详解微信小程序 相对定位和绝对定位
2017/05/11 Javascript
详解使用Node.js 将txt文件转为Excel文件
2017/07/05 Javascript
使用Vue开发动态刷新Echarts组件的教程详解
2018/03/22 Javascript
JS实现读取xml内容并输出到div中的方法示例
2018/04/19 Javascript
Vue.js结合bootstrap前端实现分页和排序效果
2018/12/29 Javascript
基于Vue实现的多条件筛选功能的详解(类似京东和淘宝功能)
2019/05/07 Javascript
Vue如何获取数据列表展示
2019/12/11 Javascript
vue在图片上传的时候压缩图片
2020/11/18 Vue.js
[01:54]胎教DOTA2 准妈妈玩家现身中国区预选赛
2016/06/26 DOTA
利用Python演示数型数据结构的教程
2015/04/03 Python
Python函数式编程指南(四):生成器详解
2015/06/24 Python
深入解析Python中的lambda表达式的用法
2015/08/28 Python
Python干货:分享Python绘制六种可视化图表
2018/08/27 Python
python随机生成大小写字母数字混合密码(仅20行代码)
2020/02/01 Python
Python 实现图片转字符画的示例(静态图片,gif皆可)
2020/11/05 Python
python读取图片颜色值并生成excel像素画的方法实例
2021/02/19 Python
css3隔行变换色实现示例
2014/02/19 HTML / CSS
html5 canvas移动浏览器上实现图片压缩上传
2016/03/11 HTML / CSS
公司端午节活动方案
2014/02/04 职场文书
文化活动实施方案
2014/03/28 职场文书
《学棋》教后反思
2014/04/14 职场文书
食品安全工作方案
2014/05/07 职场文书
2014年文明创建工作总结
2014/11/25 职场文书
装配车间主任岗位职责
2015/04/08 职场文书
2015年销售部工作总结范文
2015/04/27 职场文书
yolov5返回坐标的方法实例
2022/03/17 Python