转自:http://hi.baidu.com/pudding/blog/item/07209d163b22a551f2de3270.html
发信人: kyhpudding (想猫猫 想青菜 >_<), 信区: Programming
标 题: Javascrīpt: 从函数说起
发信站: 逸仙时空 Yat-sen Channel (Sun Jul 30 14:07:35 2006), 转信
Javascrīpt 是一种大家都很熟悉却不太注意的语言. 长期以来它都被认为只是做
点网页特效的小玩具, 开发方法通常都是 copy&paste. 只是在 2005 年以来 AJAX
流行它才引起了一点注意.
但大多数人没有意识到, Javascrīpt 是一种方便, 干净, 优美的函数式语言. 比如
这篇文章 http://www.crockford.com/javascrīpt/javascrīpt.html 就说明了它是
世界上遭到最大误解的语言.
以上这篇文章属于概述性质, 而这篇文章希望用 Javascrīpt 代码说明这门语言在
设计上的闪光之处. 说明 Javascrīpt 不仅仅是一个网页特效的小玩具, 仅仅在语
言本身, 它就有很多值得研究的地方. 另外, 本文不是 Javascrīpt 教程, 不讲解
基本语法, 这些语法与 C++/Java 很像, 不多说.
一. 函数
刚才提到, Javascrīpt 是一门函数式语言. 函数式语言的一个重要特征是: 函数
被提到与普通对象同样的位置: 它可以在任何地方参与运算, 可以动态生成和撤
销等等. 在 Javascrīpt 中, 函数是一个属于 Function 类的对象.
我们平常定义一个函数是
function add(a, b) {
return a + b;
}
其实这是一个语法糖衣 (即用一种较为简便的语法去描述原来要用复杂的表达完成的意思)
它等于:
add = function (a, b) {
return a + b;
}
实际上就是
add = new Function ("a", "b", "return a + b;");
就是一个简单的创建对象的过程. Function 是一个内置的类, 至于对象的实质我们会
在后面提到.
二. 命名空间(namespace)
这个概念熟悉 C++ 的同学应该都很清楚. 但在 LISP, Javascrīpt 等语言中, 命名
空间不是一种需显示声明的额外功能, 而是无处不在的.
我们回想 C/C++ 函数的执行过程. 函数会有一些局部变量, 它们会放在栈里面, 等
函数执行完之后, 随着栈顶指针的改变, 这些局部变量完全消失. 而在 LISP 或
Javascrīpt 中, "局部变量" 不是用栈, 而是用 "命名空间" 或称 "命名环境" 来实现
的.
考察函数
exmaple0
function add(a, b) {
var ret = a + b;
return ret;
}
在执行这个函数时, 它会创建一个新的命名空间, 成员有 a, b, ret. 并在里面进行
运算. 我们来看一个函数执行完之后的情况: 它返回了 ret 的值, 但至此之后, 整个
运行环境中没有其他部分与这次运行的 a, b, 和 ret 有关了. (我不知道这样是不是
说清楚了, 其实可以裂解为函数内部的局部变量与全局状态无关). 这时候发生的事
是: 这个命名空间连同它的成员都被取消, 因为其他部分永远没有办法再引用它了.
(这其实就是自动垃圾收集的基本思想). 于是, 我们就完成了一个类似 C/C++ 函数
返回时局部变量被删除的过程.
我们知道, 在函数中可以引用全局变量, 这说明: 命名空间不是独立的, 而是互相
关联的. 比如, 在 add 函数运行时, 它的命名空间又与全局的命名空间关联, 因而
可以访问全局变量. 这会形成一条命名空间链, 可表示如下:
{global} -> {add()}
稍微复杂一点的例子:
example1:
function test(x) {
function inc(y) {
return x + y;
}
return inc(1);
}
这是一个嵌套函数的例子, 运行时命名空间结构应该是
{global} -> {test()} -> {inc()}
这个也很好理解, 我们稍微改一改
example2
function test(x) {
function inc(y) {
return x + y;
}
return inc;
}
改成返回一个函数. 那么情况会怎么样呢?
a = test(0);
a(10); // 等于 10
b = test(10);
b(10); // 等于 20
要理解这个很简单, 用 a 替换函数的返回值 inc 代如到 test() 中, 可知
它运行时命名空间结构是
{global} -> {test()} -> {a()}
注意 a 和 b 引用的 {test()} 命名空间是不一样的, 因为它们都是在运行
test 时分别新建的, 里面 x 的值是不同的, 一个是 0, 一个是 10. 因此
a 和 b 的运行结果也不同.
在实现上, C/C++ 类的语言会认为一个函数除了引用局部变量外就只能引用
全局变量. 但在 Javascrīpt 和 LISP 中, 它采用的方法是在函数对象中
记录它所要引用的命名空间链.例如, example0 中 test 所要引用的命名空间
链只有 {global} 而 example2 所返回的函数则使用命名空间链
{global} -> {test()}
这有重大的意义: 它说明了函数不仅仅是一堆操作指令, 它还可以与数据相关.
我们知道: 将操作与数据放在一起, 可以称之为对象. 用函数可以完全实现面向
对象功能. 例如上面的例子, 描述的其实就是一个 test 类, test 类使用了一个
默认方法 (这点在 C++/Java 中无法表示) inc. 我们可以用类 C++ 的方法描述
如下:
class test {
private:
int base;
public:
test(x) { base = x; };
int default(y) { return base + y; };
};
注意: 以上这些都与 LISP 类语言完全相同, 可以参考 SICP 第三章的详细描述.
下面的内容与 LISP 类语言就有了较大的不同.
三. 对象 --- 显式的命名空间
Javascrīpt 中, 命名空间是可以显示声明, 随意操纵的. 这是 Javascrīpt 的对象
机制的基础.
我们还是从普通的写法开始
function testClass () {
this.value = 'TEST';
}
obj = new testClass();
这是一个最常用的类声明和对象申请写法. 我们要对其中的新元素 this 和 new
进行分析.
我们知道, testClass() 运行时, 它的命名空间链是
{global} -> {testClass()}
如果在里面写 var value = 'TEST';
testClass() 运行完后, 它就是消失. 那就无法完成初始化对象成员的工作了.
所以它使用了一个默认的变量 this, 它指向函数正要操作的对象.
我们会很感兴趣 this 是怎么来的, 在 C++/Java 等语言中, this 是静态的,
编译时就决定的. 但 Javascrīpt 作为动态的解释形语言, 采取了完全不同
的策略.
Function 这个系统内置类有一个叫 call 的方法, 就提供了可以定义 this 变量
的方法. 例如我们写这样的一个函数:
function testFunc(num) {
alert(this + num);
}
像这样调用它: testFunc.call('test ', 10);
结果就时 "test 10"
这就是 this 的实质. 这也说明了 Javascrīpt 中所谓成员函数不是固定的, 它是
完全可控的. 在下面我们会看到它的一个应用.
那么为什么要这么用呢? 我们看 new 的用法:
new testClass() 实际上也是一个语法糖衣, 因为它可以等于(不确切, 后面有说明)
obj = new Object; // 创建一个 Object
testClass.call(obj)
obj 就会被正确的初始化为 testClass 对象了.
然则这还是有 new 操作符啊? 我们说对象其实是一个显示的命名空间, 我们可以把它
写成 ōbj = {}, 熟悉 python 的同学会对这个写法很熟, 因为它是 python 中字典
的写法, 其实它们是一样的
其实, Javascrīpt 中的对象就是一个命名空间, 或者说是一组名字和值的对应关系,
与 python 中的字典是一样的, 甚至可以到这个地步:
Javascrīpt 中 obj['test'] = 1; 和 obj.test = 1; 是等价的.
所以对象的产生可以描述为
obj = {}
testClass.call(obj)
当然, new 还要做比这多很多的事情, 比如修改类标识, 把类 Function 对象的
prototype 的属性拷贝过来等. 但它所做的最重要的事情, 就可以描述成以上这
个样子.
这种对象机制与 C++/Java 等完全不同, 会让其程序员对 Javascrīpt 的对象机
制很迷惑. 所以 Javascrīpt 也给 Function 类提供了 prototype 的接口.
比如 testClass.prototype.value = 'TEST';
这样 testClass 的对象都会有一个初值为 'TEST' 的 value 成员了. 但这也是
语法糖衣, 由 new 拷贝 testClass.prototype 下的属性值到对象成员中完成,
可以大概表示成这个样子:
for (x in class.prototype) {
obj[x] = class.prototype.x;
}
就是这么简单的过程, 对象的创建操作完全可以由上面提到的这些基本操作组合
完成. 其核心还是 Function 的使用.
我们看到, Javascrīpt 能够用简单的语法实现复杂的对象功能, 你不用记忆一堆
奇怪的操作符. 只要理解了它到底是怎么运作的, 一切就变得简单明了了.
四. 应用
对象的私有成员:
我们把上面的例子写成这样:
function Adder(x) {
var base = x;
function add (y) {
return base + y;
}
this.add = add;
}
那么 Adder 类的对象就有了一个叫 base 的局部变量. 原因如上一节所述, 没看
懂的话是我的错...... 确实很不直接, 但很简洁漂亮.
事件响应函数:
做 AJAX 开发时, 我们常常会把一个 HTML 元素绑成一个控件, 放到一个对象中.
我们会给空间绑定事件响应函数, 很自然地, 我们认为响应函数中的 this 指向
的是我们自己创建的对象. 但事实是令人失望的: 响应函数是绑在 HTML 元素上
的, 响应函数的 this 是指向 HTML 元素而不是我们的对象. 这时候
Function.call 就派上了用场: 这段代码取自著名的 prototype.js
Function.prototype.bindAsEventListener = function(object) {
var __method = this;
return function(event) {
return __method.call(object, event || window.event);
}
}
它的作用是对一个事件响应函数进行包装, 让函数运行时, this 值指向指定的
对象. 那么, 即使是该函数绑在了 HTML 元素上, 当真正的处理函数运行时,
它也会表现得像我们自己定义的对象的成员函数一样. 完善地解决了这个问题.
五. Javascrīpt 的问题
Javascrīpt 在语言上是非常优美的, 但这个模型和它所声称的 "java\" 相差的
很远, 除了一些内置类的成员命名外, 就没什么特别相似的地方了. 这使得
Javascrīpt 要写点小效果谁都能写出来, 但真正理解它的本质和设计的就很少,
那自然也没法拿 Javascrīpt 做特别强的应用.
更严重的问题在于标准, 语言上没什么问题, 但在库和与网页对象交互部分
IE 和 Firefox 就相差太远. 浏览器兼容性想必是每个 AJAX 开发者非常头痛
的问题.
另外一个问题是开发工具的缺乏, 长期以来 Javascrīpt 调试的一个最基本手段
就是 alert() 打印参数值. 虽然 firefox 下有 debugger vankman, VS.net 也
可以调试 javascrīpt 代码. 但它的高效率开发工具对比其他常用语言还是非常
缺乏.
六. 关于 AJAX
AJAX 在 2005 年开始成为一个非常流行的概念. 其实它使用的技术包括最核心的
xmlHttpRequest 都已经出现很久 (虽然也是可恶地互不兼容). 它的最吸引人之处
在于能够在后台异步地发送 HTTP 请求并进行处理, 使 browser 端和 server 端
处于对等的位置, 大大丰富了其应用. 而以前 server 端固然能很方便地向
browser 传数据, 而反过来从 browser 端给 server 端数据的方法就很匮乏, 主要
的只有表单提交方法. 至于在 AJAX 应用中使用的漂亮的消隐效果, 拖拽效果等
都是很久以前就大量使用的东西, 算不得什么革新.
个人认为, AJAX 只是一个过渡技术, 因为: 1. 它对用户界面的改进基于对传统
HTML 元素的巧妙和复杂的运用, 编程复杂, 开发效率低. 2. 它没有改善 HTML
内容与表达混淆的毛病, 而使其更加严重, 搜索引擎要搜索这样的页面也更加
困难.
我认为, AJAX 在用户界面方面迟早被 XUL 等基于 XML 的简洁统一的界面描述
语言取代. 成为用 XUL+javascrīpt, XUL 负责界面表示, javascrīpt 负责
browser 端的业务逻辑处理和与 server 端交互的模式. (实际上 firefox 的
很多部分就是这么做的). 那是, javascrīpt 作为一种更为严肃的用于处理业务
逻辑的语言, 深入了解利用它的运行机理就更为重要了.
--
这是最好的时代, 这是最坏的时代; 这是智慧的时代, 这是愚蠢的时代;
这是信仰的时期, 这是怀疑的时期; 这是光明的季节, 这是黑暗的季节;
这是希望之春, 这是失望之冬;
人们面前有着各样事物, 人们面前一无所有;
人们正在直登天堂, 人们正在直下地狱.
---- 狄更斯《双城记》
※ 来源:.逸仙时空 Yat-sen Channel bbs.zsu.edu.cn.[FROM: 202.205.10.10]