JavaScript插件開發


https://www.jianshu.com/p/e177bcc23132

https://www.cnblogs.com/1906859953Lucas/p/10838104.html

https://www.cnblogs.com/1906859953Lucas/p/10845710.html

瞎逼逼
做個原生js插件裝逼一下

再逼逼
部分經常重復的代碼抽象出來,寫到一個單獨的文件中為以后再次使用。再看一下我們的業務邏輯是否可以為團隊服務。

插件不是隨手就寫成的,而是根據自己業務邏輯進行抽象。沒有放之四海而皆准的插件,只有對插件,之所以叫做插件,那么就是開箱即用,或者我們只要添加一些配置參數就可以達到我們需要的結果。如果都符合了這些情況,我們才去考慮做一個插件。

插件封裝的條件
插件自身的作用域與用戶當前的作用域相互獨立,也就是插件內部的私有變量不能影響使用者的環境變量;
插件需具備默認設置參數;
件除了具備已實現的基本功能外,需提供部分API,使用者可以通過該API修改插件功能的默認參數,從而實現用戶自定義插件效果;
插件支持鏈式調用;
插件需提供監聽入口,及針對指定元素進行監聽,使得該元素與插件響應達到插件效果。
插件的外包裝
用函數包裝
插件就是封裝在一個閉包中的函數集

function add(n1, n2){
return n1 + n2;
}
團隊開發中可能存在的問題
命名沖突,全局污染

用全局對象包裝
為了解決這種全局變量污染的問題。這時我們定義一個js對象來接收我們這些工具函數
let plugin = {
add: function(n1, n2) {}, //加
sub: function(n1, n2) {}, //減
mul: function(n1, n2) {} //乘
}
調用: plugin.add(1, 2)
上面的方式,在一定程度上已經解決了全部污染的問題。在團隊協作中只要約定好命名規則了,告知其它同學即可。當然不排除別人重新定義並賦值這個值,這時的解決方案

if(!plugin){ //這里的if條件可以用: (typeof plugin == 'undefined')
var plugin = {
// 邏輯
}
}

// 也可以這樣寫
var plugin;
if(!plugin){
plugin = {
// ...
}
}
備注:為什么可以在此聲明plugin變量?實際上js的解釋執行,會把所有聲明都提前。如果一個變量已經聲明過,后面如果不是在函數內聲明的,則沒有影響的。所以,就算在別的地方聲明 var plugin,也可以在這里再次聲明一次
如何判斷Javascript對象是否存在
利用閉包包裝
上面基本實現了插件的基本功能,不過我們的插件,是定義在全局域里面的。js變量的調用,從全局作用域上查找的速度比私有作用域里面慢的多。所以,我們最好將插件邏輯寫在一個私有作用域里面。實現私有作用域,最好的辦法就是使用閉包。可以把插件當做一個函數,插件內部的變量及函數的私有變量,為了在調用插件后依舊能使用其功能,閉包的作用就是延長函數(插件)內部變量的生命周期,使得函數可以重復調用,而不影響用戶自身作用域。

;(function(global, undefined) {
var plugin = {
add: function(n1, n2) {... }
...
}
//最后將插件對象暴露給全局對象
'plugin' in global && global.plugin = plugin;
})(window);
在定義插件之前添加一個分號,可以解決js合並時可能會產生的錯誤問題;
undefined在老一輩的瀏覽器是不被支持的,直接使用會報錯,js框架要考慮到兼容性,因此增加一個形參undefined,就算有人把外面的undefined定義看,里面的undefined依然不受影響;
把window對象作為參數傳入,是避免了函數執行的時候到外部去查找
直接傳window對象進去,還是不妥當,插件不一定用於瀏覽器上,所以我們不傳參數,直接取當前的全局this對象作為頂級對象用。
;(function(global, undefined) {
"use strict" //使用嚴格模式檢查,使語法更規范
var _global;
var plugin = {
add: function(n1, n2) { ... }
...
}
//最后將插件對象暴露給全局對象
_global = (function() {return this || (0, eval)('this');}());
!('plugin' in _global) && (_global.plugin = plugin);
}());
如此,我們不需要傳入任何參數,並且解決了插件對環境的依事性。如此我們的插件可以在任何宿主環境上運行了。
上面的代碼段中有段奇怪的表達式:(0, eval)('this'),實際上(0,eval)是一個表達式,這個表達式執行之后的結果就是eval這一句相當於執行eval('this')的意思,詳細解釋看此篇:(0,eval)('this')釋義或者看一下這篇(0,eval)('this')

