Node.js
既是语言也是平台,跳过了 Apache、Nginx 等 HTTP 服务器,直接面向前端开发
JavaScript 是由 ECMAScript、文档对象模型(DOM)和浏览器对象模型(BOM)组成的
而 Mozilla 则指出 JavaScript 由 Core JavaScript 和 Client JavaScript 组成
可以认为,Node.js 中所谓的 JavaScript 只是 Core JavaScript,或者说是 ECMAScript 的一个实现
最出色的一个实现: 将 JavaScript 移植到浏览器外
node.js 兴起以后,产生了 CommonJS
CommonJS 试图拟定一套完整的 JavaScript 规范,
以弥补普通应用程序所需的 API,譬如文件系统访问、命令行、模块管理、函数库集成等功能
CommonJS 制定者希望众多服务端 JavaScript 实现遵循 CommonJS 规范,
以便相互兼容和代码复用。Node.js 的部份实现遵循了CommonJS规范,
但由于两者还都处于诞生之初的快速变化期,也会有不一致的地方
- node.js 是基于 chrome V8 引擎封装的 JavaScript 运行环境(V8 是 Google Chrome 浏览器的 JavaScript 引擎,它的 JIT(Just-in-time Compilation,即时编译)快接近本地代码的执行速度)
使用了一个事件驱动、非阻塞 I/O 的模型,轻量又高效
适用于 I/O 密集型的领域: web 前端开发渲染、前端项目构建 webpack
适用于低延迟的网络应用: 如 restful API、即时聊天
node.js 不需要操作 DOM 页面文档节点,所以没有 document
node.js 没有 window,node.js 的顶级对象是 global,有 global.console、setInterval、setTimeout... ...
node.js 实现了绝大部分 ECMAScript 语法规范
- JavaScript 为客户端而生,Node.js 为网络而生
远不止开发一个网站那么简单:
- 具有复杂逻辑的网站
- 基于社交网络的大规模 Web 应用
- Web Socket 服务器
- TCP/UDP 套接字应用程序
- 命令行工具
- 交互式终端程序
- 带有图形用户界面的本地应用程序
- 单元测试工具
- 客户端 JavaScript 编译器
- Node.js 内建了 HTTP 服务器支持
- 可以轻而易举地实现一个网站和服务器,不用额外搭建一个 HTTP 服务器
这和 PHP、Perl 不一样,因为在使用 PHP 的时候,必须先搭建一个 Apache 之类的HTTP 服务器,
然后通过 HTTP 服务器的模块加载或 CGI 调用,才能将 PHP 脚本的执行结果呈现给用户
- 还可以调用C/C++ 的代码
- 可以充分利用已有的诸多函数库
- 也可以将对性能要求非常高的部分用C/C++ 来实现
- 通过提供一系列优化的 API 类库,使 V8 在浏览器之外依然能高效运行
Node 除了用 V8 引擎来解析 JavaScript 外,还提供了高度优化的应用库,用来提高服务器效率
消息事件是 JavaScript 和 Node 的核心 (一个很恰当的类比是,你从书店预订一本书,等书到货时,书店会“回调”通知你去取)
设计 Node.js 的一个主要目的是提供高度可扩展的服务器环境
早先 Netscape 服务器程序就支持 JavaScript 作为一门服务器端脚本语言(称为 LiveScript)
Node 在内存处理上比传统服务器程序高效得多,也就是能够同时快速地服务更多的用户
Node 给 Web 服务器程序开发领域引进了事件驱动编程
AJAX 中 "J" 的唯一选择就是 JavaScript,完全没有其他替代品。
这导致整个行业急需大量优秀的 JavaScript 程序员。Web 成为一个真正意义上的平台
- 缺点:
异步,所以会有回调函数嵌套太多,太深的问题(回调地狱)
异步事件模式: 要把一个完整的逻辑拆分为一个个事件,增加了开发和调试难度(第三方模块有解决方案)
单线程,处理不好 CPU 密集型任务(压缩,解压,服务器读数据库的数据)
- 特点:
最大的特点就是: 采用异步式 I/O 与事件驱动的架构设计
使用的是单线程模型,对于所有 I/O 都采用异步式的请求方式,避免了频繁的上下文切换
Node.js 在执行的过程中会维护一个事件队列,
程序在执行时进入事件循环,等待下一个事件到来,
每个异步式 I/O 请求完成后会被推送到事件队列,等待程序进程进行处理
异步机制是基于事件的
所有的磁盘 I/O、网络通信、数据库查询都以非阻塞方式请求
返回的结果由事件循环来处理
- 实例: 简单而常见的数据库查询操作
-
// 传统做法 res = db.query('SELECT * from some_table'); res.output(); // 以上代码在执行到第一行的时候,线程会阻塞,等待数据库返回查询结果,然后再继续处理。 // 然而,由于数据库查询可能涉及磁盘读写和网络通信,其延时可能相 // 当大(长达几个到几百毫秒,相比CPU的时钟差了好几个数量级), // 线程会在这里阻塞等待结果返回。对于高并发的访问,一方面线程长期阻塞等待,另一方面 // 为了应付新请求而不断增加线程,因此会浪费大量系统资源,同时线程的增多也会占用大量 // 的 CPU 时间来处理内存上下文切换,而且还容易遭受低速连接攻击
-
// Node.js 解决方案 db.query('SELECT * from some_table', function(res) { res.output(); }); // 这段代码中 db.query 的第二个参数是一个函数,我们称为回调函数。 // 进程在执行到 db.query 的时候,不会等待结果返回,而是直接继续 // 执行后面的语句,直到进入事件循环。当数据库查询结果返回时, // 会将事件发送到事件队列,等到线程进入事件循环以后, // 才会调用之前的回调函数继续执行后面的逻辑
Node.js 进程在同一时刻只会处理一个事件,完成后立即进入事件循环检查并处理后面的事件。
这样做的好处是,CPU 和内存在同一时间集中处理一件事,同时尽可能让耗时的 I/O 操作并行执行。
对于低速连接攻击,Node.js 只是在事件队列中增加请求,等待操作系统的回应,
因而不会有任何多线程开销,很大程度上可以提高 Web 应用的健壮性,防止恶意攻击
还有比如,在服务器端开发程序常常需要处理二进制文件,JavaScript 语言本身对此支持得不好,因此 V8 也如此,
- 而 Node 的 Buffer 类库提供了轻松操作二进制数据的方法
- 能在服务器端运行 JavaScript
- 异步的非阻塞的 I/O(I/O线程池,分线程),js 本身运行快,慢的是读写操作,所以采用异步读写
- 单线程(主线程 执行主要流程)
- 事件循环机制
- 跨平台(windows,mac,linux)
现在出现了越来越多用 JavaScript 编写的复杂网页应用(如 Gmail),
如果能把越多的代码共享到服务器上运行,那么开发的成本也会越低。
- 使用 Node,除了可以直接操作 V8 的 JavaScript 运行时状态
- Node 利用了 JavaScript 的事件驱动(event-driven)特性来构建高度可扩展的服务器程序
- Node 采用了事件循环(event loop)架构,让开发高效的服务器程序变得简单和安全。
- Node 高性能地实现了 多线程。
- Node 提供了一系列“非阻塞”函数库来支持事件循环特性
比如,把文件系统或数据库操作封装成事件驱动形式的函数接口。
非阻塞函数会在它获得文件内容后,通知 Node 中的程序
- 除了可以用 Node 现有的库来构建应用外,开发者也可以轻松为其扩展新的库
正因为 Node 很容易扩展,在 Node 项目对外发布后,其社区便迅速涌现出大量扩展库。
其中许多是连接数据库或其他软件的驱动接口,还有相当一部分是独立有用的软件
- 了解 Node.js 的最佳方法是使用其提供的 REPL 模式(Read-Evaluate-Print-Loop,输入 - 求值 - 输出 - 循环),即交互式命令行解析器
- 它提供了以点号(.)开头的元命令
.help 会显示帮助菜单
.clear 会清除当前运行的内容 它会清除内存中任何变量或闭包,而不需要重启解析器
.exit 将退出 Node解析器
- 不能详细显示函数,并非解析器无法显示函数内容,而是因为函数通常都很长,如果解析器把函数都展开,很可能会导致刷屏
- HTTP 模块,是专为快速非阻塞式 HTTP 服务器,而用 C 重新编写的。
- 让我们看一下 Node 采用 HTTP 服务器的“Hello World”经典例子
- 包含其他库这一方法,Node 用的是 CommonJS 模块风格
-
// 通过 require 方法把 HTTP 库包含到程序中来 var http = require('http'); // HTTP 库所具有的功能已经赋给了 http 对象 // Node 本身就是 Web 服务器 // 调用 HTTP 模块的一个工厂模式方法(createServer)来创建新的 HTTP 服务器 // 新创建的 HTTP 服务器并没有赋值给任何变量,它只会成为存活在全局范围内的匿名对象 // 传了一个匿名函数作为参数。此函数绑定在新创建服务器的事件监听器上进行 request 事件处理 http.createServer(function (req, res) { // 一个是请求的对象(req),一个是响应的对象(res) // 调用了 res 对象的几个方法,这将修改响应结果 // 必须调用 res.writeHead 方法来设置 HTTP 响应头,否则就不能返回真实内容给客户端 // 设置状态代码为 200(表示 HTTP 状态代码“200 OK”),并且传入一段 HTTP 头描述 res.writeHead(200, {'Content-Type': 'text/plain'}); // end 方法将会关闭 HTTP 连接。但因为我们同时还传入了一个字符串 res.end('Hello World\n'); // end 方法将在把此内容发送给客户端后才关闭连接 }).listen(8124, "127.0.0.1"); // 通过链式调用来初始化服务器,并告诉它监听在 8124 端口 console.log('Server running at http://127.0.0.1:8124/'); // 将在标准输出 stdout 上打印信息 // 每当一个新的访问请求到达 Web 服务器,它都将调用我们指定的函数方法来处理
- Node 的包管理系统 npm——Node Package Manager
npm 完全是用 JavaScript 和 node 编写的
npm 给开发者提供了安装工具,也给模块维护人员提供了发布工具
提供了发布代码的方法,或者将代码发布到本地,或者通过全局的 Node 模块资源库
管理代码依赖包的安装,以及其他与发布代码相关的工作。
- node.js 的包基本遵循 CommenJS 规范,包 将一组相关的模块组合在一起,形成一套工具
- 包结构: 用于组织包中的各种文件
bin 可执行的二进制文件
lib/src js 代码
doc 文档
test 单元测试
- 包描述文件 package.json: 必须,描述包的相关信息,以供外部读取
表达非代码相关信息, JSON 格式文件
包含字段:
name 包名
version 包的版本号 x.x.x-???
main 主模块 (dist/jquery.js)
dependencies 生产依赖(比如基于 jQuery 开发的,项目运行时依赖就是 jQuery)
scripts
devDependencies 开发依赖(项目构建打包时需要的依赖)
最后一个属性值结束不能有 ',' 逗号
- 通过简单的 npm install 包名 命令来安装模块包(下载指定版本的包 npm install jquery@1.12)
自动创建 node_modules 文件夹,包含下载好的包
自动创建 package-lock.json 文件,以便二次下载更快
自动在 package.json 中的 dependencies 中默认添加到生产依赖
(npm install babel --save-dev 包添加到开发依赖)
(npm remove babel 删除 包 和 这个包相关的所有信息)
npm install (下载 package.json 中的所有依赖包)
下载的包 使用,直接写包名:
// (index.js)
const $ = requery('jquery');
自定义的包 使用,必须写 ./ 或者 ../ 相对路径开头:
require(相对路径/包名);
可以安装自己本地下载的包
但更多时候需要用 npm 在注册库中安装远程的包。
注册库保存了其他 Node 开发者共享的包,供你使用
如数据库驱动、流控制库、数学库。
npm 安装的大部分库是完全用 JavaScript 编写的,但也有少数需要编译。幸运的是,npm 会帮你完成这些工作
- search 命令会列出在全局 npm 注册库中的所有包,并根据包名进行过滤
npm search packagename // 如果你没有提供包的名字,那么所有可用的包都会显示出来
- 如果包列表过时了(因为添加或删除了某个包,或者你知道某个需要的包并没有出现),可以用以下命令操作 npm cache clean 来清理缓存
npm remove packagename
npm cache clean
- 创建一个包并不需要很多额外的工作
只需要创建一个 package.json 文件,
并添加关于你的模块的简单说明(包的名字和版本号是最重要的部分)。
要快速生成一个有效的包文件,可以
在你的模块文件夹路径下运行命令 ① npm init,它会提示你输入关于该模块的描述信息,
然后该命令会在当前目录下生成一个 packages.json 文件。
如果已经存在包文件,它的属性会被作为默认值,并允许修改
要使用自己的包,可以
使用命令 ② npm install /path/to/yourpacage 进行安装。
路径可以是本地文件系统的一个文件夹,也可以是一个外部 URL(比如 GitHub)
如果你的模块对大众用户也有用,并且已经准备好了,那么可以通过 npm 的 publish 命令对外发布
1. 用 adduser 命令创建一个用户: ③npm adduser
2. 用 publish 命令发布包: ④ npm publish
3. 目前,并不需要注册或者验证有效性
因为所有人都可以发布包,而且没有进行过滤或监督,所以你用 npm 安装的包的质量是没有保证的。所以说,用者自理
npm link 命令能创建你的项目及其依赖项之间的符号链接,因此在项目开发过程中,依赖项中的任何改动都能为你所用
你想从当前项目中通过 require() 访问另外一个项目中的功能
你想在多个项目中使用同一个包,而且不需要在每个项目中都维护一个版本
- 不加参数的情况下调用 npm link 会为当前项目在全局包路径中创建一个符号链接,让你系统上的其他所有项目都能使用
- 输入 npm link packagename 会为该包创建一个从当前项目工作目录到全局模块路径中的符号链接。
比如,输入 npm link express 会在全局包路径中安装 Express 框架,并包含到你的项目中来。
每当 Express 升级,你的项目会自动从全局包文件夹中找到最新版本来使用。
如果你在多个项目中链接了 Express,所有这些项目都会同步到最新的版本,
这样就不需要在 Express 升级时一个一个地操作了。
- cnpm 淘宝对国外npm 服务器的一个完整镜像版本,每十分钟更新一次
npm install -g cnpm -registry=https://registry.npm.taobao.org
- yarn 是 Facebook 开源的新的包管理器,可以用来代替 npm
npm install yarn -g
npm install cyarn -g --registry "https://registry.npm.taobao.org"
yarn -version
yarn add express
yarn init
yarn add global packagename
yarn remove packagename
yarn add packagename --dev
yarn list
yarn info packagename
Facebook 的 React框架
更快,有进度提示
有缓存
没有自己的仓库地址,使用的是 npm 仓库地址
安装路径