不用构造函数(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 相关文章推荐
基于Jquery的开发个代阴影的对话框效果代码
Jul 28 Javascript
瀑布流布局并自动加载实现代码
Mar 12 Javascript
基于jquery的has()方法以及与find()方法以及filter()方法的区别详解
Apr 26 Javascript
js中直接声明一个对象的方法
Aug 10 Javascript
微信内置浏览器私有接口WeixinJSBridge介绍
May 25 Javascript
json+jQuery实现的无限级树形菜单效果代码
Aug 27 Javascript
BootStrap中
Dec 10 Javascript
Angular利用内容投射向组件输入ngForOf模板的方法
Mar 05 Javascript
Vue在页面右上角实现可悬浮/隐藏的系统菜单
May 04 Javascript
详解js 创建对象的几种方法
Mar 08 Javascript
微信小程序sessionid不一致问题解决
Aug 30 Javascript
jquery实现拖拽小方块效果
Dec 10 jQuery
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
使用Limit参数优化MySQL查询的方法
2008/11/12 PHP
php简单提示框alert封装函数
2010/08/08 PHP
PHP pathinfo()获得文件的路径、名称等信息说明
2011/09/13 PHP
解析php中static,const与define的使用区别
2013/06/18 PHP
PHP中的output_buffering详细介绍
2014/09/27 PHP
php开发微信支付获取用户地址
2015/10/04 PHP
php自动载入类用法实例分析
2016/06/24 PHP
Yii框架的路由配置方法分析
2019/09/09 PHP
PHP7生产环境队列Beanstalkd用法详解
2020/05/19 PHP
sina的lightbox效果。
2007/01/09 Javascript
使用EXT实现无刷新动态调用股票信息
2008/11/01 Javascript
jQuery 源码分析笔记(4) Ready函数
2011/06/02 Javascript
基于jquery的9行js轻松实现tab控件示例
2013/10/12 Javascript
jQuery实现级联菜单效果(仿淘宝首页菜单动画)
2014/04/10 Javascript
BootStrap selectpicker
2016/06/20 Javascript
jQuery mobile在页面加载时添加加载中效果 document.ready 和window.onload执行顺序比较
2016/07/14 Javascript
浅谈jQuery中Ajax事件beforesend及各参数含义
2016/12/03 Javascript
Node.js爬虫如何获取天气和每日问候详解
2019/08/26 Javascript
基于JavaScript伪随机正态分布代码实例
2019/11/07 Javascript
微信小程序实现弹框效果
2020/05/26 Javascript
JS中的变量作用域(console版)
2020/07/18 Javascript
python list使用示例 list中找连续的数字
2014/01/27 Python
使用python编写批量卸载手机中安装的android应用脚本
2014/07/21 Python
python写日志封装类实例
2015/06/28 Python
Python 详解基本语法_函数_返回值
2017/01/22 Python
最近Python有点火? 给你7个学习它的理由!
2017/06/26 Python
Python中的几种矩阵乘法(小结)
2019/07/10 Python
Python jieba库用法及实例解析
2019/11/04 Python
python实现七段数码管和倒计时效果
2019/11/23 Python
CSS3 rgb and rgba(透明色)的使用详解
2020/09/25 HTML / CSS
使用索引有什么好处
2016/07/27 面试题
计算机网络专业求职信
2014/06/05 职场文书
常务副总经理岗位职责
2015/02/02 职场文书
培训通知书模板
2015/04/17 职场文书
入党转正申请自我鉴定
2019/06/25 职场文书
Oracle 触发器trigger使用案例
2022/02/24 Oracle