立即自執行函數,有兩種寫法:

// 寫法一
(function() {})
//寫法二
(function(){} ())
兩種寫法沒區別,都是正確的寫法,第二張更像一個整體
知識點
js里面()括號就是將代碼結構變成表達式,被包在()里面的變成表達式之后,則就會立即執行,js中的代碼表達式有很多種

void function(){...}();

// 或者
!function foo(){...}();

//或者
+function foot(){...}();

當然,我們不推薦你這么用,而且亂用可能會產生一些歧義。

使用模塊化的規范包裝
大型開發,多人開發,會產生多個文件,每個人負責一個小功能,那么如何才能將所有人開發的代碼集合起來呢?要實現協同開發插件,需具備如下條件

每個功能互相之間的依賴必須明確,則必須嚴格按照依賴的順序進行合並或者加載
每個子功能分別都要是一個閉包,並且將公共的接口暴露到共享域也及即使一個被主函數暴露的公共對象
關鍵如何實現,有多種辦法,最笨的辦法就是按順序加載js

<script type="text/javascript" src="part1.js"></script>
<script type="text/javascript" src="part2.js"></script>
<script type="text/javascript" src="part3.js"></script>
...
<script type="text/javascript" src="main.js"></script>

但是不推薦這么做,這樣做與我們所追求的插件的封裝性相背。
加載器,比如require、seajs,或者也可以類似Node的方式進行加載,不過在瀏覽器端,我們還得利用打包器實現模塊加載,比如browserify。

為了實現插件的模塊化並且讓我們的插件也是一個模塊,我們就得讓我們的插件也實現模塊化的機制
實際上,只要判斷是否存在加載器,我們就是使用加載器,如果不存在加載器。我們就使用頂級域對象

if(typeof module !== "undefined" && module.exports) {
  module.exports = plugin;
} else if (type define === "function" && define.amd) {
  define(function(){return plugin;});
} else {
  _globals.plugin = plugin;
}

這樣我們的完整插件的樣子應該是這樣的:

// plugin.js
;(function(undefined) {
    "use strict"
    var _global;
    var plugin = {
      add: function(n1, n2) { return n1 + n2; }, //加
      sub: function(n1, n2) { return n1 - n2; }, //減
      mul: function(n1, n2) { return n1 * n2; }, //乘
      div: function(n1, n2) { return n1 / n2; }, //除
      sur: function(n1, n2) { return n1 % n2; } //余
    }
    //最后將插件對象暴露給全局對象
    _global = (function(){return this || (0, eval)('this');}());
    if(typeof module !=="undefined" && module.exports) {
        module.exports = plugin;
    } else if(typeof define === "function" && define.amd) {
        define(function(){return plugin;});
    } else {
        !('plugin' in _global) && (_global.plugin = plugin);
    }
}())

// 引入插件之后,則可以直接使用plugin對象
with(plugin) {
  console.log(add(2, 1)) //3
  console.log(sub(2, 1)) //1
  console.log(mul(2,1)) // 2
  console.log(div(2,1)) // 2
  console.log(sur(2,1)) // 0
}

插件的API
插件的默認參數
我們知道,函數是可以設置默認參數,而不管我們是否傳有參數,我們都應該返回一個值以告訴用戶我做了怎樣的處理

function add(param) {
  var args = !!param ?  Array.prototype.slice.call(arguments) : [];
  return args.reduce(function(pre, cur){
    return pre + cur;
  }, 0);
}

