讀書筆記之JavaScript語言精粹

作爲一個前端開發人員, js的一些坑已經踩了不少, 但這本書總結的非常好, 把精華部分和糟粕部分都非常系統詳細的列出來, 個人認爲附錄部分的js毒瘤與糟粕非常值得一讀, 如果你是剛開始接觸js, 那就更值得你好好看看了, 絕對能少踩很多坑. 這裏做個讀書筆記, 總結一下這本書的附錄部分, 也就是js的坑.

  • 全局變量

    對全局變量的依賴可能是JavaScript最糟糕的特性. 很多編程語言也有全局變量, 例如Java使用public static 關鍵字定義全局變量, 但在JavaScript中, 由於沒有連接器(linker), 所有的編譯單元都在如到一個公共全局對象中(也就是window對象). 也許在小型程序中這會帶來很多便利, 但當程序變得複雜時, 全局變量便會使程序變得難以維護, 因爲無論在全局哪一個地方更改全局變量的值, 全局變量都會改變. 更糟糕的是如果我們沒有使用var關鍵字進行變量的定義, JavaScript會默認該變量爲全局變量, 不管在程序的哪個地方.

    function test(){
      globle = 10;
    }
    test();
    console.log(globle)   //10
    

    由於globle沒有用var關鍵字修飾, JavaScript會把globle當成全局變量, 導致在函數外也可以使用. 這是一個非常糟糕的設計, 因此聲名變量時, 一定要在前面加關鍵字 var.

  • 作用域

    JavaScript與C語言類似, 都是用{}來表示代碼塊. 但糟糕的是, 與C語言不同, JavaScript沒有提供相應的塊級作用域, 也就是說, 代碼塊中生命的變量在包含此碼塊的函數的任何位置都是可見的, 聽起來十分拗口, 舉個例子就好懂多了.

    for (var i=0; i<5; i++){
      var g = i;
    }
    console.log(g)  //4
    

    for循環中的變量 g 是屬於塊級代碼, 但在全局中卻可以調用. 這就是 js 的作用域問題, 與之相對的是C語言的塊級作用域. 先看以下代碼

    #include<stdio.h>
    int main(){
       int i = 2;
       if (i){
       int j = 10;
    }
    printf(j);
    }
    

    運行這段代碼, 編譯器會報錯. error: ‘j’ was not declared in this scope. 這是由於j是在塊級作用域if語句裏面的. 所以在全局中式無法使用的.

    因此在JavaScript中要注意這一點, 由於js的這個特性, 我們最好在每個函數的開頭部分就聲明所有的變量.

  • 自動插入分號

    JavaScript並不強制使用 分號 作爲一個語句的結尾, 但當你忘記在語句中使用 分號做爲結尾時, 它會自動插入分號來試圖修正程序. 注意了, 這個特性可能會出現一些神奇的問題. 例如以下的 return 語句.

    return 
    {
      type: 1,
      msg: "ok"
    }
    

    上面的代碼看似返回一個包含元素的對象, 但由於JavaScript自動插入分號的特性, 實際執行的代碼已經變成了

    return; 
    {
      type: 1,
      msg: "ok"
    }
    

    這樣就導致返回值變成了 undefined, 一定要注意, 上例正確的寫法應該把 "{"  放在上一行,也就是

    return {
      type: 1,
      msg: "ok"
    }
    
  • typeof

    typeof 運算符可以用來識別變量或常量的類型.部分情況下它是可以正常判斷的, 但是

    type null;     // object
    

    返回的是object 而不是 null ! 這一點千萬注意.我們可以用其他的方法來判斷一個變量是否爲null,最簡單的方法就是

    value === null; 
    

    不知道你有沒有注意到,由於typeof無法判斷object 和null, 因此判斷一個變量是否爲object時,一定要再三小心.

    typeof value === 'object';    //錯誤的判斷方法
    value && type value === 'object';  // 正確,先判斷是否爲null
    
  • parseInt

    曾經我在面試的時候遇到過一道經典的JavaScript題,至今讓我印象深刻.

    var arr = ['1', '2', '3', '4', '5'];
    var num;
    num = arr.map(parseInt);
    

    很簡單的代碼,就是把arr數組中的元素轉化成整數,理論上來說應該是[1,2,3,4,5], 是的,理論上.然而實際結果確實是[1, NaN, NaN, NaN, NaN].其實原因也不難,就是因爲 parseInt 方法其實是可以帶兩個參數的,第一個參數是要轉化成數字的字符串,另一個是指定進制,默認是使用十進制來進行轉換,這裏舉兩個例子說明一下.

    parseInt('10', 8);  // 8
    parseInt('10');     // 10
    

    我想你已經知道問題所在了,還不知道?再看看map方法的語法.

    var new_array = arr.map(function callback(currentValue, index, array) {
        // Return element for new_array
    }
    

    也就是說,實際運行過程中數組的索引會被當成parseInt的第二個參數.也就是像下面這樣.所以就會出現這樣看似不合理的結果了.要避免這種情況也不難,把parseInt 換成Number 就可以了.

    parseInt('1',0);
    parseInt('2',1);
    parseInt('3',2);
    ...
    
  • 浮點數

    這個其實不算坑,只是需要注意一下,先看下面的例子.

    0.1+0.2; // 0.30000000000000004
    

    第一次看到這個結果的時候我也嚇了一跳,但其實原因很簡單,因爲CPU的計算其實都是二進制的,而二進制的浮點數不能正確的處理十進制的小數點,說以會導致計算結果不精準,這不僅是JavaScript的問題,很多編程語言也有同樣的問題,例如Python,Java.幸運地是,整數是可以完美處理的.對於有小數點的運算,我們可以指定計算精度來避免這樣的問題.

  • NaN

    不得不說這是JavaScript中一個奇葩的變量類型,它表示一個特殊的數量值,它不代表一個數字,當數學表達式無法計算時會用NaN表示,亦或者把非數字形式的字符串轉換成數字時.但

    typeof NaN === 'number'; // true
    

    也就是說typeof 是無法辨認數字與NaN的(typeof要你有何用!),更噁心的是

    NaN === NaN; // false
    

    真的無話可說,發明NaN這個關鍵字絕對是設計者腦抽了.要判斷一個變量只能用一個方法;

    isNaN(NaN); //true
    

    延伸一下,由於上面的問題,我們是無法直接用 typeof 方法來判斷一個變量是不是數字的.千萬要注意,別掉坑裏了,我們可以加一個判斷.

    typeof value === 'number' && isFinite(value);
    
  • 數組

    JavaScript 中沒有真正的數組,JavaScript 中沒有真正的數組,JavaScript 中沒有真正的數組.重要的事情說三遍.在JavaScript中,一切皆爲對象,數組其實是一個有length的對象.這並不都是壞事,由於這個特性,你不必給數組設置維度,也不用擔心越界錯誤(但其實這更容易導致出現隱藏的bug).但這也導致其性能下降的厲害.同理的,你也無法用typeof直接判斷一個變量是否爲數組(typeof要你有何用!!),你需要檢查一個名作constructor的屬性.

    typeof value === 'object' && value.constructor === Array;
    
  • 對象

    是的,JavaScript 的對象也是要我們注意的.與Python, Java 不同,JavaScript 中的對象不是通過類來定義的,而是通過原型鏈.因此,當你創建一個空對象時,該對象絕對不是空的,而是包含了從上一級中繼承下來的各種方法與屬性,這在有時候會導致問題變得很麻煩.舉個例子,你現在在做一個文本分析腳本,首先需要統計一篇文章中每個單詞出現的個數,這個時候就要十分小心了.先看下面的代碼.

    var text = "To deal with this, you can generate a stack trace in the constructor of the exception object during the throw exception statement. ";
    var words = text.toLowerCase().split(/[\s,.]+/)
    var count = {};
    var word;
    for (var i=0;i<words.length;i++){
      word = words[i];
      if (count[word]){
        count[word] += 1;
      }
      else{
        count[word] = 1;
      }
    }
    

    上面的代碼很簡單,就是統計一下每個單詞出現的次數,但如果查看 count.constructor,你會發現 輸出了這個鬼東西:"function Object() { [native code] }1",原因就是上面所說的. count對象繼承自Object.prototype.而Object.prototype 中包含着一個constructor的對象.如何避免這個問題呢,我們可以做一個判斷,檢測成員類型,是數字才進行處理.

  • 其他

    接下來的東西就不細講了,都是不建議使用的.

    == 這個不用說了吧,大家都知道

    with 別用

    eval 有坑,用前詳細查看文檔.

    new 儘量別用

    void 別用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章