面向对象的 JavaScript 编程及其 Scope 处理
?
本文首先对 JavaScript 的机制进行讲解,并结合当前流行的开源 JavaScript 框架讲解如何在 JavaScript 中实现面向对象和继承机制;之后本文将对面向对象 JavaScript 编程中容易引起误解和 Scope 的几个问题做详细阐述;最后针对面向对象的 JavaScript 编程中的 Scope 问题给出几点建议。
面向对象和模拟继承
JavaScript 是一种弱类型解释运行的脚本语言,就语言本身来讲它不是一门面向对象语言。但是我们可以利用一些语言的特性来模拟面向对象编程和继承机制,这所有的一切都需要从 JavaScript 中的 function 说起。
Function 是一个定义一次但可以调用或执行无数次的 JavaScript 代码片段。Function 可以有零个或者多个输入参数和一个返回值,它通常被用来完成一些计算或者事务处理等任务。通常我们这样定义一个 function:
回页首JavaScript 框架中的面向对象的实现
在前面一节中我们介绍了一些 JavaScript 基本的特性和如何利用这些特性模拟实现面向对象和继承。目前开源社区有众多流行的 JavaScript 类库,如 jQuery、Extjs、Dojo 等,他们各自也都对面向对象编程和继承等机制有着不同的支持。那么这些开源 JavaScript 是如何实现面向对象和继承机制的呢?接下来我们介绍一下这些开源框架是如何实现这些机制的。
清单 9. 在 Dojo 中声明一个类?在上图中,可以清楚地看到 MyClass 的 prototype 的对象是一个临时生成的函数(类)实例化生成的,而这个临时的类的 prototype 指向了 SuperClass 的 prototpye 对象,于是根据 JavaScript 中 prototype 的传递特性,MyClass 的实例可以调用 MyClass 的 prototype 对象的方法,进而可以调用 MyClass 的 prototype 对象的类即图中的临时类的 prototype 对象中的方法,也是 SuperClass 的 prototype 对象的方法。这样便实现了 MyClass 与 SuperClass 之间的继承关系。
上面解释了 MyClass 和 SuperClass 的继承关系的,这是单继承的实现方式,若是有多个类之间的单继承,则可以通过上面的方式串成一个继承链,如下图所示。
图 2. ExtJS 中的继承链?
ExtJS 只支持单继承,即一个子类最多有一个父类。而由于 Dojo 支持多继承,所以一个子类是允许有多个父类的。Dojo 首先会将多继承的关系通过某种算法转换为单继承链,在利用上面描述的 prototype 链的方式将单继承链实现。如下图所示。
图 3. 多继承关系转换为单继承链?
Dojo 中使用的多继承转换算法叫做 C3 Method Resolution Order 是一种比较通用的算法。
除了实现继承关系,ExtJS、Dojo 等 JavaScript 库还为面向对象实现了一些其他的功能和机制,比如构造函数、父类函数调用等等,这在后续的相关文章中将会详细论述。
回页首
面向对象 JavaScript 中 Scope 问题的处理
在面向对象的 JavaScript 编程中,我们包装继承了大量的对象,同时对象之间还有很多复杂的引用关系,这就使得很多时候我们在 function 调用时的 Scope 不是我们想要的 Scope。产生这些问题的原因最终都源于我们没有深入的了解 JavaScript 的机制。在下面的一节我们将就这些容易产生 Scope 问题的机制做一个简单的介绍,并给出一些如何避免 Scope 问题的建议。
Scope 和 Closure
很多程序员在使用 JavaScript 的时候都会对 function 的 Scope 运行范围和 Closure 闭包的使用很困惑,这都源于他们对 JavaScript 原理的不了解。
在 JavaScript 中 function 是作用于词法范围而不是动态运行范围。也就是说 function 的作用范围是它声明的范围,而不是它在执行时的范围。简单的来说,一个 function 执行时的上下文环境 Context 是在其定义的时候就固定下来了,就是它定义的时的作用范围。这点是我们需要注意的,很多时候我们动态的将某个方法注入到一个对象内部,然而在运行时总是得不到想要的上下文环境,这就是因为没有正确理解 JavaScript 的 Scope。
当一个 function 被 JavaScript 引擎调用执行时,这个 function 的 Scope 才起作用被加到 Scope 链中。然后将一个叫做 Call Object 调用对象或者运行对象加到 Scope 的最前面。这个调用对象初始化时会加入一个 arguments 属性,用来引用 function 调用时的参数。如果这个 function 显示的定义了调用参数的话,这些参数也会被加入到这个对象中。之后再这个函数运行中所有的局部变量也都将包含在这个对象中。这也就是在 function 体内部既可以通过 arguments 数组,又可以直接通过显示定义参数名来引用调用时传入参数的原因。需要注意的是,这个调用对象和通过?
this?关键字引用的对象是两个概念。调用对象是 function 在运行时的 Scope,其中包含了 function 在运行时的全部参数和局部变量。通过?this?关键字引用的对象,是当 function 作为一个这个对象的方法运行时对这个对象的引用。如果这个 function 没有被定义在一个对象中时,传入给?this?的对象是全局对象,因此在这个 function 内部通过this?取到的变量就是全局定义的变量。例如下面代码运行结果应该弹出"Hello,I am Qin Jian":
清单 12. 闭包定义示例Call 和 Apply 方法在 JavaScript 中提供了两个非常有意思的方法 call 和 apply,它使得你可以将一个 function 作为另一个 object 的对象方法来调用。也就是说你可以选择 function 调用时,传入给?
this?关键字的对象。这两个方法第一个参数是相同的,为你想要传入给?this?关键字的对象。不同之处是,call 方法直接将 function 参数列在后面,而 apply 方法是将所有 function 参数以一个数组的形式传入。例如下面这个例子:
清单 15. call apply 方法示例Eval 方法eval 方法用于执行某个字符串中的 JavaScript 脚本,并反回执行结果。它能够允许动态生成的变量、表达式、函数调用、语句段得到执行,使得 JavaScript 的程序的设计更为灵活,比如通过 Ajax 方式从服务器端获得代表 JavaScript 脚本的字符串,然后就可以用这发方法在浏览器端来执行这段脚本。因为传统的 Ajax 通讯设计更多的是在服务器端与浏览器端交换数据,但在这个方法的支持下就可以在两者之间交换逻辑(在 JDK1.6 支持在 JVM 中运行 JavaScript 脚本),所以这是一种很有趣很神奇的事情。
eval 用法很灵活也比较简单,调用时将要执行的 JavaScript 脚本字符串作为参数传给这个方法。比较常用的就是将服务器端发送过来的 json 字符串在浏览器端转化为 json 对象,如下面这个例子。
清单 16. eval 方法将 json 字符串转换为对象在上面的代码中,在 json 字符串又加上了一对括号,这样做可以迫使 eval 函数在评估 JavaScript 代码时强制将原最外层括号内的内容作为表达式来执行从而得到 json 对象,而非作为语句来执行。所以只有用新的小括号将原来的 json 字符串包起来才能够转换出所需的 json 对象。
对于执行一般的包含在字符串中的 JavaScript 语句,自然就不需要像上面那样再次添加括号,如下例所示。
清单 17. eval 方法执行包含在字符串中的 json 语句避免 Scope 问题的几点建议
回页首小结
本文首先介绍了 JavaScript 语言的一些特性并如何利用这些特性实现面向对象和继承机制;随后我们结合目前流行的开源 JavaScript 类库,讲解他们是如何实现面向对象和继承机制等特性的;之后我们对 JavaScript 面向对象编程中几个容易引起误解和 Scope 问题的要点,包括闭包、call 函数、apply 函数,eval 函数等做了详细的讲解;最后根据作者的开发经验给出几个如何处理 Scope 问题的建议供大家参考,希望对大家以后的开发工作有一定的帮助。
参考资料
学习