console.log(add()) //不傳參,結果輸出0,則這里已經設置了默認了參數的空數組
console.log(add(1, 2, 3, 4, 5)) //傳參,結果輸出15

則作為一個健壯的js插件,我們應該吧一些基本的狀態參數添加到我們需要的插件上去。
假設還是上面的加減乘除余的需求,我們如何實現插件的默認參數呢? 道理是一樣的

//plugin.js
;(function(undefined) {
  "use strict"
  var _global;

  function result(args, fn) {
    var argsArr = Array.prototype.slice.call(args);
    if(argsArr.length > 0) {
      return argsArr.reduce(fn);
    } else {
      return 0;
    }
  }

  var plugin = {
      add: function() {
          return result(arguments, function(pre, cur) {
              return pre + cur;
          });
      },
      sub: function() {
          return result(arguments, function(pre, cur) {
              return pre - cur;
          });
      },
      mul: function() {
          return result(arguments, function(pre, cur) {
              return pre * cur;
          });
      },
      div: function() {
          return result(arguments, function(pre, cur) {
              return pre / cur;
          });
      },
      sur: function() {
          return result(arguments, function(pre, cur) {
              return pre % cur;
          });
      }
  }

  // 將插件對象暴露給全局對象
  _global = (function(){ return this || (0, eval)('this'); }());
  if(typeof module !=="undefined" && module.exports) {
      module.exports = plugin;
  } else if (typeof define === "function" && define.amd) {
      define(function() {return plugin;});
  } else {
      !('plugin' in _global) && (_global.plugin = plugin);
  }

}());

//輸出結果
with(plugin){
    console.log(add());
    console.log(add(2, 1)); //3
}

參考 with 用法
實際上,插件都有自己的默認參數,就以我們最為常見的表單驗證插件為例 validate.js

插件的鈎子
設計一下插件,參數或者其邏輯肯定不是寫死的,我們得像函數一樣,得讓用戶提供自己的參數去實現用戶的需求。則我們的插件需要提供一個修改默認參數的入口。[API]

我們常將容易被修改和變動的方法或屬性統稱為鈎子(Hook),方法則直接叫鈎子函數,
實際上,我們知道插件可以像一條繩子上掛東西,也可以拿掉掛的東西,那么一個插件,實際上就是個形象上的鏈。不過我們上面的所有鈎子都是掛在對象上的,用於實現鏈並不是很理想。

插件的鏈式調用(利用當前對象)
插件並非都是鏈式調用的,有時候,我們只是用鈎子來實現一個計算並返回結果,取得運算結果就可以了。但是有些時候,我們用鈎子並不需要其返回結果。我們只利用其實現我們的業務邏輯,為了代碼簡潔與方便,我們常常將插件的調用按鏈式的方式進行調用。
最常見的jquery的鏈式調用如下:

$( ).show().css('color','red').width(100).height(100)...
將鏈式調用運用到我們的插件中,將業務結構改為:

var plugin = {
    add: function(n1, n2) { return this; },
    sub: function(n1, n2) { return this; },
    ...    
}

我們只要將插件的當前對象this直接返回,則在下一個方法中,同樣可以引用插件對象plugin的其它鈎子方法,然后調用得時候就可以使用鏈式了

plugin.add().sub().mul().div()  

顯然這樣做並沒有什么意義。我們這里的每一個鈎子函數都只是用來計算並且獲取返回值而已。而鏈式調用本身的意義是用來處理業務邏輯的。

插件的鏈式調用(利用原型鏈)
Js中,萬物皆對象,所有對象都是繼承自原型。JS在創建對象(不論是普通對象還是函數對象)的時候,都有一個叫proto的內容屬性,用於指向創建它的函數對象的原型對象prototype。

更改插件將plugin寫成一個構造函數,我們將插件名換為Calculate避免因為Plugin大寫的時候與window對象中的API沖突。

