JS生来就是完全面向对象的,可它却没有正式的类。这和现代大多数面向对象的语言(如,Java)是不同的。事实上,JS把类通过“构造函数”来定义,并在其中提供对“方法”和“属性”的支持。
关于“早绑定”和“晚绑定”的问题
“绑定”定义了如何将对象接口和对象实例结合起来。“早绑定”要求对象在实例化之前必须定义它的属性和方法,以便于编译器或解释器执行编译或解释操作。“晚绑定”的编译器或解释器不检查对象的类型,而只是关心对象是否支持代码中定义的属性和方法。JS的所有变量都采用晚绑定的方法,这就意味着,JS中对象的属性或方法可以在对象创建后动态指定,而不发生任何错误。这种“晚绑定”使得JS中对类和对象的处理极为灵活。
构造函数方式
因为JS对象的属性和方法可以在对象创建后动态定义,所以我们可以这样创建对象:
var oPhone = new object; oPhone.color = "black"; oPhone.sendM = function() { alert("Hello, World!"); };
这样的代码创建对象没有语法问题,但在这种情况下,我们每创建一个类似的Phone对象,就要重复这样一段不算很短的代码。因为JS是解释性语言,这样大量重复的代码既影响文件大小,还有可能影响执行速度。我们必须对此进行改造。
function Phone(sColor, sMessage) { this.color = sColor; this.sendM = function() { alert(sMessage); }; }; var oPhone = new Phone("black", "Hello,World!");
通过function内置类对Phone的属性和方法进行封装,可是实现类似于Java之类面向对象语言的类定义,这种方式类似于Java中的构造函数,所以一般被称为“构造函数方式”。“构造函数”中一般使用this表示对对象自身的引用,并将this对象作为返回值(不必显式使用return语句)。注意,使用这种方法创建对象时,会重复生成函数,为每一个对象创建独立的函数版本,是没有任何意义的。
原型方式
JS规范中定义每一个对象,都有一个prototype属性,它是所创建对象的原型。通过指定这个prototype属性,可以实现对象的原型定义,这就是“原型方式”。上文中的Phone就可以这样定义:
function Phone() { }; Phone.prototype.color = "black"; Phone.prototype.sendM = function() { alert("Hello, World!"); }; var oPhone = new Phone();
这段代码没有问题,但同样遇到了本文第一段代码的“不可复用”的问题。这的确是“原型方式”的缺点,即不能通过给构造函数传递参数初始化属性和方法。
构造函数/原型方式混合结构
问题即将被解决。混合使用构造函数和原型方式:用构造函数定义对象的所有属性,用原型方式定义对象的方法。这样的好处是,所有函数都只创建一次,而每个对象都具有自己的不同属性。上述的Phone对象就可以这样定义和实现:
function Phone(sColor, sMessage) { this.color = sColor; this.message = sMessage; }; Phone.prototype.sendM() { alert(this.message); }; var oPhone = new Phone("black", "Hello, World!");
构造函数/原型方式混合结构很好的模拟了类似于Java等面向对象编程语言中对类和对象的实现,这也是目前在JS编程中最常见的对象创建方式。
JS中真的需要实现类么?
这是一个问题。本文大致介绍了JS对类和对象的实现方式,但在本文的最后,我必须把这个问题提出来:我们真的需要在JS中实现自己的类和对象么?
今天,JS虽然得到了很大的发展,但还只是主要应用在Web领域。如果只是将JS作为一种简单的动态表现技术,只是用JS接收来自于用户代理的各种参数,我们几乎没有必要创建对象,因为要操作的各种属性直接来源于JS的内置类。我强烈反对为了一些所谓的“漂亮的动态效果”,而编写大量的JS代码。大量的JS代码是浏览器的沉重负担,而且绝大多数的网页的主要目的是向互联网用户提供易于阅读的信息,而不是不能被某些搜索引擎和浏览器抓取和显示的JS代码。
JS的效率毕竟是一个瓶颈,当我们万般无奈需要创建JS类或对象时,我们再使用这些方法吧。