歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> JavaScript 基礎優化(《JavaScript 高級程序設計》讀書筆記)

JavaScript 基礎優化(《JavaScript 高級程序設計》讀書筆記)

日期:2017/3/1 9:28:59   编辑:Linux編程

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

2、循環引用:對象 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;

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

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

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

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

其他模式的缺點:

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

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

原型模式:原型中所有屬性是被很多實例共享的,這種共享對於函數非常合適,然而對於包含引用類型值的屬性來說問題比較大。

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

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

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

7、由於閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的內存,過度使用閉包可能會導致內存占用過多。
8、由於 JavaScript 沒有塊級作用域,因此可以使用匿名函數來模仿塊級作用域:

(function(){
    //這裡是塊級作用域
})();

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

9、盡量不要使用間歇調用,因為在不加干涉的情況下,間歇調用將會一直執行到頁面卸載,而且後一個間歇調用可能會在前一個間歇調用結束之前啟動,最好使用超時調用來模擬間歇調用,對比如下兩段代碼:

間歇調用:
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);

10、為了確保跨浏覽器兼容,最好還是將 nodeType 屬性與數字值進行比較,因為IE無法訪問 Node 類型。

11、使用cloneNode()方法時在復制之前最好先移除事件處理程序,因為IE 在此存在一個 bug,它會復制事件處理程序。

12、盡量減少訪問 NodeList 的次數。因為每次訪問 NodeList,都會運行一次基於文檔的查詢。

13、由於老版本的浏覽器不支持,因此在有特殊需要時再使用事件捕獲,可以放心地使用事件冒泡。

14、因為HTML 與 JavaScript 代碼緊密耦合,因此不要使用 HTML 事件處理程序,可以使用 JavaScript 指定事件處理程序。

15、使用事件委托,在DOM 樹中盡量最高的層次上添加一個事件處理程序,不必給每個可單擊的元素分別添加事件處理程序,因為事件委托利用了事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。最適合采用事件委托技術的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。

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

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

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

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

18、不建議使用常規的 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; //選項的值

JavaScript高級程序設計(第3版)高清完整PDF中文+英文+源碼 http://www.linuxidc.com/Linux/2014-09/107426.htm

如何使用JavaScript書寫遞歸函數 http://www.linuxidc.com/Linux/2015-01/112000.htm

JavaScript核心概念及實踐 高清PDF掃描版 (邱俊濤) http://www.linuxidc.com/Linux/2014-10/108083.htm

理解JavaScript中的事件流 http://www.linuxidc.com/Linux/2014-10/108104.htm

Copyright © Linux教程網 All Rights Reserved