最近有原本是 Java 或者 .NET 背景的朋友问我:“如果学习 Javascript (后文简称 JS) 应该走什么学习路径?”
其实之前在知乎上此类问题是很多的,但是真要回答这个问题还真不容易。因为没有办法定义什么叫“学会”。JS 应用领域如此广泛,从 Web 前端、Native Client 到各种后台服务、工具链;npm 上的包超过了 20万;超过100种语言可以编译到 JS。这么多的应用领域和编程风格的尝试,使得如何定义“学会”成为一个首要的问题。
好在 NodeSchool 已经为入门安排了台阶。学习 JS 其实离不开学习 Node.JS,这个和前端后端无关。因为即使是现在的前端框架,也离不开 Node.JS 构造的工具链。
NodeSchool 将课程划分为 Core 和 Electives 两部分。Core 部分的课程是基础,Electives 则可以根据个人的工作方向进行选择。
NodeSchool的很多贡献者都是 Node.JS 的核心贡献者:GitHub/substack, GitHub/rvagg, GitHub/julianduque等等。每个课程都是独立维护的 GitHub Repo.
在这个学习过程中,先不要去想 React, Angular, Relay, Gulp, WebPack 之类的高层框架,先掌握基本知识,锻炼新的思维方式和编程方法。
Core
Core 的课程分为以下6门:
- Javascripting: 这是针对没有任何 JS 基础的程序员的,对于有经验的程序员可以快速过一遍。这门课没有牵扯到任何 ES2015 的语法,是基础的基础。
- LearnYourNode: 这门课让你尝尝 Node.JS 的滋味,学习基于 CommonJS 的模块系统的使用,重点是异步调用(基于时间、文件IO, HTTP等),体会 Callback 的风格和问题。传统的基于栈展开的异常处理突然不能用了等等。你会很快发现 Callback 给你带来的“不快”,但是需要很久才能接受这种“命运”,最终对现实世界是异步的本质真的理解,未掌握新的“工具”和思考方式打下基础。
- git-it: 这门课是 git 的入门,可能有人已经了解很深。重点是教会程序员从创建 repo.、commit、fork、pull request 到 merge 的过程,让程序员能够尽快地参与到开源社区的贡献中(例如为既有的 repo. 提交pull request)。
- How to npm: npm 以前是 Node.JS 的“家”,现在已经是所有 JS 程序员的“家”,以前的一些前端包管理工具要么停止(Component)要么萎缩(Bower)。这个教程教你如何安装你自己的依赖包,Packags.json 文件的基本定义。发布自己的包到 npm 让全世界的程序员都能使用。Node.JS 的成功有一半是因为 npm, 想想为什么。npm + js 让 “Module Driven Development” 成为现实,你也应该有意识地讲自己的代码尽量写成独立的小“模块”,拥有自己的 README,可选的样例程序或者单元测试,让使用者有一个很平缓的学习曲线。
- Scope Chains & Closures: 闭包的概念其实在 Java/.NET 中早已经添加,但是没有像在 JS 中用得如此普遍。教程最后会让你尝试一下在 Chrome 里面抓一抓内存时序的Dump,体会一下 GC对内存的影响。
- Stream Adventure: Node.JS 只有非常有限的核心库以及很少的“约定”。如果说 Callback 是第一重要的“约定”的话,那么 Stream 可以排在第二。在异步编程中,Callback 是 Request/Response的最原始实现方式,而 Stream 则用来定义如何处理异步事件流。Stream 最早的概念来自于 Unix 的管道操作,使得每个应用都可以专注于很小的功能,但是通过组合这些小应用来完成非常复杂的任务。在Node.JS中,Stream既可以像Unix管道那样传输任何二进制数据,也可以用来传递传输 JS 对象流(Gulp就是基于JS对象流设计的)。在学习完这门课程之后(或者学习完后面的 Async 课程),可以看一下 highlandjs(高层的基于 JS 对象 Stream 的库)以及 Bacon、Kefir这样的 FRP(Functional Reactive Programming) 库,或者 transducers(关注输入输出源无关的转换过程),它们都具有近似的基因,就是如何将函数式编程的方法用在变化的事件流上,不断组合出新的输出。
Electives
Electives 的课程很多,慢慢分到了不同的方向。其中甚至有 WebGL 的课程(前端程序员和Native Client是时候关心 GL 编程了)。随着时间推移,有一些课程原本不属于 Core 的课程我觉得也应该是基础课了:
- Functional Javascript:Javascript 可以支持四种编程思维:命令式,面向对象(OO),函数式编程(Functional)以及元编程(Meta Programming,例如: ES2015 的 Proxy)。其中函数式编程的方式是最应该被 Java/.NET 程序员重视的。因为Java/.NET 程序员往往工作在OO为主,FP 为辅(偶尔用用 Lambda 之类的)的环境中,但是在大量的 JS 世界中 FP 的方式要远远多于 OO的方式。函数真的是被当成第一级的公民。函数返回函数再返回函数再返回函数的代码比比皆是,大量使用递归,再加上异步的话,如果没有对既有模式形成条件反射,阅读别人得代码很容易迷失。
- Planet Proto: Javascript 原型继承绝不是你熟悉的 Java/.NET 的方式。这门课让你了解什么是原型链,消息是如何在原型链上传递的等等概念。
- Async You: 在 Promise 之前,async 库是Callback最早的治愈系。其作者GitHub/caolan 后来撰写了 highlandjs。通过这个课程你可能会体会到,异步问题有异步的解决思路和方法。如果像Java/.NET 通常那样把异步编程都包装成同步方式,看起来简单,但是缺在其他付出了更大代价。我建议你试试自己实现 async 的个别功能,这对于掌握异步函数式编程是非常有帮助的。
- Promise It Wont Hurt:Promise 已经成为 JS 的规范的一部分,被广泛使用,也被广泛支持,因此是 Callback 最直接的替代者,不可不知道。这个教程用的Promise 库是 Q,现在用Bluebird性能更好。如果前端开发,需要精简的代码,那么ES2015 的 Promise (可以用 Babel的实现)基本也够用了。
- count-to-6:这是 ES2015 (原来叫 ES6)相关的教程。其实学习到这个阶段,能看的 ES2015 的教程就太多了。阮一峰 的ECMAScript 6入门 就不错。用 ES2015 编写的包越来越多,我建议你也开始学习和使用。如果你不掌握 ES2015,你很快就会发现看不懂别人写的 JS 程序了。
- Tower-of Babel: 如果没有 Babel,你也就不能充分享受 Es2015或者 ES7带来的新功能了。Babel 已经不仅仅是为新语法做 Polyfill,同时也在帮你优化代码的性能。大量的前端库没有都是在 Babel 支持下开发的,例如 React 或者 React Native。后端用 Babel 也不亏😀。
至于其他课程,你完全可以根据自己的方向来选择,例如:
- 如果希望看到如何基于Javascript 搭建出乐高积木一样的存储引擎,那么可以从 Level Me Up Scotty开始。
- learnuv 则已经是 LibUV 的教程了。Node.JS 的消息泵、异步IO的文件和网络、时钟功能实际上都是 LibUV 提供的。了解 LibUV 有助于你了解所有和消息泵时序有关的功能(process.nextTick(), setImmediate(), setTimeout() 等等),同时也是写 Native Binding 的基础。在了解 LibUV 之后,如果真的要写 Native Binding,那么就是 Go Native这个教程了。当然这个 Native 是针对的 Google V8 引擎。
- 大部分 Node.JS 的教程都是从 Express 开始的。Node.JS 也提供了对应的课程Express Works。在学习 ESNext Generation 之后,可以学习 Koa,一个基于 Coroutine(Javascript 有很多 Coroutine 的实现库,Koa 用了 co)实现的 Web Server 框架(用来替代 Express)。
其实学到到这个阶段,对于有经验的 Java/.NET 程序员来说已经可以自由学习了,只要英文的阅读理解能够过关。但是这个阶段的学习过程绝不是把教程看一遍,作业做一遍,一定要结合上自己的设问,以及对这些问题的答案寻求(通过 Google、实验、互联网或者问身边的有竟然的人来寻求答案)。因此这个过程不应该很快,持续几个月是很正常的。
其他注意事项
不要有“成见”
对于有经验的 Java, .NET 程序员,学习 JS 最大的障碍实际是你心中的“成见”,例如:
- JS 是个玩具语言
- JS 性能差
- Callback Hell
- JS 类型约束太弱,不安全,容易犯错
- JS 是单线程的,不能写 Server 应用
- 没有有效的内存管理…
- 函数式编程消耗太大…
- 闭包造成内存泄露不释放…
- JS 不是真正的函数式语言(来自Haskell 程序员或者 F#)
“然并卵”,如果你之前掌握的语言没有足够的应用场景,语言本身再好又有什么用?再说这个世界上从来就没有所谓“最好”的编程语言,只有适合不适合当时当地场景的语言。JS 能够获得广泛运用的原因之一,就是其松散的约定和实现的低成本,结果就被大家在不同的时间装扮成了自己喜欢的样子。与其坐着骂 JS,不如想想怎么装扮 JS 来满足自己的需要。
尽量不要用 Windows 进行开发
尽管 Node.JS 号称支持 Windows,但是大部分开发者写的包都没有专门在 Windows 下调试过,尤其是那些有 Native Binding 的包,情况会变得更复杂。
不要依赖 IDE
没有 Windows 就没有 Visual Studio,而且也就没有什么 IntelliSense 之类的代码提示。专注于好的命名比工具更重要。 很遗憾,Mac/Linux 没有能媲美 Visual Studio 的工具。我的选择是速度最快的那个 Sublime,这么多年不仅没有用到 IntelliSense,Debug 也基本靠 console.log
。
大量阅读别人的代码
阅读别人的程序也是很好的学习方法。从一个库入手,捋着它的npm 依赖开始阅读,不了解的概念就 Google,或者进行实验,往往几天时间就可以有很大的提高。这个学习过程也是体会 “Module Driven Development” 的过程,你经常会惊诧于,这么一点点功能也可以包成一个 npm 包,但是如果是我来写真没人家考虑得周全。
阅读别人的代码的过程往往能发现无数的小知识点:它 npm 的 package.json 为什么有这个字段?还可以这样写?它怎么用 tap 做单元测试而不是 mocha? 这个项目用 Beefy 比 WebPack 更经济;npm shrinkwrap 是什么意思…
和有经验的人一起写
除了上面所说的,最好找有经验的人来带或者和能力高强的人一起工作。JS 是“大海”一样的领域,你永远不知道下周会出现什么新的知识,更何况海中已经有的这么多资源。学完上面的东西毕竟只是学会了游泳。自己摸索到能够扬帆出海还是要花费大量的时间成本。