JavaScript组件思维总结


前言

JavaScript组件思维总结

对于一个前端工作者来说,创建一个独属于自己的组件库是一个必不可少的过程与历程,小结一下!


一、JavaScript组件思维

直接文档流的前端开发,在制作某些页面功能件,比如页头、页尾、轮播图等重复高频反复使用的部分的时候,可以把这些制作成一个组件库,需要使用的时候用JavaScript直接调用生成即可,可大大加快工作效率!

可类比于java的面相对象编程开发,只不过JavaScript的这个对象(组件)大部分得需要我们自己制作!

目前大部分都框架比如jquery、vue等等的初步构建基础,即可认为是组件开发思维的开始!

二、组件基本组成

几乎比较完整的组建都会包括3个方面:
1、组件属性选项
2、组件方法
3、组件事件

Jquery UI小部件–折叠面板部件(Accordion Widget)
在这里插入图片描述

三、组件基本工作流程

基本上完整的组建都会有一个生命周期,主要会经过:
1、组件构造new()、初始化init()
2、事件绑定bind()
3、组件渲染render()
4、其他

1、初步闭包结构的组件

闭包结构的组件能尽量少的对主文档造成变量污染,是一个自执行的函数,绑定在window下可以直接使用,方便,但劣势,不易拓展,有一些不适用于大规模的具有同一属性、样式、渲染方式的组件库!

轻度使用组件时比较好!

 (function(x, y) {
     function JsClassName(cfg) {
          var config = cfg || {};
          x.get = function(n) {
             return config[n];
         }
         x.set = function(n, v) {
            config[n] = v;
         }
         x.init();
     }
     //对外的函数体部分
     JsClassName.prototype = {
         init: function(){},
         otherMethod: function(){}
    };            
    window.JsClassName = window.JsClassName || JsClassName;
 })(this,document);

2、利用JavaScript的类与类的继承创建组件

1)组件基类

构建组件时,需要对组件库进行分析总结归纳出基类
初步测试的组建基类如下,包括构建、初始化、事件监听、渲染、组件事件与属性基类等

该基类会自动渲染组件

class aBase {

   //组件的构造函数与初始化,组件一但new()直接运行;
   //config作为组件的配置参数,作为对象输入
    constructor(config) {
        //组件内的引用都需要对象指向this代表组件体引用
        //保存组件配置参数,后面参数用this.config来引用
        this.config = config;
        //初始化函数引用
        this.init();
        //部分配置参数缺省处理
        this.id = config.id||'body';
        this.style = config.style || '0';
        this.obj = document.querySelector(config.id) ;
        
        //组件插入位置用元素的insertAdjacentElement或者insertAdjacentHTML方法插入
        //ele.insertAdjacentHTML('afterend', "<a>111</a>")节点插入位置
        //beforebegin:ele元素前;
        //afterend:ele元素后;
        //afterbegin:ele内部首位;
        //befoerend:ele内部末尾;
        this.positon = config.positon || 'afterend';
        //初始化函数
        this.styleSet();
        this.bind();//事件绑定初始化
        this.render();//组件构造第一次渲染
        return this;//返回组件对象以便进行链式编程
    }
    //初始化设置,根据自己需要设置,因为构造函数不能再引用构造,所以用这个函数设置
    init() {
        this.type = this.config.type || '';
    }

    //构造参数的设置获取与更改
    get(x) {
        return this.config[x];
    }
    set(x, y) {
            this.config[x] = y;
            //参数更改后立即实时渲染
            this.styleSet();
            this.render();
        }
        
        //设置内部监听函数部分------------------
        //对象属性对应索引号
    _indexOf(array, key) {
        var i = -1;
        for (var x in array) {
            i += 1
            if (x == key) return i
            return -1
        }
    }

    //绑定触发的key与对应执行的事件
    on(_key, _listener) {
            //this._events存储所有的处理函数
            if (!this._events) {
                this._events = {}
            }
            if (!this._events[_key]) {
                this._events[_key] = null;
            }
           //判断,如果存在触发函数,并且_listener是一个函数时
           //存储对象:_key:_listener
            if (this._indexOf(this._events, _listener) === -1 && typeof _listener === 'function') {
                this._events[_key] = _listener;
            }
            return this
        }
        
