xuesong 2019-06-28
一直想致力于写一篇关于广义讲解Node.js系统的文章,苦于时间有限,资源有限。这篇文章是在结合自己的学习心得以及与行业大佬共同探讨下争对于熟练掌握JS语言后的广义Node.js.至于为什么叫作广义在后文会提到。希望看到这篇文章后可以激发大家对Node.js的学习兴趣,这篇文章的初衷就是致力于帮助大家可以走进Node.js世界。
优势:
这是NodeJS最理想的应用场景,可以处理数万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可。它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文本,因此流量不高,一台机器甚至也可以处理最繁忙的公司的API需求。
目前MVC的架构,在某种意义上来说,Web开发有两个UI层,一个是在浏览器里面我们最终看到的,另一个在server端,负责生成和拼接页面。
不讨论这种架构是好是坏,但是有另外一种实践,面向服务的架构,更好的做前后端的依赖分离。如果所有的关键业务逻辑都封装成REST调用,就意味着在上层只需要考虑如何用这些REST接口构建具体的应用。那些后端程序员们根本不操心具体数据是如何从一个页面传递到另一个页面的,他们也不用管用户数据更新是通过Ajax异步获取的还是通过刷新页面。
例如个性化应用,每个用户看到的页面都不一样,缓存失效,需要在页面加载的时候发起Ajax请求,NodeJS能响应大量的并发请求。
4.Javascript在nosql的应用
Javascript在nosql数据库中大量应用,使得数据存储和管理使用的都是javascript语句,与web应用有了天然的结合;比如mongoDB;
5.Javascripte运行从前台到后台
一门语言从前台后台,减少了开发客户端和服务端时,所需的语言切换,使得数据交互效率提升
1.单线程:
Nodejs跟Nginx一样都是单线程为基础的,这里的单线程指主线程为单线程,所有的阻塞的全部放入一个线程池中,然后主线程通过队列的方式跟线程池来协作。我们写js部分不需要关心线程的问题,简单了解就可以了,主要由一堆callback回调构成的,然后主线程在循环过在适当场合调用。
2.事件驱动
首先,解释下“事件驱动”这个概念。所谓事件驱动,是指在持续事务管理过程中,进行决策的一种策略,即跟随当前时间点上出现的事件,调动可用资源,执行相关任务,使不断出现的问题得以解决,防止事务堆积。 Nodejs设计思想中以事件驱动为核心,事件驱动在于异步回调,他提供的大多数api都是基于事件的、异步的风格。而事件驱动的优势在于充分利用系统资源,执行代码无须阻塞等待某种操作完成,有限的资源用于其他任务。事件驱动机制是通过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文的切换。
3.异步、非阻塞I/O
Nodejs提供的很多模块中都是异步执行的。比如,文件操作的函数。 一个异步I/O的大致流程:
1.发起I/O调用 :
①用户通过js代码调用nodejs的核心模块,将回调函数和参数传入核心模块
②将回调函数和参数封装成
2.执行回调:
①操作完成将结果储存到请求对象的result属性上,并发出完成通知。
②循环事件,如果有未完成的,就在进入对象请求I/O观察者队列,之后当做事件处理;
1.不适合CPU密集型应用;CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起;
2.只支持单核CPU,不能充分利用CPU
3.可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
4.开源组件库质量参差不齐,更新快,向下不兼容
官网下载:
1.下载地址:http://nodejs.cn/download/
2.根据自己的系统进行镜像的下载:
3.下载的为最新的node版本,当前下载的为10.8.0
nvm可以方便的在同一台设备上进行多个node版本之间切换
1.先下载安装nvm,下载地址:https://github.com/coreybutle...,选择nvm-setup压缩文件,解压后安装;
2.安装过程中出现,选择nvm的安装目录
3.选择node的安装目录
4.配置环境变量
5.查看nvm是否安装成功:
Nvm -v
6.安装nodejs
使用nvm install <version> [<arch>]命令下载需要的版本。arch参数表示系统位数,默认是64位,如果是32位操作系统,需要执行命令:nvm install 10.8.0 32,如:
Nvm install 10.8.0
7.使用下载的nodejs
执行nvm use <version> [<arch>] 命令开始使用特定版本。比如:nvm use 10.8.0或者nvm use 10.8.0 32
Nvm use 10.8.0
8.当有多个nodejs版本时,设置默认的node版本
nvm alias default v10.8.0
9.查看当前所安装的node版本
Nvm list
全局安装:
全局安装方式是键入命令:npm install gulp -g 或 npm install gulp --global,其中参数-g的含义是代表安装到全局环境里面,包安装在Node安装目录下的node_modules文件夹中,一般在 Users用户名AppDataRoaming 目录下,可以使用npm root -g查看全局安装目录。
局部安装(本地安装)
本地安装方式是键入命令:npm install gulp 或 npm install gulp --save-dev等,其中参数--save-dev的含义是代表把你的安装包信息写入package.json文件的devDependencies字段中,包安装在指定项目的node_modules文件夹下。
局部安装的意义:
1、可以实现多个项目中使用不同版本的包;
2、可以在不使用全局变量NODE_PATH的情况下,进行包的引入;
Node运行
终端运行和外部文件运行
(一)模块化
1.诞生背景:
全局变量的灾难:
函数命令的冲突:
对于公用方法的封装会出现很多命名冲突,尤其在多人开发的情况下
依赖关系的管理:
比如b.js依赖a.js,在文件引入的过程中,就要先引入b.js
最早的时候在解决上述部分问题时的解决方案是:使用匿名的自执行函数
2.模块需要解决的问题:
(二)Commonjs
1.规范:
1)模块的标识应遵循的规则(书写规范)
定义,标识,引用
2)定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴漏出来的API
3)如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖
4)如果引入模块失败,那么require函数应该报一个异常
5)模块通过变量exports来向往暴漏API,exports只能是一个对象,暴漏的API须作为此对象的属性。
2.模块的简单使用:
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum;
};
//increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
3.模块的定义
1)全局有一个module变量,用来定义模块
2)通过module.declare方法来定义一个模块(一般不通过此方式进行模块的定义)
3)module.declare方法只接收一个参数,那就是模块的factory,次factory可以是函数也可以是对象,如果是对象,那么模块输出就是此对象。
4)模块的factory函数传入三个参数:require,exports,module,用来引入其他依赖和导出本模块API
5)如果factory函数最后明确写有return数据(js函数中不写return默认返回undefined),那么return的内容即为模块的输出。
不常用:
module.declare(function(require, exports, module) {
exports.foo = "bar";
});
module.declare(function(require)
{
return { foo: "bar" };
});
常用:
module.exports={}
4.Module.exports和exports的区别:
1)module.exports 初始值为一个空对象 {}
2)exports 是指向的 module.exports 的引用
3)require() 返回的是 module.exports 而不是 exports
4)关系为var exports = module.exports={};
如:
module.exports可以赋值一个对象
module.exports={}
exports不可以赋值一个对象,只能添加方法或者属性
exports.add=function(){
}
5.模块引用
require函数的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。当我们用require()获取module时,Node会根据module.id找到对应的module,并返回module. exports,这样就实现了模块的输出。
require函数使用一个参数,参数值可以带有完整路径的模块的文件名,也可以为模块名。
假如,有三个文件:一个是a.js(存放路径:home/a.js),一个是b.js(存放路径:home/user/b.js), 一个是c.js(存放路径:home/user/c.js)。我们在a.js文件中引用三个模块,实例代码如下:
var httpModule=require('HTTP');//用 “模块名”加载服务模块http
var b=require('./user/b');//用“相对路径”加载文件b.js
var b=require('../ home/user/c');//用“绝对路径”加载文件c.js
6. 模块标识
模块标识就是传递给require方法的参数,必须符合小驼峰命名的字符串,或者以.、..开头的相对路径,或者绝对路径,默认文件名后缀.js。在Node实现中,正是基于这样一个标识符进行模块查找的,如果没有发现指定模块会报错。
根据参数的不同格式,require命令去不同路径寻找模块文件。加载规则如下:
(1)如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。比如,require('/home/marco/foo.js')将加载/home/marco/foo.js。
(2)如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。比如,require('./circle')将加载当前脚本同一目录的circle.js。
(3)如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装)。
举例来说,脚本/home/user/projects/foo.js执行了require('bar.js')命令,Node会依次搜索以下文件。
/usr/local/lib/node/bar.js
/home/user/projects/node_modules/bar.js
/home/user/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
这样设计的目的是,使得不同的模块可以将所依赖的模块本地化。
(4)如果参数字符串不以“./“或”/“开头,而且是一个路径,比如require('example-module/path/to/file'),则将先找到example-module的位置,然后再以它为参数,找到后续路径。
(5)如果指定的模块文件没有发现,Node会尝试为文件名添加.js、.json、.node后,再去搜索。.js件会以文本格式的JavaScript脚本文件解析,.json文件会以JSON格式的文本文件解析,.node文件会以编译后的二进制文件解析。
(6)如果想得到require命令加载的确切文件名,使用require.resolve()方法。此方法会返回一个完整的路径,并且还会对文件的是否存在做检测
7.二进制模块
虽然一般我们使用JS编写模块,但NodeJS也支持使用C/C++编写二进制模块。编译好的二进制模块除了文件扩展名是.node外,和JS模块的使用方式相同。虽然二进制模块能使用操作系统提供的所有功能,拥有无限的潜能,但对于前端同学而言编写过于困难,并且难以跨平台使用,因此不在本教程的覆盖范围内。
(三)Node模块的分类:
1.内置模块(核心模块)
核心模块指的是那些被编译进Node的二进制模块,它们被预置在Node中,提供Node的基本功能,如fs、http、https等。核心模块使用C/C++实现,外部使用JS封装。要加载核心模块,直接在代码文件中使用require() 方法即可,参数为模块名称,Node将自动从核心模块文件夹中进行加载。
2.第三方模块
Node使用NPM (Node Package Manager) 安装第三方模块,NPM会将模块安装到应用根目录下的node_modules文件夹中,然后就可以像使用核心模块一样使用第三方模块了。在进行模块加载时,Node会先在核心模块文件夹中进行搜索,然后再到node_modules文件夹中进行搜索。
3.文件模块
上述两种方式都是从当前目录获取模块文件,实际上,可以将文件放在任何位置,然后在加载模块文件时加上路径即可。可以使用以./ 开头的相对路径和以/ 或C: 之类的盘符开头的绝对路径。
4.文件夹模块
从文件夹中加载模块,Node首先会在该文件夹中搜索package.json文件。如果存在,Node便尝试解析它,并加载main属性指定的模块文件。如果package.json不存在,或者没有定义main属性,Node默认加载该文件夹下的index.js文件。 如从项目根目录下的 modules/hello 文件夹加载模块: var hello = require("./modules/hello");
package.json格式如下:
{ "name": "hello", "version": "1.0.0", "main": "./hello.js" }
此时,Node会去加载./modules/hello/hello.js 文件。
如果目录里没有 package.json 文件,则 Node.js 就会试图加载目录下的 index.js 或 index.node 文件。 例如,如果上面的例子中没有 package.json 文件,则 require("./modules/hello") 会试图加载:
./modules/hello /index.js
./modules/hello /index.node
(一)npm简介:
世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。
npm 是一个包管理器,它让 JavaScript 开发者分享、复用代码更方便(有点 maven 的感觉哈)。 在程序开发中我们常常需要依赖别人提供的框架,写 JS 也不例外。这些可以重复的框架代码被称作包(package)或者模块(module),一个包可以是一个文件夹里放着几个文件,同时有一个叫做 package.json 的文件。 一个网站里通常有几十甚至上百个 package,分散在各处,通常会将这些包按照各自的功能进行划分(类似我们安卓开发中的划分子模块),但是如果重复造一些轮子,不如上传到一个公共平台,让更多的人一起使用、参与这个特定功能的模块。 而 npm 的作用就是让我们发布、下载一些 JS 轮子更加方便。
(二)npm构成:
npm 由三个独立的部分组成:
网站:是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。
注册表(registry):是一个巨大的数据库,保存了每个包(package)的信息
命令行工具 (CLI):通过命令行或终端运行。开发者通过 CLI 与 npm 打交道
(三)npm更新:
查看版本:nvm -V
更新版本:nvm install npm@latest -g
(四)npm更改全局目录:
查看npm全局目录:
npm root -g
修改全局包位置 :
npm config set prefix '目标目录'
查看修改结果
npm config get prefix或npm root -g
(五)package.json
A.作用:
1.作为一个描述文件,描述了你的项目依赖哪些包
2.允许我们使用 “语义化版本规则”(后面介绍)指明你项目依赖包的版本
3.让你的构建更好地与其他开发者分享,便于重复使用
B.创建:
npm init 即可在当前目录创建一个 package.json 文件:
C.内容:
基本配置:
1.name:项目的名字
2.version:项目的版本
3.description:描述信息,有助于搜索
4.main: 入口文件,一般都是 index.js
5.scripts:支持的脚本,默认是一个空的 test
6.keywords:关键字,有助于在人们使用 npm search 搜索时发现你的项目
7.author:作者信息
8.license:默认是 MIT
9.bugs:当前项目的一些错误信息,如果有的话
注:
如果 package.json 中没有 description 信息,npm 使用项目中的 README.md 的第一行作为描述信息。这个描述信息有助于别人搜索你的项目,因此建议好好写 description 信息。
依赖包配置:
1.dependencies:在生产环境中需要用到的依赖
2.devDependencies:在开发、测试环境中用到的依赖
(六)npm的包版本规范和package.json的使用规范
npm版本规范:
如果一个项目打算与别人分享,应该从 1.0.0 版本开始。以后要升级版本应该遵循以下标准:
补丁版本:解决了 Bug 或者一些较小的更改,增加最后一位数字,比如 1.0.1
小版本:增加了新特性,同时不会影响之前的版本,增加中间一位数字,比如 1.1.0
大版本:大改版,无法兼容之前的,增加第一位数字,比如 2.0.0
Package.json的版本书写:
我们可以在 package.json 文件中写明我们可以接受这个包的更新程度(假设当前依赖的是 1.0.4 版本):
如果只打算接受补丁版本的更新(也就是最后一位的改变),就可以这么写:
1.0
1.0.x
~1.0.4
如果接受小版本的更新(第二位的改变),就可以这么写:
1
1.x
^1.0.4
如果可以接受大版本的更新(自然接受小版本和补丁版本的改变),就可以这么写:
x
(七)npm下载包
安装方式:
如果你只是想在当前项目里用 require() 加载使用,那你可以安装到本地 npm install 默认就是安装到本地的
如果你想要在命令行里直接使用,比如 grunt CLI,就需要安装到全局了
如果在你的项目里有 package.json 文件,运行 npm install 后它会查找文件中列出的依赖包,然后下载符合语义化版本规则的版本。 npm install 默认会安装 package.json 中 dependencies 和 devDependencies 里的所有模块。 如果想只安装 dependencies 中的内容,可以使用 --production 字段:
npm install --production
1.本地安装:
1)安装指定版本:
$ npm install sax@latest :最新版本
$ npm install [email protected] :指定版本
$ npm install sax@" >=0.1.0 <0.2.0” :安装0.1.0到0.2.0版本
注:
有时下载会报错:npm install error saveError ENOENT: no such file or directory, 解决办法: - 在目录下执行 npm init 创建 package.json,输入初始化信息 - 然后再执行下载命令
2)安装参数 --save 和 --save -dev
添加依赖时我们可以手动修改 package.json 文件,添加或者修改 dependencies devDependencies 中的内容即可。
另一种更酷的方式是用命令行,在使用 npm install 时增加 --save 或者 --save -dev 后缀:
npm install --save 表示将这个包名及对应的版本添加到 package.json的 dependencies
npm install --save-dev 表示将这个包名及对应的版本添加到 package.json的 devDependencies
3)更新本地package
有时候我们想知道依赖的包是否有新版本,可以使用 npm outdated 查看,如果发现有的包有新版本,就可以使用 npm update 更新它,或者直接 npm update 更新所有:
npm update 的工作过程是这样的:
先到远程仓库查询最新版本
然后对比本地版本,如果本地版本不存在,或者远程版本较新
查看 package.json 中对应的语义版本规则 (一定注意规则)
如果当前新版本符合语义规则,就更新,否则不更新
4)卸载本地 package
卸载一个本地 package 很简单,npm uninstall
2.全局安装
1)安装
npm install -g <packages>
2)权限处理(非windows处理)
在全局安装时可能会遇到 EACCES 权限问题,解决办法办法有如下 2种:
1.sudo npm install -g jshint,使用 sudo 简单粗暴,但是治标不治本
2.修改 npm 全局默认目录的权限 先获取 npm 全局目录:npm config get prefix,一般都是 /usr/local; 然后修改这个目录权限为当前用户:
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
3)更新全局包
想知道哪些包需要更新,可以使用 npm outdated -g --depth=0,然后使用 npm update -g 更新指定的包;
要更新所有全局包,可以使用 npm update -g,可以发现对比本地的,只是多了个 -g。
4)卸载全局包
npm uninstall -g <package>
3.其他命令
Npm run: 运行 package.json 中 scripts 指定的脚本
npm install from github: 从github下载资源
npm install git://github.com/package/path.git ;
npm info:npm info 可以查看指定包的信息:
3.创建项目
创建一个testxxxxx文件夹,cd到testxxxxx文件夹中,然后下载基础配置文件:
1
2 //输入以下命令,会提示配置包的相关信息,名称版本等等,都是包的基本配置信息
npm init
配置完毕开始写自己的包内代码:
创建一个index.js文件,文件内的代码如下,直接输出123456789
module.exports = 123456789;
4.发布:
开始命令行发布包,命令如下:npm publish testxxxxx
发布完毕,在npm网站上搜索,就可以搜索到自己刚刚发布的包了。
5.验证下载:
6.撤销发布
接下来说明一下怎么撤销自己发布的版本。这只是一个测试的包,最好当然还是撤销下来:
删除要用force强制删除。超过24小时就不能删除了。自己把握好时间。
npm --force unpublish testxxxxx
由于npm的源在国外,所以国内用户使用起来各种不方便。部分国内优秀的npm镜像资源,国内用户可以选择使用
1.淘宝npm镜像
搜索地址:http://npm.taobao.org/
registry地址:http://registry.npm.taobao.org/
2.cnpmjs镜像
搜索地址:http://cnpmjs.org/ registry
地址:http://r.cnpmjs.org/
3.如何使用
有很多方法来配置npm的registry地址,下面根据不同情境列出几种比较常用的方法。以淘宝npm镜像举例:
1)临时使用
npm --registry https://registry.npm.taobao.org
2)持久使用(推荐使用)
npm config set registry https://registry.npm.taobao.org
配置后可通过下面方式来验证是否成功 npm config get registry
3)通过cnpm使用 (也可以使用cnpm) (常用)
npm install -g cnpm --registry=https://registry.npm.taobao.org
4.恢复npm源镜像:
如果将npm的镜像地址改变后,在发布包时,应该将镜像改回:
npm config set registry https://registry.npmjs.org/
(一)Node-inspector的浏览器调试
1.安装node-inspector运行环境
安装命令:npm install -g node-inspector
注意:a、参数-g 将node-inspector安装到系统环境变量中,可以在任何路径下执行,尽量保留。
b、如果是Linux或Unix系统,需要使用root权限安装
2. 启动node-inspector
node-inspector启动后会生成一个服务,主要负责调试工具与nodejs程序之间的沟通工作,一个桥梁。
a、window:直接在任意路径下执行 node-inspector,进行守护
b、Linux || Unix:node-inspector &
将node-inspcetor作为后台服务,这样就不怕误操作,把窗口关掉了。出现进程PID,表示node-inspcetor已经成为后台进程,可以ctrl+c结束当前任务,node-inspcetor进程依然保持。如果想停止可以 kill -9 pid 杀掉node-inspcetor进程。
3.打开chrome,输入地址 http://127.0.0.1:8080/debug?port=5858
相信大家都看到有错误了,知道什么原因吗? 恭喜你猜对了,我们的NodeJS程序还没起来呢,目前先到这,现在需要回过头来看看我们的NodeJS程序的变化。
4. 打开NodeJS的调试模式
node --debug app.js debugger的监听端口是5858,这个端口可以修改
5.再次打开chrome,刷新页面,chrome通过node-inspector服务连接到nodejs服务上了,并显示nodejs应用的入口文件内容。
总结:
1、node-inspector依赖nodejs的运行环境。
2、调试过程中node-inspector的服务不要重启,只需要在重启nodejs应用后刷新一下chrome的页面即可。
3、严格的来说node-inspector不是一个完整的调试工具,它需要一个可视化的调试界面来展示所有的调试信息,node-inspector是调试界面与nodejs之间的桥梁,是调试界面能与nodejs沟通。
1.如下图进行点击按钮,弹出配置页面
2.添加node处理程序
3.配置程序名称和程序路径
4.配置完毕启动程序
5.打断点
结尾:主要的学习是配置环境,其实Node.js更多扮演的是一个中间层的作用,很多国内企业对Node.js的依赖并不是很高,希望感觉对自己有帮助的小伙伴可以积极的点赞加关注!!