所有的過失在未犯以前,都已定下應處的懲罰
引言
在這一篇,我打算放飛自我,既然是亂燉..那么就可以光明正大的想到什么寫什么,全無章法,天馬行空。
然,內容不會脫離基礎的范圍,所以不會多么難懂,來源則是偶然發現的讀書筆記和以前在開發中記錄的一些小Tip。
既然是Tip,不免會顯得零碎,既然有前言,我想定個大綱是為寫作之常理,筆記依然。
主要包括以下內容:
- 編程風格和推薦的編程規范(通常團隊會有一個規范,雖然不一定強制執行)。
- 一些小知識點,有些可能有助于解決Bug有些則這輩子不會用到第二次。
- 一些常用的代碼塊,通常來源于網上,多少會摻雜一些個人理解。
和網上大多數的文章一般,由于水平有限,在這種可以有很多解釋的概念上,理解往往會出現偏差,這份筆記也不能免俗。
假如有讀者的存在,還希望在讀這篇時保持自己獨立思考的能力,避免被不必要的帶偏。
筆者雖會竟可能的去驗證這些內容,但畢竟金無足赤,這篇文章輸出的內容將會很主觀,如有建議還望賜教。
編程規范和一些約定成俗的內容
這部分,我也不一定能嚴格執行,而且很主觀,很多內容僅僅是適用于之前做的項目,可以有選擇的遵循或者干脆全然不顧。
“程序是寫給人讀的,只是偶爾讓計算機執行一下”
良好的編程習慣能使人在寫代碼時思維更加敏捷,編寫起來更為流暢,甚至執行起來效率更高。
而統一的編程風格則可以使一個團隊的項目具備更好的可讀性,通常可讀性就意味著便于維護和擴展。
通常情況,一個項目不是作完就算了的,它需要迭代,升級,添加需求。
而如若為了趕工時,而至可讀性于不顧,到項目即將完成的拂曉,迎接你的恐怕將是物理上早上四點的太陽。
首先,讓我們來看看在我為數尚短的生產生活中,比較常見到的規范。
//絕對不要出現魔法數字和魔法變量,無論何時何地何因都不要
//所謂魔法數字:通常是1,2,3,4
//而魔法變量則通常是:a,b,c,d 它們往往很萬能
switch(a){
case 1:
//業務邏輯
..........
//
break;
case 2:
//業務邏輯
.......
//
break;
case 3:
//興許完全無關的業務邏輯
.....
//
break;
}
//沒有注釋的情況下這些1 2 3 就是魔法數字,沒人知道它是怎么來的,即使有注釋它們依舊難懂。
//而a是個魔法變量,沒人知道它是做什么的即使有注釋。
上面這段代碼還有一個問題,沒有默認操作,不知道什么時候就會報錯,如果之前沒有對數據類型做限制,也許會更糟糕。
在實際的生產生活中,在這種代碼上做擴展將如履薄冰,有時重新寫一遍反而更快。
//那魔法數字和魔法變量該怎么改進呢?
//我看見過一種命名方式:也就是以功能命名,避免使用Flag,來一個例子。
var documenFlag //看到這個鬼知道是什么,某種意義上和魔法變量差不多
var documenFormType //正確的做法
//明顯下面這個要好懂得多
//魔法數字通常產生于枚舉類型,比如一個Select,很多人圖省事直接將數據庫里提取出來的值就這么應用于前端頁面。
//通常后端對于枚舉值會有一個詳細的映射關系注釋,問題是前端通常沒有,雖然在寫Select時感覺不出太大影響。
//然而如果這個Select關系到前端的業務邏輯,就會變得很難懂,假如一個月后你需要回來維護你的代碼,看到一個
//if(a==2){//業務邏輯}..你會花很多的時間來弄明白a從哪來,2又是什么。
//我個人遇到這種情況,會在進入業務邏輯之前寫一個映射,將1,2,3,4,5轉換成名詞。之后使用這些字符串作判斷。
//一些常見的約定
var pageIndex //變量
var _PageIndex //受保護的變量
var PAGE_INDEX //常量
//其它還有p_pageIndex //表參數 g_pageIndex //表全局變量,但這通常不常見
//這是在let,const之前最常見的約定了,即時有Let const依舊可以這么寫,只是把var 換掉,它們很好懂。
//一種簡單的編碼風格
//文件命名
PageIndex.html //雙首字母大小寫
PageInde.min.js //統一min為壓縮混淆后的文件
PageInde-1.0.min.js //‘-’鏈接版本號
//限定一行的字數比如120個,';' 不可忽略, ',' 分行, 縮進'tab' 四個空格
//這里我無意討論 ';'的作用,很多人認為不應該寫,認為徒增操作量,沒用,認為不寫更好看。
//然而我認為無論從執行還是語義來講它還是必要的。
//況且現實來講,很多現有系統并不是基于ES6或以上的語法編寫的,維護它們的時候最好還是把';'加上。
//數學運算
1 + 1 =2 //等于單邊空格,運算符雙邊空格
if (type) {
}
function Add (x) {
}
//() 關鍵字的另一邊空格
// {} 外側空格
// 即時只有一個If 也不允許寫在單行里。
// 避免不同層次變量重名,局部變量集中在一處。比如:
var pageIndex = 0;
function next(a){
var pageIndex = 1, //避免,可以換成 _pageIndex 或者 subIndexPage
lastIndex = 0,
nexIndex = 2;
//純舉例,現實中不會有這種需要
....//業務邏輯
}
就像我之前說的,這些規則并不絕對要求遵守。
要我來說即使只是規范變量命名就能極大的提高代碼的可讀性了。
編程風格可以做的事無巨細,展開來說也是一本書的內容,既然這里只是基礎,我覺得稍微提的這點就足夠了。
JavaScript豆知識系列
剛學完JavaScript那會我很喜歡探求一些小知識點,也就是所謂豆知識,它們通常并非完全沒什么卵用。
事實上到現在我大都不記得還有啥了,所幸我找到了記在印象筆記上的一些片段。
//聲明提前
//js 所有的聲明都會被提前,var a=1 ;在執行的時候會變成var a ;/..隔著其它代碼.../ a = 1
//函數類型會自動提到頂部去,除非你這么寫..var a = function(){}...
//一個局部變量的列子
var scope = "global";
function f() {
alert(scope); //你以為是"global"?
//輸出undefined 因為 下面的局部變量聲明提前了
var scope = "local"; //覆蓋全局變量
alert(scope);
}
//這也是為啥局部變量建議要一起在頂部聲明的原因
//頁面可優化的部分
1、訪問元素的某些屬性
2、通過JavaScript修改元素的CSS屬性
3、在onScroll中做耗時任務
4、圖片的預處理(事先裁剪圖片,而不是依賴瀏覽器在布局時的縮放)
5、在其他Event Handler中做耗時任務
6、過多的動畫
7、過多的數據處理(可以考慮放入WebWorker內執行)
//基本沒實踐過,通常后端業務邏輯接口返回的速度比前端渲染要慢。
//而影響前端渲染的最大因素往往是帶寬的大小。我曾經用加帶寬這個答案來調戲面試管,關于頁面優化的問題..
//深拷貝和淺拷貝
//JavaScript中萬物皆對象,而對象通常是引用類型的。
//具體來說所謂對象是存的其實是一個指向內存中某處的地址。
//這就造成了通過 = 號賦值的對象它們指向的是同一個對象,一榮俱榮,一損俱損。
//解決這個問題就是深拷貝,思路通常就是用循環把所有的值取出來賦到一個新對象上去。
/*
jquery中可以使用$.extend深拷貝 $.extend(true,bindData,oldData); 深拷貝
ES6中assign() 可以實現extend的裝飾功能,拷貝的對象中的對象和數組是淺拷貝。
*/
//換成代碼
function cloneObj(obj){
var newObj=obj.constructor=Arry?[]:{};
if(typeof obj !=="object"){
return;
}
else{
for(key in obj){
newObj[key]=obj[key];
}
}
return newObj;
}
//基礎款,內部對象數組還是淺拷貝的..
function deepClone(obj){
var str,newObj=obj.constructor==Array?[]:{};
if(typeof obj!=="object"){
return
}
else{
for(key in obj){
newObj[key]=typeof obj[key]==='object'?deepclons(obj[key]):obj[key];
}
}
return newObj;
}
//加強款,函數還是淺拷貝來的..
//其實現實中..直接轉JSON再賦值再轉回來就可以了...不會寫的這么麻煩..以上兩個例子我從來沒用過..
//原型繼承
/*
構造函數的原型繼承,在函數中使用 argument.callee.prototype= 可以的定義原型
Object 操作
獲得父級原型對象:
subObj.__proto__ (兼容問題);
Object.getPrototypeOf(obj);
修改 obj.attrName="attrValue"; //自有
Function.prototype.attrName="attrValue"; //原型
刪除 delete 只能刪除自有成員
判斷屬性自有:obj.hasOwnProperty("attrName");
自定義子對象的原型
childObj.__proto__=FatherObj //兼容問題
Object.setPrototpeOf(childObj,FatherObj);
ECM5 的create方法
var a=Object.create( 原型對象 );
可以傳入不同的值得到一個繼承該原型對象的object 可傳入null得到一個沒有原型的純粹的object
也可傳入Array等得到繼承相應構造函數的object。
*/
//除了hasOwnProperty很多時候很有用..其它基本沒怎么用過..
//實踐中有一次將一個函數掛到Object上去..最后出了一萬個BUG..
//從此,對于操作內置對象都是謹慎小心。
//跨域,不知道為什么,很多人面試喜歡問跨域,然而在實踐中,基本不會出現跨域的場景,對于前端來說這不安全。
//當然也可以說是我菜。
//簡單來說,通過scrpit和img標簽去請求別的域名的js文件就是最簡單的跨域做法。
//還需要更復雜的操作,要么交給后端..要么去找大佬吧。
//冒泡和拖拽
/*
addEventListener('事件名',函數對象,捕獲)
捕獲:true false true 捕獲階段提前觸發,false 冒泡階段觸發
事件觸發的三個階段:1、捕獲階段,2、觸發 3、,冒泡階段
Event操作
取消冒泡:
e.stopPropagation()
e.target 獲取事件的目標函數
取消事件:e.preventDefault();
事件的坐標:
相對于屏幕:e.screenX/screenY
相對于頁面左上角的坐標: e.pageX/pageY
相對于文檔顯示區左上角的坐標: e.clientX/x e.clienY/y
相對于元素左上角的坐標 offsetX/offsetY
*/
//我曾經背過拖拽的的面試題,當時可以說是理解的深入骨髓,但是隨著之后從來沒用過,很快就忘完了。
常用JavaScript Tip及代碼塊
在中小型項目中,JavaScript往往東一榔頭西一錘子的拼湊,自己寫的可能更多的是數據處理。
這時候找現成代碼的本事通常會是決定了一個前端晚上十一點是坐家里喝酸奶,還是坐在辦公室喝咖啡。
//無題
console.time(label) console.timeEnd(label) //測試一段方法開始和結束的時間
encodeURI(str) ;encodeURIComponent(str); //中文的編碼和解碼,避免亂碼用兩層
encodeURI() //編碼
encodeURIComponent()//編碼
decodeURI() //解碼
decodeURIComponent() //解碼
base64 API //原生不支持中文
window.atob(); //解碼
window.btoa(); // 編碼
//過濾HTML結構
function filterHtml(str) {
str = str.replace(/<\/?[^>]*>/g,''); //去除HTML tag
str.value = str.replace(/[ | ]*\n/g,'\n'); //去除行尾空白
str = str.replace(/\n[\s| | ]*\r/g,'\n'); //去除多余空行
return str;
}
//去空格
function trim(str){
return str.replace(/\s|\xA0/g,"");
}
//url 參數獲取
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
//js 鍵盤
document.onkeydown = function(e){
var keyNum = window.event ? e.keyCode : e.which; //window.event 兼容IE
//判斷keyNum的值給對應事件
}
jQuery:
$(document).keydown(function(e){
var keyNum = e;
});
//瀏覽器視口
jquery:
$(document).ready(function()
{
alert($(window).height()); //瀏覽器時下窗口可視區域高度
alert($(document).height()); //瀏覽器時下窗口文檔的高度
alert($(document.body).height());//瀏覽器時下窗口文檔body的高度
alert($(document.body).outerHeight(true));//瀏覽器時下窗口文檔body的總高度 包括border padding margin
alert($(window).width()); //瀏覽器時下窗口可視區域寬度
alert($(document).width());//瀏覽器時下窗口文檔對于象寬度
alert($(document.body).width());//瀏覽器時下窗口文檔body的高度
alert($(document.body).outerWidth(true));//瀏覽器時下窗口文檔body的總寬度 包括border padding margin
}
)
瀏覽器窗口改變事件:
$(window).resize(function(){
});
//表單JSON化
$.fn.serializeObject = function()
{
var obj = {};
var arr = this.serializeArray();
$.each(arr, function() {
if (obj[this.name]) {
if (!obj[this.name].push) {
obj[this.name] = [obj[this.name]];
}
obj[this.name].push(this.value || '');
} else {
obj[this.name] = this.value || '';
}
});
return obj;
};
//一種基于閉包寫的插件方法
(function(window,document){
//邏輯代碼
window.methodName = window.methodName || methodName
})(window,document)
//阻止冒泡
原生JS
function (e){
var event = e || window.event ;//兼容寫法
//阻止冒泡
event.cancelBubble = true; //IE
event.stopPropagation();
//阻止默認事件(a標簽,input)
event.returnValue = false; //IE
event.parentDefault();
//使用jq的話
return false //既能阻止冒泡,也能阻止默認事件。
}
//六位隨機驗證碼
// 方法一
('000000' + Math.floor(Math.random() * 999999)).slice(-6);
// 方法二
Math.random().toString().slice(-6);
// 方法三
Math.random().toFixed(6).slice(-6);
// 方法四
'' + Math.floor(Math.random() * 999999);
//十六進制顏色代碼
function() {
return '#'+('00000'+
(Math.random()*0x1000000<<0).toString(16)).slice(-6)};
//多維數組展開
var foo = [1, [2, 3], ['4', 5, ['6',7,[8]]], [9], 10];
// 方法一
// 限制:數組項不能出現`,`,同時數組項全部變成了字符數字
foo.toString().split(','); // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
// 方法二
// 轉換后數組項全部變成數字了
eval('[' + foo + ']'); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 方法三,使用ES6展開操作符
// 寫法太過麻煩,太過死板
[1, ...[2, 3], ...['4', 5, ...['6',7,...[8]]], ...[9], 10]; // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
// 方法四
JSON.parse(`[${JSON.stringify(foo).replace(/\[|]/g, '')}]`); // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
// 方法五
const flatten = (ary) => ary.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
flatten(foo); // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
// 方法六
function flatten(a) {
return Array.isArray(a) ? [].concat(...a.map(flatten)) : a;
}
flatten(foo); // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]
//特殊字符轉譯
function htmlspecialchars (str) {
var str = str.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"');
return str;
}
htmlspecialchars('&jfkds<>'); // "&jfkds<>"
事實上,過度依賴找現成的代碼會逐漸影響創造力。
但是以輪播為例,你不可能在十分鐘內寫出比現成庫還要好的輪播。而使用現成的庫做輪播十分鐘已經足夠了。
然而只會用現成的庫,這將使你成為一個正真的碼農,雖然很多時候這是沒有辦法的事情。
但至少,把你在用的庫的原理搞懂,不然假如讓你定制它所不具備的功能時你會很尷尬。
好吧,通常這種時候你會去找滿足需求的庫,我知道的。
結語
這是一篇沒什么目的性的筆記,主要由我倆年前的學習筆記和偶爾記錄一下的工作筆記拼湊而成。
以文學來類比就是散文了,最后的代碼塊,網上估計很多文章中都出現過,側面反映了它們很有用。
然后其實真正決定你晚上十一點的并不是這個,而是你知道的功能庫,以及你們老大能給你爭取多少時間。
更多的時候其實更依賴于你所在的團隊到底幾個人...
至此,我認為算是JavaScript基礎的部分基本就結束了。
之后,要還能寫,也許就是ES6 Node 設計模式,項目實踐一類的。
幸運的是它們依舊隨處可見。