JavaScript 實用技巧和寫法建議

1.前言

從大學到如今,接觸前端已經有幾年了,感想方面,就是對於程序員而言,想要提升本身的技術水平和編寫易於閱讀和維護的代碼,我以爲不能天天都是平庸的寫代碼,更要去推敲,去摸索和優化代碼,總結當中的技巧,積極聽取別人的建議,這樣本身的技術水平會提升的更快。那麼今天,我在這裏就分享一下關於javascript方面的寫做的實用技巧和建議,這些技巧和建議是我日常在開發項目上會用到的,但願能讓你們學到知識,更但願能起到一個交流意見的做用,也就是說你們有什麼好的技巧或者建議,歡迎分享,或者以爲個人想法存在什麼問題,歡迎指出!javascript

2.更短的數組去重寫法

[...new Set([2,"12",2,12,1,2,1,6,12,13,6])] //[2, "12", 12, 1, 6, 13] //es6的新特性 

3.對象深淺拷貝

關於對象的深淺拷貝,我我的看法就是有一下幾點:html

1.深拷貝和淺拷貝只針對像Object, Array這樣的引用類型數據。前端

2.淺拷貝是對對象引用地址進行拷貝,並無開闢新的棧,也就是拷貝後的結果是兩個對象指向同一個引用地址,修改其中一個對象的屬性,則另外一個對象的屬性也會改變。java

3.深拷貝則是開啓一個新的棧,兩個對象對應兩個不一樣的引用地址,修改一個對象的屬性,不會改變另外一個對象的屬性。node

淺拷貝

var myInfo={name:'守候',sex:'男'}; 

var newInfo=myInfo;

newInfo.sex='女'; 

console.log(myInfo)   //{name: "守候", sex: "女"} 

假-深拷貝

假-深拷貝這個是本身隨性命名的,你們看看就好,別當真!jquery

var myInfo={name:'守候',sex:'男'};

var newInfo=Object.assign({},myInfo)

newInfo.sex='女';

console.log(myInfo)   //{name: "守候", sex: "男"} console.log(newInfo) //{name: "守候", sex: "女"} 

真-深拷貝

真-深拷貝這個是本身隨性命名的,你們看看就好,別當真!程序員

看着深淺拷貝,區別寫法很簡單,可是那個上面的深拷貝寫法是有問題的。看下面案例es6

var arr=[{a:1,b:2},{a:3,b:4}]
var newArr=Object.assign([],arr)
//截斷數組
newArr.length=1
console.log(newArr)//[{a:1,b:2}]
console.log(arr)//[{a:1,b:2},{a:3,b:4}]
//操做newArr,這裏看着對arr沒影響,實際上已經挖了一個坑,下面就跳進去
newArr[0].a=123
//修改newArr[0]這個對象,也是影響了arr[0]這個對象
console.log(arr[0])//{a: 123, b: 2}

爲何會這樣呢,由於Object.assign並非深拷貝,是披着深拷貝外衣的淺拷貝。最多也是Object.assign會課拷貝第一層的值,對於第一層的值都是深拷貝,而到第二層的時候就是 複製引用。相似的狀況還有,slice方法和concat方法等。
要解決這個問題,就得本身封裝方法!以下web

