用面向?qū)ο蠓庋b一個(gè)模態(tài)框插件

用面向?qū)ο蠓庋b一個(gè)模態(tài)框插件


最近在面試,看了很多有關(guān)面向?qū)ο缶幊痰馁Y料,原來主要是為了應(yīng)付面試的饲宿,哪知道不曉得為啥突然Get到了我的點(diǎn)乖仇,越來越感興趣,便想著自己深入研究下用在工作上类缤,渣渣平時(shí)都是面向谷歌編程的,這次要換一下思路了,這兩天看了些使用面向?qū)ο髮?shí)現(xiàn)的插件的源碼扑毡,主要是bootstrap-dialog和circles,也照著這思路寫一個(gè)類似bootstrap-dialog的模態(tài)框組件盛险,比較簡單瞄摊,api也比較少,比較適合新接觸面向?qū)ο缶幊痰睦鲜郑赡懿]有)苦掘,沒多少js經(jīng)驗(yàn)的可能你得先練練基礎(chǔ)换帜,本文主要是討論下如何用面向?qū)ο蟮乃悸匪伎紝?shí)現(xiàn)以及設(shè)計(jì)api和配置。

目錄

-1.效果
-2.實(shí)現(xiàn)思路
-3.實(shí)現(xiàn)過程
-4.結(jié)果和新的問題

效果

模態(tài)框說白了就是彈出框鹤啡,效果如下
效果

可以控制彈出框的打開和關(guān)閉惯驼,修改標(biāo)題和內(nèi)容,自定義按鈕個(gè)數(shù)和相應(yīng)處理邏輯递瑰,并且自帶鉤子函數(shù)和擴(kuò)展接口祟牲。

實(shí)現(xiàn)思路

如果是以前那樣的面向過程編程的方式,肯定是定義一個(gè)html字符串然后生成一個(gè)div元素抖部,添加到頁面去了说贝,然后再綁定點(diǎn)擊事件,最后再控制下整個(gè)div的display是否為none就能打開和隱藏了慎颗,類似下面這樣乡恕;

//面向過程偽代碼
function buildDialog () {
    var dom = document.createElement('div');
    var html = 
    `<div class ='header'><div>
        <div class = 'body'></div>
    <div class ='footer'>
        <button>確定</button><button>取消</button>    
    </div>
    `
    dom.innerHTML = html;
    document.querySelector('body').append(dom);
    var buttonList = document.querySelector('button')
    buttonList[0].onclick = function (){

    }
    buttonList[1].onclick = function (){

    }
}

當(dāng)然你也可以將三個(gè)部分分開言询,使用createElement生成dom,綁定事件后再添加到頁面中几颜,這是一般面向過程的方法倍试,這樣做有什么問題呢?

最大的問題就是沒法擴(kuò)展和修改:所有元素全部耦合在一起蛋哭,要改县习,基本上要考慮到對(duì)互相的影響,同時(shí)沒有留一點(diǎn)縫隙給外部的接口谆趾,無法擴(kuò)展躁愿;

讓我們看看用面向?qū)ο蟮姆绞降膶?shí)現(xiàn)思路。

實(shí)現(xiàn)過程

1.根據(jù)需要設(shè)計(jì)配置內(nèi)容:

首先先列出我們需要實(shí)現(xiàn)的api沪蓬,畢竟也是需要按照使用者的配置來生成的彤钟,配置就是一個(gè)json對(duì)象,把整個(gè)模態(tài)框的生成當(dāng)作一個(gè)對(duì)象跷叉。根據(jù)我們的了解逸雹,一般的對(duì)象都是有屬性和方法構(gòu)成的,而這個(gè)json就決定了模態(tài)框生成這個(gè)對(duì)象的對(duì)象的方法云挟,下面分析下我們需要什么屬性和方法梆砸。

需要的屬性:

1.模態(tài)框的頭部_title,主體_body园欣,

2.底部為若干個(gè)點(diǎn)擊按鈕帖世,這個(gè)又有自己的屬性和方法,所以傳入的按鈕應(yīng)該也是一個(gè)對(duì)象沸枯,屬性有顯示的文字:label日矫,點(diǎn)擊的回調(diào)函數(shù)action;

3.如果頁面上已經(jīng)有了一個(gè)div用來生成模態(tài)框绑榴,那么可以把這個(gè)div的id名傳過來ele哪轿;

需要的方法:

1.我們的模態(tài)框的生成需要有一個(gè)初始化方法,命名為_init()方法彭沼;

2.我們需要模態(tài)框的打開和收起缔逛,也就是:_open(),_close();

3.還有一些鉤子函數(shù)_hook();

以上就是基本的屬性和方法姓惑;

這樣看起來,用戶傳進(jìn)來的json配置應(yīng)該是這樣:

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

后面的onClosed是關(guān)閉模態(tài)框的鉤子函數(shù)按脚,現(xiàn)在先不管于毙,好了api寫好了,你可以跟其他人去宣傳使用了(誤)

2.根據(jù)傳入的內(nèi)容寫代碼

