ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

从零开始编写一个js插件

2021-05-19 18:00:55  阅读:97  来源: 互联网

标签:function 插件 name db js 从零开始 let var data


https://www.jianshu.com/p/0a3127d301b1

 

什么是js插件

首先我们必须要明白什么是js插件,说的简单点,类似于

function add(a, b){ return a+b; }

这就是一种插件的雏形,因为它还没有封装起来, 它会影响到工作区的其他参与者(函数),也就是说它有可能会影响到其他成员,这样并不好。

封装

想要插件不影响本身的命名空间的话,使用匿名函数是最好的方式之一,由于JavaScript本身是基于函数作用域的,也就是说只要是在自身的函数范围内,那么便不会与其他同名变量或者函数造成冲突。

匿名函数本身连函数名都可以不存在的(匿名函数同样可以命名,这可能多少有点矛盾,但的确如此),如此一来,我们的函数只要声明在一个匿名函数之下的话,那么就不可能会对其他作用域造成影响,如下:

(function(global){
    //some code...
}(this))

 

一些必要的讲解

  1. 这里的匿名函数传入的变量是this,而非window, 这里主要是为了兼容到服务端,在node.js中,全局变量的名称为global,而this本身是基于调用上下文,所以传入this即可完成兼容。
  2. 此外传入this的最重要原因是为了能通过原型链prototype继承到全局变量this里边,进而可以在其他作用域中调用插件中的方法。

 

可拓展性

当我们有了自己的命名空间以后,我们自然是需要一些相应的代码来填充进去

不过由于插件本身也是一个function,但是他必须兼顾到可拓展可配置, 所以如果使用普通函数的传参方式在实际拓展起来会较为麻烦, 所以这里推荐的是使用一个Object作为参数, 并且在函数内部声明一个常用的默认配置项

js插件其实有两个最为基础而且重要的内部函数,那就是config()init(),一个是基本的配置,另一个则是初始化插件状态。这两个内部函数实际上不是非得要这么写, 只是如果不使用这两个函数的话, 在实际拓展和使用起来或许会较为困难

首先来看看config()的基本写法,很简单,只要传入一个opts参数,表示由用户配置的基本参数,然后再自己内部添加自己的默认参数,再与用户的参数进行比较,一旦用户有配置的参数便使用用户参数,反之则使用默认参数,最后输出即可。

这里其实就是函数式编程的一个体现,用户传入数据,函数返回数据,相同输入相同输出,没有任何的不确定性

// 该方法有问题的, 这里只是做一个示例
  function config(opts, options) {
    //默认参数
    if (!opts) return options;
    for (var key in opts) {
      if (!!opts[key]) {
        options[key] = opts[key];
      }
    }
    return options;
  }

初始化状态(即最基本的API)

在这里直接贴出代码估计也能看的懂,不过多少还需要再讲解下。

  1. 首先需要获取到自己config()配置的参数
  2. 其次就是根据获取的参数进行操作
  3. 作为入口函数使用
/**
     * @method 初始化
     * @param { object } 由@method config() 提供的配置参数
     */
    init: function (opts) {
      var _this = this;
      var option = config(opts, this.options);//用户配置
      var _elems = document.getElementsByClassName(option.elem);
      var _elemsLength = _elems.length;
      var index = null;

      initPreviewArea(_elems);

      var yPreviewImage = document.getElementById('yPreviewImage');
      var yPreviewArea = document.getElementById('yPreviewArea');

      for (var i = 0; i < _elemsLength; i++) {
        _elems[i].setAttribute('data-id', i);
        _elems[i].style.cursor = 'pointer';
        _elems[i].addEventListener('click', function () {
          var src = this.getAttribute('src');
          yPreviewImage.setAttribute('src', src);
          yPreviewImage.setAttribute('data-id', this.getAttribute('data-id'));
          show(yPreviewArea);
        })
      }
    }

架构

现在我们已经有了国土(匿名函数), 资源(用户参数)和建筑(基本的API),那么还缺什么?没错,怎么把他们串联在一起。其实非常简单,只需要通过原型链继承即可。

在这里实现的主要步骤有这么几步

  1. 确定作用域,即匿名函数
  2. 使用严格模式
  3. 设置插件函数
  4. 配置函数原型链
  5. 将插件函数注册到全局参数中
  6. 恭喜你,你已经可以在你的js中调用该插件了
  7. 更加具体的请查看下方注释

2019.07.19

在这里更新一个新的用该方法写的一个基于localStorage的前端数据库插件的地址, 有兴趣的朋友可以参考一下

https://github.com/LElysion/nothing/tree/master/yDB

 

<!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>Document</title>
</head>
<body>
  
</body>
<script>
  (function (global) {
  "use strict";

  const YDB_DBLIST = 'YDB_DBLIST'; // 数据库本身保留字

  let yDB = function () {
    this.db = null;
    this.dbList = {}; // 用于保存所有数据库
  }

  yDB.prototype = {
    // 初始化数据库状态, 包括数据库列表等
    init() {
      // 获取数据库列表
      let dbList = localStorage.getItem(YDB_DBLIST);
      if (!dbList) { // 第一次运行, 不存在数据库列表
        localStorage.setItem(YDB_DBLIST, '{}');
        dbList = {};
      }
      this.dbList = JSON.parse(dbList);
    },
    end() {
      // 操作完数据库的最后应该保存数据到本地
      localStorage.setItem(YDB_DBLIST, JSON.stringify(this.dbList));
    },
    // 清空所有数据
    clear() {
      localStorage.setItem(YDB_DBLIST, '');
    },
    // 保存当前db到dblist中
    saveDBListItem(db, dbName) {
      this.dbList[dbName] = db;
    },
    // 使用一个数据库,如果该数据库不存在则创建为新的数据库
    use(dbName) {
      let db = this.dbList[dbName];
      if (!db) {
        db = {
          _name: dbName
        }; // 空数据库
        this.saveDBListItem(db, dbName);
      }
      this.db = db;
    },
    showDbs() {
      for (let key in this.dbList) {
        console.log(this.dbList[key]._name);
      }
      return this.dbList;
    },
    // 删除当前数据库
    dropDatabase() {
      if (!this.db) {
        console.error('you should use db first');
        return;
      }
      delete this.dbList[this.db._name];
      this.db = null; // 指向置空
    },
    // 创建集合
    createCollection(name, options) {
      this.db[name] = {
        _name: name,
        _data: []
      };
    },
    getCollection(name) {
      return this.db[name];
    },
    // 删除集合
    drop(name) {
      if (!!this.db && !!this.db[name]) {
        delete this.db[name];
      }
    },
    // 插入数据
    insert(name, data) {
      if (!this.db[name]) this.createCollection(name); // 不存在集合时自动创建集合
      let id = uuid(20);
      if(!data._id) data._id = id;
      this.db[name]._data.push(data);
    },
    // 根据某一条件查询数据
    // { id: 101 }
    find(name, condition, options) {
      let data = this.db[name]._data; // 获取该集合中的数据
      let res = [];
      let conditionString = JSON.stringify(condition);
      data.forEach(item => {
        let arr = obj2Arr(item);
        for(let i = 0; i< arr.length; i++) {
          if(JSON.stringify(arr[i]) === conditionString) {
            res.push(item);
            break;
          }
        }
      })
      return res;
    },
    remove(name, condition, options) {
      let data = this.db[name]._data;
      let res = [];
      let conditionString = JSON.stringify(condition);
      let idxs = [];
      data.forEach((item, index) => {
        let arr = obj2Arr(item);
        let flag = false;
        for(let i = 0; i< arr.length; i++) {
          if(JSON.stringify(arr[i]) === conditionString) {
            res.push(item);
            idxs.push(index);
          }
        }
      })
      this.db[name]._data = deleteArrByIdxs(data, idxs);
      return res;
    },
    update(name, condition, newData) {
      let data = this.db[name]._data;
      let conditionString = JSON.stringify(condition);
      this.db[name]._data = data.map((item) => {
        let arr = obj2Arr(item);
        for(let i = 0; i< arr.length; i++) {
          if(JSON.stringify(arr[i]) === conditionString) {
            if(!newData._id) newData._id = uuid(20);
            item = newData;
            break;
          }
        }
        return item;
      })
      return true;
    }
  }


  function deleteArrByIdxs(arr, idxs) {
    let res = arr.filter((item, index) => {
      let flag = true;
      idxs.forEach(idx => {
        if(index === idx) {
          flag = false;
        }
      })
      return flag;
    });
    return res; 
  }

  function obj2Arr(obj) {
    let res = [];
    for(let key in obj) {
      res.push({
        [key]: obj[key]
      })
    }
    return res;
  }

  function isArray(o) {
    return Object.prototype.toString.call(o) === '[object Array]';
  }

  function uuid(len) {
    let str = "",
      range = len,
      pos = "",
      arr = [
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
        'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
        'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',];
    for (let i = 0; i < range; i++) {
      pos = Math.round(Math.random() * (arr.length - 1));
      str += arr[pos];
    }
    return str;
  }

  global.yDB = new yDB();
}(this))
</script>
<script>
  yDB.use('FIRST_DB');
  yDB.use('SECOND_DB');
  yDB.showDbs();
  yDB.dropDatabase();
  yDB.showDbs();
  yDB.use('FIRST_DB');
  yDB.insert('COLLE1', { a: 1, b: 2, c: 3 });
  yDB.insert('COLLE1', { d: 5, b: 2, c: 3 });
  console.log(yDB.find('COLLE1', {b: 2}))
  // console.log(yDB.remove('COLLE1', {a: 1}))
  yDB.update('COLLE1', {a: 1}, {s: 6})
  // yDB.drop('COLLE1')
  console.log(yDB);
</script>
</html>

 

用处

市面上已经有大量的插件了,为什么我们还要自己造轮子?答案很简单,市面上再好的插件他们也不是根据你的需求所定制的,灵活性和可拓展性自然有所缺陷,而自己配置一个插件库便可以完全的为需求所服务,甚至可以将其更加完善从而更好的适应其他需求。

 

 

 

最后

一个图片预览插件,不完善, 但是单用作学习已经足够了

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="js/jquery-1.7.1.js"></script>
  <style>
    .wrap {
      max-width: 750px;
      margin: 0 auto;
    }
    .preview-image {
      width: 120px;
      color: rgb(89, 21, 21);
    }
  </style>
</head>
<body>
  <div class="wrap">
    <img src="img/1.jpg" alt="" class="preview-image">
    <img src="img/2.jpg" alt="" class="preview-image">
    <img src="img/3.jpg" alt="" class="preview-image">
  </div>
  <script>
    (function (global) {
      "use strict";
      var yPreview = function () { }
      // 有必要调用到插件本身this的, 就放在prototype上边, 功能函数尽量放在下方工具中
      yPreview.prototype = {
        options: {
          name: 'yPreview',
          elem: 'preview-images'
        },
        index: null,
        /**
         * @method 初始化
         * @param { object } 由@method config() 提供的配置参数
         */
        init: function (opts) {
          var _this = this;
          var option = config(opts, this.options);//用户配置
          var _elems = document.getElementsByClassName(option.elem);
          var _elemsLength = _elems.length;
          var index = null;

          initPreviewArea(_elems);

          var yPreviewImage = document.getElementById('yPreviewImage');
          var yPreviewArea = document.getElementById('yPreviewArea');

          for (var i = 0; i < _elemsLength; i++) {
            _elems[i].setAttribute('data-id', i);
            _elems[i].style.cursor = 'pointer';
            _elems[i].addEventListener('click', function () {
              var src = this.getAttribute('src');
              yPreviewImage.setAttribute('src', src);
              yPreviewImage.setAttribute('data-id', this.getAttribute('data-id'));
              show(yPreviewArea);
            })
          }
        }
      }

      function hide(elem) {
        elem.style.display = 'none';
      }
      function show(elem) {
        elem.style.display = 'block';
      }

      function setStyle(elem, styles) {
        for (var key in styles) {
          elem.style[key] = styles[key]
        }
      }

      function initPreviewArea(elems) {
        var imgsrcs = [];
        var elemsLength = elems.length;
        for (var i = 0; i < elemsLength; i++) {
          imgsrcs.push({
            src: elems[i].getAttribute('src'),
            id: i
          })
        }

        var div = document.createElement('div');
        div.setAttribute('id', 'yPreviewArea');
        var divStyle = {
          position: 'fixed',
          top: 0,
          left: 0,
          width: '100%',
          height: window.innerHeight + 'px',
          background: 'rgba(0, 0, 0, .6)',
          display: 'none',
          userSelect: 'none'
        }
        var img = document.createElement('img');
        img.setAttribute('id', 'yPreviewImage');
        var imgStyle = {
          position: 'relative',
          top: '50%',
          left: '50%',
          maxHeight: window.innerHeight + 'px'
        }
        setStyle(img, imgStyle);
        img.style.transform = 'translate(-50%, -50%)';
        // img.addEventListener('click', function () {
        //   div.style.display = 'none';
        // })
        div.appendChild(img);

        var next = document.createElement('a');
        next.innerText = '>';
        var nextStyle = {
          fontFamily: "黑体",
          position: 'absolute',
          top: '50%',
          right: 0,
          margin: '12px',
          width: '42px',
          height: '42px',
          lineHeight: '42px',
          display: 'block',
          background: '#1E67B9',
          cursor: 'pointer',
          color: '#fff',
          textAlign: 'center',
          fontSize: '27px',
          borderRadius: '50%'
        }
        setStyle(next, nextStyle);
        next.addEventListener('click', function () {
          var idx = img.getAttribute('data-id');
          if ((elemsLength - 1) > idx) {
            var _idx = parseInt(idx);
            img.setAttribute('src', elems[_idx + 1].getAttribute('src'));
            img.setAttribute('data-id', elems[_idx + 1].getAttribute('data-id'));
          } else {
            img.setAttribute('src', elems[0].getAttribute('src'));
            img.setAttribute('data-id', elems[0].getAttribute('data-id'));
          }
        })
        div.appendChild(next);

        var prev = document.createElement('a');
        prev.innerText = '<';
        var prevStyle = {
          fontFamily: "黑体",
          position: 'absolute',
          top: '50%',
          left: 0,
          margin: '12px',
          width: '42px',
          height: '42px',
          lineHeight: '42px',
          display: 'block',
          background: '#1E67B9',
          cursor: 'pointer',
          color: '#fff',
          textAlign: 'center',
          fontSize: '27px',
          borderRadius: '50%'
        }
        setStyle(prev, prevStyle);
        prev.addEventListener('click', function () {
          var idx = img.getAttribute('data-id');
          if (idx != 0) {
            var _idx = parseInt(idx);
            console.log(elems[_idx - 1].getAttribute('src'))
            img.setAttribute('src', elems[_idx - 1].getAttribute('src'));
            img.setAttribute('data-id', elems[_idx - 1].getAttribute('data-id'));
          } else {
            img.setAttribute('src', elems[elemsLength - 1].getAttribute('src'));
            img.setAttribute('data-id', elems[elemsLength - 1].getAttribute('data-id'));
          }
        })
        div.appendChild(prev);

        var close = document.createElement('a');
        close.innerText = '×';
        var closeStyle = {
          position: 'absolute',
          top: '12px',
          right: '12px',
          display: 'block',
          fontSize: '32px',
          color: '#fff',
          cursor: 'pointer',
          zIndex: 99
        }
        close.addEventListener('click', function () {
          hide(div);
        })
        setStyle(close, closeStyle);
        div.appendChild(close);

        setStyle(div, divStyle);
        document.body.appendChild(div)
      }

      function getNodeClass(className) {
        var _elems = document.getElementsByClassName(className);
        return _elems
      }

      // 工具函数
      // 检查非空
      function isEmpty(val) {
        return val != '' && val != null && val != undefined ? false : true;
      }

      /**
       * @method 配置
       * @param opts { object } 用户提供的参数,在没有提供参数的情况下使用默认参数 
       * @param options { object } 默认参数
       * @return options { object } 返回一个配置对象
       */
      function config(opts, options) {
        //默认参数
        if (!opts) return options;
        for (var key in opts) {
          if (!!opts[key]) {
            options[key] = opts[key];
          }
        }
        return options;
      }

      global.yPreview = yPreview;//注册到全局中, 届时可以直接new yPreview() 实例化对象
      let yn = new yPreview();
      yn.init({
        name: 'yourNewName',
        elem: 'preview-image'
      });
    }(this))
  </script>
</body>

</html>


作者:LElysion
链接:https://www.jianshu.com/p/0a3127d301b1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

标签:function,插件,name,db,js,从零开始,let,var,data
来源: https://blog.csdn.net/qq_27009517/article/details/117035972

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有