Git Product home page Git Product logo

blog's People

Contributors

ryli avatar

Watchers

 avatar  avatar

blog's Issues

开发 node 命令行程序

开发 node 命令行程序

演示

介绍两个 node 写的程序,可以通过 npm i -g saikou-cli 安装来感受一下。

开始

首先进入开发目录,开始创建项目.

0x00 创建项目

# 创建目录
mkdir baka-cli

cd baka-cli

# 初始化项目
npm init -y

完成后会多一个 package.json 文件。

0x01 配置命令名称

修改 package.json 文件,添加 bin 属性,属性值为执行的 js 入口文件。

// 如果和包名相同且只有一个命令可以直接写路径字符串
{
  "bin": "./index.js",
}

// 对象形式可以自定义名称,也可以一次定义多个命令
{
  "bin": {
    "baka": "./baka.js",
    "senkun": "./senkun.js"
  },
}

0x02 其他配置

package.json 文件里的 descriptionversion 属性也是程序需要使用的。

0x03 开始编写脚本

创建 index.js 文件。
在文件的第一行添加以下代码,告诉命令行,找到本命令后用 node 来执行本文件。

#!/usr/bin/env node

然后再加入一些简单的代码。

// 第一个命令
main()

function main() {
  console.log('hello world!')
}

0x04 本地测试

之后就可以开始本地测试啦。

# 使用如下命令让系统识别我们的程序
npm link

# 测试
baka

# expected output: hello world!

看到输出 hello world!,最简单的命令就完成了。

image

0x05 完善程序

正常使用的命令都有一些支持的参数如 -h, -v 等,简单来实现一下。

// 获取信息
const { name, version, description } = require('./package.json')

const appName = name.replace('-cli', '')

// app 支持的参数列表
const app = {
  'h': showHelp,
  'v': showVersion,
}

main()

// 修改 main 方法
function main() {
  // 获取参数
  // 有效参数从第三个开始,前两个是 node 和 index.js
  const args = process.argv.slice(2)

  // 获取操作符,去掉 '-'
  const operation = args[0].substr(1)

  // 执行程序
  app[operation]()
}

function showHelp() {
  const help = `${name} ${description}

usage: ${appName} [options] [arguments]

options:
  -h:     show help
  -v:     show version
`
  console.log(help)
}

function showVersion() {
  console.log(version)
}

保存代码后可以测试一下。

image

0x06 添加功能

简单完成一个 todoList 功能。

创建 todo.json 文件来保存数据。

再次修改代码。

const fs = require('fs')
const path = require('path')
const { name, version, description } = require('./package.json')
const todoList = require('./todo.json')

const appName = name.replace('-cli', '')

const app = {
  'c': create,
  'd': done,
  'r': remove,
  's': reset,
  'l': listAll,
  'h': showHelp,
  'v': showVersion,
}

main()

function main() {
  // 有效参数从第三个开始
  const args = process.argv.slice(2)

  // 默认输出列表
  if (!args.length) {
    listAll()
    return
  }

  // 获取操作符,支持简写和单词
  const operation = args[0].length === 2 ? args[0].substr(1) : args[0].substr(2, 1)

  // 参数错误则展示帮助
  if (!args[0].startsWith('-') || !operation || !Object.keys(app).includes(operation)) {
    showHelp()
    return
  }

  app[operation](args[1])
}

function create(job) {
  const index = todoList.todo.indexOf(job)
  const doneIndex = todoList.done.indexOf(job)
  if (index === -1 && doneIndex === -1) todoList.todo.push(job)

  save()
}

function done(job) {
  let index = todoList.todo.indexOf(job)
  if (index > -1) todoList.todo.splice(index, 1)

  index = todoList.done.indexOf(job)
  if (index === -1) todoList.done.push(job)

  save()
}

function remove(job) {
  let index = todoList.todo.indexOf(job)
  if (index > -1) todoList.todo.splice(index, 1)

  index = todoList.done.indexOf(job)
  if (index > -1) todoList.done.splice(index, 1)

  save()
}

function reset() {
  todoList.todo = []
  todoList.done = []

  save()
}

function listAll() {
  const todo = todoList.todo.length ? '☐ ' + todoList.todo.join('\n  ☐ ') : ''
  const done = todoList.done.length ? '✔ ' + todoList.done.join('\n  ✔ ') : ''
  const info = `Todo:
----------
  ${todo}

Done:
----------
  ${done}
  `

  console.log(info)
}

function showHelp() {
  const help = `${name} ${description}

usage: ${appName} [options] [arguments]

options:
  -c, --create:   create job
  -d, --done:     mark that the job is done
  -r, --remove:   remove job
  -s, --reset:    reset the list
  -l, --list:     list all job
  -h, --help:     show help
  -v, --version:  show version
`
  console.log(help)
}

function showVersion() {
  console.log(version)
}

function save() {
  const file = path.resolve(__dirname, './todo.json')
  const data = JSON.stringify(todoList)
  fs.writeFile(file, data, 'utf8', (err) => {
    if (err) {
      console.error(err)
    } else {
      listAll()
    }
  })
}