function Calculate() {}
Calculate.prototype.add = function() {return this;}
Calculate.prototype.sub = function() {return this;}
Calculate.prototype.mul = function() {return this;}

假設我們的插件是對初始化參數進行運算並輸出結果,則

// plugins.js
;(function(undefined) {
    "use strict"
    var _global;
    
    function result(args, type) {
        var argsArr = Array.prototype.slice.call(args);
        if(argsArr.length == 0) return 0;
        switch(type) {
            case 1:  return argsArr.reduce(function(p, c) {return p + c;});
            case 2:  return argsArr.reduce(function(p, c) {return p - c;});
            case 3:  return argsArr.reduce(function(p, c) {return p * c;});
            case 4:  return argsArr.reduce(function(p, c) {return p / c;});
            case 5:  return argsArr.reduce(function(p, c) {return p % c;});
            default: return 0;
      }
  }

  function Calculate() {}
  Calculate.prototype.add = function() { console.log(result(arguments,1)); return this; }
  Calculate.prototype.sub = function() { console.log(result(arguments,2)); return this; }
  Calculate.prototype.mul = function() { console.log(result(arguments,3)); return this; }
  Calculate.prototype.div = function() { console.log(result(arguments,4)); return this; }
  Calculate.prototype.sur = function() { console.log(result(arguments,5)); return this; }

// 最后將插件對象暴露給全局對象

_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = Calculate;
} else if (typeof define === "function" && define.amd) {
define(function(){return Calculate;});
} else {
!('Calculate' in _global) && (_global.Calculate = Calculate);
}
}());
調用插件

var plugin = new Calculate();
plugin
    .add(2, 1)
    .sub(2, 1);

// 結果
// 3
// 1
編寫UI組件
一般情況,如果一個js僅僅是處理一個邏輯,我們稱之為插件,但如果與dom和css有關系並且具備一定的交互性,一般叫組件。
利用原型鏈,可以將一些UI層面的業務代碼封裝在一個小組件,並利用js實現組件的交互性

需求:

實現一個彈層,此彈層可以顯示一些文字提示性的信息;
彈層右上角必須有一個關閉按扭,點擊之后彈層消失;
彈層底部必有一個“確定”按扭,然后根據需求,可以配置多一個“取消”按扭;
點擊“確定”按扭之后,可以觸發一個事件;
點擊關閉/“取消”按扭后,可以觸發一個事件。
html 代碼

index
×
hello world!
css
  • {
    padding: 0;
    margin: 0;
    }

.mydialog {
background: #fff;
box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.3);
overflow: hidden;
width: 300px;
height: 180px;
border: 1px solid #dcdcdc;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}

.close {
position: absolute;
right: 5px;
top: 5px;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
font-size: 18px;
cursor: pointer;
}

.mydialog-cont {
padding: 0 0 50px;
display: table;
width: 100%;
height: 100%;
}

.mydialog-cont .cont {
display: table-cell;
text-align: center;
vertical-align: middle;
width: 100%;
height: 100%;
}

.footer {
display: table;
table-layout: fixed;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #dcdcdc;
}

.footer .btn {
display: table-cell;
width: 50%;
height: 50px;
line-height: 50px;
text-align: center;
cursor: pointer;
}

.footer .btn:last-child {
display: table-cell;
width: 50%;
height: 50px;
line-height: 50px;
text-align: center;
cursor: pointer;
border-left: 1px solid #dcdcdc;
}
插件

function MyDialog() {} // 組件對象
MyDialog.prototype = {
    constructor: this,
    _initial: function() {},
    _parseTpl: function() {},
    _parseToDom:function(){},
    show:function(){},
    hide:function(){},
    css:function(){},
    ...

}

然后就可以將插件的功能都寫上,不過中間的業務邏輯,需要自己一步一步研究。

