node

nodejs

学着玩

输出

有颜色的输出

  1. 底层原生

  2. 封装好的

const chalk = require('chalk')
console.log(chalk.gray('hello world'))

进度条

const ProgressBar = require('progress')
const bar = new ProgressBar(':bar', { total: 10 })
const timer = setInterval(() => {
    bar.tick()
    if (bar.complete) {
        clearInterval(timer)
    }
}, 100)

输入

从命令行获取参数

例如:

node app.js joe

node app.js name=joe

获取参数值的方法是使用 Node.js 中内置process 对象。

它公开了 argv 属性,该属性是一个包含所有命令行调用参数的数组。

第一个参数是 node 命令的完整路径。

第二个参数是正被执行的文件的完整路径。

所有其他的参数从第三个位置开始。

可以使用循环迭代所有的参数(包括 node 路径和文件路径):

process.argv.forEach((val, index) => {
  console.log(`${index}: ${val}`)
})

也可以通过创建一个排除了前两个参数的新数组来仅获取其他的参数:

const args = process.argv.slice(2)

如果参数没有索引名称,例如:

node app.js joe

则可以这样访问:

const args = process.argv.slice(2)
args[0]

如果是这种情况:

node app.js name=joe

args[0]name=joe,需要对其进行解析。 最好的方法是使用 minimist 库,该库有助于处理参数:

const args = require('minimist')(process.argv.slice(2))
args['name'] //joe

但是需要在每个参数名称之前使用双破折号:

node app.js --name=joe

接受标准输入

  1. 从版本 7 开始,Node.js 提供了 readline 模块来执行以下操作:每次一行地从可读流(例如 process.stdin 流,在 Node.js 程序执行期间该流就是终端输入)获取输入。

const readline = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
})

readline.question(`你叫什么名字?\n`, name => {
    console.log(`你好 ${name}!`)
    readline.close()
})

模块系统

导入

只可以导入被公开的

const library = require('./library')

导出

使用module.exports导出

  1. 将对象赋值给 module.exports(这是模块系统提供的对象),这会使文件只导出该对象:

const car = {  brand: 'Ford',  model: 'Fiesta'}
module.exports = car
//在另一个文件中
const car = require('./car')
  1. 将要导出的对象添加为 exports 的属性。这种方式可以导出多个对象、函数或数据:

const car = {  brand: 'Ford',  model: 'Fiesta'}
exports.car = car

或直接

exports.car = {
  brand: 'Ford',
  model: 'Fiesta'
}

在另一个文件中,则通过引用导入的属性来使用它:

const items = require('./items')
items.car

const car = require('./items').car

module.exportsexport 之间有什么区别?

前者公开了它指向的对象。 后者公开了它指向的对象的属性。

NPM

安装所有依赖

如果项目具有 package.json 文件,则通过运行:

npm install

它会在 node_modules 文件夹(如果尚不存在则会创建)中安装项目所需的所有东西。

安装单个软件包

也可以通过运行以下命令安装特定的软件包:

BASH
npm install <package-name>

通常会在此命令中看到更多标志:

  • --save 安装并添加条目到 package.json 文件的 dependencies。

  • --save-dev 安装并添加条目到 package.json 文件的 devDependencies。

区别主要是,devDependencies 通常是开发的工具(例如测试的库),而 dependencies 则是与生产环境中的应用程序相关。

更新软件包

通过运行以下命令,更新也变得很容易:

npm update

npm 会检查所有软件包是否有满足版本限制的更新版本。

也可以指定单个软件包进行更新:

npm update <package-name>

运行任务

package.json 文件支持一种用于指定命令行任务(可通过使用以下方式运行)的格式:

npm run <task-name>

例如:

{
  "scripts": {
    "start-dev": "node lib/server-development",
    "start": "node lib/server-production"
  },
}

使用此特性运行 Webpack 是很常见的:

{
  "scripts": {
    "watch": "webpack --watch --progress --colors --config webpack.conf.js",
    "dev": "webpack --progress --colors --config webpack.conf.js",
    "prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
  },
}

因此可以运行如下,而不是输入那些容易忘记或输入错误的长命令:

$ npm run watch
$ npm run dev
$ npm run prod

查看所有安装的包

npm list

查看指定包的信息

npm list <package>

安装旧版本

npm install <package>@<version>

package.json

