做爲一個前端er,若是不會寫一個小插件,都很差意思說本身是混前端界的。寫還不能依賴jquery之類的工具庫,不然裝得不夠高端。那麼,如何才能裝起來讓本身看起來逼格更高呢?固然是利用js純原生的寫法啦。之前一直說,掌握了js原生,就基本上能夠解決前端的全部腳本交互工做了,這話大致上是有些浮誇了。不過,也從側面說明了原生js在前端中佔着多麼重要的一面。好了。廢話很少說。我們就來看一下怎麼去作一個本身的js插件吧。javascript
咱們寫代碼,並非全部的業務或者邏輯代碼都要抽出來複用。首先,咱們得看一下是否須要將一部分常常重複的代碼抽象出來,寫到一個單獨的文件中爲之後再次使用。再看一下咱們的業務邏輯是否能夠爲團隊服務。
插件不是隨手就寫成的,而是根據本身業務邏輯進行抽象。沒有放之四海而皆準的插件,只有對插件,之因此叫作插件,那麼就是開箱即用,或者咱們只要添加一些配置參數就能夠達到咱們須要的結果。若是都符合了這些狀況,咱們纔去考慮作一個插件。css
一個可複用的插件須要知足如下條件:html
關於插件封裝的條件,能夠查看一篇文章:原生JavaScript插件編寫指南
而我想要說明的是,如何一步一步地實現個人插件封裝。因此,我會先從簡單的方法函數來作起。前端
所謂插件,其實就是封裝在一個閉包中的一種函數集。我記得剛開始寫js的時候,我是這樣乾的,將我想要的邏輯,寫成一個函數,而後再根據不一樣須要傳入不一樣的參數就能夠了。
好比,我想實現兩個數字相加的方法:java
function add(n1,n2) { return n1 + n2; } // 調用 add(1,2) // 輸出:3
這就是咱們要的功能的簡單實現。若是僅僅只不過實現這麼簡單的邏輯,那已經能夠了,不必弄一些花裏胡哨的東西。js函數自己就能夠解決絕大多數的問題。不過咱們在實際工做與應用中,通常狀況的需求都是比較複雜得多。
若是這時,產品來跟你說,我不只須要兩個數相加的,我還要相減,相乘,相除,求餘等等功能。這時候,咱們怎麼辦呢?
固然,你會想,這有什麼難的。直接將這堆函數都寫出來不就完了。而後都放在一個js文件裏面。須要的時候,就調用它就行了。node
// 加 function add(n1,n2) { return n1 + n2; } // 減 function sub(n1,n2) { return n1 - n2; } // 乘 function mul(n1,n2) { return n1 * n2; } // 除 function div(n1,n2) { return n1 / n2; } // 求餘 function sur(n1,n2) { return n1 % n2; }
OK,如今已經實現咱們所須要的全部功能。而且咱們也把這些函數都寫到一個js裏面了。若是是一我的在用,那麼能夠很清楚知道本身是否已經定義了什麼,而且知道本身寫了什麼內容,我在哪一個頁面須要,那麼就直接引入這個js文件就能夠搞定了。
不過,若是是兩我的以上的團隊,或者你與別人一塊兒協做寫代碼,這時候,另外一我的並不知道你是否寫了add方法,這時他也定義了一樣的add方法。那麼大家之間就會產生命名衝突,通常稱之爲變量的 全局污染jquery
爲了解決這種全局變量污染的問題。這時,咱們能夠定義一個js對象來接收咱們這些工具函數。git
var plugin = { add: function(n1,n2){...},//加 sub: function(n1,n2){...},//減 mul: function(n1,n2){...},//乘 div: function(n1,n2){...},//除 sur: function(n1,n2){...} //餘 } // 調用 plugin.add(1,2)
上面的方式,約定好此插件名爲plugin
,讓團隊成員都要遵照命名規則,在必定程度上已經解決了全局污染的問題。在團隊協做中只要約定好命名規則了,告知其它同窗便可以。固然不排除有個別人,接手你的項目,並不知道此全局變量已經定義,則他又定義了一次並賦值,這時,就會把你的對象覆蓋掉。固然,可能你會這麼幹來解決掉命名衝突問題:github
if(!plugin){ //這裏的if條件也能夠用: (typeof plugin == 'undefined') var plugin = { // 以此寫你的函數邏輯 } }
或者也能夠這樣寫:npm
var plugin; if(!plugin){ plugin = { // ... } }
這樣子,就不會存在命名上的衝突了。
也許有同窗會疑問,爲何能夠在此聲明plugin變量?實際上js的解釋執行,會把全部聲明都提早。若是一個變量已經聲明過,後面若是不是在函數內聲明的,則是沒有影響的。因此,就算在別的地方聲明過var plugin,我一樣也以能夠在這裏再次聲明一次。關於聲明的相關資料能夠看阮一鋒的如何判斷Javascript對象是否存在。
基本上,這就能夠算是一個插件了。解決了全局污染問題,方法函數能夠抽出來放到一單獨的文件裏面去。
上面的例子,雖然能夠實現了插件的基本上的功能。不過咱們的plugin對象,是定義在全局域裏面的。咱們知道,js變量的調用,從全局做用域上找查的速度會比在私有做用域裏面慢得多得多。因此,咱們最好將插件邏輯寫在一個私有做用域中。
實現私有做用域,最好的辦法就是使用閉包。能夠把插件當作一個函數,插件內部的變量及函數的私有變量,爲了在調用插件後依舊能使用其功能,閉包的做用就是延長函數(插件)內部變量的生命週期,使得插件函數能夠重複調用,而不影響用戶自身做用域。
故需將插件的全部功能寫在一個當即執行函數中:
;(function(global,undefined) { var plugin = { add: function(n1,n2){...} ... } // 最後將插件對象暴露給全局對象 'plugin' in global && global.plugin = plugin; })(window);
對上面的代碼段傳參問題進行解釋一下:
undefined
定義了,裏面的 undefined 依然不受影響;其實,咱們以爲直接傳window對象進去,我以爲仍是不太穩當。咱們並不肯定咱們的插件就必定用於瀏覽器上,也有可能使用在一些非瀏覽端上。因此咱們還能夠這麼幹,咱們不傳參數,直接取當前的全局this對象爲做頂級對象用。
;(function(global,undefined) { "use strict" //使用js嚴格模式檢查,使語法更規範 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(){...}();
固然,咱們不推薦你這麼用。並且亂用可能會產生一些歧義。
到這一步,咱們的插件的基礎結構就已經算是完整的了。
雖然上面的包裝基本上已經算是ok了的。可是若是是多我的一塊兒開發一個大型的插件,這時咱們要該怎麼辦呢?多人合做,確定會產生多個文件,每一個人負責一個小功能,那麼如何才能將全部人開發的代碼集合起來呢?這是一個討厭的問題。要實現協做開發插件,必須具有以下條件:
關鍵如何實現,有不少種辦法。最笨的辦法就是按順序加載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 (typeof 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 }
咱們知道,函數是能夠設置默認參數這種說法,而無論咱們是否傳有參數,咱們都應該返回一個值以告訴用戶我作了怎樣的處理,好比:
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()); // 0 console.log(sub()); // 0 console.log(mul()); // 0 console.log(div()); // 0 console.log(sur()); // 0 console.log(add(2,1)); // 3 console.log(sub(2,1)); // 1 console.log(mul(