看一下效果。

image

image

image

0x07 补全其他内容

建议添加一个 README.md 文件,来简单说明一下本程序。

touch README.md

echo '# baka-cli' > README.md

ox08 其他

可以通过以下 npm 包来轻松的用 node 开发命令行程序。

完。

一道聚合数据的面试题

有如下数据结构:

const source = [
    
  { id1: 1, label1: '1-1', id2: 1, label2: '2-1', id3: 1, label3: '3-1' },

  { id1: 1, label1: '1-1', id2: 2, label2: '2-2', id3: 2, label3: '3-2' },
  { id1: 1, label1: '1-1', id2: 2, label2: '2-2', id3: 3, label3: '3-3' },

  { id1: 2, label1: '1-2', id2: 1, label2: '2-1', id3: 1, label3: '3-1' },
  { id1: 2, label1: '1-2', id2: 1, label2: '2-1', id3: 2, label3: '3-2' },
  { id1: 2, label1: '1-2', id2: 1, label2: '2-1', id3: 3, label3: '3-3' },

  { id1: 2, label1: '1-2', id2: 2, label2: '2-2', id3: 1, label3: '3-1' },
  { id1: 2, label1: '1-2', id2: 2, label2: '2-2', id3: 2, label3: '3-2' },
  { id1: 2, label1: '1-2', id2: 2, label2: '2-2', id3: 3, label3: '3-3' },
  { id1: 2, label1: '1-2', id2: 2, label2: '2-2', id3: 4, label3: '3-4' },
  
]

要求得到:

[
    {
        "id": 1,
        "label": "1-1",
        "children": [
            {
                "id": 1,
                "label": "2-1",
                "children": [
                    {
                        "id": 1,
                        "label": "3-1"
                    }
                ]
            },
            {
                "id": 2,
                "label": "2-2",
                "children": [
                    {
                        "id": 2,
                        "label": "3-2"
                    },
                    {
                        "id": 3,
                        "label": "3-3"
                    }
                ]
            }
        ]
    },
    {
        "id": 2,
        "label": "1-2",
        "children": [
            {
                "id": 1,
                "label": "2-1",
                "children": [
                    {
                        "id": 1,
                        "label": "3-1"
                    },
                    {
                        "id": 2,
                        "label": "3-2"
                    },
                    {
                        "id": 3,
                        "label": "3-3"
                    }
                ]
            },
            {
                "id": 2,
                "label": "2-2",
                "children": [
                    {
                        "id": 1,
                        "label": "3-1"
                    },
                    {
                        "id": 2,
                        "label": "3-2"
                    },
                    {
                        "id": 3,
                        "label": "3-3"
                    },
                    {
                        "id": 4,
                        "label": "3-4"
                    }
                ]
            }
        ]
    }
]

要求把原始对象组成的数组,转换成层级结构的数组。比较典型的应用是【省-市-区】这样的数据,比如一份省市区的 Excel 文件,使用 node 的一些 csv 的包,简单处理就可以得到最初的数据结构。然后转换成前端渲染时需要的结构,即题目要求的形式。

下面我的解题方法:

// 思路是判断数据是否已存在,不存在则新建元素。需要注意的是,每次要返回已创建或者找到的父元素
function convert(source) {
  const target = []

  // 生成基本的数据结构
  const createNode = (id, label) => ({ id, label })

  // 创建 root 对象
  const createRoot = (node, list) => {
    const isExistList = list.filter(item => item.id === node.id)
    if (!isExistList[0]) list.push(node)
    // 返回创建或已存在的数据
    return isExistList[0] || node
  }

  // 插入数据到父节点
  const insertNode = (node, parent) => {
    if (!parent.children) parent.children = []
    return createRoot(node, parent.children)
  }

  // 开始处理
  source.map(row => {
    const keys = Object.keys(row)
    let root
    // 每次 +2,跳过 label 列
    for (let i = 0; i < keys.length; i += 2) {
      // 创建本次遍历的数据
      const node = createNode(row[keys[i]], row[keys[i + 1]])

      if (i === 0) {
        root = createRoot(node, target)
      } else {
        root = insertNode(node, root)
      }
    }
  })

  return target
}

// 测试方法
console.log(JSON.stringify(convert(source), ' ', 4))

另外还有 H 同学的解题方法:

function convert(source) {
  const output = []
  
  source.forEach(row => {
    const keys = Object.keys(row)
    // 记录节点在节点树上的完整路径
    const path = [0]

    for (let i = 0; i < keys.length;) {
      const id = row[keys[i++]]
      const label = row[keys[i++]]
      
      const targetIndex = id
      path.push(targetIndex)
      path.reduce((parentNode, node, index) => {
        const isCurrentLastNode = path.length - 1 === index
        if (isCurrentLastNode) {
          if (!parentNode[node]) parentNode[node] = [ label, []]
        } 
        return parentNode[node][1]
      }, [[null, output]])
    }
  })

   // [string, arr] => {id: index, label: string, children: arr}
  const result = output.reduce(function mapper(acc, v, i) {
    if (v) {
      const [label, children] = v
      acc.push({
        id: i,
        label,
        children: children.reduce(mapper, [])
      })
    } 
    return acc
  }, [])
  // print(output, 1)
}