首先就是新建一個(gè)對(duì)象,把所有的熟悉全部賦值給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
    }
}

下面那個(gè)私有方法是如果用戶沒有傳入一個(gè)div唯沮,就自己生成一個(gè)脖旱。那幾個(gè)hook的鉤子函數(shù)可以先不要在意。

好了屬性我們給對(duì)象設(shè)好了介蛉,下面就是我們的方法萌庆,方法一般是寫在原型上,這樣無論new多少次對(duì)象币旧,這個(gè)原型都不會(huì)被復(fù)制而是用的同一個(gè)践险,減少了內(nèi)存的占用,不過一般一個(gè)頁面就一個(gè)模態(tài)框吹菱,一般不會(huì)出現(xiàn)這樣的情況巍虫,但為了養(yǎng)成好習(xí)慣,先這樣寫鳍刷。

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

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

好了基本的對(duì)外的api都掛上去了占遥,下面就是核心的init(),也就是生成的設(shè)計(jì)输瓜,在這里我介紹一個(gè)設(shè)計(jì)原則SOLID瓦胎,感興趣的可以上網(wǎng)搜一下,里面有一個(gè)原則就是單一職責(zé)原則尤揣,意思就是一個(gè)對(duì)象就負(fù)責(zé)做一件事搔啊,如果我們直接將整個(gè)框交給一個(gè)對(duì)象去生成和修改,那不還是回到原來那個(gè)面向過程那樣的高度耦合化的情況么芹缔。所以我們需要拆分init()坯癣,也就是簡單拆分為3個(gè)部分,頭部header最欠,主體body示罗,footer,各自有相對(duì)應(yīng)的build對(duì)象芝硬,各自的對(duì)象有自己的方法這樣組合起來成為一個(gè)總體的init()方法蚜点。

//組成頭部,其他類似
function BuildHeader(html){
//
}

每一個(gè)部分都有各自的creat()方法生成都沒拌阴,而由于我們需要支持各部分的熱修改绍绘,這就需要每一個(gè)部分都有方法直接取到生成的dom元素,當(dāng)然不是直接在頁面取迟赃,這時(shí)候還沒有添加到頁面陪拘,取不到(添加到頁面的職責(zé)在BuildDialog對(duì)象上)。然后我們需要一個(gè)方法將生產(chǎn)的dom賦值到this上纤壁,還需要一個(gè)get()方法從this上獲得這個(gè)dom左刽,各個(gè)方法互相獨(dú)立解耦,各有各的職責(zé)酌媒,這也體現(xiàn)了單一職責(zé)原則欠痴,只是公用同一個(gè)this 而已迄靠,也就是:

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

好了這東西怎么給BuildDialog用呢?首先不能直接用喇辽,init()只是初始化掌挚,各部分的生成需要有另外的方法調(diào)用;_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對(duì)象旦委,這個(gè)對(duì)象就是為了生成header部分奇徒,下一行就是生成過程,先生成dom對(duì)象在set到this中作為一個(gè)內(nèi)部屬性供調(diào)用缨硝,之所以能夠用類似jQuery那樣的鏈?zhǔn)秸{(diào)用方法是因?yàn)槲覀冊趕et方法中最后把this這個(gè)對(duì)象return出來了摩钙,這樣就相當(dāng)于一個(gè)將前一個(gè)函數(shù)的返回值(這里入?yún)⑹莟his)作為后一個(gè)函數(shù)的入?yún)ⅲ琯et函數(shù)中就可以從this中取得當(dāng)前dom了查辩;

然后init()函數(shù)就直接這樣調(diào)用

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

這樣在_init()方法中就能按照各自的職責(zé)各自調(diào)用對(duì)應(yīng)的build方法胖笛,而對(duì)應(yīng)的方法中又調(diào)用對(duì)應(yīng)的對(duì)象,每個(gè)對(duì)象負(fù)責(zé)一部分職責(zé)宜岛,最后組合起來长踊,現(xiàn)在我們的各部分對(duì)象的生成的元素比較簡單,但后面可以相對(duì)應(yīng)添加不同的元素萍倡,比如body部分添加輸入框身弊,選擇框等復(fù)雜的表單元素組合,footer部分還需要添加多個(gè)點(diǎn)擊按鈕列敲,每一個(gè)按鈕需要綁定不同的點(diǎn)擊回調(diào)函數(shù)阱佛,這些都只在相對(duì)應(yīng)那部分職責(zé)的對(duì)象中完成。主體對(duì)象負(fù)責(zé)調(diào)用建造主體戴而,各部分負(fù)責(zé)建造各部分的細(xì)節(jié)凑术,最終合成一個(gè)整體,這有點(diǎn)像設(shè)計(jì)模式中的建造者模式所意。

好了我們現(xiàn)在就開始完成一些細(xì)節(jié)淮逊,我們知道footer部分的dom比較復(fù)雜,他是需要又若干個(gè)按鈕的扶踊,需要單獨(dú)處理泄鹏,處理的思路也是一樣的,拆分秧耗,新建一個(gè)BuildBtn命满,負(fù)責(zé)建造一個(gè)button對(duì)象,在BuildFooter對(duì)象中調(diào)用若干次生成完整的footer绣版,同樣在BuildBtn中也需要有creat胶台,get和set方法

