用面向对象封装一个模态框插件

用面向对象封装一个模态框插件


最近在面试,看了很多有关面向对象编程的资料,原来主要是为了应付面试的,哪知道不晓得为啥突然Get到了我的点,越来越感兴趣,便想着自己深入研究下用在工作上,渣渣平时都是面向谷歌编程的,这次要换一下思路了,这两天看了些使用面向对象实现的插件的源码,主要是bootstrap-dialog和circles,也照着这思路写一个类似bootstrap-dialog的模态框组件,比较简单,api也比较少,比较适合新接触面向对象编程的老手(可能并没有),没多少js经验的可能你得先练练基础,本文主要是讨论下如何用面向对象的思路思考实现以及设计api和配置。

目录

-1.效果
-2.实现思路
-3.实现过程
-4.结果和新的问题

效果

模态框说白了就是弹出框,效果如下
用面向对象封装一个模态框插件_第1张图片
效果

可以控制弹出框的打开和关闭,修改标题和内容,自定义按钮个数和相应处理逻辑,并且自带钩子函数和扩展接口。

实现思路

如果是以前那样的面向过程编程的方式,肯定是定义一个html字符串然后生成一个div元素,添加到页面去了,然后再绑定点击事件,最后再控制下整个div的display是否为none就能打开和隐藏了,类似下面这样;

//面向过程伪代码
function buildDialog () {
    var dom = document.createElement('div');
    var html = 
    `
` dom.innerHTML = html; document.querySelector('body').append(dom); var buttonList = document.querySelector('button') buttonList[0].onclick = function (){ } buttonList[1].onclick = function (){ } }

当然你也可以将三个部分分开,使用createElement生成dom,绑定事件后再添加到页面中,这是一般面向过程的方法,这样做有什么问题呢?

最大的问题就是没法扩展和修改:所有元素全部耦合在一起,要改,基本上要考虑到对互相的影响,同时没有留一点缝隙给外部的接口,无法扩展;

让我们看看用面向对象的方式的实现思路。

实现过程

1.根据需要设计配置内容:

首先先列出我们需要实现的api,毕竟也是需要按照使用者的配置来生成的,配置就是一个json对象,把整个模态框的生成当作一个对象。根据我们的了解,一般的对象都是有属性和方法构成的,而这个json就决定了模态框生成这个对象的对象的方法,下面分析下我们需要什么属性和方法。

需要的属性:

1.模态框的头部_title,主体_body,

2.底部为若干个点击按钮,这个又有自己的属性和方法,所以传入的按钮应该也是一个对象,属性有显示的文字:label,点击的回调函数action;

3.如果页面上已经有了一个div用来生成模态框,那么可以把这个div的id名传过来ele;

需要的方法:

1.我们的模态框的生成需要有一个初始化方法,命名为_init()方法;

2.我们需要模态框的打开和收起,也就是:_open(),_close();

3.还有一些钩子函数_hook();

以上就是基本的属性和方法;

这样看起来,用户传进来的json配置应该是这样:

var option = {
            'ele':'content',
            'title':'提示',
            'body':'我是内容',
            'buttons':[
                {
                    label:'确定',
                    action:function() {
                        console.log('click enter')
                    }
                },
                {
                    label:'取消',
                    action:function() {
                        buildContent._close()
                    }
                },
            ],
            'onClosed':function () {
                console.log('close!')
            }
        }

后面的onClosed是关闭模态框的钩子函数,现在先不管,好了api写好了,你可以跟其他人去宣传使用了(误)

2.根据传入的内容写代码

首先就是新建一个对象,把所有的熟悉全部赋值给this;

function BuildDialog (option) {
    this._ele = document.querySelector('#' + option.ele) || addElement();
    this._title = option.title;
    this._body = option.body;
    this._btn = option.buttons;
    this._type = option.type;
    this._hook = new BuildHook();
    this._hook.onClosed = option.onClosed || function (){};
    this._hook.onMounted = option.onMounted || function (){};
    this._hook.onOpened = option.onOpened || function (){};

    //私有
    function addElement () {
        var newElemnt = document.createElement('div')
        document.querySelector('body').append(newElemnt)
        return newElemnt
    }
}

下面那个私有方法是如果用户没有传入一个div,就自己生成一个。那几个hook的钩子函数可以先不要在意。

好了属性我们给对象设好了,下面就是我们的方法,方法一般是写在原型上,这样无论new多少次对象,这个原型都不会被复制而是用的同一个,减少了内存的占用,不过一般一个页面就一个模态框,一般不会出现这样的情况,但为了养成好习惯,先这样写。

BuildDialog.prototype = {
    \\可以不写
    constructor : BuildDialog,
    _init(): function () {

    },
    _open : function () {
        this._ele.style.display = 'block';
    },
    _close : function () {
        this._ele.style.display = 'none';
    },
}

好了基本的对外的api都挂上去了,下面就是核心的init(),也就是生成的设计,在这里我介绍一个设计原则SOLID,感兴趣的可以上网搜一下,里面有一个原则就是单一职责原则,意思就是一个对象就负责做一件事,如果我们直接将整个框交给一个对象去生成和修改,那不还是回到原来那个面向过程那样的高度耦合化的情况么。所以我们需要拆分init(),也就是简单拆分为3个部分,头部header,主体body,footer,各自有相对应的build对象,各自的对象有自己的方法这样组合起来成为一个总体的init()方法。

//组成头部,其他类似
function BuildHeader(html){
//
}

