Node.js 一二三

238 阅读7分钟

Node.js — 3m安装法

各个平台都有相关的包管理工具,Ubuntu下的apt-getCentOS下的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 中有两种事件处理方式,callbackEventEmittercallback 采用错误优先的回调方式,后者是事件驱动力里的事件发射器。

错误优先的回调方式

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 方法处理。

使用rejectresolve 重塑流程:

  • 简单模式 🌰
    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

1、Node.js各版本对ES6特性支持

2、查看离线文档: dash

参考资料

书籍:《狼书(卷1):更了不起的Node.js》

社区:cnodejs