首次学习 Node.js,需要看着代码想问题,善用社区和文档,使用 CRM 学习法~
前置条件,比较少:
VSCode
Node.js
Node.js 文档 https://nodejs.org/zh-cn/
JS 相关知识不是必需的·
运行 node.js
不要双击 Node.js 快捷方式
如何运行 Node.js:
在cmd 里运行 node
按 ctrl + D 退出
node 接绝对路径 在一个目录下新建 1.js 文件并用 vscode 打开它
然后回到命令行用绝对路径去执行它
node 接相对路径 直接在目录下执行 1.js 文件
不接文件名一样可以执行
node -e 接字符串 不用文件直接执行代码
QWQ 有BUG,搜不到的那种
创建 todo 的实现过程 最终效果:
node todo add 任务内容
node todo list
node todo done 1
node todo delete 1
node todo edit 2
首先创建一个 todo.js
然后编辑 todo.js
:
1 2 console .log('欢迎使用 todoList 小程序' )
现在想实现 node todo add 做菜
这样的命令,但是不知道怎么做,怎么办呢?
打开 google
,搜索 node get command line
自动补全 node get command line args
:
可以在万能的 stackoverflow 上找到相关问题并得出答案:
![image-20200620130254157](/Users/xu/Library/Application Support/typora-user-images/image-20200620130254157.png)
现在我会 node 相关知识嘛?不会,我会实现这个功能吗?我会了:
1 2 3 4 5 6 7 8 console .log('欢迎使用 todoList 小程序' )console .log(process.argv)
然后可以这么操作:
1 2 3 4 5 6 7 const nodePath = process.argv[0 ]const todoPath = process.argv[1 ]const verb = process.argv[2 ]const content = process.argv[3 ]console .log('你想做的操作是' + verb)console .log('你想操作的内容是' + content)
再然后创建一个任务列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const list = []console .log(process.argv)const nodePath = process.argv[0 ]const todoPath = process.argv[1 ]const verb = process.argv[2 ]const content = process.argv[3 ]console .log('你想做的操作是' + verb)console .log('你想操作的内容是' + content)const task = contentlist.push(task) console .table(list)
好了,现在遇到了第一个问题:
数据并没有被保存,因为 const list = []
每次都把任务清空了。
那就把任务存在文件里好了,但是我还不会用 node 创建文件,很简单,搜索 nodejs create file
从谷歌继续抄:
先来抄第一行:
不理解啥意思对不对?看官网:
好了现在知道这是个文件系统,但是对我来说英文好麻烦:
这个就很棒了对不对?
这两个 api 都不是操作文档的,那就搜 write
:
这里面有个 fs.writeFileSync(file, data[, options])
异步写入文件,看起很棒对不对?点开它:
好了,继续工程:
1 2 3 4 var fs = require ('fs' )fs.writeFileSync('./db' , '到此一游' )
好了,现在会使用这个 API 了。
新的问题:const list = []
会覆盖 db 文件
1 2 3 4 const task = contentlist.push(task) fs.writeFileSync('./db' , list.toString())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var fs = require ('fs' )const list = fs.readFileSync('./db' ).toString()console .log('文件内容' + list)const nodePath = process.argv[0 ]const todoPath = process.argv[1 ]const verb = process.argv[2 ]const content = process.argv[3 ]const task = contentlist.push(task) console .table(list)
以字符串形式存取数据,就没法进行数据操作了对吧,也就是说存数据的时候就错了:
1 2 3 4 5 6 7 const task = contentlist.push(task) fs.writeFileSync('./db' , list.toString()) console .log(list)
怎么解决这个问题呢?使用一个简单的 API,JSON 序列化:
1 fs.writeFileSync('./db' , JSON .stringify(list))
将数组以 JSON 格式存储在数据库中:
1 2 const list = fs.readFileSync('./db' ).toString()console .log('文件内容:' + list)
使用反序列化将拿到的数据再转化一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var fs = require ('fs' )const fileContent = fs.readFileSync('./db' ).toString()const list = JSON .parse(fileContent)console .log('文件内容:' + list)const nodePath = process.argv[0 ]const todoPath = process.argv[1 ]const verb = process.argv[2 ]const content = process.argv[3 ]const task = contentlist.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list)
现在可以烧两桶水了!
笨代码实现过程 node todo add 任务 从之前的研究可以知道,序列化方法原理很简单:
1 2 3 JSON .stringfy = function (array ) { return '[' + array.toString().split(',' ).map(string => `'${string} '` ).join() + ']' }
存的时候序列化,取得时候反序列化。
新的问题:db 文件如果被删掉那就没法读取了!需要添加一个判断,怎么判断呢?依然是谷歌搜索:
别人怎么写你就怎么写,管他为什么:
1 2 3 4 5 if (path.existsSync('./db' )) { } else { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var fs = require ('fs' )var path = require ('path' )const verb = process.argv[2 ]const content = process.argv[3 ]if (path.existsSync('./db' )) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const task = content list.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) } else { const fileContent = fs.writeFileSync('./db' , '' ) const list = [] const task = content list.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) }
然后就获得了一个有点蠢的代码,但是我就是不优化:
为啥报错了呢?
被弃用了你不早说?那换一个 API 就好了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var fs = require ('fs' )var path = require ('path' )const verb = process.argv[2 ]const content = process.argv[3 ]if (fs.existsSync('./db' )) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const task = content list.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) } else { const fileContent = fs.writeFileSync('./db' , '' ) const list = [] const task = content list.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) }
测试一下:
好了,花了这么久,node todo add 任务
功能就实现了!
node todo list 想列出所有任务,该怎么实现呢?先把 verb
给判断一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if (verb === 'add' ) { if (fs.existsSync('./db' )) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const task = content list.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) } else { const fileContent = fs.writeFileSync('./db' , '' ) const list = [] const task = content list.push(task) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) } } else if ...
然后再来实现 todo list
:
1 2 3 4 5 6 7 8 } else if (verb === 'list' ) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) console .log(list) } else { console .log('你的动作是:' + verb) console .log('不支持此动作' ) }
但是有个 BUG,很明显无法操作空文件,后面优化的时候一起解决。
node todo delete 1 我想从数组里删除匹配的字段,咋整?搜索谷歌:
九种方法你敢信?找一个看起来舒服的:
1 2 3 4 5 6 7 } else if (verb === 'delete' ) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const n = content list.splice(n-1 , 1 ) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list)
node todo done 1 获取指定字符串再修改 好麻烦啊,来给每个任务接一个 bool
标识任务代表它没完成:
1 list.push([task, false ])
再来改 done
命令;
1 2 3 4 5 6 7 } else if (verb === 'done' ) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const n = content list[n-1 ][1 ] = true fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list)
node todo edit 1 根据之前的经验很快写出 edit
:
1 2 3 4 5 } else if(verb === 'edit') { const fileContent = fs.readFileSync('./db').toString() const list = JSON.parse(fileContent) const n = content list[n-1][0] = '???'
但是后面的编辑呢?很简单:
1 2 3 const edit = process.argv[4 ]... list[n-1 ][0 ] = edit
1 2 3 4 5 6 7 } else if (verb === 'edit' ) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const n = content list[n-1 ][0 ] = edit fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list)
代码极致优化 功能都实现完毕了,但是存在两大问题:
先使用 switch
优化 if else
语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 var fs = require ('fs' )var path = require ('path' )const verb = process.argv[2 ]const content = process.argv[3 ]const edit = process.argv[4 ]switch (verb) { case 'add' : if (fs.existsSync('./db' )) { const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const task = content list.push([task, false ]) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) } else { const fileContent = fs.writeFileSync('./db' , '' ) const list = [] const task = content list.push([task, false ]) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) } break ; case 'list' : const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) console .log(list) break ; case 'done' : const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const n = content list[n-1 ][1 ] = true fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) break ; case 'delete' : const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const n = content list.splice(n-1 , 1 ) fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) break ; case 'edit' : const fileContent = fs.readFileSync('./db' ).toString() const list = JSON .parse(fileContent) const n = content list[n-1 ][0 ] = edit fs.writeFileSync('./db' , JSON .stringify(list)) console .log(list) break ; default : console .log('你的动作是:' + verb) console .log('不支持此动作' ) break ; }
以上是BUG的代码形式,解决重复:
vscode 快捷键将 ‘./db’ 替换 dbPath:
1 const dbPath = '/Users/xu/Code/back-end/node/node-demo-todoList/db'
函数抽取公共方法:
1 2 3 4 5 6 7 8 9 function save (list ) { fs.writeFileSync(dbPath, JSON .stringify(list)) } function fetch ( ) { const fileContent = fs.readFileSync(dbPath).toString() const list = JSON .parse(fileContent) return list }
不写多与代码:
1 list.push([content, false ])
可以让代码更语义化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 var fs = require ('fs' )const verb = process.argv[2 ]const content = process.argv[3 ]const edit = process.argv[4 ]const dbPath = '/Users/xu/Code/back-end/node/node-demo-todoList/db' function save (list ) { fs.writeFileSync(dbPath, JSON .stringify(list)) } function fetch ( ) { const fileContent = fs.readFileSync(dbPath).toString() const list = JSON .parse(fileContent) return list } function display (list ) { console .log(list) } function addTask (list, content ) { list.push([content, false ]) } function removeTask (list, index ) { list.splice(index-1 , 1 ) } function markTaskAsDone (list, index ) { list[index-1 ][1 ] = true } function editTask (list, index, newContent ) { list[index-1 ][0 ] = newContent } switch (verb) { case 'add' : if (fs.existsSync(dbPath)) { const list = fetch() addTask(list, content) save(list) display(list) } else { const list = fetch() addTask(list, content) save(list) display(list) } break ; case 'list' : const list = fetch() display(list) break ; case 'done' : const list = fetch() const n = content list[n-1 ][1 ] = true save(list) display(list) break ; case 'delete' : const list = fetch() removeTask(list, content) save(list) display(list) break ; case 'edit' : const list = fetch() editTask(list, content, edit) save(list) display(list) break ; default : console .log('你的动作是:' + verb) console .log('不支持此动作' ) break ; }
更好地改代码,得从更高的纬度去看:
每一个方法都有存取文件的组成,而 add
方法太复杂了,因为它处理了一个异常情况,需要确保不存在异常情况,怎么解决呢?
在外部解决问题: fs.statSync(dbPath)
:
1 2 3 4 5 try { fs.statSync(dbPath) }catch (error){ fs.writeFileSync(dbPath, '' ) }
通过观察是否报错来做判断,确保在方法调用前,db 文件必在:
1 2 3 4 5 6 case 'add' : list = fetch() addTask(list, content) save(list) display(list) break ;
改 BUG 要在 fetch()
中对 list 变量 做一个判断,判断是否存在,并给一个空数组兜底。
代码韩国行 整容之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 var fs = require ('fs' )const verb = process.argv[2 ]const content = process.argv[3 ]const edit = process.argv[4 ]const dbPath = '/Users/xu/Code/back-end/node/node-demo-todoList/db' function save (list ) { fs.writeFileSync(dbPath, JSON .stringify(list)) } function fetch ( ) { const fileContent = fs.readFileSync(dbPath).toString() const list = JSON .parse(fileContent) || [] return list } function display (list ) { console .log(list) } function addTask (list, content ) { list.push([content, false ]) } function removeTask (list, index ) { list.splice(index-1 , 1 ) } function markTaskAsDone (list, index ) { list[index-1 ][1 ] = true } function editTask (list, index, newContent ) { list[index-1 ][0 ] = newContent } try { fs.statSync(dbPath) }catch (error){ fs.writeFileSync(dbPath, '' ) } const list = fetch()switch (verb) { case 'add' : addTask(list, content) save(list) display(list) break ; case 'list' : display(list) break ; case 'done' : markTaskAsDone(list, content) save(list) display(list) break ; case 'delete' : removeTask(list, content) save(list) display(list) break ; case 'edit' : editTask(list, content, edit) save(list) display(list) break ; default : console .log('你的动作是:' + verb) console .log('不支持此动作' ) break ; }
看到 save() display()
还是在重复,继续整容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 var fs = require ('fs' )const verb = process.argv[2 ]const content = process.argv[3 ]const edit = process.argv[4 ]const dbPath = '/Users/xu/Code/back-end/node/node-demo-todoList/db' ensureDB() const list = fetch()switch (verb) { case 'add' : addTask(list, content) break ; case 'list' : display(list) break ; case 'done' : markTaskAsDone(list, content) break ; case 'delete' : removeTask(list, content) break ; case 'edit' : editTask(list, content, edit) break ; default : console .log('你的动作是:' + verb) console .log('不支持此动作' ) break ; } display(list) if (verb !== 'list' ) { save(list) } function ensureDB ( ) { try { fs.statSync(dbPath) }catch (error){ fs.writeFileSync(dbPath, '' ) } } function save (list ) { fs.writeFileSync(dbPath, JSON .stringify(list)) } function fetch ( ) { const fileContent = fs.readFileSync(dbPath).toString() let list try { list = JSON .parse(fileContent) || [] }catch (error){ list = [] } return list } function display (list ) { console .log(list) } function addTask (list, content ) { list.push([content, false ]) } function removeTask (list, index ) { list.splice(index-1 , 1 ) } function markTaskAsDone (list, index ) { list[index-1 ][1 ] = true } function editTask (list, index, newContent ) { list[index-1 ][0 ] = newContent }
优化用户体验 使用系统变量优化代码:
1 const dbPath = __dirname + '/db'
但是这样平台兼容性不好,使用一个函数:
1 const dbPath = path.join(__dirname, 'db' )
修改一下 display()
优化体验:
1 2 3 4 5 6 7 function display (list ) { for (let i=0 ; i<list.length; i++) { const flag = list[i][1 ] === true ? '[√]' : '[.]' console .log('任务内容:' + flag + ' ' + list[i][0 ]) } }
在全局使用这个脚本文件:
1 2 3 4 5 vi ~/.bashrc alias td='node /Users/xu/Code/back-end/node/node-demo-todoList/todo.js' source ~/.bashrc
总结 虽然很短很简单,但是从这篇文章学到了:
如何获取命令行参数
如何创建文件
如何改写文件
如何使用 JSON 序列化
如何使用循环或 map
重点 Node.js 不熟悉怎么办?
真人求助
文档查阅
Google搜索
copy-run-fmodify(CRM 学习大法)
console.log() 调试
不管什么语言,都可以用 log 去调试
目的
Node.js 并不可怕
了解命令行
命令行很有趣
结论 如何学习 Node.js
做一个小功能
使用 Node.js 实现它
不会就问
完成并优化思考
再想一个小功能
系统学习 Node.js
通读官方文档
了解 Node.js Web 框架
学号 JS / TS