JavaScript 基礎優化(讀書筆記)

一、帶有 src 屬性的<script>元素不該該在其<script>和</script>標籤之間再包含額外的 JavaScript 代碼。若是包含了嵌入的代碼,則只會下載並執行外部腳本文件,嵌入的代碼會被忽略。通常都把所有 JavaScript 引用放在<body>元素中頁面內容的後面。node

二、循環引用:對象 A 中包含一個指向對象 B 的指針,而對象 B 中也包含一個指向對象 A 的引用:瀏覽器

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;

IE8及之前版本中有一部分對象並非原生 JavaScript 對象,而是使用 C++以 COM(Component Object Model,組件對象模型)對象的形式實現的的,而 COM 對象的垃圾收集機制因爲採用了引用計數策略,因此會有循環引用的問題,而循環引用會致使即便將例子中的 DOM 從頁面中移除,它也永遠不會被回收,所以會致使內存泄露。因此一旦數據再也不有用,最好經過將其值設置爲 null 來釋放其引用:閉包

myObject.element = null;
element.someObject = null;

三、未初始化的變量會自動被賦予 undefined 值,但顯式地初始化變量依然是明智的選擇,當 typeof 操做符返回"undefined"值時,咱們就知道被檢測的變量尚未被聲明,而不是還沒有初始化。函數

四、建立對象推薦組合使用構造函數模式和原型模式:this

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

組合使用構造函數模式和原型模式中構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。spa

其餘模式的缺點:prototype

工廠模式:雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型);指針

構造函數模式:每一個方法都要在每一個實例上從新建立一遍;code

原型模式:原型中全部屬性是被不少實例共享的,這種共享對於函數很是合適,然而對於包含引用類型值的屬性來講問題比較大。orm

五、JavaScript中的繼承可使用組合繼承(也叫僞經典繼承):

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    //繼承屬性
    SuperType.call(this, name);
    this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

組合繼承使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承,從而避免了原型鏈和借用構造函數的缺陷。可是組合繼承也有不足,即不管什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候(new SuperType()),另外一次是在子類型構造函數內部(SuperType.call(this, name)),寄生組合式繼承克服了這個缺點,基本模式以下:

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); //建立對象
    prototype.constructor = subType; //加強對象
    subType.prototype = prototype; //指定對象
}

所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。其背後的基本思路是:沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。咱們就能夠用調用 inheritPrototype()函數的語句,去替換前面例子中爲子類型原型賦值的語句,例如:

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
};

六、因爲耦合的問題,在編寫遞歸調用時,使用 arguments.callee 總比使用函數名更保險:

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1); //不要使用return num * factorial(num-1);
    }
}

七、因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存,過分使用閉包可能會致使內存佔用過多。
八、因爲 JavaScript 沒有塊級做用域,所以可使用匿名函數來模仿塊級做用域:

(function(){
    //這裏是塊級做用域
})();

這種作法能夠減小閉包占用的內存問題,由於沒有指向匿名函數的引用。這種技術也常常在全局做用域中被用在函數外部,從而限制向全局做用域中添加過多的變量和函數。

九、儘可能不要使用間歇調用,由於在不加干涉的狀況下,間歇調用將會一直執行到頁面卸載,並且後一個間歇調用可能會在前一個間歇調用結束以前啓動,最好使用超時調用來模擬間歇調用,對好比下兩段代碼:

間歇調用:
var num = 0;
var max = 10;
var intervalId = null;
function incrementNumber() {
    num++;
    //若是執行次數達到了 max 設定的值,則取消後續還沒有執行的調用
    if (num == max) {
        clearInterval(intervalId);
        alert("Done");
    }
}
intervalId = setInterval(incrementNumber, 500);
超時調用模擬間歇調用:
var num = 0;
var max = 10;
function incrementNumber() {
    num++;
    //若是執行次數未達到 max 設定的值,則設置另外一次超時調用
    if (num < max) {
        setTimeout(incrementNumber, 500);
    } else {
        alert("Done");
    }
}
setTimeout(incrementNumber, 500);

十、爲了確保跨瀏覽器兼容,最好仍是將 nodeType 屬性與數字值進行比較,由於IE沒法訪問 Node 類型。

十一、使用cloneNode()方法時在複製以前最好先移除事件處理程序,由於IE 在此存在一個 bug,它會複製事件處理程序。

十二、儘可能減小訪問 NodeList 的次數。由於每次訪問 NodeList,都會運行一次基於文檔的查詢。

1三、因爲老版本的瀏覽器不支持,所以在有特殊須要時再使用事件捕獲,能夠放心地使用事件冒泡。

1四、由於HTML 與 JavaScript 代碼緊密耦合,所以不要使用 HTML 事件處理程序,可使用 JavaScript 指定事件處理程序。

1五、使用事件委託,在DOM 樹中儘可能最高的層次上添加一個事件處理程序,沒必要給每一個可單擊的元素分別添加事件處理程序,由於事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。最適合採用事件委託技術的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。

1六、調用 submit()方法的形式提交表單時,不會觸發 submit 事件,所以要記得在調用此方法以前先驗證表單數據。與調用 submit()方法不一樣,調用 reset()方法會像單擊重置按鈕同樣觸發 reset 事件。

1七、讀取或設置文本框的值時不建議使用標準的 DOM 方法,而是使用 value 屬性:

var textbox = document.forms[0].elements["textbox1"];
textbox.value = "Some new value";

換句話說,不要使用 setAttribute()設置<input>元素的 value 特性,也不要去修改<textarea>元素的第一個子節點。緣由很簡單:對 value 屬性所做的修改,不必定會反映在 DOM 中。所以,在處理文本框的值時,最好不要使用 DOM 方法。

1八、不建議使用常規的 DOM 功能來訪問 option 元素的數據,由於效率比較低,最好是使用特定於選項的屬性,由於全部瀏覽器都支持這些屬性:

var selectbox = document.forms[0].elements["location"];
//不推薦
var text = selectbox.options[0].firstChild.nodeValue; //選項的文本
var value = selectbox.options[0].getAttribute("value"); //選項的值

//推薦
var text = selectbox.options[0].text; //選項的文本
var value = selectbox.options[0].value; //選項的值
相關文章
相關標籤/搜索