//利用遞歸來實現深拷貝,若是對象屬性的值是引用類型(Array,Object),那麼對該屬性進行深拷貝,直到遍歷到屬性的值是基本類型爲止。  
function deepClone(obj){ if(!obj&& typeof obj!== 'object'){ return; } var newObj= obj.constructor === Array ? [] : {}; for(var key in obj){ if(obj[key]){ if(obj[key] && typeof obj[key] === 'object'){ newObj[key] = obj[key].constructor === Array ? [] : {}; //遞歸 newObj[key] = deepClone(obj[key]); }else{ newObj[key] = obj[key]; } } } return newObj; } var arr=[{a:1,b:2},{a:3,b:4}] var newArr=deepClone(arr) console.log(arr[0])//{a:1,b:2} newArr[0].a=123 console.log(arr[0])//{a:1,b:2}

還有一個方法就是簡單粗暴法,我如今在用的一個方法!原理很簡單,就是先把對象轉成字符串,再把字符串轉成對象!也能實現一樣效果數組

var newArr2=JSON.parse(JSON.stringify(arr));
console.log(arr[0])//{a:1,b:2}
newArr2[0].a=123
console.log(arr[0])//{a:1,b:2}

上面所說的淺拷貝,真假深拷貝(本身隨性命名的),這幾種狀況,在開發上都有可能要用到,至於要使用哪種方式,視狀況而定!

4.使用事件委託

一個簡單的需求,好比想給ul下面的li加上點擊事件,點擊哪一個li,就顯示那個li的innerHTML。這個貌似很簡單!代碼以下!

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"> <title></title> </head> <body> <ul id="ul-test"> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </body> <script type="text/javascript"> var oUl=document.getElementById("ul-test"); var oLi=oUl.getElementsByTagName("li"); for(var i=0,len=oLi.length;i<len;i++){ oLi[i].addEventListener("click",function(){ alert(this.innerHTML) }) } </script> </html>

很簡單,這樣就實現了,實際上這裏有坑,也待優化!
1.for循環,循環的是li,10個li就循環10次,綁定10次事件,100個就循環了100次,綁定100次事件!
2.若是li不是原本就在頁面上的,是將來元素,是頁面加載了,再經過js動態加載進來了,上面的寫法是無效的,點擊li是沒有反應的!
因此就者須要用事件委託(即便不考慮上面的第二種狀況,也是建議使用事件委託)!代碼以下

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"> <title></title> </head> <body> <ul id="ul-test"> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> </body> <script type="text/javascript"> var oUl=document.getElementById("ul-test"); oUl.addEventListener("click",function(ev){ var ev=ev||window.event; var target=ev.target||ev.srcElement; //若是點擊的最底層是li元素 if(target.tagName.toLowerCase()==='li'){ alert(target.innerHTML) } }) </script> </html> 

這樣寫,即便是動態添加進來的li點擊也有反應,還有一個就是ul只有一個,事件綁定在ul上,不管li多少個,都是添加一次事件!可是也是可能會有問題,若是li下面還有子元素,那麼點擊的時候,target可能不是li,而是鼠標點擊那個位置的最底層元素!以下圖,若是鼠標點擊白色區域,那個target就是body元素,鼠標點擊綠色區域target就是div元素,鼠標點擊藍色區域target就是ul,點擊橙色就是li。

5.使用對象做爲函數參數

你們試想下這樣一個函數--函數接受幾個參數,可是這幾個參數都不是必填的,函數該怎麼處理?是否是下面這樣

function personInfo(name,phone,card){ ... } //以上函數,能夠任意傳參數。好比我想傳card等於1472586326。這下是否是這樣寫 personInfo('','','1472586326')

有沒有以爲上面寫法奇怪,不太優雅?下面這裏看着舒服一點!

function personInfo(opt){ ... } personInfo({card:'1472586326'})

再想一下,若是一個函數,參數不少,怎麼處理?

function test(arg1,arg2,arg3,arg4,arg5,arg6,arg7){ ... }

密集恐懼症復發沒有復發?下面這樣看着會舒服一點!

function personInfo(opt){ ... }

最後再想一下,若是需求改了,操做函數也要改!函數也要增長一個參數。

//原來函數
function personInfo(name,phone,card){ ... } //修改後 function personInfo(name,age,phone,card){ ... }

這樣就是參數修改一次,函數的參數就要修改一次!若是是用對象,就不會出現這樣問題!

//修改先後都是這樣,變得是函數的操做內容和調用時候的傳參!
function personInfo(opt){ ... }

看了上面的幾個栗子,總結來講,就是當函數的參數不固定的時候,參數多(三個或者三個以上)的時候,建議用一個對象記錄參數,這樣會比較方便,也爲之後若是參數要改留了條後路!

6.使用push和apply合併數組

合併數組這個已是老生常談的話題了,方法也是多種多樣!

concat

var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];
arr1=arr1.concat(arr2)
console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

concat會一個全新的數組,表示arr1和arr2兩個數組的組合,並讓arr1和arr2不變。簡單吧?
但若是arr1和arr2的長度都很長,那就產生了一個很長很長的數組,內存又被佔用了那麼多。可是數組長度沒限制!

for

var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];
for(var i=0,len=arr2.length;i<len;i++){ arr1.push(arr2[i]) } console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

這裏是往arr1循環添加arr2的元素,可是有一個狀況,arr1的長度遠小於arr2的長度,是否是循環arr1性能更好,循環次數更少。處理這個很簡單,可是萬一不知道arr1和arr2到底哪一個長度更少呢?並且,for循環不夠優雅!(固然,這個能夠用迭代方法來替代)

reduce

var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];
arr1 = arr2.reduce( function(coll,item){ coll.push( item ); return coll; }, arr1 ); console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

逼格高了一點,並且用ES6的箭頭函數還能夠減小一些代碼量,但它仍然須要一個函數,每一個元素都須要調用一次。

push.apply

var arr1=[1,2,3,4,5],arr2=[6,7,8,9,10];
arr1.push.apply(arr1,arr2);
console.log(arr1)//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

逼格看着高,代碼少,也不會產生新的數組,也不難理解,就是調用arr1.push這個函數實例的apply方法,同時把arr2看成參數傳入,這樣arr1.push這個方法就會遍歷arr2數組的全部元素,達到合併的效果。至關於arr1.push.apply(arr1,[6,7,8,9,10]);,最後至關於arr1.push(6,7,8,9,10)。遺憾的就是,這個方法對數組長度有限制,網上說法是不一樣瀏覽器,不一樣的長度限制,通常不超過10萬!

以前是建議用push.apply,可是如今保留意見,就是你們以爲哪一個方式用哪一個方式!這個沒有必定的對錯!

7.toFixed保留整數

在開發上,常常會遇到最多保留多少位小數或者相似的問題,針對這個,使用toFixed能夠很簡單的解決問題,可是若是數據是要和後臺交互的,並且後臺存儲的數據通常是保存數字類型,而使用toFixed後生成的是一個字符串,這下,就須要把toFixed生成的是一個字符串轉成數字類型,轉發不少。今天我說一個最簡單--+。代碼以下

var a=123.36896335.toFixed(2)
console.log(a)//'123.37' a=+a console.log(a)//123.37

PS:a=a|0和~~a也能夠實現,可是生成的是一個整數,以下

var a=123.36896335.toFixed(2)
console.log(a)//'123.37' a=a|0 console.log(a)//123 //---------------------------------分割線 var a=123.36896335.toFixed(2) console.log(a)//'123.37' a=~~a console.log(a)//123 

8.其它類型數據轉布爾數據

下面的轉換,你們一看就明白了,很少說。

console.log(!!'123') //true !!12 //true !!-1 //true !![] //true !!'' //false !!null //false

9.緩存變量

for循環緩存length

var arr=[1,2,3,4,5,6]
for(var i=0,i<arr.length;i++){ ... } //------------------------分割線 var arr=[1,2,3,4,5,6] for(var i=0,len=arr.length;i<len;i++){ ... }

第一段就是每一次循環的時候,都要查詢一次arr.length。第二段代碼就是緩存了arr.length,每次對比len就好,理論上是第二段代碼的寫法比較好,性能比較高!可是隨着瀏覽器的發展,這個細節的性能上的影響貌似遠遠小於預期,如今仍是建議緩存!我寫了下面的測試用例(谷歌瀏覽器測試)!

var arr100=[], arr10000=[];
for(var i=0;i<100;i++){ arr100.push(i) } for(var i=0;i<10000;i++){ arr10000.push(i) } //緩存狀況 function testCache(arr){ console.time(); for(var i=0,len=arr.length;i<len;i++){ } console.timeEnd() } //不緩存狀況 function testNoCache(arr){ console.time(); for(var i=0,len=arr.length;i<len;i++){ } console.timeEnd() } testCache(arr100)//default: 0.007ms testCache(arr10000)//default: 0.035ms testNoCache(arr100)//default: 0.012ms testNoCache(arr10000)//default: 0.109ms //這只是一個最簡單的數組,若是遍歷的是一個nodeList(元素列表),效果可能會更明顯。 

元素事件

這裏我用jquery來說解,比較容易理解,原生js也是這個道理!以下代碼

$('.div1').click(function(){ ... }) //--------------------------分割線 var $div1=$('.div1'); $div1.click(function(){ ... })

上面的代碼,改變的也是緩存了$('.div1'),可是這裏就建議是第二種寫法了,由於第一種點擊一次就要查詢一次.div1,Dom的操做仍是能減小就減小!

10.使用innerHTML添加元素

好比有一個需求,往ul裏面添加10個li,兩種方法,以下代碼

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"> <title></title> </head> <body> <ul id="ul-test"> </ul> </body> <script type="text/javascript"> var oUl=document.getElementById("ul-test"); //createElement方式 console.time(); for(var i=0;i<10;i++){ var oLi=document.createElement('li'); oLi.innerHTML=i; oUl.appendChild(oLi); } console.timeEnd(); //innerHTML方式 console.time(); var _html=''; for(var i=0;i<10;i++){ _html+='<li>'+i+'</li>' } oUl.innerHTML=_html; console.timeEnd(); </script> </html> 

你們把代碼用瀏覽器打開,發現基本是第二種方式更快,第8點也說了,DOM操做能少就少!第一種要操做10次DOM,第二種只須要操做1次DOM。還有一個就是,這個只是很簡單的li,若是是下面的列表呢?用第一種方式,得createElement多少次,innerHTML多少次,appendChild多少次?代碼多,各個節點的邏輯和嵌套關係也亂!用第二種方式就是一個拼接字符串的操做,比第一種方式好多了,若是用es6的模板字符串,就更簡單了!

11.將參數轉成數組

函數裏的arguments,雖然擁有length屬性,可是arguments不是一個數組,是一個類數組,沒有push,slice等方法。有些時候,須要把arguments轉成數組,轉的方法也不止一個,推薦的是是下面的寫法!

var _arguments=Array.prototype.slice.apply(arguments)

12.函數節流

這裏拿一個栗子說,好比mousemove,onscroll,onresize這些事件觸發的時候,可能已經觸發了60次事件,這樣很消耗性能,並且實際上,咱們並不須要這麼頻繁的觸發,只要大約100毫秒觸發一次就好!那麼這樣就須要函數節流了!

普通寫法

var count = 0;
function beginCount() { count++; console.log(count); } document.onmousemove = function () { beginCount(); };

效果

節流寫法

var count = 0;
function beginCount() { count++; console.log(count); } function delayFn(method, thisArg) { clearTimeout(method.props); method.props = setTimeout(function () { method.call(thisArg) },100) } document.onmousemove = function () { delayFn(beginCount) }; 

效果

這種方式,實際上是有問題的,在不斷觸發停下來等待100ms纔開始執行,中間操做得太快直接無視。因而在網上找到下面這種方案!

第二種節流寫法

function delayFn2 (fn, delay, mustDelay){ var timer = null; var t_start; return function(){ var context = this, args = arguments, t_cur = +new Date(); //先清理上一次的調用觸發(上一次調用觸發事件不執行) clearTimeout(timer); //若是不存觸發時間,那麼當前的時間就是觸發時間 if(!t_start){ t_start = t_cur; } //若是當前時間-觸發時間大於最大的間隔時間(mustDelay),觸發一次函數運行函數 if(t_cur - t_start >= mustDelay){ fn.apply(context, args); t_start = t_cur; } //不然延遲執行 else { timer = setTimeout(function(){ fn.apply(context, args); }, delay); } }; } var count=0; function fn1(){ count++; console.log(count) } //100ms內連續觸發的調用,後一個調用會把前一個調用的等待處理掉,但每隔200ms至少執行一次 document.onmousemove=delayFn2(fn1,100,200)

我如今函數節流用得不多,這兩個寫法是比較基礎的,但願你們能共享下本身的比較好的方法!

 

13.其餘寫做建議

關於其它的一些寫法技巧和建議,都是比較老生常談的,好比命名規範,函數單一性原則等。這一部份內容我本身總結和別人寫的基本一致!我就不展開說了(感受展開說也基本是複製粘貼別人的文章,這事我不幹),因此我推薦你們去看這篇文章(如何優雅的編寫 JavaScript 代碼)。有些知識我也是從這裏得到的!

14.小結

好了,關於我本身總結的一些實用技巧和建議,就到這裏了!關於javascript的技巧和建議,這點你們仍是要多看網上的資源,也要本身多總結,畢竟我本身總結的只是我本身發現的,只是冰山一角。但仍是但願這篇文章能幫到你們,讓你們學習到知識。固然,更但願的是能起到一個交流意見的做用。若是你們有什麼建議,技巧。也歡迎分享。以爲我哪裏寫錯了,寫得不夠好,也歡迎指出!讓你們一塊兒互相幫助,互相學習!

做者:守候i 連接:https://juejin.im/post/5a0c199851882531926e4297 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索