Node.js — 3m安装法
各个平台都有相关的包管理工具,Ubuntu
下的apt-get
,CentOS
下的yum
,macOS
下的homebrew
,便于安装和卸载软件。Node.js是名副其实的版本帝,版本更新之快使得上述的工具不适用,开发机器有可能需要同时存在几个Node.js的大版本,每个Node.js内置的npm又有版本的差异,而且,国内网络访问npmjs.org镜像速度很慢,所以,推荐适用3m安装法。
- nvm (node version manager): 解决多版本共存、切换。
- npm (node package manager): 用于解决Node.js模块安装,本身它也是Node.js模块,每次安装都会内置某个版本的npm
- nrm (node registry manager): 解决npm 镜像访问慢的问题,提供测速,切换下载源功能
nvm
nvm
是一个开源的Node.js版本管理器,通过简单的bash脚本来管理、切换多个Node.js版本。
注意:nvm不支持windows版本,但有替代品nvm-windows
安装:
# nvm 安装
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
# node.js 安装 官网最新LTS
nvm install 12.13.1
查看环境配置:
cat ~/.bash_profile
执行source
命令,是环境生效
source ~/.bash_profile
注意:也可根据当前终端shell类型查看不同配置。如果是zsh,查看.zshrc,如果是bash,查看.bashrc
查看可安装版本:
nvm ls-remote
本机已安装版本:
nvm ls
版本类型:
- LTS版本指的是长期支持版本(Long-term Support),有官方支持,推荐给绝大多数用户使用,一般在生成环境上
- Current版本指的是当前正在开发的版本,它通常较新,功能点有变动,但没有完全稳定,在经过一段时间之后,当前版本可能会变为LTS版本,一般用于学习
tip: 一般奇数版本都是尝试性的,一般LTS版本都是偶数
使用 nvm
安装 Node.js 8.x
nvm install 8
指定默认版本:
nvm alias default 11
切换版本:
nvm use 12
npm
npm
通常称为node包管理器。它的主要功能就是管理Node.js的包,包括:安装、卸载、更新、查看、搜索、发布等。它最开始的初衷是只是Node.js包管理器,随着前端技术react、webpack、browserify等发展,目前npm的定位是广义的包管理器,包括js、react、mobile、angularjs、browsers、jquery、cordova、bower、gulp、grunt、browserify、docpad、nodebots、tessel等,是开源世界里最大、生态最健全的包管理器。
Node.js集成了npm
,所以安装Node.js时也一并安装了。也可以使用npm
命令来更新安装。
[sudo] npm install npm -g
#指定版本
[sudo]npm install -g npm@2.9
使用场景有:
- 从npm镜像服务器下载第三方模块
- 从npm镜像服务器下载并安装命令程序到本地
- 自己发布模块到npm镜像服务器供他人使用
使用npm
安装模块:略
nrm
Node.js和其他语言一样,默认将模块托管在 npmjs.org
,这是官方的registry(源),registry是指从哪个源下载Node.js模块,当然其他组织或个人也是可以自建npm registry
(源)的,这些非官方的镜像会定期的和npm官方registry(源)进行同步,一般在10分钟左右一次。
npm提供的配置源:
npm config set registry <registry url>
这个处理的问题在于切换源的时候比较麻烦。nrm
就是专门用于解决这个问题,它可以帮助你简单、快速的在不同的npm registry(源)之间进行切换,它默认内置了很多常用的源,包括npm、cnpm、taobao、 nj、rednpm、npmMirror,当然你可以自己通过nrm add维护自己的源。
安装 nrm
:
npm install -g nrm
测速:
➜ ~ nrm test
npm ---- 732ms
yarn --- 745ms
* cnpm --- 180ms
taobao - 226ms
nj ----- Fetch Error
npmMirror 4493ms
edunpm - Fetch Error
查看源:
➜ ~ nrm ls
npm -------- https://registry.npmjs.org/
yarn ------- https://registry.yarnpkg.com/
* cnpm ------- http://r.cnpmjs.org/
taobao ----- https://registry.npm.taobao.org/
nj --------- https://registry.nodejitsu.com/
npmMirror -- https://skimdb.npmjs.com/registry/
edunpm ----- http://registry.enpmjs.org/
切换源: 不需要记住 registry 的具体URL,使用registry的名字
nrm use <registry name>
➜ ~ nrm use taobao
Registry has been set to: https://registry.npm.taobao.org/
增加源:
nrm add <regsitry url>
目的:
- 内网安装速度快,
- 私有模块,仅供企业内部使用,安全
异步调用和流程控制
Node.js异步原理
浏览器中的异步核心技术是Ajax,异步Javascript和XML。Node.js的异步原理的核心是EventLoop。
看图说话:
Ajax 异步处理:
Node.js 异步处理:
调用Node.js API的方法的时候,它会把具体操作和回调函数交给 EventLoop 去执行,EventLoop 维护了一个任务队列(microTask),等异步操作执行完成,队列中的回调函数会按照先进先出(FIFO)的顺序执行。
🌰 获取目录下的所有文件的API
- 异步写法
# hello-async.js
const fs = require('fs')
const path = '.'
fs.readdir(path, function(err, files) {
if (err) {
console.log(err)
return;
}
console.log(files)
})
- 同步写法
# hello-sync.js
const fs = require('fs')
console.log(fs.readdirSync('.'))
注意:高并发场景下慎用同步写法,不然可能会成为性能瓶颈,跟Node.js的设计初衷是相悖的。
Node.js自带的异步写法
Node.js 中有两种事件处理方式,callback
和 EventEmitter
。
callback
采用错误优先的回调方式,后者是事件驱动力里的事件发射器。
错误优先的回调方式
Node.js 极其依赖异步代码和回调来保证执行效率。Node.js SDK 中的 callback 使用 错误优先回调(error-first-callback)写法。规则如下:
- 回调函数的第一个参数返回的是
error
对象,如果发生错误,该对象会作为第一个参数返回,正常返回null。 - 回调函数的第二个参数返回的是所有成功响应的结果数据。
🌰见 hello-err-first.js
这个🌰告诉我们:
- 异步流程控制中,异常处理很重要!
- 只有同步代码块才能使用
try-catch
EventEmitter
简单理解为“发布/订阅”模式,和前端的事件机制类似,如Vue里面的 $emit
和 $on
。
const EventEmitter = require('events')
const obsever = new EventEmitter()
obsever.on('topic', function() {
console.log('topic has occured')
})
function main() {
console.log('start')
obsever.emit('topic')
console.log('end')
}
main()
**注意:对于EventEmitter 的 on
来说,Node.js允许同一个事件最多指定10个回调函数,超过会发出警告。可以通过设置setMaxListeners
方法改变。 **
更好的异步流程控制
回调地狱
Node.js采用了错误优先的回调写法,导致SDK中导出的都是回调函数,如果组合调用这些函数,经常会出现回调里嵌套回调的问题。这种写法称之为回调地狱。
🌰
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
Node.js如何解决回调地狱:
- async.js 早期的解决方案之一
- Thunk
- Promise
- 生成器Generators/ yield
- Async/ await
Promise
🌰 1
const fs = require('fs')
function hello(file) {
return new Promise(function(resolve, reject ) {
fs.readFile(file, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data.toString())
}
})
})
}
hello('./hello-events.js')
.then(function(res) {
console.log(res)
})
.catch(function(err) {
console.log(err)
})
使用Promise 实例处理异步流程控制,约定了每个函数的返回值都是 Promise 对象,因此都可以使用 then
方法处理。
使用reject
和 resolve
重塑流程:
- 简单模式 🌰
const fs = require('fs')
function hello(file) {
return new Promise(function(resolve, reject ) {
fs.readFile(file, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data.toString())
}
})
})
}
hello('../main.js')
.then (function(data) {
return new Promise(function(resolve, reject) {
console.log('promise1\n', data)
resolve(data)
})
})
.then(function(data) {
return new Promise(function(resolve, reject) {
console.log('promise2\n', data)
resolve(1)
})
})
.then(function(data) {
return new Promise(function(resolve, reject) {
console.log('promise3\n', data)
reject(new Error('reject'))
})
})
.catch(function(err) {
console.log('catch\n', err)
})
- 嵌套模式 🌰
const fs = require('fs')
function hello(file) {
return new Promise(function(resolve, reject ) {
fs.readFile(file, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data.toString())
}
})
})
}
hello('./main.js')
.then (function(data) {
return new Promise(function(resolve, reject) {
console.log('promise1\n', data)
resolve(data)
})
.then(function(res) {
return new Promise(function(resolve, reject) {
console.log('promise2\n', data)
resolve(1)
})
})
.catch(function(err) {
console.log('catch', err)
})
})
.catch(function(err) {
console.log('catch\n', err)
})
- 链式写法
const fs = require('fs')
function hello(file) {
return new Promise(function(resolve, reject ) {
fs.readFile(file, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data.toString())
}
})
})
}
const step1 = function(data) {
return new Promise(function(resolve, reject) {
console.log('promise1\n', data)
resolve(data)
})
.then(function(res) {
return new Promise(function(resolve, reject) {
console.log('promise2\n', data)
resolve(1)
})
})
.catch(function(err) {
console.log('catch', err)
})
}
const step2 = function(data) {
return new Promise(function(resolve, reject) {
console.log('promise3\n', data)
reject(new Error('reject'))
})
}
hello('./hello-sync.js')
.then(step1)
.then(step2)
.catch(function(err) {
console.log(err)
})
- 最终版:每个操作放到独立文件里变成模块
# hello-reflow-module.js
const hello = require('./tasks/hello')
const step1 = require('./tasks/step1')
const step2 = require('./tasks/step2')
hello('../hello-json.json')
.then(step1)
.then(step2)
.catch(function(err) {
console.log(err)
})
Tips
2、查看离线文档: dash参考资料
书籍:《狼书(卷1):更了不起的Node.js》
社区:cnodejs