JavaScript 實用技巧和寫法建議

守候i發表於2017-12-14

1.前言

從大學到現在,接觸前端已經有幾年了,感想方面,就是對於程式設計師而言,想要提高自己的技術水平和編寫易於閱讀和維護的程式碼,我覺得不能每天都是平庸的寫程式碼,更要去推敲,去摸索和優化程式碼,總結當中的技巧,積極聽取別人的建議,這樣自己的技術水平會提高的更快。那麼今天,我在這裡就分享一下關於javascript方面的寫作的實用技巧和建議,這些技巧和建議是我平常在開發專案上會用到的,希望能讓大家學到知識,更希望能起到一個交流意見的作用,也就是說大家有什麼好的技巧或者建議,歡迎分享,或者覺得我的想法存在什麼問題,歡迎指出!

2.更短的陣列去重寫法

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

3.物件深淺拷貝

關於物件的深淺拷貝,我個人見解就是有一下幾點:

1.深拷貝和淺拷貝只針對像Object, Array這樣的引用型別資料。

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

3.深拷貝則是開啟一個新的棧,兩個物件對應兩個不同的引用地址,修改一個物件的屬性,不會改變另一個物件的屬性。

淺拷貝

var myInfo={name:'守候',sex:'男'};
複製程式碼

JavaScript 實用技巧和寫法建議

var newInfo=myInfo;
複製程式碼

JavaScript 實用技巧和寫法建議

newInfo.sex='女';
複製程式碼

JavaScript 實用技巧和寫法建議

console.log(myInfo)   //{name: "守候", sex: "女"}
複製程式碼

假-深拷貝

假-深拷貝這個是自己隨性命名的,大家看看就好,別當真!

var myInfo={name:'守候',sex:'男'};複製程式碼

JavaScript 實用技巧和寫法建議

var newInfo=Object.assign({},myInfo)複製程式碼

JavaScript 實用技巧和寫法建議

newInfo.sex='女';複製程式碼

JavaScript 實用技巧和寫法建議

console.log(myInfo)   //{name: "守候", sex: "男"}
console.log(newInfo)   //{name: "守候", sex: "女"}
複製程式碼

真-深拷貝

真-深拷貝這個是自己隨性命名的,大家看看就好,別當真!

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

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方法等。
要解決這個問題,就得自己封裝方法!如下

//利用遞迴來實現深拷貝,如果物件屬性的值是引用型別(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。

JavaScript 實用技巧和寫法建議

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的模板字串,就更簡單了!

JavaScript 實用技巧和寫法建議

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();
};複製程式碼

效果

JavaScript 實用技巧和寫法建議

節流寫法

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)
};
複製程式碼

效果

JavaScript 實用技巧和寫法建議

這種方式,其實是有問題的,在不斷觸發停下來等待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)複製程式碼

JavaScript 實用技巧和寫法建議

我現在函式節流用得很少,這兩個寫法是比較基礎的,希望大家能共享下自己的比較好的方法!


13.其他寫作建議

關於其它的一些寫法技巧和建議,都是比較老生常談的,比如命名規範,函式單一性原則等。這一部分內容我自己總結和別人寫的基本一致!我就不展開說了(感覺展開說也基本是複製貼上別人的文章,這事我不幹),所以我推薦大家去看這篇文章(如何優雅的編寫 JavaScript 程式碼)。有些知識我也是從這裡獲得的!

14.小結

好了,關於我自己總結的一些實用技巧和建議,就到這裡了!關於javascript的技巧和建議,這點大家還是要多看網上的資源,也要自己多總結,畢竟我自己總結的只是我自己發現的,只是冰山一角。但還是希望這篇文章能幫到大家,讓大家學習到知識。當然,更希望的是能起到一個交流意見的作用。如果大家有什麼建議,技巧。也歡迎分享。覺得我哪裡寫錯了,寫得不夠好,也歡迎指出!讓大家一起互相幫助,互相學習!


-------------------------華麗的分割線--------------------
想了解更多,關注關注我的微信公眾號:守候書閣

JavaScript 實用技巧和寫法建議


相關文章