
第1章 代码重用和优化
JavaScript受到了许多不公平的评价。许多人说JavaScript在面向对象编程上存在局限,甚至有人认为JavaScript不能归为面向对象编程(OOP)语言。尽管JavaScript和C++、Java有许多相似之处,但它没有等价Class的声明,也没有显而易见的方式去实现流行的OOP技术,如继承(代码复用)和封装。JavaScript的类型非常松散,也没有编译器,因此在运行出错前只能提供很少的错误或警告。JavaScript是把双刃剑,一方面给了程序员很大的自由,另一方面也给程序员带来一些陷阱。
JavaScript中充满了对传统编程“过失”的忽略,传统的程序员可能对此颇为郁闷。比如,在JavaScript中全局函数和变量是默认行为,而忘记加分号是完全可接受的。对JavaScript的工作方式缺乏了解,往往导致程序员无比郁闷。如果首先了解一些基础事实,将有助于你编写JavaScript应用:
- JavaScript不是一个基于类的语言;
- 写好代码,并不一定需要基于类的面向对象编程语言。
有些编程人员尝试用JavaScript 写C++风格的代码。尽管在某种程度上可以达到目标,但最终结果往往让人感觉不自然。
没有任何编程语言是完美的,人们有理由争论某个编程语言或OOP本身的优越性是否仅仅是皇帝的新衣。根据我的个人经验,用C++、Java或PHP编写的软件生成的bug和问题,并不比用JavaScript编写的软件生成的少。我认为JavaScript的灵活性和表达力,可以使你更快地进行项目开发。
幸运的是,大部分JavaScript的缺点都不是无药可医。解决之道并不是一味模仿其他语言,而是扬长避短:利用Javascript的灵活性,而小心避开难处理的部分。基于类的其他语言容易引起笨拙的类层次和臃肿的代码,JavaScript则提供了同样有效但更轻量级的继承模式。
JavaScript可以有许多种方法来实现继承。下面的代码使用原型继承来创建一个Pet对象,和一个继承它的Cat对象。JavaScript教程中常常能见到这种“经典”的继承模式。

上述代码可以工作,但不是特别优雅。如果你熟悉其他OOP语言比如C++或Java,new声明是好理解的。但关键字prototype显得很啰嗦,并且没有隐私;注意外部代码将petCat的legs属性改成了一个不合理的值:7。这种继承方法没有提供对外部继承的保护,在涉及多个程序员的复杂项目中这个缺点也许会影响很大。
另一个选项无需使用prototype或new,而是利用JavaScript的“函数继承(functional inheritance)”特性来吸收和增强对象实例(object instances):

这里没有可笑的prototype,而且所有东西都封装得很漂亮。最重要的是:legs变量是私有的。如果尝试从cat外部修改不存在的公共legs属性,仅导致创建一个没有用过的legs属性。真正的legs值安全地保存在pet的getDetails()方法创建的闭包(Closure)内部。闭包在函数执行结束后,保持了函数的局部变量。在这个例子中这个函数指的是pet()。
事实上,用JavaScript实现继承并没有所谓“正确”的方法。但我个人认为函数继承方式非常自然。你和你的应用也许倾向其他方法。通过搜索“JavaScript Inheritance”你可以找到许多在线资源。
提示
使用原型继承的好处之一是内存效率;不管它被继承多少次,对象的原型属性和方法只被保存一次。
函数继承则相反:每个新的实例都会创建重复的属性和方法。如果你要创建许多(如上千个)大对象的实例,内存消耗可能会成为一个问题。不过这个问题很容易解决:可以将较大的属性或方法保存在一个对象中,并将其作为参数传给构建函数。这样所有实例就可以共同使用一个对象资源,而不是创建自己的版本。