什么是 Node.js
简单的说 Node.js 就是运行在服务端的 JavaScript 。
Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。
Node.js 是一个事件驱动 、 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎,V8 引擎执行 JavaScript 的速度非常快,性能非常好。
Node.js 自身哲学 ,是花最小的硬件成本,追求更高的并发,更高的处理性能。
Node.js 官网:https://nodejs.org/en/
特点:Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js 的特点
- 单线程
- 非阻塞 I/O
- 事件驱动
Node.js 特点之一 : 单线程
在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存。也就是说,理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右。要让Web应用程序支持更多的用户,就需要增加服务器的数量,而Web应用程序的硬件成本当然就上升了。
Node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4万用户的连接。另外,单线程的带来的好处,还有操作系统完全不再有线程创建、销毁的时间开销。
如下图:
Node.js 特点之二 : 非阻塞I/O non-blocking I/O
例如,当在访问数据库取得数据的时候,需要一段时间。在传统的单线程处理机制中,在执行了访问数据库代码之后,整个线程都将暂停下来,等待数据库返回结果,才能执行后面的代码。也就是说,I/O阻塞了代码的执行,极大地降低了程序的执行效率。
由于Node.js中采用了非阻塞型I/O机制,因此在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。
当某个I/O执行完毕时,将以事件的形式通知执行I/O操作的线程,线程执行这个事件的回调函数。为了处理异步I/O,线程必须有事件循环,不断的检查有没有未处理的事件,依次予以处理。
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞模式下,一个线程永远在执行计算操作,这个线程的CPU核心利用率永远是100%。所以,这是一种特别有哲理的解决方案:与其人多,但是好多人闲着;还不如一个人玩命,往死里干活儿。
Node.js 特点之三 : 事件驱动 event-driven
在Node中,客户端请求建立连接,提交数据等行为,会触发相应的事件。在Node中,在一个时刻,只能执行一个事件回调函数,但是在执行一个事件回调函数的中途,可以转而处理其他事件(比如,又有新用户连接了),然后返回继续执行原事件的回调函数,这种处理机制,称为“事件环”机制。
Node.js底层是C++(V8也是C++写的)。底层代码中,近半数都用于事件队列、回调函数队列的构建。用事件驱动来完成服务器的任务调度,这是鬼才才能想到的。
如下图:
适合 Node 的业务
Node.js 适合用来开发什么样的应用程序呢?
当应用程序需要处理大量并发的I/O,而在向客户端发出响应之前,应用程序内部并不需要进行非常复杂的处理的时候,Node.js非常适合。Node.js也非常适合与web socket
配合,开发长连接的实时交互应用程序。比如:
- 用户表单收集
- 考试系统
- 聊天室
- 图文直播
- 提供 JSON 的 API (为前台Angular使用)
Node.js本是就是极客追求性能极致的产物,缺少了很多服务器的健壮考量。所以 Node.js 不可能应用在银行、证券、电信等需要极高可靠性的业务中。
中国的企业实战中,创业型公司(正处于A轮、B轮)非常爱使用 Node.js 做核心业务:
- 功夫熊的APP,后台是Node.js在伺服
- 实现网,整站为Node.js搭建
成熟大企业,基本上都是用Node实现某一方面的功能:
- 知乎用了一个Node进程,跑起了“站内信”功能
- 百度的很多表单,是用Node保存到数据库的
Node.js 就是你工具箱中的一个小工具而已。
Node.js 安装
1、下载对应你系统的Node.js 版本,官网。
2、选择安装目录进行安装,默认即可。
3、测试,在命令提示符下输入命令: node -v
,会显示当前版本。
在cmd中,输入node -v
就能够查看nodejs的版本。你会发现,我们现在的盘符,不在安装目录下,但是也能够运行,这就是因为有系统环境变量。系统的环境变量已经有了c:\program files\nodejs
了,所以,这个文件夹中的 node.exe
就能够在任何盘符运行。
快速入门
1、控制台输出
我们现在做个最简单的小例子,演示如何在控制台输出,创建文本文件 demo1.js
,代码内容:
var a = 1;
var b = 2;
console.log(a + b);
我们在命令提示符下输入命令:node demo1.js
2、使用函数
创建文本文件 demo2.js
var c = add(100, 200);
console.log(c);
function add(a, b) {
return a + b;
}
命令提示符输入命令:node demo2.js
运行后看到结果为 300
3、模块化编程
创建文本文件demo3_1.js
exports.add = function (a, b) {
return a + b;
}
创建文本文件demo3_2.js
var demo = require('./demo3_1');
console.log(demo.add(100, 200));
我们在命令提示符下输入命令:node demo3_2.js
, 结果为:300
4、创建 web 服务器
示例一:
创建文本文件 demo4.js
// 创建 web服务器
var http = require('http');
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200,{'Content-Type': 'text/plain' });
// 发送响应数据 "Hello World"
response.end('Hello World\n');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');
http
为node内置的web模块,我们在命令提示符下输入命令: node demo4.js
服务启动后,我们打开浏览器,输入网址: http://localhost:8888/
即可看到网页输出结果 Hello World
,心情是不是很激动呢?Ctrl+c 终止运行。
示例二:
创建文本文件 demo4_1.js
//require表示引包,引包就是引用自己的一个特殊功能
var http = require("http");
//创建服务器,参数是一个回调函数,表示如果有请求进来,要做什么
var server = http.createServer(function (req, res) {
//req表示请求,request; res表示响应,response
//设置HTTP头部,状态码是200,文件类型是html,字符集是utf8
res.writeHead(200, {
"Content-type": "text/html;charset=UTF-8"
});
res.end("哈哈哈哈,我买了一个iPhone" + (1 + 2 + 3) + "s");
// response.end('Hello World\n');
});
//运行服务器,监听3000端口(端口号可以任改)
server.listen(3000, "127.0.0.1");
命令提示符下输入命令: node demo4_1.js
服务启动后,我们打开浏览器,输入网址: http://127.0.0.1:3000/
即可看到网页输出结果 哈哈哈哈,我买了一个iPhone6s
。
5、理解服务端渲染
我们创建 demo5.js
,将上边的示例一写成循环的形式
// 创建 web服务器
var http = require('http');
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200,{'Content-Type': 'text/plain' });
// 发送响应数据 "Hello World"
for (var i = 1; i < 10; i++) {
response.write('Hello World\n');
}
response.end('');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');
我们在命令提示符下输入命令启动服务:node demo5.js
浏览器地址栏输入 http://127.0.0.1:8888 即可看到查询结果。
我们右键查看源代码发现,并没有我们写的for循环语句,而是直接的10条Hello World,这就说明这个循环是在服务端完成的,而非浏览器(客户端)来完成。这与我们原来的JSP很是相似。
6、接收参数
创建文本文件 demo6.js
//接受参数
var http = require('http')
var url = require('url');
http.createServer(function (request, response) {
response.writeHead(200, {
'Content-Type': 'text/plain;charset=UTF8'
});
// 访问地址:http://127.0.0.1:8888/?name=zcf
var params = url.parse(request.url, true).query;
console.log(request.url); // /?name=zcf
response.write("name:" + params.name); // name:zcf
response.write("\n");
response.end();
}).listen(8888);
console.log('Server running at http://127.0.0.1:8888');
我们在命令提示符下输入命令: node demo6.js
,在浏览器测试结果。
我们现在来看一下 req
里面能够使用的东西。最关键的就是 req.url
属性,表示用户的请求URL
地址。所有的路由设计,都是通过 req.url
来实现的。
Node.js是服务器的程序,写的js语句,都将运行在服务器上。返回给客户的,都是已经处理好的纯html。
你会发现,我们本地写一个 js
,打死都不能直接拖入浏览器运行,但是有了 node
,我们任何一个js文件,都可以通过 node
来运行。也就是说,node
就是一个 js
的执行环境。
我们现在,要跑起来一个服务器,这个服务器的脚本,要以 .js
存储。是一个 js
文件。用 node
命令运行这个 js
文件罢了。
Node.js
没有根目录的概念,因为它根本没有任何的web容器!
让 node.js
提供一个静态服务,都非常难!
也就是说,node.js
中,如果看见一个网址是 127.0.0.1:3000/fang
,别再去想,一定有一个文件夹,叫做 fang
了。可能 /fang
的物理文件,是同目录的 test.html
URL
和真实物理文件,是没有关系的。URL
是通过了 Node
的顶层路由设计,呈递某一个静态文件的。
包资源管理器 NPM
什么是 NPM
是一个引用别人的 module
做成自己的项目,而别人的 module
又是引用别别人的 module
的,别别别人的 module
又是引用别别别别人的 module
的……
npm
全称 Node Package Manager
,他是node包管理和分发工具。其实我们可以把NPM
理解为前端的Maven
,
npm
的主要职责是安装开发包和管理依赖项。- 安装开发包,使用
npm install
命令;更新,使用npm update
命令。 - 管理依赖项,借助
package.json
文件。最简单生成package.json
的方法就是npm init
。
我们通过 npm
可以很方便地下载 js库
,管理前端工程。
最近版本的 node.js
已经集成了 npm
工具,在命令提示符输入 npm -v
可查看当前npm
版本。
NPM命令
初始化工程
init
命令是工程初始化命令。
建立一个空文件夹,在命令提示符进入该文件夹 执行命令初始化:npm init
。
按照提示输入相关信息,如果是用默认值则直接回车即可。
name
: 项目名称version
: 项目版本号description
: 项目描述keywords:{Array}
关键词,便于用户搜索到我们的项目
最后会生成 package.json
文件,这个是包的配置文件,相当于maven
的 pom.xml
,我们之后也可以根据需要进行修改。
package.json
内容如下:
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
本地安装
install
命令用于安装某个模块,如果我们想安装 express
模块(node的web框架
),输出命令:npm install --save express
。
出现黄色的是警告信息,可以忽略,请放心,你已经成功执行了该命令。
在该目录下已经出现了一个node_modules
文件夹 和 package-lock.json
。
node_modules
文件夹用于存放下载的js库(相当于maven的本地仓库)。
package-lock.json
是当 node_modules
或 package.json
发生变化时自动生成的文件。
这个文件主要功能是确定当前安装的包的依赖,以便后续重新安装的时候生成相同的依赖,而忽略项目开发过程中有些依赖已经发生的更新。
我们再打开 package.json
文件,发现刚才下载的 express
已经添加到依赖列表中了。
package.json文件内容:
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
}
}
关于版本号定义:
指定版本:比如1.2.2,遵循“大版本.次要版本.小版本”的格式规定,安装时只安装指定版
本。
波浪号(tilde)+指定版本:比如~1.2.2,表示安装1.2.x的最新版本(不低于1.2.2),但
是不安装1.3.x,也就是说安装时不改变大版本号和次要版本号。
插入号(caret)+指定版本:比如ˆ1.2.2,表示安装1.x.x的最新版本(不低于1.2.2),但
是不安装2.x.x,也就是说安装时不改变大版本号。需要注意的是,如果大版本号为0,则插
入号的行为与波浪号相同,这是因为此时处于开发阶段,即使是次要版本号变动,也可能带来
程序的不兼容。
latest:安装最新版本。
全局安装
刚才我们使用的是本地安装,会将js库安装在当前目录,而使用全局安装会将库安装到你的全局目录下。
如果你不知道你的全局目录在哪里,执行命令 npm root -g
我的全局目录在:C:\Users\zcf\AppData\Roaming\npm\node_modules
比如我们全局安装jquery
,输入命令:npm install jquery -g
批量下载
我们从网上下载某些代码,发现只有package.json
,没有node_modules
文件夹,这时我们需要通过命令重新下载这些js库。
进入目录(package.json
所在的目录)输入命令:npm install
,此时,npm
会自动下载package.json
中依赖的js库。
淘宝 NPM 镜像
有时我们使用 npm
下载资源会很慢,所以我们可以安装一个 cnmp
(淘宝镜像)来加快下载速度。
输入命令,进行全局安装淘宝镜像。
npm install -g cnpm --registry=https://registry.npm.taobao.org
安装后,我们可以使用以下命令来查看cnpm
的版本: cnpm -v
使用 cnpm
:cnpm install 需要下载的js库
运行工程
如果我们想运行某个工程,则使用 run
命令
如果 package.json
中定义的脚本如下
dev
是开发阶段测试运行
build
是构建编译工程
lint
是运行js代码检测
我们现在来试一下运行dev
:npm run dev
编译工程
我们接下来,测试一个代码的编译.编译后我们就可以将工程部署到nginx中啦~
编译后的代码会放在dist文件夹中,首先我们先删除dist文件夹中的文件,进入命令提示符,输入命令:npm run build
生成后我们会发现只有个静态页面,和一个static文件夹
这种工程我们称之为单页Web应用(single page web application,SPA
),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。这里其实是调用了webpack
来实现打包的。