学Node.js实际上就是另外一个环境的JS, 所以依然要看一看基础如何操作. 这里目的是要写一个小程序, 会用到最基本的内容, 比如命令行, JSON存储数据等.

  1. Node.js 命令行读取参数
  2. 用JSON存储数据
  3. 简单app – 添加note功能
  4. 简单app – 查看和删除note功能
  5. 用箭头函数重构
  6. 查找功能
  7. Node.js 的 debug功能

Node.js 命令行读取参数

动态语言经常就有直接就可以使用的读取输入的函数, 好比Python的内建函数.

Node.js有一个内建的全局对象process, 用其的.argv参数就可以获取命令行模式运行node时候传入的参数, 和静态语言的args一样, 这个process.argv也是一个数组, 放了字符串形式的所有内容.

其中第一个固定是node.exe, 第二个是js文件名称, 第三个(也就是索引2)就是实际的参数. app.js改成如下试验一下:

const args = process.argv;

for (let eachArg of args) {
    console.log(eachArg);
}

可以任意执行这个文件加上各种参数:

node app.js -l x fdkj

在我的机器上输出如下:

D:\coding\notes-app>node app.js -l x fdkj
D:\software\node\node.exe
D:\coding\notes-app\app.js
-l
x
fdkj

如果靠process.argv, 一个一个分析也不是不可以, 不过有一些库可以方便的给命令行添加功能, 比如yargs.

使用npm i yargs安装其为本地版本, 在其官网上有文档, 这就是一个增强版本的解析参数的工具, 其内部操作process.argv.

app.js现在如下:

const yargs = require("yargs");
console.log(yargs.argv);

node app.js 1 2的方式运行, 会得到如下结果:

{ _: [ 1, 2 ], '$0': 'app.js' }

可见结果是一个对象, 键名为_的是参数的数组, 而$0固定对应的是JS文件的名称. 然后可以简单的添加指令:

yargs.version('1.1.0');
yargs.command({
    command: 'add',
    describe: 'Add a new note',
    handler: function () {
        console.log('Adding a new note!')
    }
});

yargs.command({
    command: 'delete',
    describe: 'Delete a note',
    handler: function () {
        console.log('Delete a note!')
    }
});

version在文档了对应的是 –version 这种选项, 而command就是对应的add delete这种参数. 对于具体的参数搭配, 可以在.command()命令中的对象添加具体的builder对象:

yargs.command({
    command: 'add',
    describe: 'Add a new note',
    builder:{
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        }
    },
    handler: function (argv) {
        console.log("Title: " + argv.title);
    }
});

如此配置之后, 在add参数后边就可以加上 –title选项, 还可以在builder中添加多个选项. 然后handler对应的函数可以传入一个argv对象, argv对象就可以用选项名称获取选项的内容, 比如使用如下命令:

node app.js add  --title="321"

显示结果就是:

Title: 321
{ _: [ 'add' ], title: '321', '$0': 'app.js' }

有了这个库之后, 就可以方便的操作一个命令+一批选项了.

用JSON存储数据

用JavaScript不用JSON, 就好比用Java不用面向对象思想一样. JSON说实在还是挺好用的. 来看看Node.js里如何使用JSON吧.

JSON对象在原版JS中就是一个内置的对象, 在Node中也是, 可以直接使用JSON.stringify()来将一个对象转换成JSON字符串, 用JSON.parse()来将一个JSON字符串转换成一个对象.

和原版JS一样, JSON只能支持字符串和数值类型的转换. 所以语法就不用说了. 这里主要是和之前介绍的写入和读取文件函数来搭配使用:

const fs = require('fs');

const game = {
    name: "Baldur's Gate 3",
    release: 2020

};

//从对象生成JSON字符串
const bg3JSON = JSON.stringify(game);

console.log(bg3JSON);

//写入到文件
fs.writeFileSync('data.json', bg3JSON);

//读出文件 直接读出的是一个字节数组
const content = fs.readFileSync('data.json');

//这个字节数组有一个指定编码来将其转换成字符串的方法
const dataJSON = content.toString("UTF-8");

//将JSON字符串解析成对象
const gamebg3 = JSON.parse(dataJSON);

console.log(gamebg3);

开始编写一个简单的app 通过命令行添加和删除数据

