小磊

小磊 查看完整档案

北京编辑北方工业大学  |  计算机 编辑  |  填写所在公司/组织填写个人主网站
编辑

以一颗更加开放,更加多元,更加包容的心走进别人的世界

个人动态

小磊 关注了专栏 · 4月12日

前端开发那些事儿

前端知识:HTML、CSS、JS、React,nodejs、Chrome、数据结构与算法,计算机网络等精华知识分享交流。

关注 6686

小磊 关注了用户 · 4月12日

阿宝哥 @angular4

http://www.semlinker.com/
聚焦全栈,专注分享 Angular、TypeScript、Node.js/Java 、Spring 技术栈等全栈干货

欢迎各位小伙伴关注本人公众号全栈修仙之路

关注 2407

小磊 关注了标签 · 4月12日

node.js

图片描述
Node 是一个 Javascript 运行环境(runtime)。实际上它是对 Google V8 引擎(应用于 Google Chrome 浏览器)进行了封装。V8 引擎执行 Javascript 的速度非常快,性能非常好。Node 对一些特殊用例进行了优化,提供了替代的 API,使得 V8 在非浏览器环境下运行得更好。例如,在服务器环境中,处理二进制数据通常是必不可少的,但 Javascript 对此支持不足,因此,V8.Node 增加了 Buffer 类,方便并且高效地 处理二进制数据。因此,Node 不仅仅简单的使用了 V8,还对其进行了优化,使其在各环境下更加给力。

关注 82071

小磊 发布了文章 · 3月31日

Koa1技术分享

写在前面

  Koa使用了ES6规范的generator和异步编程是一个更轻量级Web开发的框架,Koa 的先天优势在于 generator。由于是我个人的分享交流,所以Node基础、ES6标准、Web开发基础以及Koa的"Hello World"程序都不在讨论,希望各位小伙伴提出意见和指导。
  PS:Koa 内核中没有捆绑任何中间件,但不用担心,Koa 拥有极其强悍的拓展性,正文所有中间件都可以在npm官网下载安装,但国内域名安装会有一些限制,提供一个国内镜像安装方法,速度非常快,在直接npm模块失败的时候非常好用,使用npm --registry=http://registry.npmjs.org install XXXXX –XX 命令安装,只需要在install后面加上要安装的中间件名称和相应的参数即可。

一、使用Koa搭建Web项目流程

1、Koa项目创建
  个人认为不管任何框架,Web项目搭建必需的几个方面,页面、中间件、路由、会话和存储、日志、静态文件指定,以及错误的处理。当然,网站开发不止这些东西,还有许多主题,比如实时通讯,搜索引擎架构,权限控制,邮件优先队列,日志记录分析,对Web开发还刚刚入门属于菜鸟级别,这里就不做深入的讨论了。了解Express框架的小伙伴一定知道Express的部署过程,不管是通过express-generator生成还是WebStorm等编译器直接创建,它的目录结构大概是这样的:

|——app.js
|——bin
|——node_modules
|——package.json
|——public
|——routes
|——views

  *app.js,是程序启动文件
  *bin,存放执行程序
  *node_modules,存放项目依赖库
  *package.json,是配置和一些相关信息
  *public,存放静态文件(css,js,img)
  *routes,存放路由文件
  *views,存放前台页面文件
  这些结构基本包含了上述提到的Web项目搭建的要素,但是目前类似express-generator的Koa部署工具Koa-generator(非官方)并不完善并且个人测试存在些许错误。其实Koa-generator也是仿造上述express-generator生成的目录,既然这样还不如手动创建目录来的爽快(generator-k是另一款生成器,用上去感觉还行),在根目录新建app.js作为程序的启动文件,创建三个文件夹分别命名public、routes和views,最后新建package.json文件存放你的项目的一些信息。完成这些创建之后,用npm命令安装Koa,这样的话一个基本的Koa框架就搭建好了,非常的的轻量级,它的目录结构如下:

    |——app.js
    |——node_modules
    |——public
    |    |——img
    |    |——css
    |    |——js
    |
    |——routes
    |    |——index.js
    |    |——user.Js
    |
    |——views
    |    |——_layout.html
    |    |——index.html
    |
    |——package.json
    Koa项目运行:node --harmony app.js
    必须加 --harmony ,这样才会支持 ES6 语法。

2、Koa日志
  日志是项目error调试和日常维护的基本手段,Koa有日志模块Koa-logger,npm install Koa-logger后使用app.use(logger());命令程序就会在控制台自动打印日志,当然如果你对Koa-logger的风格不满意或者想要看到更多得信息也可以自己编辑代码实现有自己风格的日志打印。
例如:

    auto map route -> [get]/authority/saveAddUser/
    auto map route -> [get]/authority/searchUserInfo/
    auto map route -> [get]/authority/updateUser/
    auto map route -> [get]/authority/deletedUser/
    auto map route -> [get]/authority/getSelectValues/
    auto map route -> [get]/authority/saveAuthority/

  最后呢,如果有需要,要把日志进行存储。
3、Koa的错误处理
  Koa 有 error 事件,当发生错误时,可以通过该事件,对错误进行统一的处理。

var Koa = require('koa');
var app = Koa();
app.on('error', function(err,ctx){
    console.log(err);
});   
app.listen(3000);

  上面这段代码在如果捕获到错误,页面会打印出 “Internal Server Error” (这是Koa对错误的默认处理)。这个错误我们在综合监控系统中也经常见到,那么我们显然无法根据这条日志得到什么信息

TypeError: Cannot read property 'split' of undefined
at Object.Home.index (d:\test\route\home.js:143:31)
at GeneratorFunctionPrototype.next (native)
at Object.dispatch (d:\test\node_modules\koa-router\lib\router.js:97:44)
at GeneratorFunctionPrototype.next (native)

  这些错误信息是怎么报出来的的呢,其实是Koa-onerror 中间件,它优化错误信息,根据这些错误信息就能更好的捕获到错误。
Koa-onerror使用方法:

    var onerror = require('Koa-onerror');
    onerror(app);

4、Koa静态文件指定
  Koa静态文件指定中间件Koa-static,npm install Koa-static之后就可以使用Koa-static负责托管 Koa 应用内的静态资源。映射了静态文件目录,引用的时候直接去该目录下寻找资源,会减少一些消耗。(不知道讲的准确不准确,只是个人的理解)指定public为静态文件目录的代码如下:

    var staticServer = require('koa-static');
    var path = require('path');
    app.use(staticServer(path.join(__dirname,'public')));

5、ejs模板的使用
  渲染页面需要一种模板,这里选择风格接近html的ejs模板。npm install Koa-ejs后就可以在Koa框架中使用ejs模版。

    var render = require('koa-ejs');
    render(app, {
        root: path.join(__dirname, 'views'),
        layout: '__layout',
        viewExt: 'html',
        cache: false,
        debug: true
    });
    app.use(function *(){
        yield this.render('index',{layout:false});
    });

6、Koa路由设置
  Koa个极简的web框架,简单到连路由模块都没有配备。自己手写路由是这样的:

    app.use(function *(){
        //我是首页
        if(this.path==='/'){
        }
    });

  使用更加强大的路由中间件,Koa中设置路由一般安装Koa-router,Koa-router支持五种方法

    router.get()
    router.post()
    router.put()
    router.del()
    router.patch()

  GET方法举例:

    var app = require('koa')();
    var Router = require('koa-router');
    var myRouter = new Router();
    myRouter.get('/', function *(next) {
      yield this.render('index',{layout:false});
    });
    app.use(myRouter.routes());
    app.listen(3000);

  Koa-router 拥有丰富的 api 细节,用好这些 api ,可以让页面代码更为优雅与可维护。
接收query参数

    http://localhost:3000/?a=1(条件)
    index.js
    var router = require('koa-router')();
          router
          .get('/',function *(next){
          console.log(this.query);
          yield this.render('index',{layout:false});
      })
          .get('/home',function *(ctx,next){
          ctx.render('home');
      });
      //ctx为Koa2.0中支持
      ... ...
      module.exports = router;
      控制台打印:
      <-- GET /?a=1
      { a: '1' }
      { a: '1' }
      接收params参数 
      http://localhost:3000/users/123(参数)
      router.get('/user/:id', function *(next) {
        console.log(this.params.id);
      });

  param() 用于封装参数处理中间件,当访问 /detail/:id 路由时,会先执行 param() 定义的 generator function 逻辑。函数的第一个是路由参数的值,next 是中间件流程关键标识变量。
yield next;
  表示执行下一个中间件。

      app.param('id',function *(id,next){
          this.id = Number(id);
          if ( typeof this.id != 'number') return this.status = 404;
          yield next;
      }).get('/detail/:id', function *(next) {
          //我是详情页面
          var id = this.id; //123
          this.body = id;
      });

7、Koa中间件
  Koa的中间件很像Express的中间件,也是对HTTP请求进行处理的函数,但是必须是一个Generator函数即 function *(){} 语法,不然会报错。可以这么说,Nodejs的Web程序中任何请求和响应都是中间件在操作。

      app
      .use(logger())               //日志中间件
      .use(serve(__dirname + '/public'))        //静态文件指定中间件
      .use(router.routes())          //路由中间件
      .use(router.allowedMethods());             //路由中间件

  app.use 加载用于处理http请求的middleware(中间件),当一个请求来的时候,会依次被这些 middlewares处理。执行的顺序是你定义的顺序。中间件的执行顺序规则是类似“栈”的结构,所有需要执行的中间件都被一个一个放入“栈”中,当没有遇到next()的时候,“栈”里边的这些中间件被逆序执行。

      app.use(function *(next){
        this; // is the Context
        this.request; // is a Koa Request
        this.response; // is a Koa Response
      });

说明:
  •this是上下文
  •*代表es6里的generator
  http模型里的请求和响应
  •this.request
  •this.response
  app.use() 究竟发生了什么不可思议的化学反应呢?
其实 app.use() 就干了一件事,就是将中间件放入一个数组,真正执行逻辑的是:app.listen(3000);
Koa 的 listen() 除了指定了 http 服务的端口号外,还会启动 http server,等价于:

     var http = require('http');
      http.createServer(app.callback()).listen(3000);

  后面这种繁琐的形式有什么用呢?
  一个典型的场景是启动 https 服务,默认 app.listen(); 是启动 http 服务,启动 https 服务就需要:

      var https = require('https');
      https.createServer(app.callback()).listen(3000);

二、异步编程

1、异步流程控制
  异步编程对 JavaScript 语言太重要。JavaScript 只有一根线程,如果没有异步编程,根本没法用,非卡死不可。
  以前,异步编程的方法,大概有下面四种。
  回调函数
  事件监听
  发布/订阅
  Promise 对象
  JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。它的英语名字 callback,直译过来就是"重新调用"。
读取文件进行处理,是这样写的。

    fs.readFile('/etc/passwd', function (err, data) {
        if (err) throw err;
        console.log(data);
      });

  上面代码中,readFile 函数的第二个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了 /etc/passwd 这个文件以后,回调函数才会执行。回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。

      fs.readFile(fileA, function (err, data) {
        fs.readFile(fileB, function (err, data) {
          // ...
        });
      });

  不难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就称为"回调函数噩梦"(callback hell)。Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。采用Promise,连续读取多个文件,写法如下。

      var readFile = require('fs-readfile-promise');
      readFile(fileA)
      .then(function(data){
        console.log(data.toString());
      })
      .then(function(){
        return readFile(fileB);
      })
      .then(function(data){
        console.log(data.toString());
      })
      .catch(function(err) {
        console.log(err);
      });

  上面代码中,我使用了 fs-readfile-promise 模块,它的作用就是返回一个 Promise 版本的 readFile 函数。Promise 提供 then 方法加载回调函数,catch方法捕捉执行过程中抛出的错误。可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
  Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。
  那么,有没有更好的写法呢?
  ECMAScript 6 (简称 ES6 )作为下一代 JavaScript 语言,将 JavaScript 异步编程带入了一个全新的阶段。异步编程的语法目标,就是怎样让它更像同步编程。
  Koa 的先天优势在于 generator。
  generator指的是

      function* xxx(){
      }

  是es6里的写法。

      var r = 3;  
      function* infinite_ap(a) {
          for( var i = 0; i < 3 ; i++) {
              a = a + r ;
              yield a;
          }
      }
      var sum = infinite_ap(5);
      console.log(sum.next()); // returns { value : 8, done : false }
      console.log(sum.next()); // returns { value : 11, done: false }
      console.log(sum.next()); // returns { value : 14, done: false }
      console.log(sum.next()); //return { value: undefined, done: true }

  yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined。当第一次调用 sum.next() 时 返回的a变量值是5 + 3,同理第二次调用 sum.next() ,a变量值是8 +3,知道循环执行结束,返回done:true标识。大家有没有发现个问题,Koa 中 generator 的用法与上述 demo 演示的用法有非常大得差异,那是因为 Koa 中的 generator 使用了 co 进行了封装。
