讀《編寫可維護的javascript》筆記


第一章 基本的格式化

  • 縮進層級:推薦 tab:4;

  • 換行:在運算符后面換行,第二行追加兩個縮進;

// Good: Break after operator, following line indented two levels
callAFunction(document, element, window, "some string value", true, 123,
        navigator);

// Bad: Following line indented only one level
callAFunction(document, element, window, "some string value", true, 123,
    navigator);

// Bad: Break before operator
callAFunction(document, element, window, "some string value", true, 123
        , navigator);
  • 添加空行:在方法之間; 在方法的局部變量和第一條語句之間; 在多行或單行注釋之前; 在方法內的邏輯片段插入空行提高可讀性(比如for, if);

// good
if (wl && wl.length) {

    for (i = 0, l = wl.length; i < l; ++i) {
        p = wl[i];
        type = Y.Lang.type(r[p]);

        if (s.hasOwnProperty(p)) {

            if (merge && type == 'object') {
                Y.mix(r[p], s[p]);
            } else if (ov || !(p in r)) {
                r[p] = s[p];
            }
        }
    }
}
  • 常量:采用大寫字符加下划線的方式(var MAX_COUNT);

var MAX_COUNT = 10;
var URL = "http://www.nczonline.net/";

if (count < MAX_COUNT) {
    doSomething();
}
  • 不推薦顯示的構建數組和對象(采用new的方式);

  • 字符串:推薦使用雙引號; 
// Good
var name = "Nicholas";

// Bad: Single quotes
var name = 'Nicholas';

// Bad: Wrapping to second line
var longString = "Here's the story, of a man \
named Brady.";
  • 不要使用“\”來連接字符串,推薦使用“+”;

第二章 注釋

  • 注釋:注釋前面空一行,與下一行對其; 程序代碼比較明了的時候,不推薦添加注釋; 可能被認為錯誤的代碼可加注釋提醒不要隨意修改;  
/*
 *注意前面的空格(不好)
 * 注意前面的空格(好)
 */

第三章 語句和表達式

  • 塊語句間的空格,推薦使用下面這種
if (condition) {
    doSomething();
}
  • 不要使用 for in 來循環數組,因為 index 是字符串形式,而不是數字形式,容易出現問題。可以用來循環對象;

第四章 變量、函數和運算符

  • 嚴格模式:只在需要的地方添加,盡量不要全局使用,除非你已經明確都要嚴格模式。
// 推薦使用方式
function fn() {
    "use strict";
    //代碼
}

//或者
(function() {
    "use strict";
    function fn1() {

    }
    function fn2() {
        
    }
})();
  • 相等:推薦使用“===” 和 “!==”這樣可以避免轉換,出現潛在的風險;

  • 盡量不適用 eval , Function, 以及不要給 setTimeout,setInterval 傳入字符串;

第五章 UI的松耦合

  • 避免使用css 表達式 expression;

  • 用 js 修改樣式的時候,采用添加或者移除類名的方式,而不是直接使用.style.color .style.cssText;

// Bad
element.style.color = "red";
element.style.left = "10px";
element.style.top = "100px";
element.style.visibility = "visible";

element.style.cssText = "color: red; left: 10px; top: 100px; visibility: hidden";

//樣式
.reveal {
    color: red;
    left: 10px;
    top: 100px;
    visibility: visible;
}

// Good - Native
element.className += " reveal";

// Good - HTML5
element.classList.add("reveal");

// Good - YUI
Y.one(element).addClass("reveal");

// Good - jQuery
$(element).addClass("reveal");

// Good - Dojo
dojo.addClass(element, "reveal");
  • 在HTML中不要使用javascript;比如嵌入在HTML中的onClick

<!-- Bad -->
<button onclick="doSomething()" id="action-btn">Click Me</button>
  • 將HTML從JavaScript中 抽離。

// Bad
var div = document.getElemenetById("my-div"); div.innerHTML = "<h3>Erroe</h3>";
  • 解決辦法有三種:1、從服務器加載;
function loadDialog(name, oncomplete) {
    var xhr = new XMLHttpRequest();
    xhr.open("get", "/js/dialog/" + name, true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
            var div = document.getElementById("dlg-holder");
            div.innerHTML = xhr.responseText;
            oncomplete();
        } else {
            // handle error
        }
    };
    xhr.send(null);
}
  • 2、使用簡單客戶端模板;
// 模板
<li><a href="%s">%s</a></li>

function sprintf(text) {
    var i=1, args=arguments;
    return text.replace(/%s/g, function() {
        return (i < args.length) ? args[i++] : "";
    });
}
// usage
var result = sprintf(templateText, "/item/4", "Fourth item");
  • 3、復雜客戶端模板; 比如使用 Handlebars
// 模板放在 script 中
<script type="text/x-handlebars-template" id="list-item">
    <li><a href="{{url}}">{{text}}</a></li>
</script>

function addItem(url, text) {
    var mylist = document.getElementById("mylist"),
    script = document.getElementById("list-item"),
    templateText = script.text,
    template = Handlebars.compile(script.text),
    div = document.createElement("div"),
    result;

    result = template({
        text: text,
        url: url
    });

    div.innerHTML = result;
    list.appendChild(div.firstChild);
}

// 使用
addItem("/item/4", "Fourth item");

第六章 避免使用全局變量

  • 避免使用全局變量,存在問題:命名沖突;代碼的脆弱性;難以測試

  • 意外的全局變量:忘了var;  解決辦法: 使用 JSLint 或者 JSHint ;

  • 單全局變量: 比如YUI定義了一個YUI全局變量; jQuery 定義了兩個全局變量,$ 和 jQuery;

  • 命名空間: 比如 YUI.event 下的所有方法都是和事件相關的。

  • 模塊: AMD模塊 和 YUI 模塊;

// AMD    
define("my-books", [ "dependency1", "dependency2" ],
     function(dependency1, dependency2) {
          var Books = {};
          Books.MaintainableJavaScript = {
          author: "Nicholas C. Zakas"
     };
    return Books;
  });
  • 零全局變量:使用前提是內部代碼與外部代碼無聯系。
    (function(win) {
        var doc = win.document;

    })(window)

第七章 事件處理

  • 事件處理典型用法
// 不好的
function handleClick(event) {
    var popup = document.getElementById("popup");
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "reveal";
}

// addListener() from Chapter 7
addListener(element, "click", handleClick);
  • 規則1: 隔離應用邏輯
// 好的 - separate application logic
var MyApplication = {
    handleClick: function(event) {
        this.showPopup(event);
    },
    showPopup: function(event) {
        var popup = document.getElementById("popup");
        popup.style.left = event.clientX + "px";
        popup.style.top = event.clientY + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(event) {
    MyApplication.handleClick(event);
});
  • 規則2: 不要分發事件對象;上面存在的問題是event 事件被無節制分發。從匿名事件處理函數到另一個函數,再到另一個函數。最佳方法: 讓事件處理程序使用event對象處理事件,然后拿到所有需要的數據傳給應用邏輯。
// Good
var MyApplication = {
    handleClick: function(event) {
        this.showPopup(event.clientX, event.clientY);
    },
    showPopup: function(x, y) {
        var popup = document.getElementById("popup");
        popup.style.left = x + "px";
        popup.style.top = y + "px";
        popup.className = "reveal";
    }
};
addListener(element, "click", function(event) {
    MyApplication.handleClick(event); // this is okay
});
  • 當處理事件時候,最好讓事件處理程序成為接觸到event對象的唯一的函數。
// Good
var MyApplication = {
    handleClick: function(event) {
        // assume DOM Level 2 events support
        event.preventDefault();
        event.stopPropagation();
        // pass to application logic
        this.showPopup(event.clientX, event.clientY);
    },
    showPopup: function(x, y) {
        var popup = document.getElementById("popup");
        popup.style.left = x + "px";
        popup.style.top = y + "px";
        popup.className = "reveal";
    }
};
addListener(element, "click", function(event) {
    MyApplication.handleClick(event); // this is okay
});

第八章 避免 “空比較“

  • 下面是不好的示范,你很多都與null不相等,包括數字,字符串,對象等,從而帶來隱藏的錯誤。只有當期待的值是null的時候,可以和null進行比較。
var Controller = {
    process: function(items) {
        if (items !== null) { // Bad
            items.sort();
            items.forEach(function(item) {
                // do something
            });
        }
    }
};
  • 檢測基本類型使用typeof, 引用類型使用instanceof, 還有一種鴨式辨型,采用對象特有的方法或者屬性是否存在來判斷,例如array.sort(), 正則的test方法。通用的方法是,其他類型類似:
function isArray(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
}
  • 檢測屬性,判斷屬性最好的辦法是用in
// 不好,當屬性值是0, "", false, null或者 undefined 
if (object[propertyName]) {
    // do something
}

// Bad: Compare against null
if (object[propertyName] != null) {
    // do something
}

// Bad: Compare against undefined
if (object[propertyName] != undefined) {
    / do something
}

var object = {
    count: 0,
    related: null
};

// Good
if ("count" in object) {
    // this executes
}
if ("related" in object) {
    // this executes
}

//當你只想檢查實例對象的某個屬性是否存在的時候使用hasOwnProperty()
// Good for all non-DOM objects
if (object.hasOwnProperty("related")) {
    //this executes
}
// Good when you're not sure
if ("hasOwnProperty" in object && object.hasOwnProperty("related")) {
    //this executes
}

 第九章 將配置數據從代碼中分離出來

  • 什么是配置數據? 就是JavaScript代碼中有可能發生改變的。比如:url, 需要展現給用戶的字符串, 重復的值, 設置, 任何可能發生變化的值。下面是不好的實例,其中的 invalid value, href的值, selcted 都是配置數據。
// Configuration data embedded in code
function validate(value) {
    if (!value) {
        alert("Invalid value");
        location.href = "/errors/invalid.php";
    }
}
function toggleSelected(element) {
    if (hasClass(element, "selected")) {
        removeClass(element, "selected");
    } else {
        addClass(element, "selected");
    }
}
  •  抽離配置數據
// Configuration data externalized
var config = {
    MSG_INVALID_VALUE: "Invalid value",
    URL_INVALID: "/errors/invalid.php",
    CSS_SELECTED: "selected"
};
function validate(value) {
    if (!value) {
        alert(config.MSG_INVALID_VALUE);
        location.href = config.URL_INVALID;
    }
}
function toggleSelected(element) {
    if (hasClass(element, config.CSS_SELECTED)) {
        removeClass(element, config.CSS_SELECTED);
    } else {
        addClass(element, config.CSS_SELECTED);
    }
}

 第十章 拋出自定義錯誤

  • 推薦在錯誤消息中包含函數名稱,以及函數失敗的原因。
function getDivs(element) {
    if (element && element.getElementsByTagName) {
        return element.getElementsByTagName("div");
    } else {
        throw new Error("getDivs(): Argument must be a DOM element.");
    }
}
  •  何時拋出錯誤? 辨別代碼中哪些部分在特定的情況下最優可能導致失敗,並只在哪些地方拋出錯誤才是關鍵所在。我們的目的不是防止錯誤,而是錯誤發生的時候更加容易地調試。
// 不好的,太多檢查
function addClass(element, className) {
    if (!element || typeof element.className != "string") {
        throw new Error("addClass(): First argument must be a DOM element.");
    }
    if (typeof className != "string") {
        throw new Error("addClass(): Second argument must be a string.");
    }
    element.className += " " + className;
}
// 好的寫法  第二個參數是null 或者一個數字或者一個布爾值時是不會拋出錯誤的
function addClass(element, className) {
    if (!element || typeof element.className != "string") {
        throw new Error("addClass(): First argument must be a DOM element.");
    }
    element.className += " " + className;
}
  •  使用try catch 語句,當在try中 發生一個錯誤,會立即跳到catch語句,傳入一個錯誤對象。還可以增加一個finally 模塊,不管錯誤發不發生都會執行。
try {
    somethingThatMightCauseAnError();
} catch (ex) {
    handleError(ex);
} finally {
    continueDoingOtherStuff();
}

 第十一章 不是你的對象不要動

  • 以下這些對象不要嘗試去修改他們,因為他們已經存在了: 原生對象(Object, Array), DOM對象(document), 瀏覽器對象模型(BOM)對象(window), 類庫的對象。 對待他們的原則是:不覆蓋方法; 不新增方法; 不刪除方法; 下面是一些不好的示例:
document.getElementById = function() {
    return null; // talk about confusing
};

document._originalGetElementById = document.getElementById;
document.getElementById = function(id) {
    if (id == "window") {
        return window;
    } else {
        return document._originalGetElementById(id);
    }
};

document.sayImAwesome = function() {
    alert("You're awesome.");
};

Array.prototype.reverseSort = function() {
    return this.sort().reverse();
};

YUI.doSomething = function() {
    // code
};

document.getElementById = null;

 更好的途徑:有三種,見代碼

//基於對象的繼承
var person = {
    name: "Nicholas",
    sayName: function() {
    alert(this.name);
}
};
var myPerson = Object.create(person);
myPerson.sayName(); // pops up "Nicholas"

//基於類型的繼承
function MyError(message) {
    this.message = message;
}
MyError.prototype = new Error();
var error = new MyError("Something bad happened.");
console.log(error instanceof Error); // true
console.log(error instanceof MyError); // true

//門面模式  與適配器唯一不同是 其創建新的接口,后者實現已經存在的接口
function DOMWrapper(element) {
    this.element = element;
}
DOMWrapper.prototype.addClass = function(className) {
    this.element.className += " " + className;
};
DOMWrapper.prototype.remove = function() {
    this.element.parentNode.removeChild(this.element);
};

// Usage
var wrapper = new DOMWrapper(document.getElementById("my-div"));

// add a CSS class
wrapper.addClass("selected");

// remove the element
wrapper.remove();

 第十二章 瀏覽器嗅探

  • User-agent 檢測
// Bad
if (navigator.userAgent.indexOf("MSIE") > -1) {
    // it's Internet Explorer
} else {
    // it's not
}

// good 不要試圖檢測IE 9及其高版本,而應該只檢測IE8以及之前的
if (isInternetExplorer8OrEarlier) {
    // handle IE8 and earlier
} else {
    // handle all other browsers
}
  • 特性檢測,推薦使用特性檢測
// Bad
if (navigator.userAgent.indexOf("MSIE 7") > -1) {
    // do something
}

// Good
if (document.getElementById) {
    // do something
}
  • 避免特性推斷
// Bad - uses feature inference
// 不能從一個特性推斷出另一個特性是否存在
function getById (id) {
    var element = null;
    if (document.getElementsByTagName) { // DOM
        element = document.getElementById(id);
    } else if (window.ActiveXObject) { // IE
        element = document.all[id];
    } else { // Netscape <= 4
        element = document.layers[id];
    }
    return element;
}
  • 避免瀏覽器推斷,你不能根據某種方法存在就判定是某一種瀏覽器
// Bad
if (document.all) { // IE
    id = document.uniqueID;
} else {
    id = Math.random();
}

var isIE = !!document.all;

var isIE = !!document.all && document.uniqueID;

后面的章節是另一部分,比如文件目錄,自動化啊等,不好總結。

如果覺得好,請點擊下方的推薦噢,感謝您的閱讀!


免責聲明!

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



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