        //发送通知on的函数,与on的_key一一对应
        //监听对象为fire的_key后面的参数
    fire(_key) {
            //如果fire与on没有一一对应的_key,直接退出,没有监听对象
            if (!this._events || !this._events[_key]) return
            //获取fire函数的第2个以及之后的参数,返回数组array对象
            var args = Array.prototype.slice.call(arguments, 1) || []
            //提取出on中对应的_key的函数,并执行对应函数
            var _listeners = this._events[_key]
            //执行函数,fn.apply(null,传人函数的参数数组)并执行
            _listeners.apply(null, args)
            return this
        }
        //取消监听
    off(_key, _listener) {
            if (!_key && !_listener) {
                this._events = {}
            }
            //不传监听函数,就去掉当前_key下面的所有的监听函数
            if (_key && !_listener) {
                delete this._events[_key]
            }
            if (_key && _listener) {
                //删除特定属性
                Reflect.deleteProperty(this._events, _key)
            }
            return this;
        }
        
        //绑定事件与监听
    bind(event, fn) {
            try {
                var e0 = this;
                //增加当前组件对象的事件函数
                e0.obj.addEventListener(event, () => {
                    this.render();//要先渲染再监听
                    fn ? fn() : '';//监听函数
                }, false);
            } catch (error) {
                console.log(error)
            }

        }
        //渲染函数
    render() {
        //获取组件类型对应的组建模板
        var eg = this.example(this.type);
        //同一对象组件重复性判断
        var jst = false;
        //获取当前选定类型的所有已创建的组件集合
        var egs = document.querySelectorAll('[class^=' + this.type + ']');
        for (var ele of egs) {
            if (ele.className.indexOf(this.id) != -1) {
       //如果组件集合里面的class名包含当前对象id,说明已经创建过当前组件
       //标记已经创建
                jst = true;
                break;
            }
        }
        // console.log(egs.length)

      //获取当前即将插入组件的位置是否已经有同类型组件
      //如果没有,就新建组件
      //如果已经创建了,就让cEle=this.nowEle当前组件
        var cEle = null;
        switch (this.positon) {
            case 'afterend':
                if (!this.obj.nextElementSibling) break;
                cEle = this.obj.nextElementSibling.className.indexOf(this.type) != -1 ? this.obj.nextElementSibling : null;
                break;
            case 'beforebegin':
                if (!this.obj.previousElementSibling) break;

                cEle = this.obj.previousElementSibling.className.indexOf(this.type) != -1 ? this.obj.nextElementSibling : null;
                break;
            case 'afterbegin':
                if (!this.obj.parentElement.firstChild) break;
                cEle = this.obj.parentElement.firstElementChild.className.indexOf(this.type) != -1 ? this.obj.nextElementSibling : null;
                break;
            case 'beforeend':
                if (!this.obj.parentElement.lastChild) break;
                cEle = this.obj.parentElement.lastElementChild.className.indexOf(this.type) != -1 ? this.obj.nextElementSibling : null;
                break;
        }
        // console.log(cEle)
        //如果已经创建了同类型组件,就不新建组件
        if (cEle && jst == true) {
            this.nowEle = cEle;
        } else if (jst == false) {
            //如果标记到,就直接新建组件
            //如果组件模板是字符,就用先转换成元素再插入
            if (typeof eg == 'string') {
                this.nowEle = document.createElement('div');
                this.nowEle.className = this.type + (egs.length + 1);
                this.nowEle.innerHTML = eg;
            } else {
            //如果是函数创建的元素,就直接插入
                this.nowEle = eg();
            }
            //开始插入组件,并重命名组件的className
            this.obj.insertAdjacentElement(this.positon, this.nowEle);
            this.nowEle.className = this.type + egs.length + '_' + this.id;
            //配置组件的默认样式
            this.nowEle.style = this.defaultStyle;
        }

        let self = this;
        //加载当前类型组件对应的特定事件,必须用self代替this传入函数
        //注意尾部加括号表示执行函数
        self.egEvent(self.type, self.nowEle, self)();
    }
    //销毁所有组件绑定的事件监听
    destroy() {
            this.off();
        }