js 继承与原型链

来自 MDN 的说明

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象( prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

const o = {}

// --> true
Object.getPrototypeOf(o) === Object.prototype
Object.getPrototypeOf(Object.prototype) === null

o.__proto__ === Object.getPrototypeOf(o)
o.__proto__ === Object.prototype
o.__proto__ === o.constructor.prototype

o.__proto__.__proto__ === null

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

基于原型链的继承

JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

遵循ECMAScript标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__
但它不应该与构造函数 func 的 prototype 属性相混淆。被构造函数创建的实例对象的 [[prototype]] 指向 func 的 prototype 属性。Object.prototype 属性表示 Object 的原型对象。

使用语法结构创建的对象

// o 这个对象继承了 Object.prototype 上面的所有属性
// 原型链如下:
// o ---> Object.prototype ---> null
var o = {a: 1};

// a ---> Array.prototype ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];

// f ---> Function.prototype ---> Object.prototype ---> null

function f(){
  return 1;
}

使用构造器创建的对象

在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。

new 关键字有什么作用?

new关键字与构造函数一起使用来创建对象。

下面看看例子:

function Employee(name, position, yearHired) {
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

new关键字做了4件事:

  1. 创建空对象 {}
  2. 将空对象分配给 this 值
  3. 将空对象的__proto__指向构造函数的prototype
  4. 如果没有使用显式return语句,则返回this

根据上面描述的,它将首先创建一个空对象{},然后它将this值赋给这个空对象this={},并向这个对象添加属性。因为我们没有显式的return语句,所以它会自动为我们返回this。

使用 Object.create 创建的对象

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

使用 class 关键字创建的对象

class 只是一个语法糖,JavaScript 仍然基于原型。

class Foo {
  constructor(name) {
     this.name = name
  }
}

// 当执行
var o = new Foo();

// 实际上执行的是(或者类似这样):
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);

// 当执行 
o.someProp
// 它会检查 o 是否具有 someProp 属性。如果没有,它会查找 Object.getPrototypeOf(o).someProp
// 如果仍旧没有,它会继续查找 Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp

hasOwnProperty 是 JavaScript 中一个处理属性并且不会遍历原型链的方法。(另一种这样的方法:Object.keys())

prototype 和 Object.getPrototypeOf

function A 有一个叫做 prototype 的特殊属性。该特殊属性可与 JavaScript 的 new 操作符一起使用。对原型对象的引用被复制到新实例的内部 [[Prototype]] 属性。例如,当执行 var a1 = new A(); 时,JavaScript(在内存中创建对象之后,和在运行函数 A() 把 this 指向对象之前)设置 a1.[[Prototype]] = A.prototype;

执行 a1.doSomething() 相当于执行 Object.getPrototypeOf(a1).doSomething.call(a1)

简而言之, prototype 是用于类的,而 Object.getPrototypeOf() 是用于实例的(instances),两者功能一致。

继承示例

function A(a){
  this.varA = a;
}

// 重点:类型被定义在 .prototype 中
A.prototype = {
  // A.prototype.varA 总是会被 this.varA 遮蔽,如果varA并不是在每个实例中都被初始化,那这样做将是有效果的
  varA : null,
  doSomething : function(){
    // ...
  }
}

function B(a, b){
  A.call(this, a);
  this.varB = b;
}
// 重点:用 Object.create() 来继承
B.prototype = Object.create(A.prototype, {
  varB : {
    value: null, 
    enumerable: true, 
    configurable: true, 
    writable: true 
  },
  doSomething : { 
    value: function(){ // override
      A.prototype.doSomething.apply(this, arguments); 
      // call super
      // ...
    },
    enumerable: true,
    configurable: true, 
    writable: true
  }
});
B.prototype.constructor = B;

var b = new B();
b.doSomething();

prototype 和 Object.getPrototypeOf

prototype 是用于类的,而 Object.getPrototypeOf() 是用于实例的(instances),两者功能一致.

Object.getPrototypeOf(fn) === fn.__proto__ ===  Function.prototype

// true
Object.prototype.__proto__ === null
Object.constructor.__proto__ === Object.constructor.prototype
Object.__proto__ === Object.constructor.prototype
({}).__proto__ === Object.prototype
Array.__proto__ === Object.constructor.prototype
Array.prototype.__proto__ === Object.prototype
Function.__proto__ === Object.constructor.prototype

其他

Array.prototype本身就是一个数组,并且它的长度为0

console.log(Array.prototype)
// [constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]

Array.prototype.length === 0

引用

mdn
70个javaScript面试题集锦

调试

编程有三等境界 (狼叔):

  1. 测试驱动开发,在写代码前先写测试
  2. 是断点调试,可以直观的跟踪代码执行逻辑、调用栈
  3. 打日志,简单略显低级

本文准备介绍 VSCode 和 Node 的调试方式。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.