對象合並函數
// 對象合並
function extend(o, n, override) {
for(var key in n) {
if(n,hasOwnProperty(key) && (!o.hasOwnProperty(key) || override)) {
o[key] = n[key];
}
}
return o;
}
自定義模版引擎解釋函數
function templateEngine(html, data) {
var re = /<%([^%>]+)?%>/g,
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.)?/g,
code = 'var r=[];\n',
cursor = 0;
var match;
var add = function(line, js) {
js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\"') + '");\n' : '');
return add;
}
while (match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
}
查找class獲取dom函數
// 通過class查找dom
if(!('getElementsByClass' in HTMLElement)){
HTMLElement.prototype.getElementsByClass = function(n, tar){
var el = [],
_el = (!!tar ? tar : this).getElementsByTagName('
');
for (var i=0; i<_el.length; i++ ) {
if (!!_el[i].className && (typeof _el[i].className == 'string') && _el[i].className.indexOf(n) > -1 ) {
el[el.length] = _el[i];
}
}
return el;
};
((typeof HTMLDocument !== 'undefined') ? HTMLDocument : Document).prototype.getElementsByClass = HTMLElement.prototype.getElementsByClass;
}
結合工具函數,再去實現每一個鈎子函數具體邏輯結構:

// plugin.js
;(function(undefined) {
"use strict"
var _global;

...

// 插件構造函數 - 返回數組結構
function MyDialog(opt){
    this._initial(opt);
}
MyDialog.prototype = {
    constructor: this,
    _initial: function(opt) {
        // 默認參數
        var def = {
            ok: true,
            ok_txt: '確定',
            cancel: false,
            cancel_txt: '取消',
            confirm: function(){},
            close: function(){},
            content: '',
            tmpId: null
        };
        this.def = extend(def,opt,true);
        this.tpl = this._parseTpl(this.def.tmpId);
        this.dom = this._parseToDom(this.tpl)[0];
        this.hasDom = false;
    },
    _parseTpl: function(tmpId) { // 將模板轉為字符串
        var data = this.def;
        var tplStr = document.getElementById(tmpId).innerHTML.trim();
        return templateEngine(tplStr,data);
    },
    _parseToDom: function(str) { // 將字符串轉為dom
        var div = document.createElement('div');
        if(typeof str == 'string') {
            div.innerHTML = str;
        }
        return div.childNodes;
    },
    show: function(callback){
        var _this = this;
        if(this.hasDom) return ;
        document.body.appendChild(this.dom);
        this.hasDom = true;
        document.getElementsByClass('close',this.dom)[0].onclick = function(){
            _this.hide();
        };
        document.getElementsByClass('btn-ok',this.dom)[0].onclick = function(){
            _this.hide();
        };
        if(this.def.cancel){
            document.getElementsByClass('btn-cancel',this.dom)[0].onclick = function(){
                _this.hide();
            };
        }
        callback && callback();
        return this;
    },
    hide: function(callback){
        document.body.removeChild(this.dom);
        this.hasDom = false;
        callback && callback();
        return this;
    },
    modifyTpl: function(template){
        if(!!template) {
            if(typeof template == 'string'){
                this.tpl = template;
            } else if(typeof template == 'function'){
                this.tpl = template();
            } else {
                return this;
            }
        }
        // this.tpl = this._parseTpl(this.def.tmpId);
        this.dom = this._parseToDom(this.tpl)[0];
        return this;
    },
    css: function(styleObj){
        for(var prop in styleObj){
            var attr = prop.replace(/[A-Z]/g,function(word){
                return '-' + word.toLowerCase();
            });
            this.dom.style[attr] = styleObj[prop];
        }
        return this;
    },
    width: function(val){
        this.dom.style.width = val + 'px';
        return this;
    },
    height: function(val){
        this.dom.style.height = val + 'px';
        return this;
    }
}

_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
    module.exports = MyDialog;
} else if (typeof define === "function" && define.amd) {
    define(function(){return MyDialog;});
} else {
    !('MyDialog' in _global) && (_global.MyDialog = MyDialog);
}

}());
到這一步,我們的插件已經達到了基礎需求了。我們可以在頁面這樣調用:

插件的監聽
彈出框插件我們已經實現了基本的顯示與隱藏的功能。不過我們在怎么時候彈出,彈出之后可能進行一些操作,實際上還是需要進行一些可控的操作。就好像我們進行事件綁定一樣,只有用戶點擊了按扭,才響應具體的事件。那么,我們的插件,應該也要像事件綁定一樣,只有執行了某些操作的時候,調用相應的事件響應。
這種js的設計模式,被稱為 訂閱/發布模式,也被叫做觀察者模式。我們插件中的也需要用到觀察者模式,比如,在打開彈窗之前,我們需要先進行彈窗的內容更新,執行一些判斷邏輯等,然后執行完成之后才顯示出彈窗。在關閉彈窗之后,我們需要執行關閉之后的一些邏輯,處理業務等。這時候我們需要像平時綁定事件一樣,給插件做一些“事件”綁定回調方法。
我們jquery對dom的事件響應是這樣的:
$( ).on("click",function(){})

我們照上面的方式設計了對應的插件響應式這樣的:
mydialog.on('show',function(){})

我們需要實現一個事件機制,以到達監聽的事件效果。關於自定義事件監聽。參考漫談js自定義事件、DOM/偽DOM自定義事件

最終插件

// plugin.js
;(function(undefined) {
"use strict"
var _global;

// 工具函數
// 對象合並
function extend(o,n,override) {
    for(var key in n){
        if(n.hasOwnProperty(key) && (!o.hasOwnProperty(key) || override)){
            o[key]=n[key];
        }
    }
    return o;
}
// 自定義模板引擎
function templateEngine(html, data) {
    var re = /<%([^%>]+)?%>/g,
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
        code = 'var r=[];\n',
        cursor = 0;
    var match;
    var add = function(line, js) {
        js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
        return add;
    }
    while (match = re.exec(html)) {
        add(html.slice(cursor, match.index))(match[1], true);
        cursor = match.index + match[0].length;
    }
    add(html.substr(cursor, html.length - cursor));
    code += 'return r.join("");';
    return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
}
// 通過class查找dom
if(!('getElementsByClass' in HTMLElement)){
    HTMLElement.prototype.getElementsByClass = function(n){
        var el = [],
            _el = this.getElementsByTagName('*');
        for (var i=0; i<_el.length; i++ ) {
            if (!!_el[i].className && (typeof _el[i].className == 'string') && _el[i].className.indexOf(n) > -1 ) {
                el[el.length] = _el[i];
            }
        }
        return el;
    };
    ((typeof HTMLDocument !== 'undefined') ? HTMLDocument : Document).prototype.getElementsByClass = HTMLElement.prototype.getElementsByClass;
}

// 插件構造函數 - 返回數組結構
function MyDialog(opt){
    this._initial(opt);
}
MyDialog.prototype = {
    constructor: this,
    _initial: function(opt) {
        // 默認參數
        var def = {
            ok: true,
            ok_txt: '確定',
            cancel: false,
            cancel_txt: '取消',
            confirm: function(){},
            close: function(){},
            content: '',
            tmpId: null
        };
        this.def = extend(def,opt,true); //配置參數
        this.tpl = this._parseTpl(this.def.tmpId); //模板字符串
        this.dom = this._parseToDom(this.tpl)[0]; //存放在實例中的節點
        this.hasDom = false; //檢查dom樹中dialog的節點是否存在
        this.listeners = []; //自定義事件,用於監聽插件的用戶交互
        this.handlers = {};
    },
    _parseTpl: function(tmpId) { // 將模板轉為字符串
        var data = this.def;
        var tplStr = document.getElementById(tmpId).innerHTML.trim();
        return templateEngine(tplStr,data);
    },
    _parseToDom: function(str) { // 將字符串轉為dom
        var div = document.createElement('div');
        if(typeof str == 'string') {
            div.innerHTML = str;
        }
        return div.childNodes;
    },
    show: function(callback){
        var _this = this;
        if(this.hasDom) return ;
        if(this.listeners.indexOf('show') > -1) {
            if(!this.emit({type:'show',target: this.dom})) return ;
        }
        document.body.appendChild(this.dom);
        this.hasDom = true;
        this.dom.getElementsByClass('close')[0].onclick = function(){
            _this.hide();
            if(_this.listeners.indexOf('close') > -1) {
                _this.emit({type:'close',target: _this.dom})
            }
            !!_this.def.close && _this.def.close.call(this,_this.dom);
        };
        this.dom.getElementsByClass('btn-ok')[0].onclick = function(){
            _this.hide();
            if(_this.listeners.indexOf('confirm') > -1) {
                _this.emit({type:'confirm',target: _this.dom})
            }
            !!_this.def.confirm && _this.def.confirm.call(this,_this.dom);
        };
        if(this.def.cancel){
            this.dom.getElementsByClass('btn-cancel')[0].onclick = function(){
                _this.hide();
                if(_this.listeners.indexOf('cancel') > -1) {
                    _this.emit({type:'cancel',target: _this.dom})
                }
            };
        }
        callback && callback();
        if(this.listeners.indexOf('shown') > -1) {
            this.emit({type:'shown',target: this.dom})
        }
        return this;
    },
    hide: function(callback){
        if(this.listeners.indexOf('hide') > -1) {
            if(!this.emit({type:'hide',target: this.dom})) return ;
        }
        document.body.removeChild(this.dom);
        this.hasDom = false;
        callback && callback();
        if(this.listeners.indexOf('hidden') > -1) {
            this.emit({type:'hidden',target: this.dom})
        }
        return this;
    },
    modifyTpl: function(template){
        if(!!template) {
            if(typeof template == 'string'){
                this.tpl = template;
            } else if(typeof template == 'function'){
                this.tpl = template();
            } else {
                return this;
            }
        }
        this.dom = this._parseToDom(this.tpl)[0];
        return this;
    },
    css: function(styleObj){
        for(var prop in styleObj){
            var attr = prop.replace(/[A-Z]/g,function(word){
                return '-' + word.toLowerCase();
            });
            this.dom.style[attr] = styleObj[prop];
        }
        return this;
    },
    width: function(val){
        this.dom.style.width = val + 'px';
        return this;
    },
    height: function(val){
        this.dom.style.height = val + 'px';
        return this;
    },
    on: function(type, handler){
        // type: show, shown, hide, hidden, close, confirm
        if(typeof this.handlers[type] === 'undefined') {
            this.handlers[type] = [];
        }
        this.listeners.push(type);
        this.handlers[type].push(handler);
        return this;
    },
    off: function(type, handler){
        if(this.handlers[type] instanceof Array) {
            var handlers = this.handlers[type];
            for(var i = 0, len = handlers.length; i < len; i++) {
                if(handlers[i] === handler) {
                    break;
                }
            }
            this.listeners.splice(i, 1);
            handlers.splice(i, 1);
            return this;
        }
    },
    emit: function(event){
        if(!event.target) {
            event.target = this;
        }
        if(this.handlers[event.type] instanceof Array) {
            var handlers = this.handlers[event.type];
            for(var i = 0, len = handlers.length; i < len; i++) {
                handlers[i](event);
                return true;
            }
        }
        return false;
    }
}

// 最后將插件對象暴露給全局對象
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
    module.exports = MyDialog;
} else if (typeof define === "function" && define.amd) {
    define(function(){return MyDialog;});
} else {
    !('MyDialog' in _global) && (_global.MyDialog = MyDialog);
}

}());
調用

var mydialog = new MyDialog({
tmpId: 'dialogTpl',
cancel: true,
content: 'hello world!'
});
mydialog.on('confirm',function(ev){
console.log('you click confirm!');
// 寫你的確定之后的邏輯代碼...
});
document.getElementById('test').onclick = function(){
mydialog.show();
}
案例Demo

參考資料
原生JavaScript插件編寫指南
js原型鏈

作者:Jabo
鏈接:https://www.jianshu.com/p/e177bcc23132
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM