背景
先介绍一下背景吧,笔者在大学期间主要学的是JAVA,但学的也不是很深,就会基本的课设程度的网站编写水平。找工作的时候公司面试并没有问语言相关的东西(我当时也没注意),等到进来之后才知道会统一进行培训,学习其他的语言。算机缘巧合吧,就选择了node方向。经过了一段时间的学习与实践,目前应该可以说入了个门了,故分享一下现阶段的学习心得,可以纯粹当个故事看。
我出这个文章的初心是觉得现在网上学习一门语言的教程大多都是从入门到精通这种,如果纯看视频,需要耗费很长的时间。其实在已经学习过一门语言的情况下,学习另一门语言应该是很快的事情。我希望做到的就是让有其他语言基础、希望快速学习一门新的语言的人,在看完我的教程之后能比较快的了解到node这门语言的特性,学习路线和一些可以跳过的坑。经验的东西会比知识多一些。
当然如果你和我有相同的经历或者有转node的想法,希望这篇文章对你有些借鉴意义。
一些感受
首先说一下我对这门语言的直观感受。node给我的感觉最大的就是:发展很快,但是学习资料很少,不太适合没有学过其他语言的,完全新手的人学习。其次,node跟其他语言的比较起来也挺尴尬的。跟python相比,似乎node有的特性python都有,速度上应该也比他快不了多少。跟传统的JAVA写的服务来说又不及他稳定。可能最大的优点就是他是用javaScript来编程,对于前端来说可以无缝衔接过来,所以学习的成本比较低。而且,现阶段node的岗位需求量还是挺大的,至少我感觉阿里系一直在招node前端,所以如果深度学习了node的话,至少不愁找不到工作。
在刚刚接触这门语言的时候,你能感觉到和java最大的不同就是:学java的时候你程序出问题了报异常,你把异常信息在百度上一查就能查到,但node现在还做不到,出了问题有的时候就得自己摸索,社区方面还没有java那么完善。
在学习的过程中我也翻阅了一些和node有关的书籍,感觉在时间上都比较滞后,这种滞后是相对与node的发展过于迅猛来说的,即便有一些新的内容的也不及官网翻阅的方便。
综上,直接学习node会遇到各种各样的问题,这也是我写这个帖子的初心,就是想把自己学习中遇到的问题与一些心得体会分享出来。如果你只学习过JAVA,不妨也接触一下这一门函数式编程的语言。
建议
如果你想快速上手这门语言,我给你的建议是:
1、快速学习一下javaScript的一些语法。 一开始没有必要接触的概念(坑)有:原型链。js也支持class写了,一开始就去了解原型链性价比太低。
2、学习koa并简单做个东西。
3、深入学习一下egg这个框架。
我的理由很简单,koa这个框架很简单,核心代码就几百行,很快就能上手。而egg这个框架是中国人开发出来的,文档资料也都是中文,甚至你哪些地方不明白都能以中文的形式询问到开发人员。
书籍、教程
很多人会选择在刚接触一项新的技术的时候观看教学视频来快速上手。但我觉得,学生时代你可以这么做,工作的时候真不一定有那么多的时间,如果可以学会以书籍、教程的方式进行学习,在时间上是要比看视频高出不少的。最重要的一点就是,书籍与教程无论是在实体书还是在网站上,他都比视频更容易复用,当你忘记的时候可以更方便进行查阅。
so,先推荐基本必看的
2、ES6入门 (Es和js概念差不多,基本上可以说es就是js。)
以上两本书介绍的是javaScript的语法。第一本书比较基础。第二本书说的是这门语言的一些新特性,其中promise、async 和await最为关键。要知道,node是运行环境,编程时使用的语言是javaScript。
菜鸟教程这么有名,就不用过多介绍了,学编程应该没有没用过的。
4、node官网
5、如果你要买一些书《JavaScript精髓》、朴灵的《Node深入浅出》。
你可以先用半天/一两天时间过一遍javaScript入门,然后再来看以下部分。
开始胡诌
先介绍一下node:
Node.js是基于Chrome JavaScript运行时建立的一个平台, 采用事件驱动和非阻塞I/O模型。
在node出现之前,我们写的javaScript代码都是运行在浏览器的javaScript解析器上。后来有一个叫 Ryan Dahl的外国人,他希望用高级语言来写高性能web服务。而高性能web服务的有两个东西是必须的( 非阻塞IO、事件驱动),他发现javaScript正好符合这两个个特点,而且在javaScript中,IO只能写成异步调用的(这对Ryan Dahl来说是个有点),所以他选择了javaScript。同时,他把Chrome浏览器中运行javaScript的部分迁移出来(v8引擎),使普通环境下也可运行javaScript,他就相当与java的虚拟机,有了他,我们就可以在编写完代码之后,直接命令行node wenjian.js运行,不同于java的是,node程序不会说编译成一个.class文件再来执行,他每一次都是动态编译。
第一个问题: 为什么选择Node
这个问题的另一面是:node适合什么样的业务。
结论是:node适合IO密集型的应用。请注意,IO不单单是指操作硬盘,实际中网络传输、硬件获取信息都可以抽象成IO。
传统的apache服务器在应对网络请求的时候,都是一个请求对应一个线程或者一个有效请求对应一个线程。在当请求中包含了对磁盘的操作的时候,应用需要解决数据之间的锁问题、要对线程进行生成、上下文切换等一系列操作。这这起中花费的时间不说,cpu在调用数据的时候是阻塞的状态,所以他会一直等这个数据回来,这就造成了cpu的等待、空转。在这里面,使用户的响应变慢的瓶颈不在cpu,而在与IO。
使用node的话,由于他是单线程,所以没有线程切换、锁的问题。其次他是非阻塞IO,调用了IO操作之后不要求数据直接就能返回,cpu直接就开始处理下一个操作,等到了IO操作结束之后,IO操作会去通知cpu执行接下来的操作。这就使计算机的IO处理速度大大提升。
第二个问题: Node怎么实现这个异步过程
这里只做代码层面上的分析。
首先,什么是异步?举个例子就是:cpu向硬盘请求一个数据,他发送了一个请求之后就自己干自己的东西去了,等硬盘查找到了数据之后,会通知cpu进行接下来的处理。
这里涉及两个东西,硬盘找到东西之后,处理什么?怎么处理?
处理什么:硬盘中找到的数据。
怎么处理:cpu一开始就得把硬盘查找结束后要执行的代码一并发给硬盘,告诉他,你找完了数据,带着这个函数,一起回来找我继续处理。
这就是一个异步过程。
写个伪代码,比如说我要cpu发送去硬盘查找一个数据,找完之后输出出来
// 同步letres=getData(位置x);输出res;//异步functionprint(data){输出data}getData(位置x,print)//注意 这里的print是一个函数
这里运行了getData这个函数之后,cpu就会跟硬盘说“去位置x的地方给我找个数据,找到了之后,带着数据(data),和后面要执行的函数(print),一起返回来找我继续处理”。
硬盘就开始查找了,过了一段时间(这个时间不确定),硬盘找到了数据,假设是b,硬盘就会过来告诉cpu说,诶,执行一下print(b)。最后,b就被输出到了屏幕上。
从这个例子中就可以看出,要想能够实现这种异步编程,有一个必要的条件就是,函数可以作为参数传递。而javaScript原生就支持这种特性。
javaScript可以进行函数式编程,简单来说,函数是一个普通对象,可以作为参数传递,也可作为返回值返回。学习函数式编程对我的冲击还是不小的,比如说现在可以这么写一段代码。
functionfunc1(func,arg){returnfunc(arg);}functionfunc2(arg){console.log(arg);}func1(func2,3);//输出3
刚开始接触的人应该都会这样的疑惑,这个函数不是脱裤子放屁吗。
但是,正因为有了这种写法,我们可以在return func(arg)前加上一些处理逻辑。这时候代码就变成了。
functionfunc1(func,arg){//func1的逻辑returnfunc(arg);}
上述中func称为func1的回调函数。
回调函数,就是将函数作为参数传入另一个函数中,等他办完了自己的事情再回头来调用。
举一个别人举过的例子:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。
带来的问题
回头看看这个回调函数的写法,就可以发现其实他在语义上实在是非常反人类的。举个跟上面类似的例子。比如我现在要写一个函数,他要先执行一个mysql的操作(IO操作),然后再将结果输出
function printResult(sqlStr) {
connection.query(sqlStr,print);//看到这个print会不会觉得很莫名其妙。
}
function print(err,result) {
console.log(result);
}
printResult('select * from user'); //输出[Object object]
上面的connection.query是一个操作数据库的方法,他的参数就是(sql,callback,fields)callback就是回调函数的意思,而query方法会把这个回调函数的参数的第一参数识别为err,第二个参数识别为result。我们设计这个print的时候也要根据这个设计。
假设我们瞎设计这个print,写成这样,那就没有办法进行正常输出。
function printResult(sqlStr) {
connection.query(sqlStr,print);
}
function print(result) {
console.log(result);
}
printResult('select * from user'); //输出null 因为没有异常
回调函数使用和阅读的过程中一个很大的问题就是函数与函数之间的关联比较大,不好理解。这还只是一层回调函数,如果我想经过一些判断再进行这个查询和printResult。
function fun1(printResult,bol) { //懒得起名字
if(bol){
printResult('select *from user');
} else {
printResult('select *from manager');
}
}
你看,传统的异步函数的写法就是这个回调函数,他的一个极大的问题就是要经过层层的回调来实现它的“通知”的功能,当逻辑变得复杂,这里面的层数也会不断加深。
现在,一个比较好的解决方案就是async和await。根据ES6电子书中的介绍,我们可以这么写我们的代码。
async printResult(sqlStr) {
const [rows] = await promisePool.query(sqlStr);
console.log(rows);
return 1;
}
async func(){
const res = await printResult('select * from user');
}
这里的写法和上面最大的区别就在于,直观上我现在直接就从数据库获取了数据之后就可以进行输出了,而不用再写成回调。
async 和await的一个简单解释就是,可以把耗时操作写进async函数中,而在async函数中如果要获取另一个async函数的返回值,就可以用await来获取(如上面的rows和res)。
这样就将异步的回调写法转换成为直观的同步写法了,而且它还可以享受到异步的速度。需要指出的是,async和await是通过Promise来实现的,Promise这个工具可以解决一些回调问题,而async更多的是强调异步问题。可以用async写的都可以用Promise来写,反之则不行,具体可以看看上面推荐的Es6的电子书。
注意
请注意,虽然这种方法将异步转化为了同步,但是他内部还是使用这种回调,每当有异步请求,内部一定是通过参数层层往下走,执行完了再层层往上的,这一点决定了node中网络编程和其他同步语言的不同。在阅读网上的各种博客的时候,心里要想清楚这是回调的写法还是async的写法还是Promise的写法,并且他们之间是可以穿插使用的。
我想,这就是node最基础的东西。有了这些,几乎就可以开始写代码了。
后记
第一次尝试输出自己的知识,可能很多地方做得不太好,我也在积极地寻找问题在哪里。后续会持续地发一些我觉得有价值的东西,有感兴趣的朋友可以和我交流交流。