之前已经添加了yargs, 然后学会了JSON以及将JSON保存在文件中的操作, 现在就可以来编写一个简单的命令行指令, 用来记录一条条信息的应用了.

这个应用的核心就是首先通过yargs来进行配置命令与对应的参数, 之后读出文件中的JSON, 添加记录后再写回JSON文件, 就是将上边了解过的东西组合起来.

这里创建一个app.js, 然后创建一个notes.js, app.js作为主程序, 而notes.js, 将所有的与操作notes文件相关的内容都放入其中. 先来看一个最简单的添加记录的方法.

const fs = require("fs");

//添加记录的函数
const addNote = function (title, body) {
    //加载文件转换而来的数组
    const notes = loadNotes();

    //向数组中添加一个对象
    notes.push({
        title: title,
        body: body
    });

    //然后进行保存
    saveNotes(notes);
};

//从文件中读出JSON并转换成对象的函数, 如果出现错误, 就返回一个空的数组
const loadNotes = function () {
    try {
        return JSON.parse(fs.readFileSync("notes.json").toString());
    } catch (e) {
        return [];
    }
};

//保存notes的工具函数
const saveNotes = function (notes) {
    fs.writeFileSync("notes.json", JSON.stringify(notes));
};


//导出的时候导出一个对象, addNote键对应的就是同名函数
module.exports = {
    addNote: addNote
};

然后来编写app.js, 其核心思想如下:

  1. 先利用yargs来为add命令添加两个参数 –title 和 –body
  2. 然后将add命令的执行命令设置为notes.js中的添加函数

这样就可以以命令行模式执行来添加title和body, 来编写一下看看, 其实之前也基本编写的差不多了:

const notes = require("./notes");
const yargs = require("yargs");

yargs.version("0.0.1");

//注册一个add命令, 带两个选项title和body
yargs.command({
    command: 'add',
    describe: 'Add a new note',
    builder: {
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        },
        body: {
            describe: 'Note body',
            demandOption: true,
            type: 'string'
        }
    },
    handler: function (argv) {
        notes.addNote(argv.title, argv.body)
    }
});

//启动yargs的解析功能, 这一行不要忘记
yargs.parse();

编写完之后, 用如下命令:

node app.js add --title="t" --body="fdsafsda"
node app.js add --title="bg3" --body="hurry"

就可以看到生成了notes.json文件, 内容如下:

[{"title":"t","body":"fdsafsda"},{"title":"bg3","body":"hurry"}]

这里其实还有一个判断重复添加的过程, 可以规定当title相同的时候, 就算是重复, 则可以修改一下程序如下:

const addNote = function (title, body) {
    const notes = loadNotes();

    //检测是否有相同的内容

    let isDuplicated = false;

    for (let eachNote of notes) {
        if (eachNote.title === title) {
            isDuplicated = true;
            console.log("Title: " + title + " is already taken.");
            return;
        }
    }

    notes.push({
        title: title,
        body: body
    });
    saveNotes(notes);

};

在每次添加的时候,遍历数组检查是否有相同的标题, 有就直接退出.

简单app – 查看和删除note功能

根据相同的做法,继续添加对应的指令和notes.js中的函数:

yargs.command({
    command: 'remove',
    describe: 'Remove a new note',
    builder: {
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        },
    },
    handler: function (argv) {
        notes.removeNote(argv.title);
    }
});


yargs.command({
    command: 'list',
    describe: 'Remove a new note',
    handler: function () {
        notes.listNote()
    }
});
const removeNote = function (title) {
    const notes = loadNotes();

    let counter = 0;

    const filteredNotes = notes.filter(x => {
        if (x.title === title) {
            counter = counter + 1;
            return false;
        } else {
            return true;
        }
    });
    if (counter === 0) {
        console.log("Title: " + title + "does not exists!");
    } else {
        saveNotes(filteredNotes);
        console.log("note removed");
    }
}

const listNote = function () {
    const notes = loadNotes();

    console.log(chalk.green('Notes List'));

    for (let eachNote of notes) {
        console.log(chalk.red(eachNote.title), '\t', chalk.blue(eachNote.body));
    }

};

这样一个最基础的app就做好了.

用箭头函数重构

ES6一大特点就是箭头函数, 这里就用箭头函数尽量将所有的内容重构. 新的notes.js如下:

