当今企业间的竞争,本质上是学习速度的竞争。在这样一个信息过载的时代,技术人员往往淹没于各种大量出现的新技术、新概念、新名词之中,应接不暇。然而这些新技术大部分出自于美国。有一个问题被严肃地提了出来:是跟随?还是超越?在一个闲适的周末,阳光满满,泡上一壶绿茶,借着清新的茶香,我们将学习一下近些年被IT领域密切关注的一些前沿技术。用周末的两天时间,我们将围绕Node.js为中心,学习JavaScript、redis、Nginx、HBase、JQuery、Ajax、HTML5等等这些可以构成新兴IT公司的技术元素,通过周末两天的学习,掌握这些技术元素的核心概念并可以将它们应用IT产品的技实现中。如果你的绿茶已经泡好,那么就让我们开始吧!
上面提到的这些技术元素之所以被密切关注,是因为它们可以帮助新兴IT公司以较低的开发部署成本,获得高性能的web应用服务的快速实现。“快速”、“低成本”、“高性能”是这些技术元素的目的,也是技术进步的要求,是新兴IT公司快速发布并迭代产品的技术需求,我们将反复提到。我们假定读者具有一定编程经验,懂得任意一门编程语言,了解IP/TCP网络的5层结构,http协议。我们将深入分析各个概念模型,上机实践每一个例程,并理解每一行代码的运行机理,浏览每一个链接。通过两天的学习,希望我们可以获得“用低成本的硬件资源和简单的开发方式,搭建高性能的web应用服务”的能力。
中文:
为了理解Node.js,我们先分析JavaScript的编程模型。
一. JavaScript的 编程模型
1995年5月,对JavaScript的设计,其实只用了十天。而且,当时在网景公司的设计师Brendan Eich,是为了向公司交差,本人并不愿意这样设计。这导致了JavaScript具有一些小小的缺陷。然而瑕不掩瑜,JavaScript的优秀,并不体现在一些语法细节上(不像Ruby那样地飘逸),不体现在清晰的代码组织上(不像Python那样地亲切),而是体现在它传达出来的设计思想。那就是:应用程序应该是web化的。
何谓web化,就是对桌面应用程序的颠覆,应用程序的显示、逻辑处理、数据资源、互操作和硬件操作都应该是分立,可以由不同的设备提供,也可以由不同的设备处理,而不是通过链接静态MFC框架调用windows系统函数那样的紧耦合来实现程序的功能。由于技术发展和商业利益(微软)的限制,这一颠覆过程至今没有完成。即使是缓慢,即使是饱受质疑,但JavaScript代表着技术发展的趋势。一切真理都必须经历三个阶段,首先是想法遭嘲笑的阶段,其次是想法遭强烈反对的阶段,最后是想法被认为是理所当然的真理的阶段。JavaScript正在成为IT技术创新的中心。所以JavaScript就是本次专题学习的第一个内容。
我们很认可《Android核心分析》的作者@maxleng的观点:
“研究分析是从设计者的意图出发,从抽象的甚至从哲学的高度,从最简单的系统原型开始,从设计猜想开始,而不是一开始就从代码分析展开。”
“各种计算机语言,建模工具,不外乎就是建立一个更接近人的思维方式的概念空间,再使用工具从该概念空间向另外一个概念空间映射,我称之为人性思维空间向01序列描述空间的一个映射。”
我们知道:
然而我们不从Node.js和JavaScript的源代码研究它们的特性,而是从它们所体现出来的外部特性来来分析它们的编程模型。任何一个事物它都需要存在,需要和其他事物进行互操作,需要自身变化和引起其他事物变化。比方说人:
这些关于外部特性的问题可以清晰地定义人。以至于一些更深刻的问题,比如说:你是谁?从哪来?要到哪去?那是门口保安和哲学家们研究的问题,不属于我们的讨论范围。我们接下来继续分析JavaScript 。
1.1. 生存空间。
如果有JavaScript编程经验的人,可以试着回答这几个问题:JavaScript写成的逻辑可以运行在哪里?是什么给他提供了运行环境?这些逻辑之间是怎样组织的?
我们分析下面这个例程将解释客户端JavaScript写成的逻辑的运行环境和组织形式:
例程1:
test1.html如下:
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
test1.js如下:?
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
test2.js如下:
[backcolor=rgb(248, 248, 248) !important]
test1.js和test2.js就是被嵌入到网页test1.html中的客户端JavaScript逻辑。当网页被访问时,浏览器会形成如下图所示的数据结构。其中window.document.body.childNodes[3]和window.document.body.childNodes[5]就是我们的两个的JavaScript逻辑。这些逻辑在网页被加载时默认会被浏览器顺序执行一遍。而window正是JavaScript逻辑运行的变量空间。例程中test1.js里定义的message变量,就处于window之下。在调试窗口中键入window.message就可以看到该变量。所有被网页所引入的JavaScript逻辑都共享window这一变量空间,也是这些客户端JavaScript逻辑之间唯一的组织形式。
用Chrome浏览器打开网页,在网页空白处点击右键,选择“审查元素”,在调试窗口中键入“window”并回车,就可以看到上面的数据结构。
上面我们分析了客户端JavaScript逻辑的运行环境和组织形式,没有出现“include ‘stdio.h’”这样的语法,也没用经过编译、链接静态库,依赖动态库这样的运行方式。那么服务端JavaScript逻辑Node.js需要怎样的运行环境,逻辑之间是怎样相互引用的呢?我们看下面一个例程。
例程2:
demo1.js如下:
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
message.js如下:
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
安装好Node.js,运行node demo1.js即可开启服务器。开发环境的搭建请参见:开发环境的搭建。demo1.js中,“var http = require(“http”);”类似于python的“import XXX”这样的模块导入机制。变量http是从Node.js平台的navite本地代码中得来,变量i是直接定义的数,我们重点分析变量message。字符串变量message由message.js定义,并由“module.exports = message;”指定为message模块的导出变量。当demo1.js被执行,所有被导入的模块都会被顺序加载,Node.js维护了一个根变量空间,与客户端JavaScript的window类似,只不过这里不叫window,而叫root。这里和客户端JavaScript形成区别的是,不是所有模块js代码里定义的变量都会被放在root之下。在root下除了一些运行环境要用到的变量对象外,只有被导入的和直接定义的这些变量。具体到例程2中,root之下可以找到的http、message、i这3个变量。
1.2 接口。
这里说的接口,并不是c++编程里说的接口。这里说的”接口“是指,我们研究的对象可以去操作、影响、改变其他对象的方式,和被其他对象操作、影响、改变的方式。比如说白炽灯,它的接口是给它通电。它会被外界这个动作通电所影响而发光。
c++编程里面的接口我们可以这么理解:比如说富士康定义了一种接口叫“i工人”,凡是被挂了“i工人”这个接口的人,富士康都可以叫他“去干活”。不管这个人是清洁工还是装配工,也不管这个人是叫张小五还是叫王晓丽。每一个具体的人的具体工作就是一个实现了的虚函数,而“排班表”就是传说中的“虚函数表”。但是我们这里所说的接口却不是这个概念,我们可以简单地这么来理解,我们为王晓丽提供了一种接口叫做工作。如果王晓丽愿意来工作,她可以操作生产线,维修生产线,改进生产线,工作本身也会对王晓丽提供工资和尊重的回报。
通过生存空间的分析我们知道,JavaScript是一种由浏览器或服务端的脚本引擎提供运行环境的程序逻辑。那么这些程序逻辑可以操作的资源对象有哪些呢?硬件设备为程序逻辑提供了“显示、逻辑处理、数据资源、互操作和硬件”等等计算资源,任何程序逻辑都难以做到全部访问,JavaScript作为脚本语言在这方面的限制则更多,它可以访问或操作的资源只是这个集合的一个真子集。
对于客户端:
(1). 操作浏览器(打开关闭网页,提示和日志……)
(2). 操作dom树,改变网页显示
(3). 操作ajax对象,发送ajax请求;操作WebSocket对象,建立WebSocket连接
(4). 操作特殊对象(canvas, WebGL, audio, video), 进行2d绘图、3d绘图、音频、视频
(5). 操作浏览器插件,与包括flash在内的各种插件通信
(6). 通过PhoneGap等平台访问移动设备的硬件资源,例如GPS、加速计、摄像头、触摸屏交互、触摸手势、振动等等
(7). 在一些可访问对象中注册回调函数,当注册事件发生后回调程序逻辑
对于服务端:
(1). 操作以“http”模块为代表的网络模块提供网络服务
(2). 操作以“fs”为代表的本地资源模块访问服务器本地资源
(3).?在一些可访问对象中注册回调函数,当注册事件发生后回调程序逻辑
上述就是客户端和服务端JavaScript逻辑可以访问或操作的资源的集合。它们表明了运行在V8脚本引擎中的JavaScript除了执行自身的行为逻辑(if for while)之外的行为能力。那么要完成行为能力,JavaScript的内部机制有哪些呢?
1.3 行为。
这一节我们将分析JavaScript的一些特性,如果对于没有JavaScript编程经验的人来说,每一部分展开来都可以有一篇文章的内容。但我们是极限学习,我们的任务是用最短的时间理解核心概念,付诸实践。我们不需要去完整地认识这些概念的全部,但是一定要深入地理解下面例程中,每一行代码后面的含义。在项目实践中,配合上Google,它们够用了。本文的所有例程都会分享出来。
(1).面向实例(JSON)
学习JavaScript语言结构,我们一定要摆脱传统面向对象语言思维习惯的干扰。传统面向对象,既有对象的类,也有对象的实例。JavaScript只有对象的实例,没有类。如果我们熟悉python语言,分析下面这段代码:
学习JavaScript语言结构,我们一定要摆脱传统面向对象语言思维习惯的干扰。传统面向对象,既有对象的类,也有对象的实例。JavaScript只有对象的实例,没有类。如果我们熟悉python语言,分析下面这段代码:
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
上例中class A为类(class),而a是这个类的一个实例(instance)。要使用对象A中的变量或函数,必须先将对象实例化。我们可以这么理解,类A就是一张建筑图纸,而实例a是根据这张图纸建成的一座房子。而在JavaScript的对象模型里,没有类,只有实例。也就是说我们直接建设的是房子,而不需要图纸。没有继承,没有多态。JavaScript可以通过对象的原型(prototype)实现对象的复制,这就类似于根据一座房子的模样再建造另一座房子。很多受到脱传统面向对象语言思维习惯的干扰的人以为这就是JavaScript的继承机制。不过我还是建议大家不要这么干,因为这是不必要的。如果一个对象需要使用另一个对象的变量或函数,为什么不将它们组合起来?JavaScript已经是世界上最优秀的对象模型了,如果你觉得不够用,那就再分析一下你的程序到底是要实现什么功能。
请分析下面这段代码instance.js:这是全篇最重要的一个例程,一定要上机实践,分析清楚它的每一行代码。
请分析下面这段代码instance.js:这是全篇最重要的一个例程,一定要上机实践,分析清楚它的每一行代码。
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
JSON是字符串化的JavaScript对象。它可以和JavaScript对象相互转化,上例中JSON.stringify()就是将JavaScript对象转化为字符串,JSON.parse()就是将字符串转化为JavaScript对象。
(2).闭包性
JavaScript的闭包性深受人们的赞誉也深受人们的反对。对于初学者,闭包性会带来变量命名空间的混乱。然而它是一种非常优秀的变量空间模型。还记得C++里面的”using namespace XXX;”这样的语句吧?它可以使一段c++代码访问另一段代码的变量、函数和类。
JavaScript的闭包性(closure)就是:让函数在运行时能够访问到函数定义时的所处作用域内的所有变量。函数定义时能访问到什么变量,那么在函数运行时通过相同的变量名一样能访问到。我们分析如下代码:
JavaScript的闭包性深受人们的赞誉也深受人们的反对。对于初学者,闭包性会带来变量命名空间的混乱。然而它是一种非常优秀的变量空间模型。还记得C++里面的”using namespace XXX;”这样的语句吧?它可以使一段c++代码访问另一段代码的变量、函数和类。
JavaScript的闭包性(closure)就是:让函数在运行时能够访问到函数定义时的所处作用域内的所有变量。函数定义时能访问到什么变量,那么在函数运行时通过相同的变量名一样能访问到。我们分析如下代码:
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
(3).回调函数
与JavaScript的闭包性深受人们的赞誉不同,对JavaScript的回调函数却是承受着更多的批评。回调函数,匿名回调函数,特别是嵌套匿名回调函数,是造成代码逻辑混乱的主要原因,也是大型JavaScript项目的代码可读性很差的原因。如果我们能够清晰地理解上面例程中”匿名函数“被调用的机制,那么JavaScript的回调函数就被我们掌握了。JavaScript的回调函数可以帮助我们更方便地实现异步逻辑,它的实现机理其实就是函数指针。如果阅读windows源代码,可以发现它的各种异步逻辑处理比如说消息循环,都是通过传递句柄也就是函数指针来实现。
与JavaScript的闭包性深受人们的赞誉不同,对JavaScript的回调函数却是承受着更多的批评。回调函数,匿名回调函数,特别是嵌套匿名回调函数,是造成代码逻辑混乱的主要原因,也是大型JavaScript项目的代码可读性很差的原因。如果我们能够清晰地理解上面例程中”匿名函数“被调用的机制,那么JavaScript的回调函数就被我们掌握了。JavaScript的回调函数可以帮助我们更方便地实现异步逻辑,它的实现机理其实就是函数指针。如果阅读windows源代码,可以发现它的各种异步逻辑处理比如说消息循环,都是通过传递句柄也就是函数指针来实现。
(4).单线程(事件引擎消息循环)
既然提到了消息循环,我们就来分析一下JavaScript的事件机制。Node.js和webkit都采用的是一种事件驱动的非阻塞I/O模型。为什么使用单线程的Node.js可以提供高速的并发能力?实际上著名Nginx也是基于单线程的。对于Nginx,我们后面的内容将会详细讲到。不像Apache+PHP那样派生出很多的OS线程来解决并发的问题,Node.js只有1个线程轮询它周围发生的各个事件。而每个事件的”发生“和”处理“过程却和这个线程无关。这个线程只负责查看某个事件是不是已经”发生“了,如果”发生“了,就通知与事件相应的回调函数慢慢”处理“这个事件。轮询线程不会等待这个事件的”处理“过程,它只是通知一下,紧接着查看其它事件的”发生“状态。事件本质上一个时空不一致的非线性模型,或所谓的“异步(Asynchronization)”。事件发生的顺序按照外界对其发出的时刻而确定,有的在先,有的在后,有时也可以齐头并进,一起同时触发,结束时也可以快的快、慢的慢。为了加速Node.js对于网络的并发访问,在实现上Node.js采用了epoll技术。对于webkit事件引擎的机制也是类似,这不过它处理的对象不是网络事件,更多的是dom树上的用户操作,这不需要太多关注并发性能。这也就是Node.js出现以前,对于JavaScript事件引擎人们关注不多的原因。不管是服务端还是客户端JavaScript我们都需要理解,事件引擎+回调函数构成了JavaScript运行模型。
既然提到了消息循环,我们就来分析一下JavaScript的事件机制。Node.js和webkit都采用的是一种事件驱动的非阻塞I/O模型。为什么使用单线程的Node.js可以提供高速的并发能力?实际上著名Nginx也是基于单线程的。对于Nginx,我们后面的内容将会详细讲到。不像Apache+PHP那样派生出很多的OS线程来解决并发的问题,Node.js只有1个线程轮询它周围发生的各个事件。而每个事件的”发生“和”处理“过程却和这个线程无关。这个线程只负责查看某个事件是不是已经”发生“了,如果”发生“了,就通知与事件相应的回调函数慢慢”处理“这个事件。轮询线程不会等待这个事件的”处理“过程,它只是通知一下,紧接着查看其它事件的”发生“状态。事件本质上一个时空不一致的非线性模型,或所谓的“异步(Asynchronization)”。事件发生的顺序按照外界对其发出的时刻而确定,有的在先,有的在后,有时也可以齐头并进,一起同时触发,结束时也可以快的快、慢的慢。为了加速Node.js对于网络的并发访问,在实现上Node.js采用了epoll技术。对于webkit事件引擎的机制也是类似,这不过它处理的对象不是网络事件,更多的是dom树上的用户操作,这不需要太多关注并发性能。这也就是Node.js出现以前,对于JavaScript事件引擎人们关注不多的原因。不管是服务端还是客户端JavaScript我们都需要理解,事件引擎+回调函数构成了JavaScript运行模型。
(5).迭代器
除了对象,一些JavaScript的内置数据结构,比如说字符串(string),数组(array),正则表达式(RegExp),可以大大方便我们程序逻辑的实现。对于有对象颗粒的数据结构,JavaScript内置的迭代器可以让我们的程序逻辑更加清晰明了。请看下面一段代码iterator.js,这是本章的最后一个例程,请大家思考程序的输出会是什么?
除了对象,一些JavaScript的内置数据结构,比如说字符串(string),数组(array),正则表达式(RegExp),可以大大方便我们程序逻辑的实现。对于有对象颗粒的数据结构,JavaScript内置的迭代器可以让我们的程序逻辑更加清晰明了。请看下面一段代码iterator.js,这是本章的最后一个例程,请大家思考程序的输出会是什么?
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
[backcolor=rgb(248, 248, 248) !important]
JavaScript还有很多其他特性,很多编程编程语言里面也有各种迭代器。JavaScript也可以使用”for (var i = 0; i < 10; i++) {}“的循环控制语法。JavaScript的迭代器看似没有什么稀奇的,为什么我们在这里要单独分析它呢?那是因为,它和回调函数、事件引擎一起,是JavaScript运行时模型的重要组成部分。JavaScript没有继承、封装和多态,没有代理、模板、预编译、接口、多继承这些在传统面向对象编程里习以为常的概念,在概念的世界里它是简单的。但是它有”反射“,编程语言对自身的操作。我们可以在调试窗口中执行这么一句代码:”eval('a="这是字符串";');“,它的含义就是将”a="这是字符串";“一句代码导入到程序空间中并执行。虽然正如我们上面讨论的web开发本身是非常复杂的,但JavaScript却是如此的简洁。正是它的简洁性,成为了JavaScript最大的优点,也为我们复杂的开发中提供了一种高生产率的工具。这就是我们要掌握JavaScript的理由,不管是客户端还是服务端。你可能会觉得困难,那是因为,世界上大部分值得去做的事情都有一个非常陡的学习曲线。但至少证明这个事情值得去做。
前面的路还很远,你可能会哭,但是一定要走下去,一定不能停。