package.json 文件是项目的清单。 它可以做很多完全互不相关的事情。 例如,它是用于工具的配置中心。 它也是 npmyarn 存储所有已安装软件包的名称和版本的地方。

  • version 表明了当前的版本。

  • name 设置了应用程序/软件包的名称。

  • description 是应用程序/软件包的简短描述。

  • main 设置了应用程序的入口点。

  • private 如果设置为 true,则可以防止应用程序/软件包被意外地发布到 npm

  • scripts 定义了一组可以运行的 node 脚本。

  • dependencies 设置了作为依赖安装的 npm 软件包的列表。

  • devDependencies 设置了作为开发依赖安装的 npm 软件包的列表。

  • engines 设置了此软件包/应用程序在哪个版本的 Node.js 上运行。

  • browserslist 用于告知要支持哪些浏览器(及其版本)。

以上所有的这些属性都可被 npm 或其他工具使用。

dependencies

设置作为依赖安装的 npm 软件包的列表。

当使用 npm 或 yarn 安装软件包时:

npm install <PACKAGENAME>
yarn add <PACKAGENAME>

该软件包会被自动地插入此列表中。

package-lock.json

简单理解:用来锁版本

==事件循环==

事件循环是了解 Node.js 最重要的方面之一。

为什么这么重要? 因为它阐明了 Node.js 如何做到异步且具有非阻塞的 I/O,所以它基本上阐明了 Node.js 的“杀手级应用”,正是这一点使它成功了。

Node.js JavaScript 代码运行在单个线程上。 每次只处理一件事。

这个限制实际上非常有用,因为它大大简化了编程方式,而不必担心并发问题。

只需要注意如何编写代码,并避免任何可能阻塞线程的事情,例如同步的网络调用或无限的循环。

通常,在大多数浏览器中,每个浏览器选项卡都有一个事件循环,以使每个进程都隔离开,并避免使用无限的循环或繁重的处理来阻止整个浏览器的网页。

该环境管理多个并发的事件循环,例如处理 API 调用。 Web 工作进程也运行在自己的事件循环中。

主要需要关心代码会在单个事件循环上运行,并且在编写代码时牢记这一点,以避免阻塞它。

阻塞事件循环

任何花费太长时间才能将控制权返回给事件循环的 JavaScript 代码,都会阻塞页面中任何 JavaScript 代码的执行,甚至阻塞 UI 线程,并且用户无法单击浏览、滚动页面等。

JavaScript 中几乎所有的 I/O 基元都是非阻塞的。 网络请求、文件系统操作等。 被阻塞是个异常,这就是 JavaScript 如此之多基于回调(最近越来越多基于 promise 和 async/await)的原因。

案例1

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {  console.log('foo')  bar()  baz()}
foo()

输出

foo
bar
baz

案例2

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {  console.log('foo')  setTimeout(bar, 0)  baz()}
foo()

输出

foo
baz
bar

消息队列

当调用 setTimeout() 时,浏览器或 Node.js 会启动定时器。 当定时器到期时(在此示例中会立即到期,因为将超时值设为 0),则回调函数会被放入“消息队列”中。

在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。 类似 onLoad 这样的 DOM 事件也如此。

事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。

我们不必等待诸如 setTimeout、fetch、或其他的函数来完成它们自身的工作,因为它们是由浏览器提供的,并且位于它们自身的线程中。 例如,如果将 setTimeout 的超时设置为 2 秒,但不必等待 2 秒,等待发生在其他地方。

ES6 作业队列

ECMAScript 2015 引入了作业队列的概念,Promise 使用了该队列(也在 ES6/ES2015 中引入)。 这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。

在当前函数结束之前 resolve 的 Promise 会在当前函数之后被立即执行。

有个游乐园中过山车的比喻很好:消息队列将你排在队列的后面(在所有其他人的后面),你不得不等待你的回合,而工作队列则是快速通道票,这样你就可以在完成上一次乘车后立即乘坐另一趟车。

示例:

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {  console.log('foo')  setTimeout(bar, 0)  new Promise((resolve, reject) =>    resolve('应该在 baz 之后、bar 之前')  ).then(resolve => console.log(resolve))  baz()}
foo()

这会打印:

foo
baz
应该在 baz 之后、bar 之前
bar

这是 Promise(以及基于 promise 构建的 async/await)与通过 setTimeout() 或其他平台 API 的普通的旧异步函数之间的巨大区别。

Last updated