const fs = require("fs");
const chalk = require("chalk");

const addNote = (title, body) => {
    const notes = loadNotes();

    let isDuplicated = false;

    for (let eachNote of notes) {
        if (eachNote.title === title) {
            isDuplicated = true;
            console.log("Title: " + title + " is already taken.");
            return;
        }
    }

    notes.push({
        title: title,
        body: body
    });
    saveNotes(notes);
    console.log("note added");
};

const removeNote = (title) => {
    const notes = loadNotes();

    let counter = 0;

    const filteredNotes = notes.filter(x => {
        if (x.title === title) {
            counter = counter + 1;
            return false;
        } else {
            return true;
        }
    });
    if (counter === 0) {
        console.log("Title: " + title + "does not exists!");
    } else {
        saveNotes(filteredNotes);
        console.log("note removed");
    }
}

const listNote = () => {
    const notes = loadNotes();

    console.log(chalk.green('Notes List'));

    for (let eachNote of notes) {
        console.log(chalk.red(eachNote.title), '\t', chalk.blue(eachNote.body));
    }

};


const loadNotes = () => {
    try {
        return JSON.parse(fs.readFileSync("notes.json").toString());
    } catch (e) {
        return [];
    }
};

const saveNotes = (notes) => {
    fs.writeFileSync("notes.json", JSON.stringify(notes));
};


module.exports = {
    addNote: addNote,
    removeNote: removeNote,
    listNote: listNote
};

新的app.js则使用ES6对象的新语法, 可以直接使用同名函数名称:

const notes = require("./notes");
const yargs = require("yargs");

yargs.version("0.0.1");

yargs.command({
    command: 'add',
    describe: 'Add a new note',
    builder: {
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        },
        body: {
            describe: 'Note body',
            demandOption: true,
            type: 'string'
        }
    },
    handler(argv) {
        notes.addNote(argv.title, argv.body)
    }
});

yargs.command({
    command: 'remove',
    describe: 'Remove a new note',
    builder: {
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        },
    },
    handler(argv) {
        notes.removeNote(argv.title);
    }
});

yargs.command({
    command: 'list',
    describe: 'Remove a new note',
    handler() {
        notes.listNote()
    }
});

yargs.parse();

查找功能

增删改查其实目前只有增删和列出功能, 改可以用先删后增来实现, 还有一个查找功能, 也很简单, 用title查找即可, 命令如下:

yargs.command({
    command: 'search',
    describe: 'Search a note by title',
    builder: {
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        },
    },
    handler(argv) {
        notes.search(argv.title)
    }
});

notes.js添加一个新函数及引出对象也添加上该函数:

const search = (title) => {
    const notes = loadNotes();

    const result = notes.filter(s => s.title === title);

    if (result.length === 0) {
        console.log("Cannot find title: " + title);
    } else {
        for (let eachNote of result) {
            console.log(chalk.red(eachNote.title), '\t', chalk.blue(eachNote.body));
        }
    }
};

module.exports = {
    addNote: addNote,
    removeNote: removeNote,
    listNote: listNote,
    search:search
};

Node.js 的 debug功能

在node中debug, 有若干形式:

  1. 用console.log在很多地方输出变量值, 来进行debug. 太低级
  2. Node.js 的inspect功能, 结合Chrome

Node.js 有一个inspect功能, 对于这个app, 使用如下:

node inspect app.js

之后得到如下显示:

< Debugger listening on
<  ws://127.0.0.1:9229/8c3f8ba7-388c-4c5a-b0bc-3cbd6d3b920f
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in app.js:1
> 1 const notes = require("./notes");
  2 const yargs = require("yargs");
  3
debug>

这其实是启动了Node.js的debugger, 然后会在本地端口进行等待, 可以通过浏览器调试, 打开Chrome, 在地址栏输入 chrome://inspect, 会看到一个页面:

Remote Target
#LOCALHOST
Target (v12.18.3)
trace
app.js
file:///D:/_Coding_notes-app-1_app.js

这说明本地端口有一个可以进行inspect的内容, 点击inspect就会打开一个debugger窗口, 然后就可以在其中看到代码.

由于Chrome是V8引擎, 因此是目前唯一支持Node.js debugger的浏览器.

在其中的sources中可以在行号前打断点. 然后点靠近右上部分的箭头可以执行并且跟踪变量.