我的小結--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:'男'};

clipboard.png

var newInfo=myInfo;

clipboard.png

newInfo.sex='女';

clipboard.png

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

假-深拷貝

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

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

clipboard.png

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

clipboard.png

newInfo.sex='女';

clipboard.png

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方法等。
要解決這個問題,就得本身封裝方法!以下數組

//利用遞歸來實現深拷貝,若是對象屬性的值是引用類型(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。

clipboard.png

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

clipboard.png

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();
};

效果

clipboard.png

節流寫法

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)
};

效果

clipboard.png

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

clipboard.png

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

13.其餘寫做建議

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

14.小結

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

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

clipboard.png

相關文章
相關標籤/搜索