編寫高質量的JavaScript代碼(一)


歡迎大家關注騰訊雲技術社區-博客園官方主頁,我們將持續在博客園為大家推薦技術精品文章哦~

2016年6月加入騰訊,目前在SNG社交網絡質量部從事內部平台工具的研發。熟悉PHP、JS、CSS,喜歡彈吉他。

一、理解JavaScript的浮點數

由IEEE754標准制定,JavaScript中所有的數字都是雙精度浮點數,即64位編碼數字。
JavaScript大多數的算術運算符可以進行整數、浮點數或者兩者的組合進行計算。但是位運算符比較特殊,JavaScript不會直接把操作數作為浮點數進行運算。需要這些步驟完成運算:

1、把操作數8和1轉換成32位整數;

2、每一位按位或運算;

3、把結果轉換成64位浮點數。比如:

 8 | 1;  // 9
 //000000000000000000000000000001000 | 000000000000000000000000000001001 = 000000000000000000000000000001001

浮點數的計算是不精確的,浮點運算只能四舍五入到最接近的可表示的實數。當執行一系列的運算時,隨着舍入誤差的積累,運算結果會越來越不精確。比如:

0.1 + 0.2;  //0.30000000000000004
0.1 + 0.2 + 0.3;   //0.6000000000000001

加法中的結合律在JavaScript中對於浮點數有時候並不成立:

(0.1 + 0.2) + 0.3;   //0.6000000000000001
0.1 + (0.2 + 0.3);   //0.6

小心浮點數,解決其計算不精確的一個簡單策略就是將浮點數轉換成整數進行運算,整數的運算是精確的,不用擔心舍入誤差。

二、當心隱式的強制轉換

JavaScript中,運算符+既重載了數字相加,又重載了字符串連接操作,這取決於其參數的類型,簡單總結如下:

(1)如果兩個操作數都是數值,執行常規加法運算

(2)如果有一個操作數是字符串,則將另一個操作數轉換成字符串,再進行字符串的拼接

(3)如果有一個操作數是對象、數值或布爾值,如果 toString 方法存在並且返回原始類型,返回 toString 的結果。如果toString 方法不存在或者返回的不是原始類型,調用 valueOf 方法,如果 valueOf 方法存在,並且返回原始類型數據,返回 valueOf 的結果。其他情況,拋出錯誤。如果是undefined、null、NaN會調用String()函數取得字符串值’undefined’、’null’、’NaN’,再按照情形(2)進行運算

算數運算符-*/、和%在計算之前都會嘗試將其參數轉換為數字,簡單總結如下:

(1)如果兩個操作數都是數值,執行常規運算

(2)如果有一個數是NaN,則結果是NaN

(3)如果有一個操作數字符串、布爾值、null或undefined,則先調用Number()方法將其轉換為數值,再進行運算

(4)如果有一個操作數是對象,如果 valueOf 存在,且返回原始類型數據,返回 valueOf 的結果。如果 toString 存在,且返回原始類型數據,返回 toString 的結果。其他情況,拋出錯誤。再按照上面規則進行運算。

因此,valueOf()toString()方法應該被同時重寫,並返回相同的數字字符串或數值表示,才不至於強制隱式轉換時得到意想不到的結果。
  
邏輯運算符||&&可以接受任何值作為參數,會將參數隱式的強制轉換成布爾值。JavaScript中有6個假值:false、0、“”、NaN、null和undefined,其他所有的值都為真值。因此在函數中判斷參數是否是undefined不能簡單的使用if,而應該使用typeof:

function isUndefined(a){
    if (typeof a === 'undefined'){    //或者a === undefined
        console.log('a is not defined')
    }
}

三、避免對混合類型使用==運算符

"1.0e0" == {valueOf: function(){return true}};   //true

相等操作符==在比較兩個參數時會參照規則進行隱式轉換,判斷兩個值是否相等,使用全等操作符===是最安全的。j簡單總結一下==的隱式轉換規則:

四、盡量少用全局對象,始終聲明局部變量

定義全局變量會污染共享的公共命名空間,可能導致意外的命名沖突,不利於模塊化,導致程序中獨立組件間的不必要耦合。全局變量在瀏覽器中會被綁定到全局的window對象,添加或修改全局變量會自動更新全局對象,更新全局對象也會自動更新全局全局命名空間。

window.foo;  //undefined
var foo = 'global foo';
window.foo;  //"global foo"
window.foo = 'changed'
foo;  //changed

JavaScript會把沒有使用var聲明的變量簡單地當做全局變量,如果忘記聲明局部變量,改變量會被隱式地轉變成全局變量。任何時候都應該使用var聲明局部變量。

function swap(array, i, j){
    var temp = a[i];    //使用var聲明局部變量,否則temp會變成全局變量
    a[i] = a[j];
    a[j] = temp;
}

五、理解變量提升

JavaScript不支持塊級作用域,變量定義的作用域並不是離其最近的封閉語句或代碼塊,而是包含它們的函數。來看一個例子。

function test(params) {
    for(var i = 0; i < 10; i++){
        var params = i;
    }
    return params;
}
test(20);  //9

在for循環中聲明了一個局部變量params,由於JavaScript不支持塊級作用域,params重新聲明了函數參數params,導致最后的結果並不是我們傳進去的值。

理解JavaScript變量聲明需要把聲明變量看作由聲明和賦值兩部分組成。JavaScript隱式地提升聲明部分到封閉函數的頂部,而將賦值留在原地。也就是變量的作用域是整個函數,在=語句出現的位置進行賦值。下面第一種方式會被JavaScript隱式地提升聲明部分,等價於第二種方式那樣。建議手動提升局部變量的聲明,避免混淆。

function f() {                              function f() {
     /*do something*/                           var x;
    //...                                       //...
    {                                           {
        //...                                       //...
        var x = /*...*/                             x = /*...*/
        //...                                       //...
    }                                           }
}                                            }

JavaScript沒有塊級作用域的一個例外是異常處理,try-catch語句將捕獲的異常綁定到一個變量,該變量的作用域只是catch語句塊。下面的例子中catch語句塊中的x值的改變並沒有影響最初聲明的x的值,說明該變量的作用域只是catch語句塊。

function test(){
    var x = 'var', result = [];
    result.push(x);
    try{
        throw 'exception';
    } catch(x){
        x = 'catch';
    }
    result.push(x);
    return result;
}
test();  //["var", "var"]

六、熟練掌握高階函數

  高階函數是那些將函數作為參數或返回值的函數,是一種更為抽象的函數。函數作為參數(其實就是回調函數)在JavaScript中被大量使用:

[3,2,1,1,4,9].sort(function(){
    if(x < y){
        return -1;
    }
    if(x > y){
        return 1;
    }
    return 0;
});   //[1,1,2,3,4,9]

var name = ['tongyang', 'Bob', 'Alice'];
name.map(function(name){
    return name.toUpperCase();
});   //['TONGYANG', 'BOB', 'ALICE']

學會使用高階函數通常可以簡化代碼並消除繁瑣的樣板代碼,如果出現重復或者相似的代碼,我們可以考慮使用高階函數。

var aIndex = "a".charCodeAt(0);   //97
var alphabet = "";
for(var i = 0; i < 26; ++i){
    alphabet += String.fromCharCode(aIndex + i)
}
alphabet;  //"abcdefghijklmnopqrstuvwxyz"

var digits = "";
for(var i = 0; i < 10; ++i){
    digits += i;
}
digits;  //0123456789

var random = "";
for(var i = 0; i < 8; ++i){
    random += String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
}
random;  //atzuvtcz

這三段代碼有相同的基本邏輯,按照特定的規則拼接字符串。我們使用高階函數來重寫這段代碼

function buildString(number, callback){
    var result = "";
    for(var i = 0; i < number; ++i){
        result += callback(i);
    }
    return result;
}

var aIndex = "a".charCodeAt(0);   //97
var alphabet = buildString(26, function(i){
    return String.fromCharCode(aIndex + i);
});
var digits = buildString(10, function(i){
    return i;
});
var random = buildString(8, function(){
    return String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
});

相比之下,高階函數更簡捷,邏輯更清晰,掌握高階函數會提高代碼質量,這需要多讀優秀的源碼,多在項目中實踐才能熟練的掌握。

七、在類數組對象上復用通用的數組方法

Array.prototype中的標准方法被設計成其他對象可復用的方法,即使這些對象沒有繼承Array。

在JavaScript中很常見的類數組對象是DOM中的NodeList。類似document.getElementsByTagName這樣的操作會查詢Web頁面中的節點,並返回NodeList作為搜索的結果。我們可以在NodeLIst對象上面使用通用的數組方法,比如forEach、map、filter。

scriptNodeList = document.getElementsByTagName('script');
[].forEach.call(scriptNodeList, function(node){
    console.log(node.src);
});

類數組對象有兩個基本特征:

(1)具有一個整形length屬性

(2)length屬性大於該對象的最大索引。索引是一個整數,它的字符串表示的是該對象中的一個key

可以用一個對象字面量來創建類數組對象:

var arrayLike = {0: "a", 1: "b", 2: "c", length: 3};
var result = [].map.call(arrayLike, function(s){
    return s.toUpperCase();
});
result;  //["A", "B", "C"]

字符串也可以使用通用的數組方法

var result = [].map.call("abc", function(s){ 
    return s.toUpperCase();
}); //["A", "B", "C"]

只有一個Array方法不是通用的,即數組連接方法concat。這個方法會檢查參數的[[Class]]屬性。如果參數是一個真實的數組,則會將該數組的內容連接起來作為結果;否則,參數將以一個單一的元素來連接.

function namesColumn() {
    return ["Names"].concat(arguments);
}
namesColumn('tongyang', 'Bob', 'Frank');  //["Names", Arguments[3]]

可以使用slice方法來達到我們的目的

function namesColumn() {
    return ['Names'].concat([].slice.call(arguments));
}
namesColumn('tongyang', 'Bob', 'Frank');  /*["Names", "tongyang", "Bob", "Frank]*/

在類數組對象上復用通用的數組方法可以極大的減少冗余代碼,提高代碼質量

歡迎加入QQ群:374933367,與騰雲閣原創作者們一起交流,更有機會參與技術大咖的在線分享!

相關閱讀

Kotlin Native 詳細體驗,你想要的都在這兒
Java 程序員快速上手 Kotlin 11招
JavaScriptCore全面解析 (上篇)


此文已由作者授權騰訊雲技術社區發布,轉載請注明文章出處
原文鏈接:https://www.qcloud.com/community/article/560964
獲取更多騰訊海量技術實踐干貨,歡迎大家前往騰訊雲技術社區


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM