分享,进步,自足

分类 技术笔记 下的文章

13/5
2019

使用JS完成简单事件管理

之前在这篇文章写过,但是当时不理解。其实考察的是设计模式,发布订阅模式,理解后再手动写一次。
总结一下:维护一个对象,属性值为事件名称,值为一个数组,里面存的是所有订阅者的回调函数,emit事件时循环执行数组中的函数,同时查看函数的_once值是否为true,如果是在调用函数后取消监听这个事件。


// 类型: 校招
// 时长: 30 分钟
// 姓名:
// 日期:
// 实现下面的 EventEmitter 类, 用于简单的事件管理.
// 提示:
// 运行代码快捷键: Ctrl + S
class EventEmitter {
    constructor () {
        this.listener = {}
    }
    emit(type, ...args) {
        if (!this.listener.hasOwnProperty(type)) {
            return
        } else {
            this.listener[type].forEach(element => {
                element(...arg)
                if (element._once === true) {
                    this.off(type, element)
                }
            });
        }
    }
    once(type, listener) {
        listener._once = true
        if(!this.listener.hasOwnProperty(type)) {
            this.listener[type] = [listener]
        } else {
            this.listener[type].push(listener)
        }
    }
    on(type, listener) {
        if(!this.listener.hasOwnProperty(type)) {
            this.listener[type] = [listener]
        } else {
            this.listener[type].push(listener)
        }
    }
    off(type, listener) {
        const funcarr = this.listener[type]
        if(funcarr.indexOf(listener) !== -1) {
            funcarr.splice(funcarr.indexOf(listener), 1)
            if(funcarr.length === 0) {
                delete this.listener[type]
            }
        }
    }
}
// 在不修改下面代码的情况下, 能满足下面列举的使用
console.log('输出结果:');
const target = new EventEmitter();
// once
target.once('ready', id => console.log('ready', id));
target.emit('ready', 1);
target.emit('ready', 2);
const messageListener = (...args) => console.log(args);
// on
target.on('message', messageListener);
target.emit('message', 'hello');
target.emit('message', 'world', '!');
// off
target.off('message', messageListener);
target.emit('message', 'nothing');
// 输出结果参考
// 输出结果:
// ready 1
// hello
// world !
// (off 后无输出)

+ MORE

17/3
2019

毕业设计(二)<--文章发布、文章查询-->基于JavaScript的博客系统

完成功能

完成了文章发布的功能和文章查询的功能。实现过程比较简单,花了这么多时间主要都去解决这个问题去了:帖子链接

  • 剩下的时间重新规划了数据表;
  • 把token授权的部分单独提出出来以前的想法是错误的,不能提前在路由中判断,需要到指定的路由中判断
  • 优化了if--else语法,把else全部去掉了,提前到前面,一旦发现错误马上返回,这样就避免了多层的if嵌套。

已知的不足:明显没有用到面向对象的思想,大部分还是面向过程了,这可能是思想问题。ECMAScript新功能用的较少。
大致的框架已经完成,后续的功能需要加快速度,4月底有一次中期答辩,希望时间够。

一直有个问题,现在新出的ECMAScript标准到底是属于ES6的扩充,还是像ES2015(ES6)、ES2016(ES7)、ES2017(ES8)这类的命名。到底哪个是正确的...有这个疑问是因为阮一峰老师的《ES6标准入门》有这么一段话:ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,但是各大前端社区都直接称之为ESXXXX之类的额,都没有算进ES6的范畴中

+ MORE

7/3
2019

毕业设计(一)<--token授权、koa路由、mysql操作-->基于JavaScript的博客系统

选题及构思

学的硬件,毕业设计如果做硬件实物又是一笔钱,最后选择了一个软件的题目,正好用来练手。
前后端基于JS的博客系统,用于完成毕业设计,初想使用koa作为后端,vue作为前端来完成,以后可能会改变

目录结构

第一部分先完成后端的koa路由、token授权、mysql操作类。


│ index.js
│ package-lock.json

├─config
│  config.js
│  sql.js
│  token.js

└─router
  article.js


以上为项目基础目录:index.js是入口文件;config为配置目录,其中的config.js主要是参数配置文件,sql.js为mysql的操作类,token.js生成token的方法、以及注册账号功能;router目录下的所有文件将自动加载为koa的路由,加载规则为:"/文件名/内置路由名",例如此例路由为:“/article/”。

程序代码


index.js文件:

const Koa = require("koa");//web服务库
const Router = require("koa-router");//koa路由
const Koapost = require("koa-body");//基于koa的post接收库
const jwt = require("jwt-simple");//json web token库
const config = require("./config/config");//配置文件
const jstSecret = config.jstSecret;//token密钥
const app = new Koa();
const router = new Router();

app.use(Koapost());//获得post信息

let token = require(__dirname + '/config/token.js');//加载登陆注册中间件
router.use('/user', token.routes(), token.allowedMethods());
app.use(router.routes());//挂载登陆注册中间件


app.use(async (ctx, next) => {
    if (ctx.request.header.authorization !== undefined) {
        let user = jwt.decode(ctx.request.header.authorization, jstSecret);
        if(Date.now() > user.exp){
            ctx.response.status = 403;
            ctx.response.body = {
                msg: "token已过期,请重新授权",
                "status": "-1"
            }
        }else{
            await next();
        }
    } else {
        ctx.response.status = 401;
        ctx.response.body = {
            msg: "非授权操作!",
            "status": "-1"
        }
    }
});

const fs = require("fs");
const urls = fs.readdirSync(__dirname + '/router');//获得所有子路由文件
urls.forEach(element => {//循环注册所有子路由
    let url = require(__dirname + '/router/' + element);
    router.use('/' + element.replace('.js', ''), url.routes(), url.allowedMethods());
});

app.use(router.routes());//把子路由挂载到koa之上
app.listen(3000);

config.js文件:

module.exports = {
    jstSecret: 'No permission',//jwt密钥
    tokenExpiresTime: 1000 * 60 * 60 * 24 * 7,//token过期时间
    mysql:{
        host: "No permission",//数据库地址
        port: "No permission",//端口
        user: "No permission",//用户名
        password: "No permission",//密码
        database: "No permission"//数据库名
    }
}

sql.js文件:

const mysql = require("mysql");//mysql操作库
const config = require("./config");//配置文件
const _db = Symbol("db");
class Sql {
    constructor() {
        this[_db] = mysql.createPool({
            host: config.mysql.host,
            port: config.mysql.port,
            user: config.mysql.user,
            password: config.mysql.password,
            database: config.mysql.database
        });
    }
    query(sql, value) {
        return new Promise((resove, reject) => {
            this[_db].getConnection((err, connection) => {
                if(err){
                    reject(err);
                }else{
                    connection.query(sql, value, (err, rows)=>{
                        if(err){
                            reject(err);
                        }else{
                            resove(rows);
                        }
                    })
                    connection.release();
                }
            })
        })
    }
}
module.exports = Sql;

token.js文件:

const Router = require("koa-router");//koa路由
const jwt = require("jwt-simple");//json web token库
const utility = require("utility");//sh1、md5加密库
const sql = require("./sql");//mysql封装类
const config = require("./config");//配置文件
const sqlQuery = new sql();
const token = new Router();
const tokenExpiresTime = config.tokenExpiresTime;//token超时时间
const jstSecret = config.jstSecret;//token密钥

token.post('/login', async ctx => {
    if(ctx.request.body.user === undefined || ctx.request.body.password === undefined){
        //检查是否存在post数据
        ctx.response.status = 400;
        ctx.response.body = {
            "msg": "登陆参数不正确!",
            "status": "-1"
        }
    }else{
        //验证账号密码的正确性
        let post_user = ctx.request.body.user;
        let post_password = utility.md5(utility.md5(ctx.request.body.password));
        let con = await sqlQuery.query("SELECT * FROM user WHERE name=? AND password=?", [post_user, post_password]);
        if(con.length === 1){
            const payload = {
                'sub': post_user,
                'exp': Date.now() + tokenExpiresTime
            };
            const token = jwt.encode(payload, jstSecret);
            ctx.response.status = 200;
            ctx.response.body = {
                "msg": "授权成功!",
                "user": post_user,
                "token": token,
                "status": "0"
            };
        }else{
            ctx.response.status = 200;
            ctx.response.body = {
                "msg": "账号或者密码错误!",
                "status": "-1"
            }
        }
    }
})
module.exports = token;

article.js文件:

const Router = require("koa-router");
const article = new Router();
article.get('/', async (ctx, next) => {
    ctx.response.status = 200;
    ctx.response.body = "article";
    await next();
});
article.get('/list', async (ctx, next) => {
    ctx.response.status = 200;
    ctx.response.body = "article/list";
    await next();
});
module.exports = article;

程序执行流程

  1. 运行index.js文件,先把Koapost()方法挂载到koa中间件上,它的作用是接收post过来的参数,可以用ctx.request.body来获得
  2. 加载config/token.js路由到koa中间件,这个中间站在第二个位置,它的作用是token授权;
  3. 接着就是整体路由监控,排除授权token接口外所有接口都要经过此接口,token验证失败时就会报错,验证成功才能进行其它操作。
  4. 然后循环读取router文件夹下的所有文件,把其中定义的路由全部加载到koa中间件下

整体框架大致如此,之后的文章将不会在贴代码,而只记录实现过程,代码将会托管在github中

+ MORE

21/2
2019

webpack基础学习+自定义封装AJAX类

webpack

本文用于记录webpack的简单使用,以及用原生JS封装XMLHttpRequest对象。
中文文档:webpack中文文档

  1. 首先生成package.json:npm init -y

  2. 安装webpack和webpack-cli:npm i webpack webpack-cli -D

  3. 新建一个webpack.config.js配置文件:

    const path = require("path");
    module.exports = {
     entry: "./src/app.js",//入口文件
     output: {//输出文件
         path: path.resolve(__dirname, "dist"),
         filename: "bundle.js"
     },
     mode: 'production'
    }
  4. package.json文件的scripts对象内添加:"webpack": "webpack",如下:

    "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "webpack": "webpack"
    }
  5. 在index.html中引入bundle.js文件,运行即可在浏览器终端看见结果。

    <!DOCTYPE html>
    <html lang="en">
    <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <title>webpack学习</title>
    </head>
    <body>
    </body>
    <script src="./dist/bundle.js"></script>
    </html>
  6. 以后使用npm run webpack运行webpack命令


AJAX

文件结构:
QQ截图20190221190310.png
以下为ajax.js文件内容:

export default class Ajax {
    constructor() {
        this.xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");//兼任IE5及以下
    }
    send(options){
        let xhr = this.xhr;
        let opt = {
            type: options.type||"GET",
            url: options.url||"",
            async: options.async||"true",
            data: options.type == "POST" ? options.data||"" : ""
        }
        return new Promise(function(resolve, reject){
            xhr.open(opt.type, opt.url, opt.async);
            xhr.onreadystatechange = ()=>{
                if (xhr.readyState !== 4) {
                    return;
                  }
                  if (xhr.status === 200) {
                    resolve(xhr.response);
                  } else {
                    reject(new Error(xhr.statusText));
                  }
            }
            xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
            xhr.send(opt.data);
        })
    }
}

使用方法,在app.js:

import xmlhttp from "./ajax";
let tianqi = new xmlhttp();
tianqi.send({
    type: "GET",
    url: "https://api.isoyu.com/index.php/api//Movie/playing_movie_list?start=0&count=9", 
}).then((value) => {
    console.log(value);
});

大致过程:创建一个Ajax类,构造函数中新建XMLHttpRequest对象,创建一个send方法,接收一个对象。使用Promise对象进行AJAX发送,并且返回这个Promise对象

+ MORE

20/2
2019

JS实现简单的事件管理

这是一道面试题:

// 类型: 校招
// 时长: 30 分钟
// 姓名:
// 日期:

// 实现下面的 EventEmitter 类, 用于简单的事件管理.

// 提示:
// 运行代码快捷键: Ctrl + S 

class EventEmitter {

    emit(type, ...args) {

    }

    oncets(that, type, listener) {

    }

    on(type, listener) {

    }

    off(type, listener) {

    }
  }

  // 在不修改下面代码的情况下, 能满足下面列举的使用

  console.log('输出结果:');

  const target = new EventEmitter();

  // once

  target.once('ready', id => console.log('ready', id));

  target.emit('ready', 1);
  target.emit('ready', 2);

  const messageListener = (...args) => console.log(args);

  // on

  target.on('message', messageListener);
  target.emit('message', 'hello');
  target.emit('message', 'world', '!');

  // off
  target.off('message', messageListener);

  target.emit('message', 'nothing');

  // 输出结果参考

  // 输出结果:
  // ready 1
  // hello
  // world !
  // (off 后无输出)

我在此之前完全没有写过事件驱动型的程序,对事件驱动我还停留在DOM的事件管理上,所有这题没有做出来,但是当时的思路有一些了。
事后在查阅网上资料后完成了以下代码,特此记录:

// [makeflow: event-emitter]
// 类型: 校招
// 时长: 30 分钟
// 姓名:
// 日期:

// 实现下面的 EventEmitter 类, 用于简单的事件管理.

// 提示:
// 运行代码快捷键: Ctrl + S 

class EventEmitter {
    constructor() {
        this.eventList = {};
    }

    emit(type, ...args) {
        let event = this.eventList[type];
        if(!event){
            return false;
        }else if(typeof event =="function"){
            event(...args);//如果只有一个值就直接执行
        }else if(Array.isArray(event)){//循环遍历执行
             for(let i = 0;i < event.length;i++){
                 event[i](...args);
             }
        }
    }

    once(type, listener) {
        this.on(type, this.oncets(this, type, listener));
    }

    oncets(that, type, listener) {
        //执行回调时首先删除自己,再执行
        function funconce() {
            that.off(type,funconce);
            listener(arguments);
        }
        return funconce;
    }

    on(type, listener) {
        //注册事件
        let event = this.eventList[type];
        if(!event){//直接添加到数组
            this.eventList[type] = listener;
        }else if(typeof event == "function"){//注册第二个事件时把属性改为数组
            this.eventList[type] = [event, listener];
        }else if(Array.isArray(type)){//第三次之后直接向数组中添加函数体
            this.eventList[type].push(listener);
        }
    }

    off(type, listener) {
        let event = this.eventList[type];
        if(typeof event =="function"){
            if(event == listener){//一个值,直接删除
                delete this.eventList[type];
            }
        }else if(Array.isArray(event)){
            //多个值,找到相同的删除掉
            for(let i = 0;i < event.length;i++){
                if(this.eventList[type][i] === listener){
                    this.eventList[type][i] = this.eventList[type][this.eventList[type].length - 1];
                    this.eventList[type].pop();
                    i--;
                    if(this.eventList[type].length === 0){
                        delete this.eventList[type];
                    }
                }
            }
        }
    }
  }

  // 在不修改下面代码的情况下, 能满足下面列举的使用

  console.log('输出结果:');

  const target = new EventEmitter();

  // once

  target.once('ready', id => console.log('ready', id));

  target.emit('ready', 1);
  target.emit('ready', 2);

  const messageListener = (...args) => console.log(args);

  // on

  target.on('message', messageListener);
  target.emit('message', 'hello');
  target.emit('message', 'world', '!');

  // off
  target.off('message', messageListener);

  target.emit('message', 'nothing');

  // 输出结果参考

  // 输出结果:
  // ready 1
  // hello
  // world !
  // (off 后无输出)

最后大致完成了,校招题都这样啊,难受。

+ MORE