Node 的重要性已经不言而喻,很多互联网公司都已经有大量的高性能系统运行在 Node 之上。Node 凭借其单线程、异步等举措实现了极高的性能基准。此外,目前最为流行的 Web 开发模式是前后端分离的形式,即前端开发者与后端开发者在自己喜欢的 IDE 上独立进行开发,然后通过 HTTP 或是 RPC 等方式实现数据与流程的交互。这种开发模式在 Node 的强大功能的引领下变得越来越高效,也越来越受到各个互联网公司的青睐。
什么是 Node.js
官方定义
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O的模型,使其轻量又高效。Node.js 的包管理工具 npm 是全球最大的开源库生态系统。
Node.js 不是一门语言,也不是 JavaScript 的框架,也不是像Nginx一样的Web服务器 ,Node.js 是 JavaScript 在服务器端的运行环境(平台)。
Node.js 的组成
在 Node.js 里运行 JavaScript,跟在 Chrome 里运行 JavaScript 有什么不同?
二者采用的是同样的 JS 引擎。在 Node.js 里写 JS,和在前端写 JS,几乎没有不同。在写法上的区别在于:Node.js 没有浏览器、页面标签相关的 API,但是新增了一些 Node.js 相关的 API。通俗来说,对于开发者而言,在前端写 JS 是用于控制浏览器;而 Node.js 环境写 JS 可以控制整个计算机。
我们知道,JavaScript 的组成分为三个部分:
ECMAScript
DOM:标签元素相关的API
BOM:浏览器相关的API
ECMAScript 是 JS 的语法;DOM 和 BOM 浏览器端为 JS 提供的 API。
而 Node.js 的组成分为:
ECMAScript。ECMAScript 的所有语法在 Node 环境中都可以使用。
Node 环境提供的一些附加 API(包括文件、网络等相关的 API)。
如下图所示:
Node.js 的架构和依赖
Node.js 的架构如下:
Node.js 内部采用 Google Chrome 的 V8 引擎,作为 JavaScript 语言解释器;同时结合自行开发的 libuv 库,扩展了 JS 在后端的能力(比如 I/O 操作、文件读写、数据库操作等)。使得 JS 既可以在前端进行 DOM 操作(浏览器前端),又可以在后端调用操作系统资源,是目前最简单的全栈式语言。
其次,Node 生态系统活跃,提供了大量的开源库,使得 JavaScript 语言能与操作系统进行更多的交互。
Node.js 运行环境的核心:V8 引擎 和 libuv 库
Node.js 是 JavaScript 在服务器端的运行环境,在这个意义上,Node.js 的地位其实就是 JavaScript 在服务器端的虚拟机,类似于 Java 语言中的 Java 虚拟机。
V8 引擎 :编译和执行 JS 代码、管理内存、垃圾回收。V8 给 JS 提供了运行环境,可以说是 JS 的虚拟机。V8 引擎本身是用 C++ 写的。
libuv: libuv 是一个专注于异步 I/O 的跨平台类库,目前主要在 Node.js 上使用。它是 Node.js 最初的作者 Ryan Dahl 为 Node.js 写的底层类库,也可以称之为虚拟机。libuv 本身是用 C 写的。
V8 的内存限制
在一般的后端开发语言中,在基本的内存使用上没有什么限制,然而在 Node 中通过 JavaScript 使用内存时就会发现只能使用部分内存(64 位系统下约为 1.4GB,32 位系统下约为 0.7GB)。在这样的限制下,将会导致 Node 无法直接操作大内存对象。
造成这个问题的主要原因在于 Node 基于 V8 构建,所以在 Node 中使用的 JavaScript 对象基本上都是通过 V8 自己的方式来进行分配和管理的。V8 的这套内存管理机制在浏览器的应用场景下使用起来绰绰有余,足以胜任前端页面中的所有需求。但在 Node 中,这却限制了开发者随心所欲使用大内存的想法。
Node.js 的特点
异步、非阻塞 IO 模型
事件循环
单线程
Node.js 的性能和效率非常高。
传统的 Java 语言是一个请求开启一个线程,当请求处理完毕后就关闭这个线程。而 Node.js 则完全没有采用这种模型,它本质上就是一个单线程。
你可能会疑问:一个线程如何服务于大量的请求、如何处理高并发的呢?这是因为,Node.js 采用的是异步的、非阻塞的模型。
这里所谓的“单线程”,指的是 Node 的主线程只有一个。为了确保主线程不被阻塞,主线程是用于接收客户端请求。但不会处理具体的任务。而 Node 的背后还有一个线程池,线程池会处理长时间运行的任务(比如 IO 操作、网络操作)。线程池里的任务是通过队列和事件循环的机制来执行。
使用 Node.js 时的劣势
程序运行不稳定,可能会出现服务不可用的情况
程序运行效率较低,每秒的请求数维持在一个较低的水平
前端同学对服务器端的技术不太熟悉。
模块化的理解
什么是模块化
概念:将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并组合在一起。
模块的内部数据、实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。
最早的时候,我们会把所有的代码都写在一个js文件里,那么,耦合性会很高(关联性强),不利于维护;而且会造成全局污染,很容易命名冲突。
模块化的好处
避免命名冲突,减少命名空间污染
降低耦合性;更好地分离、按需加载
高复用性:代码方便重用,别人开发的模块直接拿过来就可以使用,不需要重复开发类似的功能。
高可维护性:软件的声明周期中最长的阶段其实并不是开发阶段,而是维护阶段,需求变更比较频繁。使用模块化的开发,方式更容易维护。
部署方便
模块化规范
模块化规范的引入
假设我们引入模块化,首先可能会想到的思路是:在一个文件中引入多个js文件。如下:
<body>
<script src="zepto.js"></script>
<script src="fastClick.js"></script>
<script src="util/login.js"></script>
<script src="util/base.js"></script>
<script src="util/city.js"></script>
</body>
但是这样做会带来很多问题:
请求过多:引入十个js文件,就有十次http请求。
依赖模糊:不同的js文件可能会相互依赖,如果改其中的一个文件,另外一个文件可能会报错。
以上两点,最终导致:难以维护。
于是,这就引入了模块化规范。
模块化的概念解读
模块化起源于 Node.js。Node.js 中把很多 js 打包成 package,需要的时候直接通过 require 的方式进行调用(CommonJS),这就是模块化的方式。
那如何把这种模块化思维应用到前端来呢?这就产生了两种伟大的 js:RequireJS 和 SeaJS。
模块化规范
服务器端规范:
- CommonJS规范:是 Node.js 使用的模块化规范。
CommonJS 就是一套约定标准,不是技术。用于约定我们的代码应该是怎样的一种结构。
浏览器端规范:
- 异步加载模块;
- 依赖前置、提前执行:require([`foo`,`bar`],function(foo,bar){}); //也就是说把所有的包都 require 成功,再继续执行代码。
- define 定义模块:define([`require`,`foo`],function(){return});
同步加载模块;
依赖就近,延迟执行:require(./a) 直接引入。或者Require.async 异步引入。 //依赖就近:执行到这一部分的时候,再去加载对应的文件。
define 定义模块, export 导出:define(function(require, export, module){});
PS:面试时,经常会问AMD 和 CMD 的区别。