2、co的使用
  Ps:(这里只是简单介绍,后续可以作为一个专题来讲)
  co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
  比如,有一个 Generator 函数,用于依次读取两个文件。

      var gen = function* (){
        var f1 = yield readFile('/etc/fstab');
        var f2 = yield readFile('/etc/shells');
        console.log(f1.toString());
        console.log(f2.toString());
      };

  co 函数库可以让你不用编写 Generator 函数的执行器。

      var co = require('co');
      co(gen);

  上面代码中,Generator 函数只要传入 co 函数,就会自动执行。
  co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。

      co(gen).then(function (){
        console.log('Generator 函数执行完成');
      })

  上面代码中,等到 Generator 函数执行结束,就会输出一行提示。
  为什么 co 可以自动执行 Generator 函数?
  前面文章说过,Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
  两种方法可以做到这一点。
  (1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
  (2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。
  co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。
  参考:http://www.ruanyifeng.com/blog/2015/05/co.html
3、Koa 中间件机制实现原理
使用 Koa 的同学一定会有如下疑问:

  1. Koa 的中间件机制是如何实现?
  2. 为什么中间件必须是 generator function?
  3. next 实参指向是什么?为什么可以通过 yield next 可以执行下一个中间件?
  4. 为什么中间件从上到下执行完后,可以从下到上执行 yield next 后的逻辑?

  通过实现简单的 Koa 框架(剥离除中间件外所有的逻辑)来解答上述问题,这个框架的名字叫 SimpleKoa:

      var co = require('co');
      function SimpleKoa(){
          this.middlewares = [];
      }
      SimpleKoa.prototype = {
          //注入个中间件
          use: function(gf){
              this.middlewares.push(gf);
          },
          //执行中间件
          listen: function(){
              this._run();
          },
          _run: function(){
              var ctx = this;
              var middlewares = ctx.middlewares;
              return co(function *(){
                  var prev = null;
                  var i = middlewares.length;
                  //从最后一个中间件到第一个中间件的顺序开始遍历
                  while (i--) {
                   //实际Koa的ctx应该指向server的上下文,这里做了简化
                  //prev 将前面一个中间件传递给当前中间件
                      prev = middlewares[i].call(ctx, prev);
                  }
                //执行第一个中间件
                  yield prev;
              })();
          }
      };

  写个 demo 印证下中间件执行顺序:

      var app = new SimpleKoa();
      app.use(function *(next){
          this.body = '1';
          yield next;
          this.body += '5';
          console.log(this.body);
      });
      app.use(function *(next){
          this.body += '2';
          yield next;
          this.body += '4';
      });
      app.use(function *(next){
          this.body += '3';
          });
      app.listen();

  执行后控制台输出:123456,对照 Koa 中间件执行顺序,完全一致!寥寥几行代码,我们就实现了 Koa 的中间件机制!这就是 co 的魔力。

三、Koa中涉及但本次没有讲的问题

1、Koa中的cookie和session(后续详细讲解)
  web应用程序都离不开cookie和session的使用,是因为Http是一种无状态性的协议。保存用户状态信息的一种方法或手段,Session 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。
2、Koa中nosql(后续技术分享会详细讲解)
  mongodb是一个基于文档的非关系型数据库,所有数据是从磁盘上进行读写的,其优势在于查询功能比较强大,能存储海量数据。
  redis是内存型数据库,数据保存在内存中,通过tcp直接存取,优势是速度快,并发高,缺点是数据类型有限,查询功能不强,一般用作缓存。它由C语言实现的,与 NodeJS工作原理近似,同样以单线程异步的方式工作,先读写内存再异步同步到磁盘,读写速度上比MongoDB有巨大的提升,当并发达到一定程度时,即可考虑使用Redis来缓存数据和持久化Session。

      var mongoose = require('mongoose');
      // 引入 mongoose 模块
      mongoose.connect('mongodb://localhost/blog');
      // 然后连接对应的数据库:mongodb://localhost/test
      // 其中,前面那个 mongodb 是 protocol scheme 的名称;localhost 是 mongod 所在的地址;
      // 端口号省略则默认连接 27017;blog是数据库的名称
      // mongodb 中不需要建立数据库,当你需要连接的数据库不存在时,会自动创建一个出来。
      module.exports = mongoose;
      // 导出 mongoose 模块
      var mongoose = require('../modules/db');
      // 引入 mongoose 模块
      var User = mongoose.model('User',{
          name: {type: String, match: /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/},
          password: String
      });
      //创建了一个名为 User 的 model
      var user1 = new User({name:'12345@qqqqqq.com'});
      user1.password = 'a5201314';  
      user1.save(function(err){
         if(err){
             console.log("save error");
         }
      });
查看原文

赞 0 收藏 0 评论 0

小磊 发布了文章 · 3月31日

Node交互式命令行工具开发——自动化文档工具

  nodejs开发命令行工具,流程相对简单,但一套完整的命令行程序开发流程下来,还是需要下点功夫,网上资料大多零散,这篇教程意在整合一下完整的开发流程。
  npm上命令行开发相关包很多,例如minimistoptimistnoptcommander.jsyargs等等,使用方法和效果类似。其中用得比较多的是TJ大神的commanderyargs,本文以commander为基础讲述,可以参考这篇教程,yargs教程可以参考阮大神的或者这一篇
  另外,一个完整的命令行工具开发,还需要了解processshelljspathlinebyline等模块,这些都是node基础模块或一些简单模块,非常简单,就不多说了,另外如果你不想用回调函数处理异步还需要了解一下PromiseGenerator函数。这是教程:i5ting大神的《深入浅出js(Node.js)异步流程控制》和阮大神的异步编程教程以及promise小人书,另外想尝试ES7 stage3阶段的async/await异步解决方案,可参考这篇教程async/await解决方案需要babel转码,这是教程。本人喜欢async/await(哪个node开发者不喜欢呢?)但不喜欢倒腾,况且async/await本身就是Promise的语法糖,所以没选择使用,据江湖消息,nodejs将在今年晚些时候(10月份?)支持async/await,很是期待。
  以下是文章末尾实例用到的一些依赖。

"dependencies": {
    "bluebird": "^3.4.1",
    "co": "^4.6.0",
    "colors": "^1.1.2",
    "commander": "^2.9.0",
    "dox": "^0.9.0",
    "handlebars": "^4.0.5",
    "linebyline": "^1.3.0",
    "mkdirp": "^0.5.1"
  }

  其中bluebird用于Promise化,TJ大神的co用于执行Generator函数,handlebars是一种模板,linebyline用于分行读取文件,colors用于美化输出,mkdirp用于创建目录,另外教程中的示例是一款工具,可以自动化生成数据库和API接口的markdown文档,并通过修改git hooks,使项目的每次commit都会自动更新文档,借助了TJ大神的dox模块。
  <span style="color:rgb(0, 136, 204)">所有推荐教程/教材,仅供参考,自行甄选阅读。</span>

安装Node

  各操作系统下安装见Nodejs官网,安装完成之后用node -v或者which node等命令测试安装是否成功。which在命令行开发中是一个非常有用的命令,使用which命令确保你的系统中不存在名字相同的命令行工具,例如which commandName,例如which testdev命令返回空白那么说明testdev命令名称还没有被使用。

初始化

  1. 新建一个.js文件,即是你的命令要执行的主程序入口文件,例如testdev.js。在文件第一行加入#!/usr/bin/env node指明系统在运行这个文件的时候使用node作为解释器,等价于node testdev.js命令。
  2. 初始化package.json文件,使用npm init命令根据提示信息创建,也可以是使用npm init -y使用默认设置创建。创建完成之后需要修改package.json文件内容加入"bin": {"testdev": "./testdev.js"}这条信息用于告诉npm你的命令(testdev)要执行的脚本文件的路径和名字,这里我们指定testdev命令的执行文件为当前目录下的testdev.js文件。
  3. 为了方便测试在testdev.js文件中加入代码console.log('hello world');,这里只是用于测试环境是否搭建成功,更加复杂的程序逻辑和过程需要按照实际情况进行编写

测试

  使用npm link命令,可以在本地安装刚刚创建的包,然后就可以用testdev来运行命令了,如果正常的话在控制台会打印出hello world

commander

  TJ的commander非常简洁,README.md已经把使用方法写的非常清晰。下面是例子中的代码:

const program = require('commander'),
  co = require('co');

const appInfo = require('./../package.json'),
  asyncFunc = require('./../common/asyncfunc.js');

program.allowUnknownOption();
program.version(appInfo.version);

program
  .command('init')
  .description('初始化当前目录doc.json文件')
  .action(() => co(asyncFunc.initAction));

program
  .command('show')
  .description('显示配置文件状态')
  .action(() => co(asyncFunc.showAction));

program
  .command('run')
  .description('启动程序')
  .action(() => co(asyncFunc.runAction));

program
  .command('modifyhook')
  .description('修改项目下的hook文件')
  .action(() => co(asyncFunc.modifyhookAction));

program
  .command('*')
  .action((env) => {
    console.error('不存在命令 "%s"', env);
  });

program.on('--help', () => {
  console.log('  Examples:');
  console.log('');
  console.log('    $ createDOC --help');
  console.log('    $ createDOC -h');
  console.log('    $ createDOC show');
  console.log('');
});

program.parse(process.argv);

  定义了四个命令和个性化帮助说明。

交互式命令行process

  commander只是实现了命令行参数与回复一对一的固定功能,也就是一个命令必然对应一个回复,那如何实现人机交互式的命令行呢,类似npm init或者eslint --init这样的与用户交互,交互之后根据用户的不同需求反馈不同的结果呢。这里就需要node内置的process模块。
  这是我实现的一个init命令功能代码:

exports.initAction = function* () {
  try {
    var docPath = yield exists(process.cwd() + '/doc.json');
    if (docPath) {
      func.initRepl(config.coverInit, arr => {
        co(newDoc(arr));
      })
    } else {
      func.initRepl(config.newInit, arr => {
        co(newDoc(arr));
      })
    }
  } catch (err) {
    console.warn(err);
  }

  首先检查doc.json文件是否存在,如果存在执行覆盖交互,如果不存在执行生成交互,try...catch捕获错误。
  交互内容配置如下:

    newInit:
    [
        {
            title:'initConfirm',
            description:'初始化createDOC,生成doc.json.确认?(y/n)  ',
            defaults: 'y'
        },
        {
            title:'defaultConfirm',
            description:'是否使用默认配置.(y/n)  ',
            defaults: 'y'
        },
        {
            title:'showConfig',
            description:'是否显示doc.json当前配置?(y/n)  ',
            defaults: 'y'
        }
    ],
    coverInit:[
        {
            title:'modifyConfirm',
            description:'doc.json已存在,初始化将覆盖文件.确认?(y/n)  ',
            defaults: 'y'
        },
        {
            title:'defaultConfirm',
            description:'是否使用默认配置.(y/n)  ',
            defaults: 'y'
        },
        {
            title:'showConfig',
            description:'是否显示doc.json当前配置?(y/n)  ',
            defaults: 'y'
        }
    ],

  人机交互部分代码也就是initRepl函数内容如下:

//初始化命令,人机交互控制
exports.initRepl = function (init, func) {
  var i = 1;
  var inputArr = [];
  var len = init.length;
  process.stdout.write(init[0].description);
  process.stdin.resume();
  process.stdin.setEncoding('utf-8');
  process.stdin.on('data', (chunk) => {
    chunk = chunk.replace(/[\s\n]/, '');
    if (chunk !== 'y' && chunk !== 'Y' && chunk !== 'n' && chunk !== 'N') {
      console.log(config.colors.red('您输入的命令是: ' + chunk));
      console.warn(config.colors.red('请输入正确指令:y/n'));
      process.exit();
    }
    if (
      (init[i - 1].title === 'modifyConfirm' || init[i - 1].title === 'initConfirm') &&
      (chunk === 'n' || chunk === 'N')
    ) {
      process.exit();
    }
    var inputJson = {
      title: init[i - 1].title,
      value: chunk,
    };
    inputArr.push(inputJson);
    if ((len--) > 1) {
      process.stdout.write(init[i++].description)
    } else {
      process.stdin.pause();
      func(inputArr);
    }
  });
}

  人机交互才用向用户提问根据用户不同输入产生不同结果的形式进行,顺序读取提问列表并记录用户输入结果,如果用户输入n/N则终止交互,用户输入非法字符(除y/Y/n/N以外)提示输入命令错误。

文档自动化

  文档自动化,其中数据库文档自动化,才用依赖sequelize的方法手写(根据需求不同自行编写逻辑),API文档才用TJ的dox也很简单。由于此处代码与命令行功能相关度不大,请读者自行去示例地址查看代码。

示例地址

github地址
npm地址

查看原文

赞 12 收藏 5 评论 1

小磊 发布了文章 · 3月31日

深入剖析let/const在for循环中的作用原理

快速排序

'use strict';

const quickSort = function (array, low, high) {
  if (low >= high) {
    return;
  }
  let i = low;
  let j = high;
  const tmp = array[i];
  while (i < j) {
    while (i < j && array[j] >= tmp) {
      j--;
    }
    if (i < j) {
      array[i++] = array[j];
    }
    while (i < j && array[i] <= tmp) {
      i++;
    }
    if (i < j) {
      array[j--] = array[i];
    }
  }
  array[i] = tmp;
  quickSort(array, low, i - 1);
  quickSort(array, i + 1, high);
}

const arr = [1, 23, 7, 123, 45, 78, 10];
console.log(arr);
quickSort(arr, 0, arr.length - 1);
console.log(arr);

归并排序

'use strict';

// 两个有序数组合并
const memeryArray = function (arr1, arr2) {
  const c = [];
  let i = 0;
  let j = 0;
  let k = 0;
  while (i < arr1.length && j < arr2.length) {
    if (arr1[i] < arr2[j]) c[k++] = arr1[i++];
    else c[k++] = arr2[j++];
  }
  while (i < arr1.length) c[k++] = arr1[i++];
  while (j < arr2.length) c[k++] = arr2[j++];

  return c;
}

// 将同一数组,两段有序序列合并
const memery = function (arr, first, mid, last) {
  const temp = [];
  let i = first;
  const j = mid;
  let m = mid + 1;
  const n = last;
  let k = 0;
  while (i <= j && m <= n) {
    if (arr[i] < arr [m]) temp[k++] = arr[i++];
    else temp[k++] = arr[m++];
  }
  while (i <= j) temp[k++] = arr[i++];
  while (m <= n) temp[k++] = arr[m++];
  for (i = 0; i < k; i++) arr[first + i] = temp[i];
}

const mergeSort = function (arr, first, last) {
  if (!Array.isArray(arr)) throw new Error('argument is not a array');
  if (first < last) {
    const mid = parseInt((first + last) / 2, 10);
    mergeSort(arr, first, mid);
    mergeSort(arr, mid + 1, last);
    memery(arr, first, mid, last);
  }
};

const arr1 = [1, 7, 13, 20, 6, 9, 10, 10, 11, 26, 29];
console.log(arr1);
mergeSort(arr1, 0, arr1.length - 1);
console.log(arr1);
查看原文

赞 1 收藏 0 评论 0

小磊 发布了文章 · 3月31日

JS面试题 - 台阶走法和字符串压缩、快速排序和归并排序

这篇文章继续分享一些公司常问面试题,以供参考。

台阶走法问题

'use strict';

// 递归实现
const recursionCount = function (n) {
  if (n <= 0) return 0;
  if (n === 1) return 1;
  if (n === 2) return 2;

  return count(n - 1) + count(n - 2);
};

// 非递归 用数组实现
const unrecursionCount1 = function (n) {
  if (n <= 0) return 0;
  if (n === 1) return 1;
  if (n === 2) return 2;
  const steps = [1, 2];
  for (let i = 2; i < n; i++) {
    steps[i] = a[i - 1] + a[i - 2];
  }

  return steps[n - 1];
};

// 非递归 用迭代实现
const unrecursionCount2 = function (n) {
  let i = 2,
    a = 1,
    b = 2,
    sum = 0;
  if (n < 1) {
    return -1;
  }
  for (; i < n; i++) {
    sum = a + b;
    a = b;
    b = sum;
  }

  return sum;
};

// 打印路径
const printSteps = function (n, preStr) {
  if (typeof n !== 'number') throw new Error('not a number');
  const str = preStr ? preStr : '';
  if (n < 0) {
    console.log('can\'t print Steps, n < 0');

    return;
  }
  if (n === 0) {
    console.log(str);

    return;
  }
  if (n === 1) {
    console.log(`${str} 1`);

    return;
  }
  for (let i = 1; i <= 2; i++) {
    printSteps(n - i, `${str} ${i}`);
  }

  return;
}

printSteps(3, 'steps: ')

字符串压缩,比如abbbc压缩为ab3c。

分析:如果字符是数字需要处理,比如aa2222b压缩为a224b,解压的时候就不知道是224个a,还是2个a,4个2或者其他。
解决办法,如果是数字,在前面加一个特殊字符标识,同时这个特殊字符也要单独处理
(前面加特殊字符是最精简的方法,只需要一个特殊字符,另外对于比较散乱的字符串,也就是单个字符很多的情况,会有很多1,所以如果字符数量是1则不压缩。)

'use strict';

const small = function (arr) {
  let strList;
  if (Array.isArray(arr)) strList = arr;
  else if (typeof arr === 'string') strList = arr.split('');
  else throw new Error('....');

  let arrStr = '';
  let count = 1;
  for (let i = 0; i < strList.length; i++) {
    if (strList[i + 1] === strList[i]) {
      count++;
    } else {
      if (/\d/.test(strList[i]) || /'/.test(strList[i])) arrStr += `'${strList[i]}`;
      else arrStr += `${strList[i]}`;
      if (count !== 1) arrStr += `${count}`;
      count = 1;
    }
  }

  return arrStr;
};

const str = 'abgjldfff11111111111f4ous\'\'\'';
console.log(small(str));

快速排序

'use strict';

const quickSort = function (array, low, high) {
  if (low >= high) {
    return;
  }
  let i = low;
  let j = high;
  const tmp = array[i];
  while (i < j) {
    while (i < j && array[j] >= tmp) {
      j--;
    }
    if (i < j) {
      array[i++] = array[j];
    }
    while (i < j && array[i] <= tmp) {
      i++;
    }
    if (i < j) {
      array[j--] = array[i];
    }
  }
  array[i] = tmp;
  quickSort(array, low, i - 1);
  quickSort(array, i + 1, high);
}

const arr = [1, 23, 7, 123, 45, 78, 10];
console.log(arr);
quickSort(arr, 0, arr.length - 1);
console.log(arr);

归并排序

'use strict';

// 两个有序数组合并
const memeryArray = function (arr1, arr2) {
  const c = [];
  let i = 0;
  let j = 0;
  let k = 0;
  while (i < arr1.length && j < arr2.length) {
    if (arr1[i] < arr2[j]) c[k++] = arr1[i++];
    else c[k++] = arr2[j++];
  }
  while (i < arr1.length) c[k++] = arr1[i++];
  while (j < arr2.length) c[k++] = arr2[j++];

  return c;
}

// 将同一数组,两段有序序列合并
const memery = function (arr, first, mid, last) {
  const temp = [];
  let i = first;
  const j = mid;
  let m = mid + 1;
  const n = last;
  let k = 0;
  while (i <= j && m <= n) {
    if (arr[i] < arr [m]) temp[k++] = arr[i++];
    else temp[k++] = arr[m++];
  }
  while (i <= j) temp[k++] = arr[i++];
  while (m <= n) temp[k++] = arr[m++];
  for (i = 0; i < k; i++) arr[first + i] = temp[i];
}

const mergeSort = function (arr, first, last) {
  if (!Array.isArray(arr)) throw new Error('argument is not a array');
  if (first < last) {
    const mid = parseInt((first + last) / 2, 10);
    mergeSort(arr, first, mid);
    mergeSort(arr, mid + 1, last);
    memery(arr, first, mid, last);
  }
};

const arr1 = [1, 7, 13, 20, 6, 9, 10, 10, 11, 26, 29];
console.log(arr1);
mergeSort(arr1, 0, arr1.length - 1);
console.log(arr1);
查看原文

赞 0 收藏 0 评论 0

小磊 发布了文章 · 3月31日

javascript创建4位不重复邀请码思路

起因

关于随机不重复字符串,如果没有长度限制,那么最简单的方法当前时间戳 + 固定位数随机字符串的形式完全可以满足(仍有概率重复,但几乎可以忽略),至于长度,有很多办法解决,但最终也无法做到很短。

最近公司有个小需求,把邀请码降低到四位,四位不重复邀请码的实现方式就不那么随意了,直观的想法是通过10个数字(0-9) + 26个小写字母(a-z)排列组合实现,即所谓的排列可重复问题。

这类问题公式如下,每次n种选择,选择r次的排列共有:n的r次幂。这很好理解,一次有n种选择,第二次有n∗n种选择,……,第r次有nr种选择。

对应我们的需求,也就是可以有36的4次幂 = 1679616种组合,完全可以满足一个小型项目的需求。如果项目比较大,完全可以通过加入其他项(比如特殊字符、大写字母等等)到可择列表实现,最终还可以提升邀请码位数进一步扩展。

思路比较直观简单,但也比较实用,欢迎大家学习交流。

实现

思路:

1、首先要确定随机数的依据,采用递增数字(可以对应数据库id)作为基础,创建随机串。
2、打乱10个数字 + 26个字母的组合,这样随机数看起来会舒服一些。
3、把基数转换成36进制数字,转换后不足4位则补充0位。
4、把四位数字的每一位作为下标对应打乱后10个数字 + 26个字母的组合的列表。
5、组合成4位唯一随机字符串。

下面是代码:

exports.random = (number) => {
  // 打乱的10个数字 + 26个字母
  const arr = ["m","0","j","f","8","o","z","w","5","t","p","a","1","d","s","h","v","x","9","b","r","y","2","e","7","4","3","q","6","n","u","l","c","g","i","k"];
  // 把number由十进制数转换成36进制数
  const transNumber = binaryConversion(+number, 10, 36);
  // 排除大于4位的情况
  const len = transNumber.toString().length;
  if (len > 4) {
    console.log('数字过大');
    return;
  }
  // 转换后的数字不足4位则补充0位,并按字符转换成数组
  const list = prefixInteger(transNumber, 4).toString().split('');
  const len4Arr = [];
  for (const num of list) {
    // 判断当前字符是数字还是字母
    const type = checkStrType(num);
    // 如果是数字不处理,如果是字母则转换成对应的数组(10 - 35)
    const index = type === 'string' ? stringTonum(num) + 9 : num;
    len4Arr.push(arr[+index]);
  }

  // 返回结果
  return len4Arr.join('');
}

补充

缺点:基数数字低于36无法创建,比如起始数字大于36。

欢迎大家提出高效思路,不足之处请指出。

其他代码如下:


function binaryConversion(num, m ,n) {
  return parseInt(String(num), m).toString(n);
}
function prefixInteger(num, length) {
 return (Array(length).join('0') + num).slice(-length);
}
function stringTonum(a) {
  const str = a.toLowerCase().split('');
  const al = str.length;
  const getCharNumber = charx => charx.charCodeAt() - 96;
  let numout = 0;
  let charnum = 0;
  for (let i = 0; i < al; i++) {
      charnum = getCharNumber(str[i]);
      numout += charnum * Math.pow(26, al - i - 1);
  };

  return numout;
}

function checkStrType(str) {
  //验证是否是英文
  var pattern = new RegExp("[A-Za-z]+");
  if (pattern.test(str)) return 'string';
  //验证是否是数字
  var patternNumber = new RegExp("[0-9]+");
  if (patternNumber.test(str)) return 'number';

  return '';
}
查看原文

赞 1 收藏 1 评论 0

小磊 关注了用户 · 3月23日

码哥字节 @magebyte

公众号-码哥字节

一个有品位的博主,不跟风不扯淡,助力程序员成长。

一线大厂互联网工作经验,左手微服务、右手中间件、前脚高并发、后脚分布式。

关注 6345

小磊 关注了用户 · 3月23日

码农小胖哥 @10000000

技术公众号:Felordcn 欢迎关注
微信圈子:程序员交流圈 欢迎投稿交流
个人独立博客: https://felord.cn

关注 5320

认证与成就

  • 获得 37 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-08-18
个人主页被 1.1k 人浏览