function BuildBtn (html) {
    this.creatBtn = function () {
        var Dom = document.createElement('button');
        Dom.innerHTML = html.label;
        //用戶的action就作為點(diǎn)擊的回調(diào)函數(shù)
        Dom.onclick = html.action;
        return Dom
    }
    this.getBtn = function () {
        return this.$Btn
    }
    this.setBtn = function ($html) {
        this.$Btn= $html
        return this
    }
}

BuildFooter中這樣調(diào)用

function BuildFooter(html){
    this.creatFooter = function () {
        var Dom = document.createElement('div');
        for(let item of html){
            //html就是buttons這個(gè)數(shù)組
            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
    }
}

這樣就生成了若干個(gè)按鈕,而且點(diǎn)擊事件都在各自的對(duì)象中綁定了杂抽,init()中直接調(diào)用BuildFooter就好了诈唬,好了到這里我們的基本初始化就完成了,對(duì)比面向過程的方式寫的代碼還是比較多的缩麸,但是下一步的實(shí)現(xiàn)就方便了铸磅。

首先就是各個(gè)部分的熱修改。在BuildDialog對(duì)象中的原型中加一個(gè)updateHeader方法杭朱;

_updateHeader : function (newTitle) {
        this._title = newTitle;
        //內(nèi)部就已經(jīng)緩存了header這個(gè)dom對(duì)象阅仔,不需要再取了;甚至可以直接使用對(duì)象.HeaderDom.innerHTML的方式訪問修改弧械,但這樣又造成了職責(zé)的不清晰
        this.HeaderDom.innerHTML = `<span>${this._title}</span>`
        return this
    },

其他部分可以照著寫八酒。

然后就是一些鉤子函數(shù)和拓展:

新設(shè)一個(gè)鉤子屬性:

this.hook = {};
//'||'后面是為了讓他有值,不至于調(diào)用一個(gè)underfine出錯(cuò)刃唐;
this._hook.onClosed = option.onClosed || function (){};
this._hook.onMounted = option.onMounted || function (){};this._hook.onOpened = option.onOpened || function (){}
//然后在各個(gè)需要的地方調(diào)用羞迷,比如onClosed;
_close : function () {
    this._ele.style.display = 'none';
    this.Modal.style.display = 'none';
    this._hook.onClosed(this);
},

擴(kuò)展就簡單了画饥,用戶可以自定義某些方法和屬性:

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);

結(jié)果和新的問題

好了這樣一個(gè)模態(tài)框就實(shí)現(xiàn)了衔瓮,我們咋使用呢?在html中這樣使用就行了

    <script>
        window.onload = function() {
            var option = {'url':'',
            'ele':'content',
            'title':'提示',
            'body':'我是內(nèi)容',
            'buttons':[
                {
                    label:'確定',
                    action:function() {
                        console.log('click enter')
                    }
                },
                {
                    label:'取消',
                    action:function() {
                        buildContent._close()
                    }
                },
            ],
            'onClosed':function () {
                console.log('close!')
            }
        }
            var buildContent = new BuildDialog(option);
            Dialog = buildContent._init();
            Dialog._open();
        }
    </script>

第一次用面向?qū)ο蠓绞綄憱|西還是問題很多的抖甘,比如生成header和body那部分有很多代碼是一樣的热鞍,其實(shí)我們可以建立一個(gè)buildHtml這樣一個(gè)基類,然后繼承出三個(gè)部分的build方法衔彻,有興趣可以自己改下薇宠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市米奸,隨后出現(xiàn)的幾起案子昼接,更是在濱河造成了極大的恐慌,老刑警劉巖悴晰,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慢睡,死亡現(xiàn)場離奇詭異,居然都是意外死亡铡溪,警方通過查閱死者的電腦和手機(jī)漂辐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棕硫,“玉大人髓涯,你說我怎么就攤上這事」纾” “怎么了纬纪?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蚓再,是天一觀的道長。 經(jīng)常有香客問我包各,道長摘仅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任问畅,我火速辦了婚禮娃属,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘护姆。我一直安慰自己矾端,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布卵皂。 她就那樣靜靜地躺著秩铆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渐裂。 梳的紋絲不亂的頭發(fā)上豺旬,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音柒凉,去河邊找鬼族阅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛膝捞,可吹牛的內(nèi)容都是我干的坦刀。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蔬咬,長吁一口氣:“原來是場噩夢啊……” “哼鲤遥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起林艘,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤盖奈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后狐援,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钢坦,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年啥酱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爹凹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镶殷,死狀恐怖禾酱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤颤陶,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布颗管,位于F島的核電站,受9級(jí)特大地震影響指郁,放射性物質(zhì)發(fā)生泄漏忙上。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一闲坎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茬斧,春花似錦腰懂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娄蔼,卻和暖如春怖喻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背岁诉。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工锚沸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涕癣。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓哗蜈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坠韩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子距潘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350