每一个部分都有各自的creat()方法生成都没,而由于我们需要支持各部分的热修改,这就需要每一个部分都有方法直接取到生成的dom元素,当然不是直接在页面取,这时候还没有添加到页面,取不到(添加到页面的职责在BuildDialog对象上)。然后我们需要一个方法将生产的dom赋值到this上,还需要一个get()方法从this上获得这个dom,各个方法互相独立解耦,各有各的职责,这也体现了单一职责原则,只是公用同一个this 而已,也就是:

function BuildHeader(html){
    this.creatHeader = function () {
        var Dom = document.createElement('div');
        Dom.innerHTML = `${html}`
        return Dom
    }
    this.getHeader = function () {
        return this.$hearder
    }
    this.setHeader = function ($html) {
        this.$hearder = $html;
        return this
    }
}

好了这东西怎么给BuildDialog用呢?首先不能直接用,init()只是初始化,各部分的生成需要有另外的方法调用;_BuildHeader,_buildBody,_buildBody
各自的build方法是这样的:

 _BuildHeader : function () {
        var html = this._title;
        var initHeader = new BuildHeader(html);
        var HeaderDom = initHeader.setHeader(initHeader.creatHeader(html)).getHeader();
        this._ele.append(HeaderDom)
        return HeaderDom
    },

讲解一下,取得title传给BuildHeader对象,这个对象就是为了生成header部分,下一行就是生成过程,先生成dom对象在set到this中作为一个内部属性供调用,之所以能够用类似jQuery那样的链式调用方法是因为我们在set方法中最后把this这个对象return出来了,这样就相当于一个将前一个函数的返回值(这里入参是this)作为后一个函数的入参,get函数中就可以从this中取得当前dom了;

然后init()函数就直接这样调用

_init : function(){
        this.HeaderDom = this._BuildHeader();
        this.BodyDom = this._buildBody();
        this.Footer = this._buildFooter();      
        return this
    },

这样在_init()方法中就能按照各自的职责各自调用对应的build方法,而对应的方法中又调用对应的对象,每个对象负责一部分职责,最后组合起来,现在我们的各部分对象的生成的元素比较简单,但后面可以相对应添加不同的元素,比如body部分添加输入框,选择框等复杂的表单元素组合,footer部分还需要添加多个点击按钮,每一个按钮需要绑定不同的点击回调函数,这些都只在相对应那部分职责的对象中完成。主体对象负责调用建造主体,各部分负责建造各部分的细节,最终合成一个整体,这有点像设计模式中的建造者模式。

好了我们现在就开始完成一些细节,我们知道footer部分的dom比较复杂,他是需要又若干个按钮的,需要单独处理,处理的思路也是一样的,拆分,新建一个BuildBtn,负责建造一个button对象,在BuildFooter对象中调用若干次生成完整的footer,同样在BuildBtn中也需要有creat,get和set方法

function BuildBtn (html) {
    this.creatBtn = function () {
        var Dom = document.createElement('button');
        Dom.innerHTML = html.label;
        //用户的action就作为点击的回调函数
        Dom.onclick = html.action;
        return Dom
    }
    this.getBtn = function () {
        return this.$Btn
    }
    this.setBtn = function ($html) {
        this.$Btn= $html
        return this
    }
}

BuildFooter中这样调用

function BuildFooter(html){
    this.creatFooter = function () {
        var Dom = document.createElement('div');
        for(let item of html){
            //html就是buttons这个数组
            var initBtn = new BuildBtn(item);
            var btnHtml = initBtn.setBtn(initBtn.creatBtn(item)).getBtn()
            Dom.append(btnHtml);
        }
        return Dom
    }
    this.getFooter = function () {
        return this.$Footer
    }
    this.setFooter = function ($html) {
        this.$Footer = $html;
        return this
    }
}

这样就生成了若干个按钮,而且点击事件都在各自的对象中绑定了,init()中直接调用BuildFooter就好了,好了到这里我们的基本初始化就完成了,对比面向过程的方式写的代码还是比较多的,但是下一步的实现就方便了。

首先就是各个部分的热修改。在BuildDialog对象中的原型中加一个updateHeader方法;

_updateHeader : function (newTitle) {
        this._title = newTitle;
        //内部就已经缓存了header这个dom对象,不需要再取了;甚至可以直接使用对象.HeaderDom.innerHTML的方式访问修改,但这样又造成了职责的不清晰
        this.HeaderDom.innerHTML = `${this._title}`
        return this
    },

其他部分可以照着写。

然后就是一些钩子函数和拓展:

新设一个钩子属性:

this.hook = {};
//'||'后面是为了让他有值,不至于调用一个underfine出错;
this._hook.onClosed = option.onClosed || function (){};
this._hook.onMounted = option.onMounted || function (){};this._hook.onOpened = option.onOpened || function (){}
//然后在各个需要的地方调用,比如onClosed;
_close : function () {
    this._ele.style.display = 'none';
    this.Modal.style.display = 'none';
    this._hook.onClosed(this);
},

扩展就简单了,用户可以自定义某些方法和属性:

BuildDialog.prototype.addMethod = function (name, fn) {
    this.prototype[name] = fn;
    return this;
}
BuildDialog.prototype.addProp = function (name, prop) {
    this.prototype[name] = prop;
    return this;
}
//用户可以这样用
var newFn = function () {}
BuildDialog.addMethod('newFn', newFn);
BuildDialog.addProp('newProp', 'newProp');
var Dialog = new BuildDialog();
Dialog.newFn();
console.log(Dialog.newProp);

结果和新的问题

好了这样一个模态框就实现了,我们咋使用呢?在html中这样使用就行了

    

第一次用面向对象方式写东西还是问题很多的,比如生成header和body那部分有很多代码是一样的,其实我们可以建立一个buildHtml这样一个基类,然后继承出三个部分的build方法,有兴趣可以自己改下。

你可能感兴趣的:(用面向对象封装一个模态框插件)