前言瀏覽器
也算老生常談的問題了,再深刻搞一搞怎麼玩兒封裝,若是看到這篇文章的你,正好你也是追求完美的代碼潔癖狂者,那麼這篇文章相信很是適合你。緩存
舉一個例子,編寫一個Person類,具備name和birthday(時間戳)兩個屬性及對應的getter和setter方法,注意,setBirthday輸入的參數是日期字符串,如"2016-04-08"。getBirthday一樣獲得的也是日期字符串。那麼這個類是這樣的——安全
var Person = function(name, birthday) { this.name = name; this.birthday = birthday; // timestamp }; function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } Person.prototype = { setName: function(name) { this.name = name; }, getName: function() { return this.name; }, /** * 設置生日 * @param dateString */ setBirthday: function(dateString) { this.birthday = getTimestampOfInput(dateString); }, /** * 獲取生日 * @returns {*} */ getBirthday: function() { return getFormattedDay(this.birthday); } };
若是採用面向過程的方式去寫,咱們須要藉助自執行匿名函數閉包的方式,如——服務器
// 經常使用模式一:單例/靜態 - 私有變量&共有方法 // 生成一我的 var person = (function() { // 私有變量 var name = ''; var birthday = new Date().getTime(); // 默認是時間戳方式 // 共有方法 return { setName: function(newName) { name = newName; }, getName: function() { return name; }, setBirthday: function(dateString) { // 私有函數 function getTimestampOfInput() { return new Date(dateString).getTime(); } birthday = getTimestampOfInput(); }, getBirthday: function() { return getFormattedDay(birthday); // 函數式 - 不訪問外界變量,沒有閉包的呈現 // 有了輸入,便有了預想中的輸出,不保存狀態 // 私有函數 - 已工具方法存在 function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } } }; })(); person.setName('king'); console.log(person.getName()); person.setBirthday('2016-4-8'); console.log(person.getBirthday());
1、精分面向過程的寫法cookie
要知道,上面的面向過程person是一個單例,這種寫法更像是一種命名空間提供工具函數的方式,如——閉包
1 /** 2 * @file cookie 3 * @author 4 */ 5 define(function (require, exports, module) { 6 7 /** 8 * 操做 cookie 9 * 10 * 對外暴露三個方法: 11 * 12 * get() 13 * set() 14 * remove() 15 * 16 * 使用 cookie 必須瞭解的知識: 17 * 18 * 一枚 cookie 有以下屬性: 19 * 20 * key value domain path expires secure 21 * 22 * domain: 瀏覽器只向指定域的服務器發送 cookie,默認是產生 Set-Cookie 響應的服務器的主機名 23 * path: 爲特定頁面指定 cookie,默認是產生 Set-Cookie 響應的 URL 的路徑 24 * expires: 日期格式爲(Weekday, DD-MON-YY HH:MM:SS GMT)惟一合法的時區是 GMT,默認是會話結束時過時 25 * secure: 使用 ssl 安全鏈接時纔會發送 cookie 26 * 27 * 有點相似命名空間的意思 28 * 29 */ 30 31 'use strict'; 32 33 /** 34 * 一小時的毫秒數 35 * 36 * @inner 37 * @const 38 * @type {number} 39 */ 40 var HOUR_TIME = 60 * 60 * 1000; 41 42 /** 43 * 把 cookie 字符串解析成對象 44 * 45 * @inner 46 * @param {string} cookieStr 格式爲 key1=value1;key2=value2; 47 * @return {Object} 48 */ 49 function parse(cookieStr) { 50 51 if (cookieStr.indexOf('"') === 0) { 52 // 若是 cookie 按照 RFC2068 規範進行了轉義,要轉成原始格式 53 cookieStr = cookieStr.slice(1, -1) 54 .replace(/\\"/g, '"') 55 .replace(/\\\\/g, '\\'); 56 } 57 58 var result = { }; 59 60 try { 61 // Replace server-side written pluses with spaces. 62 // If we can't decode the cookie, ignore it, it's unusable. 63 // If we can't parse the cookie, ignore it, it's unusable. 64 cookieStr = decodeURIComponent(cookieStr.replace(/\+/g, ' ')); 65 66 $.each( 67 cookieStr.split(';'), 68 function (index, part) { 69 var pair = part.split('='); 70 var key = $.trim(pair[0]); 71 var value = $.trim(pair[1]); 72 73 if (key) { 74 result[key] = value; 75 } 76 } 77 ); 78 } 79 catch (e) { } 80 81 return result; 82 } 83 84 /** 85 * 設置一枚 cookie 86 * 87 * @param {string} key 88 * @param {string} value 89 * @param {Object} options 90 */ 91 function setCookie(key, value, options) { 92 93 var expires = options.expires; 94 95 if ($.isNumeric(expires)) { 96 var hours = expires; 97 expires = new Date(); 98 expires.setTime(expires.getTime() + hours * HOUR_TIME); 99 } 100 101 document.cookie = [ 102 encodeURIComponent(key), '=', encodeURIComponent(value), 103 expires ? ';expires=' + expires.toUTCString() : '', 104 options.path ? ';path=' + options.path : '', 105 options.domain ? ';domain=' + options.domain : '', 106 options.secure ? ';secure' : '' 107 ].join(''); 108 } 109 110 /** 111 * 讀取 cookie 的鍵值 112 * 113 * 若是不傳 key,則返回完整的 cookie 鍵值對象 114 * 115 * @param {string=} key 116 * @return {string|Object|undefined} 117 */ 118 exports.get = function (key) { 119 var result = parse(document.cookie); 120 return $.type(key) === 'string' ? result[key] : result; 121 }; 122 123 /** 124 * 寫入 cookie 125 * 126 * @param {string|Object} key 若是 key 是 string,則必須傳 value 127 * 若是 key 是 Object,可批量寫入 128 * @param {*=} value 129 * @param {Object=} options 130 * @property {number=} options.expires 過時小時數,如 1 表示 1 小時後過時 131 * @property {string=} options.path 路徑,默認是 / 132 * @property {string=} options.domain 域名 133 * @property {boolean=} options.secure 是否加密傳輸 134 */ 135 exports.set = function (key, value, options) { 136 137 if ($.isPlainObject(key)) { 138 options = value; 139 value = null; 140 } 141 142 options = $.extend({ }, exports.defaultOptions, options); 143 144 if (value === null) { 145 $.each( 146 key, 147 function (key, value) { 148 setCookie(key, value, options); 149 } 150 ); 151 } 152 else { 153 setCookie(key, value, options); 154 } 155 }; 156 157 /** 158 * 刪除某個 cookie 159 * 160 * @param {string} key 161 * @param {Object=} options 162 * @property {string=} options.path cookie 的路徑 163 * @property {string=} options.domain 域名 164 * @property {boolean=} options.secure 是否加密傳輸 165 */ 166 exports.remove = function (key, options) { 167 168 if (key == null) { 169 return; 170 } 171 172 options = options || { }; 173 options.expires = -1; 174 175 setCookie( 176 key, 177 '', 178 $.extend({ }, exports.defaultOptions, options) 179 ); 180 }; 181 182 /** 183 * 默認屬性,暴露給外部修改 184 * 185 * @type {Object} 186 */ 187 exports.defaultOptions = { 188 path: '/' 189 }; 190 191 });
對於這個person單例或者理解爲一個普通的(命名空間)對象,咱們會發現兩個工具函數(用於birthday的格式化)——dom
getTimestampOfInput:服務於setBirthday這個方法
getFormattedDay:服務於getBirthday這個方法
1.1 將工具函數私有性封裝,利用閉包緩存該工具函數ide
會發現,每一次執行setBirthday,都會建立getTimestampOfInput這個函數,執行完setBirthday以後,getTimestampOfInput又會被銷燬;同理getFormattedDay方法。私有性,咱們作到了,可是每一次都須要去建立工具函數(getTimestampOfInput和getFormattedDay)。若是咱們想把工具函數僅僅執行一次,能夠這樣寫——函數
// 經常使用模式一:單例/靜態 - 私有變量&共有方法 // 生成一我的 var person = (function() { // 私有變量 var name = ''; var birthday = new Date().getTime(); // 默認是時間戳方式 // 共有方法 return { setName: function(newName) { name = newName; }, getName: function() { return name; }, setBirthday: (function() { // 私有函數 function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } return function(dateString) { getTimestampOfInput(dateString); }; })(), getBirthday: (function() { // 函數式 - 不訪問外界變量,沒有閉包的呈現 // 有了輸入,便有了預想中的輸出,不保存狀態 // 私有函數 - 已工具方法存在 function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } return function() { return getFormattedDay(birthday); }; })() }; })();
要看見裏面用了一層閉包哦,也就是多須要耗損內存,但換來了性能上的優化。工具
1.2 將工具函數抽取爲私有
咱們繼續變態的走下去,把這兩個工具函數抽取出來,如——
// 經常使用模式一:單例/靜態 - 私有變量&共有方法 // 生成一我的 var person = (function() { // 私有變量 var name = ''; var birthday = new Date().getTime(); // 默認是時間戳方式 // 函數式 - 不訪問外界變量,沒有閉包的呈現 // 有了輸入,便有了預想中的輸出,不保存狀態 // 私有函數 - 已工具方法存在 function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } // 函數式 - 不訪問外界變量,沒有閉包的呈現 // 有了輸入,便有了預想中的輸出,不保存狀態 // 私有函數 - 已工具方法存在 function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } // 共有方法 return { setName: function(newName) { name = newName; }, getName: function() { return name; }, setBirthday: function(dateString) { birthday = getTimestampOfInput(dateString); }, getBirthday: function() { return getFormattedDay(birthday); } }; })();
那麼這兩個工具方法一樣具備私有性,可是它可以服務的方法就更多了,全部對外暴露的方法(如未來有個新的方法getCreateDay),均可以使用這兩個工具函數。
1.3 將工具函數顯示聲明爲私有
OK,咱們看到上面的例子中,name,birthday,包含兩個工具方法都是私有的,咱們可使用"_"的方式來顯示聲明它是私有的,就能夠這樣去改裝——
// 經常使用模式一:靜態私有變量&共有方法 // 生成一我的 var person = { // 單例的私有屬性 - 或者可理解爲靜態變量 _name: '', // 單例的私有屬性 - 或者可理解爲靜態變量 _birthday: new Date().getTime(), // 默認是時間戳方式 // 工具函數 _getTimestampOfInput: function(dateString) { return new Date(dateString).getTime(); }, // 工具函數 _getFormattedDay: function(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); }, // 共有方法 setName: function(newName) { this._name = newName; }, getName: function() { return this._name; }, setBirthday: function(dateString) { this._birthday = this._getTimestampOfInput(dateString); }, getBirthday: function() { return this._getFormattedDay(this._birthday); } };
看起來還不錯,可是私有屬性仍是能夠被訪問的,如person._birthday,
1.4 利用private和public命名空間來實現私有和共有
那麼,咱們想要讓私有的屬性達到真正的私有,並藉助命名空間的方式,會有這個方式——
// 經常使用模式一:靜態私有變量&共有方法 // 生成一我的 var person = (function() { // 該對象保存靜態屬性 // 保存單例的狀態 var _private = { // 單例的私有屬性 - 或者可理解爲靜態變量 _name: '', // 單例的私有屬性 - 或者可理解爲靜態變量 _birthday: new Date().getTime(), // 默認是時間戳方式 // 工具函數 getTimestampOfInput: function(dateString) { return new Date(dateString).getTime(); }, // 工具函數 _getFormattedDay: function(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); }, getFormattedDayOfBirthday: function() { return this._getFormattedDay(this._birthday); } }; // 共有對象 var _public = { setName: function(newName) { _private._name = newName; }, // 直接從_private對象中獲取 getName: function() { return _private._name; }, /** * 可直接操做_private中的靜態屬性 * @param dateString */ setBirthday: function(dateString) { _private._birthday = _private.getTimestampOfInput(dateString); }, getBirthday: function() { return _private.getFormattedDayOfBirthday(); } }; return _public; })();
_private和_public這兩個命名空間還不錯。在此基礎上,建議把工具函數拿出來,能夠這樣——
// 經常使用模式一:靜態私有變量&共有方法 // 生成一我的 var person = (function() { // 工具函數 // 可供_private和_public對象共用 function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } // 工具函數 // 可供_private和_public對象共用 function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } // 該對象保存靜態屬性 // 保存單例的狀態 var _private = { // 單例的私有屬性 - 或者可理解爲靜態變量 _name: '', // 單例的私有屬性 - 或者可理解爲靜態變量 _birthday: new Date().getTime() // 默認是時間戳方式 }; // 共有對象 var _public = { setName: function(newName) { _private._name = newName; }, // 直接從_private對象中獲取 getName: function() { return _private._name; }, /** * 可直接操做_private中的靜態屬性 * @param dateString */ setBirthday: function(dateString) { _private._birthday = getTimestampOfInput(dateString); }, getBirthday: function() { return getFormattedDay(_private._birthday); } }; return _public; })();
1.5 將工具函數就近於它的調用者
有些同窗很是喜歡將工具函數靠近與它的調用者,相似於這樣——
// 經常使用模式一:靜態私有變量&共有方法 // 生成一我的 var person = (function() { // 該對象保存靜態屬性 // 保存單例的狀態 var _private = { // 單例的私有屬性 - 或者可理解爲靜態變量 _name: '', // 單例的私有屬性 - 或者可理解爲靜態變量 _birthday: new Date().getTime() // 默認是時間戳方式 }; _private.name = ''; _private.birthday = new Date().getTime(); // 默認是時間戳方式 var _public = {}; _public.setName = function(newName) { _private._name = newName; }; _public.getName = function() { return _private._name; }; // 工具函數 // 可供_private和_public對象共用 function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } _public.setBirthday = function(dateString) { _private._birthday = getTimestampOfInput(dateString); }; // 工具函數 // 可供_private和_public對象共用 function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } _public.getBirthday = function() { return getFormattedDay(_private._birthday); }; return _public; })();
1.6 將工具函數放入util等全局命名空間
一樣的,咱們發現這兩個工具函數具備通用性,能夠放置於全局,供全部函數使用,那麼就有這樣的方式,如——
// 這裏的工具類,能夠以單獨文件存在,供全局工程來使用 var util = { /** * 生日格式化顯示 * @param timestamp * @returns {string} * @private */ getFormattedDay: function(timestamp) { // 模擬實現靜態方法 var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); }, /** * 根據用戶輸入來獲取時間戳,如輸入'1995-10-05' * @param timestamp * @returns {string} * @private */ getTimestampOfInput: function(dateString) { return new Date(dateString).getTime(); } }; var person = (function() { // 私有變量 var name = ''; var birthday = new Date().getTime(); // 默認是時間戳方式 // 共有方法 return { setName: function(newName) { name = newName; }, getName: function() { return name; }, setBirthday: function(dateString) { birthday = util.getTimestampOfInput(dateString); }, getBirthday: function() { return util.getFormattedDay(birthday); } }; })();
上面這種方式,也是咱們最經常使用的方式,很直觀,易維護。
OK,那麼面向過程的寫法方式,就算是精分完了,很變態對不對?
總之,沒有嚴格的對錯,按照你認同喜歡的模式來。下面精分一下面向對象的寫法。
2、精分面向對象的寫法
面向對象的寫法,要注意prototype中的方法供全部實例對象所共有,且這裏的方法都是對實例狀態變動的說明,即對實例屬性的操做的變動。
2.1 不要把工具函數放入prototype中
基於前言裏面的例子,咱們經常不注意的將工具函數也都放在prototype當中,如——
// 多實例 var Person = function(name, birthday) { this.name = name; this.birthday = birthday; // timestamp }; Person.prototype = { setName: function(name) { this.name = name; }, getName: function() { return this.name; }, /** * 設置生日 * @param dateString */ setBirthday: function(dateString) { this.birthday = this._getTimestampOfInput(dateString); }, // 工具函數 _getTimestampOfInput: function(dateString) { return new Date(dateString).getTime(); }, /** * 獲取生日 * @returns {*} */ getBirthday: function() { return this._getFormattedDay(this.birthday); }, // 工具函數 _getFormattedDay: function(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } };
會看見上面的_getTimestampOfInput和_getFormattedDay兩個方法也都放置在了prototype當中,然而這裏的方法並無操做實例屬性,所以不該該將這類工具方法置於prototype當中。
2.2 不要將緩存變量放入this當中
還有一個你們經常犯的一個大錯誤,就是習慣性把各個方法間通信的變量放入到this當中,以下——
var Person = function(name, birthday) { this.name = name; this.birthday = birthday; // timestamp }; function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } Person.prototype = { setName: function(name) { this.name = name; }, getName: function() { return this.name; }, /** * 設置生日 * @param dateString */ setBirthday: function(dateString) { this.birthday = getTimestampOfInput(dateString); }, /** * 獲取生日 * @returns {*} */ getBirthday: function() { // 不要把緩存變量放置於this中 this.birthdayOfFormatted = getFormattedDay(this.birthday); return this.birthdayOfFormatted; } };
會看到,這裏的this.birthdayOfFormatted是一個緩存變量,並不能表明這個實例的某個狀態。好了,咱們回到正確的方式。
2.3 將工具函數就近於方法的調用者
// 多實例 - 抽取工具函數 var Person = function(name, birthday) { this.name = name; this.birthday = birthday; // timestamp }; Person.prototype.setName = function(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; }; // 工具函數 function getTimestampOfInput(dateString) { return new Date(dateString).getTime(); } Person.prototype.setBirthday = function(dateString) { this.birthday = getTimestampOfInput(dateString); }; // 工具函數 function getFormattedDay(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); } Person.prototype.getBirthday = function() { return getFormattedDay(this.birthday); };
在維護性方面略勝一籌,主要看我的的變成習慣。
2.4 將工具函數放入類命名空間中,充當類的靜態函數
// 多實例 - 抽取工具函數 var Person = function(name, birthday) { this.name = name; this.birthday = birthday; // timestamp }; // 工具函數 - 對外靜態變量 Person.getTimestampOfInput = function (dateString) { return new Date(dateString).getTime(); }; // 工具函數 - 對外靜態變量 Person.getFormattedDay = function(timestamp) { var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); }; Person.prototype = { setName: function(name) { this.name = name; }, getName: function() { return this.name; }, /** * 設置生日 * @param dateString */ setBirthday: function(dateString) { this.birthday = Person.getTimestampOfInput(dateString); }, /** * 獲取生日 * @returns {*} */ getBirthday: function() { return Person.getFormattedDay(this.birthday); } };
我我的比較推薦這種寫法,固然也能夠把工具函數放入某個相似於util的命名空間中,供全局調用。
2.5 將工具函數放入util等全局命名空間
// 這裏的工具類,能夠以單獨文件存在,供全局工程來使用 var util = { /** * 生日格式化顯示 * @param timestamp * @returns {string} * @private */ getFormattedDay: function(timestamp) { // 模擬實現靜態方法 var datetime = new Date(timestamp); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var date = datetime.getDate(); return year + '-' + (String(month).length < 2 ? "0" + month : month) + "-" + (String(date).length < 2 ? "0" + date : date); }, /** * 根據用戶輸入來獲取時間戳,如輸入'1995-10-05' * @param timestamp * @returns {string} * @private */ getTimestampOfInput: function(dateString) { return new Date(dateString).getTime(); } }; // 多實例 - 抽取工具函數 var Person = function(name, birthday) { this.name = name; this.birthday = birthday; // timestamp }; Person.prototype = { setName: function(name) { this.name = name; }, getName: function() { return this.name; }, /** * 設置生日 * @param dateString */ setBirthday: function(dateString) { this.birthday = util.getTimestampOfInput(dateString); }, /** * 獲取生日 * @returns {*} */ getBirthday: function() { return util.getFormattedDay(this.birthday); } };
好啦,整個面向對象的寫法方式介紹到這兒。
總之,歸於一點——要知道什麼方法能夠當作工具函數處理,併合理地放置工具函數的位置。
3、總結
整篇文章主要圍繞工具函數的寫法展開,模式不一樣,沒有對與錯,依照自身的編碼習慣而定。歡迎看到文章的博友補充。