    //设置组件样式模板,后面继承的特定部件再写
    styleSet() {
        switch (this.style) {
        }
    }
    //设置组件内容模板,后面继承的特定部件再写
    example(type) {
        var pre = {}
        return pre[type]
    }
     //设置组件事件模板,后面继承的特定部件再写
    egEvent(type, nowEle, self) {
            var pre = {}
            return pre[type]
        }
        //
    //设置返回对象,内部对象的外部接口
    getObj() {
        //返回数组,0表示目标对象,1表示创建的组建
        return [this.obj, this.nowEle];
    }
}
 

2)下拉菜单的例子

基类写好之后,可以开始写特定的组件了,用类的继承续写

//下拉菜单meunDrop继承aBase基类
class meunDrop extends aBase {
    //重写初始化参数,自动传入组件类型,标记组件特定的数据
    //数据的引用用this.config,见基类的构造函数部分
    init() {
        this.type = 'meunDrop'
        this.data = this.config.data;
    }
    
    //重写基类的组建模板
    //meun的模板数据,先设计好主体,再转换成字符变量,传入基类的render中自动渲染
    example(type) {
        var meun = "";
        meun += "<div class=\"meun\">";
        meun += "";
        meun += "            <style>";
        meun += "                div[class^='meun'] * {";
        meun += "                    padding: 0;";
        meun += "                    margin: 0;";
        meun += "                    list-style: none;";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li {";
        meun += "                    background-color: rgba(255, 255, 255);";
        meun += "                    margin: 4px 0;";
        meun += "                    box-shadow: 1px 1px 1px 2px rgba(241, 241, 241, 0.5);";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li .title,";
        meun += "                div[class^='meun'] li .title.active {";
        meun += "                    position: relative;";
        meun += "                    display: inline-block;";
        meun += "                    width: 100%;";
        meun += "                    height: 40px;";
        meun += "                    font-size: 18px;";
        meun += "                    line-height: 40px;";
        meun += "                    text-transform: capitalize;";
        meun += "                    font-style: italic;";
        meun += "                    color: white;";
        meun += "                    background-color: rgb(0, 195, 126);";
        meun += "                    box-sizing: border-box;";
        meun += "                    padding: 0 20px;";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li .title::before,";
        meun += "                div[class^='meun'] li .title.active::before {";
        meun += "                    display: block;";
        meun += "                    width: 20px;";
        meun += "                    height: 20px;";
        meun += "                    position: absolute;";
        meun += "                    font-size: 16px;";
        meun += "                    font-style: normal;";
        meun += "                    top: 0;";
        meun += "                    right: 5px;";
        meun += "                    z-index: 4;";
        meun += "                    color: #fff";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li .title::before {";
        meun += "                    content: '▼';";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li .title.active::before {";
        meun += "                    content: '▲';";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li section {";
        meun += "                    height: 0;";
        meun += "                    position: relative;";
        meun += "                    width: 80%;";
        meun += "                    margin: 0 auto;";
        meun += "                    overflow: hidden;";
        meun += "                    transition: height .45s ease;";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li section.active {";
        meun += "                    height: 120px;";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li header {";
        meun += "                    font-size: 20px;";
        meun += "                    margin: 6px 0 2px;";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li header::before {";
        meun += "                    content: '■';";
        meun += "                    position: relative;";
        meun += "                    top: -4px;";
        meun += "                    font-size: 16px;";
        meun += "                    width: 20px;";
        meun += "                    height: 20px;";
        meun += "                    color: rgb(0, 195, 126);";
        meun += "                    \/* line-height: 20px;";
        meun += "                    \/* vertical-align: middle; *\/";
        meun += "                }";
        meun += "                ";
        meun += "                div[class^='meun'] li p {";
        meun += "                    font-size: 12px;";
        meun += "                    margin-bottom: 5px;";
        meun += "                }";
        meun += "            <\/style>";
        meun += "         </div>";

        //非常重要,返回字符化的组建主体,包括样式表部分,可自己测试后在浏览器查看
        return meun;
    }
    
    //重写组件对应的特定事件即组件自动生成的JavaScript代码部分
    //从提前的设计里面改过来到组件里面
    //包括3个参数:(组件类型,当前组件,组件的对应引用self)
    egEvent(type, nowEle, self) {
        var pre = {
           //这里是可以写多个方法,必须要有组件的生成方法函数
           //其他方法自己添加
           //meunDrop组建对应的JavaScript方法
           //这个生成方法是根据构造函数参数自动生成组件的内容
            meunDrop: function() {
                console.log(data)
                var data = self.data;

                if (data.length == 0) {
                    alert('请输入菜单数据')
                    return;
                }
                var iOl = document.createElement('ul');
                for (let i = 0; i < data.length; i++) {
                    var iLi = document.createElement('li');
                    var iTitle = document.createElement('p');
                    iTitle.className = 'title'
                    var iSection = document.createElement('Section');
                    var iHeader = document.createElement('header');
                    var iCont = document.createElement('p');
                    iSection.appendChild(iHeader).appendChild(iCont);
                    iSection.appendChild(iCont);
                    iLi.appendChild(iTitle).appendChild(iSection);
                    iLi.appendChild(iSection);
                    iTitle.innerHTML = data[i][0];
                    iHeader.innerHTML = data[i][1];
                    iCont.innerHTML = data[i][2];
                    iOl.appendChild(iLi);
                }
                nowEle.querySelector("div[class^='meun']").appendChild(iOl)
                var titles = nowEle
                    .querySelectorAll("div[class^='meun'] li .title");
                for (let i = 0; i < titles.length; i++) {
                    titles[i].onclick = () => {
                        for (let k = 0; k < titles.length; k++) {
                            if (k == i) {
                                continue;
                            } else {
                                titles[k].parentElement.children[1].className = '';
                                titles[k].className = 'title';
                            }
                        }
                        titles[i].parentElement.children[1].classList.toggle('active');
                        titles[i].classList.toggle('active');
                    }
                }
            }

        }
        return pre[type]
    }
    
    //外部访问组件对象更改样式的接口
    styleX(sty) {
        //样式用对象方式传入
        //菜单项
        if (sty['title']) {
            var titles = this.getObj()[1].querySelectorAll('.title');
            for (let x of titles) {
                x.style = sty['title']
            }
        }
        //内容标题
        if (sty['header']) {
            var headers = this.getObj()[1].querySelectorAll('header');
            for (let x of headers) {
                x.style = sty['header']
            }
        }
        //菜单项容器/背景
        if (sty['bg']) {
            var bg = this.getObj()[1].querySelectorAll('li');
            for (let x of bg) {
                x.style = sty['bg']
            }
        }
        //主体文字样式
        if (sty['txt']) {
            var txt = this.getObj()[1].querySelectorAll('section p');
            for (let x of txt) {
                x.style = sty['txt']
            }
        }
    }
}

3)新建菜单组件

a、新建组件

let m = new meunDrop({
    id: 'body',//组件插入的对象,默认为body对象,是css3选择器,不是id
    positon: 'afterbegin',//在对象的内部首位创建组建
    //自定义菜单对象[[title,header,txt内容],[菜单2],[菜单3],...]
    //根据data内容自动创建菜单个数
    data: [
        ['菜单 1', 'Just look at the arrow above', 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.'],
        ['菜单 2', 'Just look at the arrow above', 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.'],
        ['菜单 3', '这是一个测试', 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.']
    ],

});

b、自定义更改样式,css3写法

m.styleX({
    title: 'background-color:lightgray;color:rgba(45,45,45,.8)',
    bg: 'background-color:rgba(45,45,45,.2);color:white',
    header: 'color:rgb(236, 78, 32)',
    txt: 'text-shadow: 1px 2px 3px rgba(45,45,45,.5)'
})

c、动图演示

请添加图片描述

d、组件事件的监听

监听组件的菜单个数,当点击键盘时alert菜单个数,这个例子不太好,如果监听输入框的字符个数会好一些
这个主要是自定义的内部监听,外部监听事件也可以,但有些内容访问不了
这个监听可以把对象的字符变动当成监听事件,也可以把其他自定义事件当成监听对象,不一定是浏览器标准事件

m.bind('keyup', () => {
    m.fire('num', m.getObj()[1].querySelectorAll('li').length)
});
m.on('num', e => alert(e))

四、总结

组件库的创建可能一开始比较麻烦,内容较多,但一单基类做好,组件的添加也比较快速方便!
因为是原生js,部分内容可能偏复杂,如有勘误请大佬指点交流!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一路向前的小Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值