JavaScript高級程序設計 - 讀書筆記

JavaScript高級程序設計 - 讀書筆記 by JerryChan

第2章 在HTML中嵌入JavaScript

2.5 小結

  • 把JavaScript插入到HTML頁面中要使用<script>元素。使用這個元素能夠把JavaScript嵌入到HTML頁面中,讓腳本與標記混合在一塊兒;也能夠包含外部的JavaScript文件。而咱們須要注意的地方有:
    • 在包含外部JavaScript文件時,必須將src屬性設置爲指向相應文件 的 URL。而這個文件既能夠是與包含它的頁面位於同一個服務器上的文件,也能夠是其餘任何域中的文件。
    • 全部<script>元素都會按照它們在頁面中出現的前後順序依次被解析。在不使用defer和async屬性的狀況下,只有在解析完前面<script>元素中的代碼以後,纔會開始解析後面<script>元素中的代碼。
    • 因爲瀏覽器會先解析完不使用defer屬性的<script>元素中的代碼,而後再解析後面的內容,因此通常應該把<script>元素放在頁面最後,即主要內容後面,</body>標籤前面。
    • 使用defer屬性可讓腳本在文檔徹底呈現以後再執行。延遲腳本老是按照指定它們的順序執行。
    • 使用async屬性能夠表示當前腳本沒必要等待其餘腳本,也沒必要阻塞文檔呈現。不能保證異步腳本按照它們在頁面中出現的順序執行。
  • 另外,使用<noscript>元素能夠指定在不支持腳本的瀏覽器中顯示的替代內容。但在啓用了腳本的狀況下,瀏覽器不會顯示<noscript>元素中的任何內容。

第3章 基本概念

3.1.4 嚴格模式

  • ECMAScript 5引入了嚴格模式(strictmode)的概念。嚴格模式是爲JavaScript定義了一種不一樣的解析與執行模型。在嚴格模式下,ECMAScript 3中的一些不肯定的行爲將獲得處理,並且對某些不安全的操做也會拋出錯誤。要在整個腳本中啓用嚴格模式,能夠在頂部添加以下代碼:javascript

    "use strict";php

  • 在函數內部的上方包含這條編譯指示,也能夠指定函數在嚴格模式下執行:html

    functiondoSomething(){
         "usestrict";
         //函數體
     }

嚴格模式是一個編譯指示,目的是爲了兼容 ECMAScript 3語法。html5

3.3 變量

  • 雖然省略var操做符能夠定義全局變量,但這也不是咱們推薦的作法。由於在局部做用域中定義的全局變量很難維護,並且若是有意地忽略了var操做符,也會因爲相應變量不會立刻就有定義而致使沒必要要的混亂。給未經聲明的變量賦值在嚴格模式下會致使拋出ReferenceError錯誤。

省略var可聲明全局變量,但不推薦使用。java

3.4 數據類型

  • ECMAScript中有5種簡單數據類型(也稱爲基本數據類型):Undefined、Null、Boolean、Number和String。

使用操做符typeof能夠檢測變量的數據類型。jquery

3.4.3 Null類型

  • 實際上,undefined值是派生自null值的,所以ECMA-262規定對它們的相等性測試要返回true:web

    alert(null == undefined); //trueajax

  • 不管在什麼狀況下都沒有必要把一個變量的值顯式地設置爲undefined,但是一樣的規則對null卻不適用。換句話說,只要意在保存對象的變量尚未真正保存對象,就應該明確地讓該變量保存null值。這樣作不只能夠體現null做爲空對象指針的慣例,並且也有助於進一步區分null和undefined。編程

3.4.4 Boolean類型

  • 能夠對任何數據類型的值調用Boolean()函數,並且總會返回一個Boolean值。至於返回的這個值是true仍是false,取決於要轉換值的數據類型及其實際值。下表給出了各類數據類型及其對應的轉換規則。
數據類型 轉換爲true的值 轉換爲false的值
Boolean true false
String 任何非空字符串 ""(空字符串)
Number 任何非零數字值(包括無窮大) 0和NaN(參見本章後面有關NaN的內容)
Object 任何對象 bull
Undefined n/a undefined

n/a(或N/A),是notapplicable的縮寫,意思是「不適用」。json

3.4.5 Number類型

八進制字面量在嚴格模式下是無效的,會致使支持的JavaScript引擎拋出錯誤。在進行算術計算時,全部以八進制和十六進制表示的數值最終都將被轉換成十進制數值。

  • 浮點數值的最高精度是17位小數,但在進行算術計算時其精確度遠遠不如整數。例如,0.1加0.2的結果不是0.3,而是0.30000000000000004。這個小小的舍入偏差會致使沒法測試特定的浮點數值。例如:

    if(a + b == 0.3){
         //不要作這樣的測試!
         alert("You got 0.3.");
     }

永遠不要測試某個特定的浮點數值。這是使用基於IEEE754數值的浮點計算的通病。

  • 因爲內存的限制,ECMAScript並不能保存世界上全部的數值。ECMAScript可以表示的最小數值保存在Number.MIN_VALUE中——在大多數瀏覽器中,這個值是5e-324;可以表示的最大數值保存在Number.MAX_VALUE中——在大多數瀏覽器中,這個值是1.7976931348623157e+308。若是某次計算的結果獲得了一個超出JavaScript數值範圍的值,那麼這個數值將被自動轉換成Infinity值。具體來講,若是這個數值是負數,則會被轉換成-Infinity(負無窮),若是這個數值是正數,則會被轉換成Infinity(正無窮)。

  • NaN,即非數值(NotaNumber)是一個特殊的數值,這個數值用於表示一個原本要返回數值的操做數未返回數值的狀況(這樣就不會拋出錯誤了)。NaN有兩個特色。首先,任何涉及NaN的操做(例如NaN/10)都會返回NaN,這個特色在多步計算中有可能致使問題。其次,NaN與任何值都不相等,包括NaN自己。例如,下面的代碼會返回false:

    alert(NaN == NaN); //false
  • 針對NaN的這兩個特色,ECMAScript定義了isNaN()函數。這個函數接受一個參數,該參數能夠是任何類型,而函數會幫咱們肯定這個參數是否「不是數值」。例如:

    alert(isNaN(NaN));   //true 
     alert(isNaN(10));    //false(10是一個數值)
     alert(isNaN("10"));  //false(能夠被轉換成數值10)
     alert(isNaN("blue"));//true(不能轉換成數值)
     alert(isNaN(true));  //false(能夠被轉換成數值1)

使用parseInt()進行轉換時,應當在第二個參數指定基數。

3.4.6 String類型

  • 在不知道要轉換的值是否是是否是null或undefined的狀況下,還可使用轉型函數String(),這個函數可以將任何類型的值轉換爲字符串。String()函數遵循下列轉換規則:
    • 若是值有toString()方法,則調用該方法(沒有參數)並返回相應的結果;
    • 若是值是null,則返回"null";
    • 若是值是undefined,則返回"undefined"。

3.4.7 Object類型

  • 僅僅建立Object的實例並無什麼用處,但關鍵是要理解一個重要的思想:即在ECMAScript中,(就像Java中的java.lang.Object對象同樣)Object類型是全部它的實例的基礎。換句話說,Object類型所具備的任何屬性和方法也一樣存在於更具體的對象中。

  • Object的每一個實例都具備下列屬性和方法。
    • Constructor:保存着用於建立當前對象的函數。
    • hasOwnProperty(propertyName):用於檢查給定的屬性在當前對象實例中(而不是在實例的原型中)是否存在。其中,做爲參數的屬性名(propertyName)必須以字符串形式指定(例如:o.hasOwnProperty("name"))。
    • isPrototypeOf(object):用於檢查傳入的對象是不是另外一個對象的原型。
    • propertyIsEnumerable(propertyName):用於檢查給定的屬性是否可以使用for-in語句來枚舉。
    • toLocaleString():返回對象的字符串表示,該字符串與執行環境的地區對應。
    • toString():返回對象的字符串表示。
    • valueOf():返回對象的字符串、數值或布爾值表示。一般與toString()方法的返回值相同。

3.5 操做符

  • 左移/右移(<</>>):以符號位填充。
  • 無符號左移/右移(<<</>>>):以0填充。負數會變成無符號數(出現大整數的錯誤結果)。
  • 在涉及Infinity、-Infinity、NaN等數值的運算時要倍加當心。
  • 字符串比較時,比較的是字符編碼(如B>a爲真,"23" < "3"爲真),最好避免隱式轉換。
  • 儘可能使用全等===和不全等!==操做符,避免隱式轉換帶來的問題。

3.6 語句

  • with語句:大量使用with語句會致使性能降低,同時也會給調試代碼形成困難,所以在開發大型應用程序時,不建議使用with語句。
  • for-in語句:用於枚舉對象的全部屬性。

3.7 函數

  • 關於返回值:推薦的作法是要麼讓函數始終都返回一個值,要麼永遠都不要返回值。不然,若是函數有時候返回值,有時候有不返回值,會給調試代碼帶來不便。

  • 嚴格模式對函數有一些限制:
    • 不能把函數命名爲eval或arguments;
    • 不能把參數命名爲eval或arguments;
    • 不能出現兩個命名參數同名的狀況。

對參數的理解

  • ECMAScript函數的參數與大多數其餘語言中函數的參數有所不一樣。ECMAScript函數不介意傳遞進來多少個參數,也不在意傳進來參數是什麼數據類型。也就是說,即使你定義的函數只接收兩個參數,在調用這個函數時也未必必定要傳遞兩個參數。能夠傳遞一個、三個甚至不傳遞參數,而解析器永遠不會有什麼怨言。之因此會這樣,緣由是ECMAScript中的參數在內部是用一個數組來表示的。函數接收到的始終都是這個數組,而不關心數組中包含哪些參數(若是有參數的話)。若是這個數組中不包含任何元素,無所謂;若是包含多個元素,也沒有問題。實際上,在函數體內能夠經過arguments對象來訪問這個參數數組,從而獲取傳遞給函數的每個參數。

其實,arguments對象只是與數組相似(它並非Array的實例),由於可使用方括號語法訪問它的每個元素(即第一個元素是arguments[0],第二個元素是arguments[1],以此類推),使用length屬性來肯定傳遞進來多少個參數。ECMAScript不支持重載,但利用這一屬性,能夠模仿方法的重載。

function sayHi() { 
    alert("Hello" + arguments[0] + "," + arguments[1]); 
}

這個重寫後的函數中不包含命名的參數。雖然沒有使用name和message標識符,但函數的功能依舊。這個事實說明了ECMAScript函數的一個重要特色:命名的參數只提供便利,但不是必需的。

第4章 變量、做用域和內存問題

4.1 基本類型和引用類型的值

  • 第3章討論了5種基本數據類型:Undefined、Null、Boolean、Number和String。這5種基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值。

  • 引用類型的值是保存在內存中的對象(Object)。與其餘語言不一樣,JavaScript不容許直接訪問內存中的位置,也就是說不能直接操做對象的內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。爲此,引用類型的值是按引用訪問的。

思考:

function setName( obj) { 
    obj. name = "Nicholas"; 
    obj = new Object(); 
    obj.name = "Greg"; 
} 
var person = new Object(); 
setName( person); 
alert( person. name); //"Nicholas"
//函數結束後,函數內建立的引用被銷燬,只有對原引用的修改生效。
  • 類型檢測:判斷基本數據類型使用typeof,判斷對象類型用instanceof。

    alert( person instanceof Object);

4.2 執行環境和做用域

  • JavaScript沒有塊級做用域,函數是最小的執行環境。執行環境是其中聲明變量的做用域。

4.3 垃圾收集

  • 大多數現代瀏覽器在變量離開執行環境時就會進行內存清除工做。
  • 頻繁調用垃圾收集會產生性能問題(不建議使用window. CollectGarbage()或相似的函數)。
  • 優化內存佔用的方式:
    • 一旦數據再也不有用,將其值設置爲null,即解引用(主要針對全局變量)。
    • 局部變量離開執行環境時會被自動解引用。

第五章 引用類型

5.2 Array類型

  • length屬性並不是只讀,能夠經過length修改長度。
  • 檢測對象是否Array:
    • 若是使用instanceof操做符,在包含多個全局執行環境的狀況下可能出錯。
    • 應使用isArray()。
  • Array提供了棧、隊列、排序、鏈接、查找等方法。重點掌握:
    • splice():基於當前數組的一個或多個項建立數組。

      var colors = ["red", "green", "blue", "yellow", "purple"]; 
       var colors2 = colors. slice( 1); 
       var colors3 = colors. slice( 1, 4); 
       alert( colors2); //green, blue, yellow, purple 
       alert( colors3); //green, blue, yellow
    • splice():對數組指定項執行刪除/插入/替換操做。

      array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
       // start:開始位置 deleteCount:刪除項的數量 item1...:插入的項
    • 迭代方法:every(),forEach(),filter(),map(),some()
      傳入的參數爲迭代函數,該函數接受item, index, array三個量。

      var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
       var everyResult = numbers.every(function(item, index, array){ 
           return (item > 2);
       }); 
       alert( everyResult);
    • 縮小方法:reduce(), reduceRight() 與上相似,經常使用於累加等操做。

5.3 Date類型

  • 建立Date對象:
    • 無參數:取當前日期和時間
    • 使用 Date.parse("datestring")做爲參數:注意非法日期返回值
    • 使用 Date.UTC(year,month,day,hrs,min,sec,msec)做爲參數(月份從0開始)
  • 繼承的方法:
    • toString()toLocaleString()有格式差異。
    • valueOf()返回日期的毫秒錶示。

5.4 RegExp類型

  • 三種標誌:
    • g:表示全局(global)模式,即模式將被應用於全部字符串,而非在發現第一個匹配項時當即中止;
    • i:表示不區分大小寫(case-insensitive)模式,即在肯定匹配項時忽略模式與字符串的大小寫;
    • m:表示多行(multiline)模式,即在到達一行文本末尾時還會繼續查找下一行中是否存在與模式匹配的項。
  • 兩種構建形式:
    • 使用字面量構建:var pattern1 = /[bc]at/i;
    • 使用RegExp構造函數構建:var pattern2 = new RegExp("[bc]at", "i");
    • 使用RegExp構造函數,轉義字符須要雙重轉義。

5.5 Function類型

  • 沒有重載
  • 內部屬性:argumentsthis
    • argumentscallee屬性,該指針指向擁有該arguments的函數。
    • callee屬性用於函數遞歸能夠下降函數的耦合程度。
    • this引用的是函數據以執行的環境對象。
    • 函數有caller屬性,該指針指向調用該當前函數的引用。
  • 函數屬性:lengthprototype
    • length定義了函數但願接受的命名參數的個數。
    • prototype與函數繼承的實現有關,以後詳述。
  • 函數方法:apply()call()
    • apply()至關於設置函數體內this對象的值。

      function sum(num1, num2){ 
           return num1 + num2; 
       } 
       function callSum1(num1,num2){ 
           return sum.apply( this, arguments); //傳入arguments對象 
       } 
       function callSum2(num1, num2){ 
           return sum.apply( this, [num1, num2]); // 傳入數組 
       }
       alert( callSum1( 10, 10)); //20 
       alert( callSum2( 10, 10)); //20
    • call()apply()做用相同,但傳遞參數的方式不一樣。

      function sum(num1, num2){ 
           return num1 + num2; 
       } 
       function callSum(num1, num2){ 
           return sum. call( this, num1, num2); 
       } 
       alert( callSum( 10, 10)); //20
    • apply()call()真正強大的地方是可以擴充函數賴以運行的做用域。使用call()apply()來擴充做用域的最大最大好處,就是對象不須要與方法有任何耦合關係。
    • ECMAScript 5還定義了一個方法:bind()。這個方法會建立一個函數的實例,其this值會被綁定到傳給bind()函數的值。

      window. color = "red"; 
       var o = { color: "blue" }; 
       function sayColor(){ 
           alert(this. color); 
       }
       var objectSayColor = sayColor. bind(o); 
       objectSayColor(); //blue

5.6 基本包裝類型

  • 爲了便於操做基本類型值,ECMAScript還提供了3個特殊的引用類型:Boolean、Number和String。每當讀取一個基本類型值的時候,後臺就會建立一個對應的基本包裝類型的對象,從而讓咱們可以調用一些方法來操做這些數據。如String的substring()方法等。
  • 實際處理過程以下:
    • 建立String類型的一個實例;
    • 在實例上調用指定的方法;
    • 銷燬這個實例。

      思考:
       var s1 = "some text";
       s1. color = "red";
       alert(s1.color); //undefined. why?
  • 實踐中永遠不使用Boolean對象

第六章 面向對象的程序設計

6.2 建立對象

ECMAScript 經過原型來實現面向對象的程序設計,而非類或接口。

  • 不推薦在產品化的程序中修改原生對象的原型。若是因某個實現中缺乏某個方法,就在原生對象的原型中添加這個方法,那麼當在另外一個支持該方法的實現中運行代碼時,就可能會致使命名衝突。並且,這樣作也可能會意外地重寫原生方法。

原型模式的問題:對於包含引用類型屬性的類而言,修改其中一個對象的引用類型屬性會致使其餘共享同一原型的對象也發生變化。
例:person1,person2共享同一原型,原型包含Array類型的friend屬性,執行person1.friend.push_back("Jacob"); alert(person2.friend); // Jacob

  • 建立自定義類型的最多見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。例:

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

其餘建立方式:動態原型模式、寄生構造函數模式、穩妥構造函數模式(須要時查詢)。

6.3 繼承

  • JavaScript使用原型鏈實現繼承,其本質是重寫原型對象,代之以一個新類型的示例。

  • 當以讀取模式訪問一個實例屬性時,首先會在實例中搜索該屬性。若是沒有找到該屬性,則會繼續搜索實例的原型。在經過原型鏈實現繼承的狀況下,搜索過程就得以沿着原型鏈繼續向上。在找不到屬性或方法的狀況下,搜索過程老是要一環一環地前行到原型鏈末端纔會停下來。

  • 能夠經過兩種方式來肯定原型和實例之間的關係。第一種方式是使用instanceof操做符,第二種方式是使用isPrototypeOf()方法。

  • 原型鏈的第二個問題是:在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。

  • 繼承方式:最經常使用的是組合繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。例:

    //組合使用構造函數模式和原型模式
     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.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

第7章 函數表達式

  • 關於函數聲明:函數聲明提高:是在執行代碼以前會先讀取函數聲明。這就意味着能夠把函數聲明放在調用它的語句後面。

7.1 遞歸實現

  1. 使用arguments.callee
  2. 在嚴格模式下不能經過腳本訪問arguments.callee,則可使用匿名函數。如:

    var factorial = (function f(num){ 
         if (num <= 1){ 
             return 1;
         } else { 
             return num * f(num- 1); 
         } 
     });

7.2 閉包

  • 閉包是指有權訪問另外一個函數做用域中的變量的函數。建立閉包的常見方式,就是在一個函數內部建立另外一個函數。

  • 在匿名函數從上一級函數中被返回後,它的做用域鏈被初始化爲包含上一級函數的活動對象和全局變量對象。這樣,匿名函數就能夠訪問在上一級函數中定義的全部變量。

  • 更爲重要的是,上一級函數在執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象。換句話說,當上一級函數返回後,其執行環境的做用域鏈會被銷燬,但它的活動對象仍然會留在內存中;直到匿名函數被銷燬後,上級函數的活動對象纔會被銷燬。

過分使用閉包會致使內存佔用過多,請只在絕對必要時考慮使用閉包。

  • 做用域鏈的反作用:閉包只能取得包含函數中任何變量的最後一個值。例如:

    function createFunctions(){ 
         var result = new Array(); 
         for (var i= 0; i < 10; i++){ 
             result[ i] = function(){ 
                 return i; 
             }; 
         } 
         return result; 
     }
     // 執行 var a = createFunctions(); a[0](); // 10
  • 緣由:result中每一項都是匿名函數,這些匿名函數返回了createFunctions()中聲明的i。createF1unctions()運行結束時,i的值爲10,因爲所以調用全部的匿名函數返回的值都是10。另外,這些匿名函數不銷燬,createFunctions()中的變量也不會釋放。

  • 解決辦法:建立另外一個匿名函數,並傳入對應的參數。

    function createFunctions(){ 
         var result = new Array(); 
         for (var i = 0; i < 10; i++){ 
             result[i] = function(num){ 
                 return function(){ 
                     return num; 
                 }; 
             }(i); 
         } 
         return result; 
     }
  • this對象的問題:匿名函數的執行環境具備全局性,所以其this對象一般指向window。

  • 解決辦法:使用閉包,可讓匿名函數的this對象指向特定的對象。例如:

    var object = { 
         name : "My Object",
         getNameFunc : function(){ 
             var that = this; 
             return function(){ 
                 return that. name; 
             }; 
         } 
     }; 
     alert( object. getNameFunc()()); //"My Object"
  • 使用that獲取目標對象的this,而後在匿名函數使用that

7.3 塊級做用域

  • 使用匿名函數模擬塊級做用域:

    (function(){ 
         //這裏是塊級做用域 
     })();
    
     // or
    
     var someFunction = function(){ 
         //這裏是塊級做用域}; 
     someFunction();
  • ECMAScript6以上的版本,使用let取代var來聲明變量,便可實現塊級做用域變量的聲明。

7.4 私有變量

  • 嚴格來說,JavaScript中沒有私有成員的概念;全部對象屬性都是公有的。不過,卻是有一個私有變量的概念。任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數的外部訪問這些變量。私有變量包括函數的參數、局部變量和在函數內部定義的其餘函數。

簡單來講就是經過做用域鏈的特性來隱藏不該該被直接修改的數據,例如:

function Person(name){ 
    this. getName = function(){ 
        return name; 
    }; 
    this. setName = function (value) {
        name = value;
    }; 
} 
var person = new Person(" Nicholas");
alert( person. getName()); //"Nicholas" 
person. setName(" Greg"); 
alert( person. getName()); //"Greg"

此外還支持使用閉包來實現單例模式,須要時瀏覽 7.4.3 模塊模式。

第8章 BOM

8.1 window對象

  • window對象也是ECMAScript的global對象,全局做用域中的變量、函數都會變成window對象的屬性和方法。經過查詢window對象,能夠知道某個可能未聲明的變量是否存在,例如:

    //這裏會拋出錯誤,由於oldValue未定義 
     var newValue = oldValue; 
    
     //這裏不會拋出錯誤,由於這是一次屬性查詢
     //newValue的值是undefined
     var newValue = window. oldValue;

8.1.6 超時調用和間歇調用

  • 通常認爲,使用超時調用來模擬間歇調用的是一種最佳模式。在開發環境下,不多使用真正的間歇調用,緣由是後一個間歇調用可能會在前一個間歇調用結束以前啓動。而像前面示例中那樣使用超時調用,則徹底能夠避免這一點。因此,最好不要使用間歇調用。

本章我的認爲不是重點。

第9章 客戶端檢測

9.1 能力檢測

  • 最經常使用也最爲人們普遍接受的客戶端檢測形式是能力檢測(又稱特性檢測)。能力檢測的目標不是識別特定的瀏覽器,而是識別瀏覽器的能力。採用這種方式沒必要顧及特定的瀏覽器如何如何,只要肯定瀏覽器支持特定的能力,就能夠給出解決方案。

  • 要理解能力檢測,首先必須理解兩個重要的概念。如前所述,第一個概念就是先檢測達成目的的最經常使用的特性。對前面的例子來講,就是要先檢測document.getElementById(),後檢測document.all。先檢測最經常使用的特性能夠保證代碼最優化,由於在多數狀況下均可以免測試多個條件。第二個重要的概念就是必須測試實際要用到的特性。一個特性存在,不必定意味着另外一個特性也存在。即:不要經過一個特性是否存在來判斷瀏覽器類型。

  • 通常來講,使用typeof操做符來進行能力檢測(判斷方法是否是函數),但由於typeof在IE中存在行爲不標準的狀況,所以建議使用下面的函數來測試任何對象的某個特性是否存在:

    //做者: Peter Michaux 
     function isHostMethod(object, property) { 
         var t = typeof object[property]; 
         return t==' function' || (!!(t==' object' && object[property])) || t==' unknown';
     }

9.2 怪癖檢測

  • 與能力檢測相似,怪癖檢測(quirksdetection)的目標是識別瀏覽器的特殊行爲。但與能力檢測確認瀏覽器支持什麼能力不一樣,怪癖檢測是想要知道瀏覽器存在什麼缺陷(「怪癖」也就是bug)。通常來講,「怪癖」都是個別瀏覽器所獨有的,且一般被歸爲bug。

建議僅檢測那些對你有直接影響的「怪癖」,並且最好在腳本一開始就執行此類檢測,以便儘早解決問題。

第10章 DOM

  • 本章主要講述什麼是DOM,如何用Javascript訪問、操做DOM。上課已經涉及,此部分略讀。
  • DOM由各類節點構成,簡要總結以下。
    • 最基本的節點類型是Node,用於抽象地表示文檔中一個獨立的部分;全部其餘類型都繼承自Node。
    • Document類型表示整個文檔,是一組分層節點的根節點。在JavaScript中,document對象是Document的一個實例。使用document對象,有不少種方式能夠查詢和取得節點。
    • Element節點表示文檔中的全部HTML或XML元素,能夠用來操做這些元素的內容和特性。
    • 另外還有一些節點類型,分別表示文本內容、註釋、文檔類型、CDATA區域和文檔片斷。
  • 理解DOM的關鍵,就是理解DOM對性能的影響。DOM操做每每是JavaScript程序中開銷最大的部分,而因訪問NodeList致使的問題爲最多。NodeList對象都是「動態的」,這就意味着每次訪問NodeList對象,都會運行一次查詢。有鑑於此,最好的辦法就是儘可能減小DOM操做。

第11章 DOM擴展

  • 對DOM的兩種主要擴展:Selectors API和HTML5

11.1 Selectors API

  • 根據CSS選擇符選擇與某個模式匹配的DOM元素。jQuery的核心就是經過CSS選擇符查詢DOM文檔取得元素的引用,從而拋開getElementById()getElementByTagName()
  • Selectors API是W3C開發制定的一個標準,致力於讓瀏覽器原生支持CSS查詢,從而改善性能。
  • Selectors API Level 1 的核心是兩個方法:querySelector()querySelectorAll()。在兼容的瀏覽器中,能夠經過Document及Element類型的實例調用它們。

11.1.1 querySelector()

  • querySelector()方法接收一個CSS選擇符,返回與該模式匹配的第一個元素,若是沒有找到匹配的元素,返回null。例:

    //取得body元素 
      var body = document.querySelector(" body");
    
      //取得ID爲"myDiv"的元素 
      var myDiv = document.querySelector("# myDiv");
    
      //取得類爲"selected"的第一個元素 
      var selected = document.querySelector(". selected");
  • 經過Doument類型調用querySelector()方法時,會在文檔元素的範圍內查找匹配的元素。而經過Element類型調用querySelector()方法時,只會在該元素後代元素的範圍內查找匹配的元素。

11.1.2 querySelectorAll()

  • querySelectorAll()一樣接受CSS選擇符,但返回匹配該選擇符的全部元素(一個NodeList實例)。若是沒有匹配的元素,NodeList爲空。取得這些元素能夠經過item()方法,也可使用方括號訪問。

  • querySelector()相似,可以調用querySelectorAll()方法的類型包括Document、DocumentFragment和Element。

11.2 元素遍歷

  • 對於元素間的空格,IE9及以前版本不會返回文本節點,而其餘全部瀏覽器都會返回文本節點。這樣,就致使了在使用childNodes和firstChild等屬性時的行爲不一致。爲了彌補這一差別,而同時又保持DOM規範不變,ElementTraversal規範新定義了一組屬性。

  • Element Traversal API 爲DOM元素添加了如下5個屬性。
    • childElementCount:返回子元素(不包括文本節點和註釋)的個數。
    • firstElementChild:指向第一個子元素;firstChild的元素版。
    • lastElementChild:指向最後一個子元素;lastChild的元素版。
    • previousElementSibling:指向前一個同輩元素;previousSibling的元素版。
    • nextElementSibling:指向後一個同輩元素;nextSibling的元素版。

11.3 HTML5

  • 由於HTML5涉及的面很是廣,本節只討論與DOM節點相關的內容。HTML5的其餘相關內容將在本書其餘章節中穿插介紹。

11.3.1 與類相關的擴充

  1. getElementByClassName():返回帶有指定類的全部元素的NodeList,只有位於調用元素子樹中的元素纔會返回。
  2. classList屬性:方便管理元素的class屬性(原來是字符串,修改字符串相對不方便)。使用classList屬性的add(value), contains(value), remove(value), toggle(value)便可很是方便地管理class屬性。

11.3.2 焦點管理

  • 相關方法和屬性:focus():得到焦點, document.activeElement:獲取document中的焦點元素, hasFocus()判斷元素是否得到焦點。

11.3.3 HTMLDocument的變化

  • readyState屬性:使用document.readyState來確認文檔是否已經加載完。
  • 插入標記:
    • innerHTML:DOM子樹,可讀可寫。並非全部元素都支持innerHTML屬性。不支持innerHTML的元素有:<col>、<colgroup>、<frameset>、<head>、<html>、<style>、<table>、<tbody>、<thead>、<tfoot>和<tr>。此外,在IE8及更早版本中,<title>元素也沒有innerHTML屬性。
    • outerHTML:返回元素自身及其子樹,可讀可寫。支持outerHTML屬性的瀏覽器有IE4+、Safari4+、Chrome和Opera8+。Firefox7及以前版本都不支持outerHTML屬性。
    • insertAdjacentHTML():第一個參數必須爲下列參數之一,第二個參數爲要插入的元素。
      • "beforebegin",在當前元素以前插入一個緊鄰的同輩元素;
      • "afterbegin",在當前元素之下插入一個新的子元素或在第一個子元素以前再插入新的子元素;
      • "beforeend",在當前元素之下插入一個新的子元素或在最後一個子元素以後再插入新的子元素;
      • "afterend",在當前元素以後插入一個緊鄰的同輩元素。
    • 性能問題:在使用上述插入標記時,最好先手工刪除要被替換的元素的全部事件處理程序和JavaScript對象屬性(會有內存佔用問題);儘可能減小賦值次數,修改DOM耗費大量性能。
  • scrollIntoView()方法:能夠在全部HTML元素上調用。傳入true或不傳參數時,該元素會經過滾動儘可能出如今窗口頂部,傳入false時,該元素會經過滾動儘可能出如今窗口底部。

11.4 專有擴展

11.4.2 children屬性

  • 因爲IE9以前的版本與其餘瀏覽器在處理文本節點中的空白符時有差別,所以就出現了children屬性。children屬性只包含元素子節點,而childNodes則包含節點之間的空白符。

11.4.3 contains()方法

  • 判斷某個節點是否是另外一個節點的後代,調用方法:ancestorElement.contains(childElement);支持contains()方法的瀏覽器有IE、Firefox9+、Safari、Opera和Chrome。
  • 使用DOMLevel3的compareDocumentPosition()也可以肯定節點間的關係。支持這個方法的瀏覽器有IE9+、Firefox、Safari、Opera9.5+和Chrome。如前所述,這個方法用於肯定兩個節點間的關係,返回一個表示該關係的位掩碼(bitmask)。下表列出了這個位掩碼的值。

    掩碼 節點關係
    1 無關(給定的節點不在當前文檔中)
    2 居前(給定的節點在DOM樹中位於參考節點以前)
    4 居後(給定的節點在DOM樹中位於參考節點以後)
    8 包含(給定的節點是參考節點的祖先)
    16 被包含(給定的節點是參考節點的後代)

第12章 DOM2和DOM3

  • 涉及較多目前不熟悉的知識,打算另開一篇博客專門寫這一塊。

第13章 事件

  • JavaScript與HTML之間的交互是經過事件實現的。事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。可使用偵聽器(或處理程序)來預訂事件,以便事件發生時執行相應的代碼。這種在傳統軟件工程中被稱爲觀察員模式的模型,支持頁面的行爲(JavaScript代碼)與頁面的外觀(HTML和CSS代碼)之間的鬆散耦合。

13.1 事件流

  • 事件冒泡(Event Bubbling):事件從下到上傳播,如<div> -> <body> -> <html> -> document;
  • 事件捕獲(Event Capturing):事件從上到下傳播,如document -> <html> -> <body> -> <div>;
  • 「DOM2級事件」規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件作出響應。之前面簡單的HTML頁面爲例,單擊<div>元素會按照下圖所示順序觸發事件。

  • 在DOM事件流中,實際的目標(<div>元素)在捕獲階段不會接收到事件。這意味着在捕獲階段,事件從document到<html>再到<body>後就中止了。下一個階段是「處於目標」階段,因而事件在<div>上發生,並在事件處理(後面將會討論這個概念)中被當作冒泡階段的一部分。而後,冒泡階段發生,事件又傳播迴文檔。

13.2 事件處理程序

13.2.2 DOM0級事件處理程序

var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
    alert(" Clicked"); 
};
  • 在此,咱們經過文檔對象取得了一個按鈕的引用,而後爲它指定了onclick事件處理程序。以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。刪除事件處理程序的方式爲將onclick事件設置爲null

13.2.3 DOM2級事件處理程序

  • 「DOM2級事件」定義了兩個方法,用於處理指定和刪除事件處理程序的操做:addEventListener()和removeEventListener()。
  • 全部DOM節點中都包含這兩個方法,而且它們都接受3個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值參數若是是true,表示在捕獲階段調用事件處理程序;若是是false,表示在冒泡階段調用事件處理程序。例如:

    var btn = document. getElementById(" myBtn"); 
     btn. addEventListener(" click", function(){ 
         alert( this. id); 
     }, false);
  • 經過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移除時傳入的參數與添加處理程序時使用的參數相同。這也意味着經過addEventListener()添加的匿名函數將沒法移除。

大多數狀況下,都是將事件處理程序添加到事件流的冒泡階段,這樣能夠最大限度地兼容各類瀏覽器。最好只在須要在事件到達目標以前截獲它的時候將事件處理程序添加到捕獲階段。若是不是特別須要,咱們不建議在事件捕獲階段註冊事件處理程序。

13.2.5 跨瀏覽器的事件處理程序

  • 爲了以跨瀏覽器的方式處理事件,很多開發人員會使用可以隔離瀏覽器差別的JavaScript庫,還有一些開發人員會本身開發最合適的事件處理的方法。本身編寫代碼其實也不難,只要恰當地使用能力檢測便可(能力檢測在第9章介紹過)。要保證處理事件的代碼能在大多數瀏覽器下一致地運行,只需關注冒泡階段。

13.3 事件對象

  • 觸發DOM上的事件時,兼容DOM的瀏覽器會將一個event對象傳入到事件處理程序中。不管指定事件處理程序時使用什麼方法(DOM0級或DOM2級),都會傳入event對象。
  • event對象包含的屬性和方法:bubbles, cancelBubble, cancelable, composed, currentTarget, defaultPrevented, eventPhase, target, timeStamp, type, isTrusted, initEvent(), preventDefault(), stopImmediatePropagation(), stopPropagation(),詳細使用查詢:MDN web docs - Event

13.4 事件類型

  • 「DOM3級事件」規定了如下幾類事件。
    • UI(UserInterface,用戶界面)事件,當用戶與頁面上的元素交互時觸發;
    • 焦點事件,當元素得到或失去焦點時觸發;
    • 鼠標事件,當用戶經過鼠標在頁面上執行操做時觸發;
    • 滾輪事件,當使用鼠標滾輪(或相似設備)時觸發;
    • 文本事件,當在文檔中輸入文本時觸發;
    • 鍵盤事件,當用戶經過鍵盤在頁面上執行操做時觸發;
    • 合成事件,當爲IME(InputMethodEditor,輸入法編輯器)輸入字符時觸發;
    • 變更(mutation)事件,當底層DOM結構發生變化時觸發。
  • DOM事件類型很是多,使用時查詢:MDN web docs - 事件類型一覽表

  • 除了DOM事件,還有HTML5事件、設備事件、觸摸與手勢事件。須要用到時再查詢。

13.5 內存和性能

  • 若是給頁面中每個可點擊對象都添加事件,性能損失會比較嚴重。
  • 事件委託:在高層次的DOM樹節點上添加事件處理程序,經過event.target來判斷點擊位置,使得多個節點共用同一事件處理程序。最適合採用事件委託技術的事件包括click、mousedown、mouseup、keydown、keyup和keypress。
  • 移除事件處理程序:在不須要的時候移除事件處理程序。
  • 使用innerHTML從文檔中移除帶有事件處理程序的元素時,事件處理程序仍會保持引用關係,仍然被保存在內存中,應先置空再刪除。
  • IE8及更早版本卸載頁面前需先使用onunload事件移除全部事件處理程序。

13.6 模擬事件

  • 也可使用JavaScript在任意時刻來觸發特定的事件,而此時的事件就如同瀏覽器建立的事件同樣。也就是說,這些事件該冒泡還會冒泡,並且照樣可以致使瀏覽器執行已經指定的處理它們的事件處理程序。在測試Web應用程序,模擬觸發事件是一種極其有用的技術。DOM2級規範爲此規定了模擬特定事件的方式,IE九、Opera、Firefox、Chrome和Safari都支持這種方式。IE有它本身模擬事件的方式。
  • 能夠在document對象上使用createEvent()方法建立event對象。這個方法接收一個參數,即表示要建立的事件類型的字符串。在DOM2級中,全部這些字符串都使用英文複數形式,而在DOM3級中都變成了單數。這個字符串能夠是下列幾字符串之一:UIEvents, MouseEvents, MutationEvents, HTMLEvents,具體使用時查詢MDN web docs - Event

第16章 HTML5腳本編程

16.1 跨文檔消息傳遞

  • 跨文檔消息傳送(cross-documentmessaging),有時候簡稱爲XDM,指的是在來自不一樣域的頁面間傳遞消息。例如,www.wrox.com域中的頁面與位於一個內嵌框架中的p2p.wrox.com域中的頁面通訊。XDM把這種機制規範化,讓咱們能既穩妥又簡單地實現跨文檔通訊。
  • XDM的核心是postMessage()方法。在HTML5規範中,除了XDM部分以外的其餘部分也會提到這個方法名,但都是爲了同一個目的:向另外一個地方傳遞數據。
  • postMessage()方法接收兩個參數:一條消息和一個表示消息接收方來自哪一個域的字符串。第二個參數對保障安全通訊很是重要,能夠防止瀏覽器把消息發送到不安全的地方。
  • 接收到XDM消息時,會觸發window對象的message事件。這個事件是以異步形式觸發的,所以從發送消息到接收消息(觸發接收窗口的message事件)可能要通過一段時間的延遲。觸發message事件後,傳遞給onmessage處理程序的事件對象包含如下三方面的重要信息。
    • data:做爲postMessage()第一個參數傳入的字符串數據。
    • origin:發送消息的文檔所在的域,例如"http://www.wrox.com"。(檢測消息來源頗有安全上的必要性)
    • source:發送消息的文檔的window對象的代理。這個代理對象主要用於在發送上一條消息的窗口中調用postMessage()方法。若是發送消息的窗口來自同一個域,那這個對象就是window。

16.2 原生拖放

16.2.1 拖放事件

  • 拖動某元素時,將依次觸發下列事件:
    1. dragstart(按下鼠標並開始移動時觸發)
    2. drag(鼠標移動期間持續觸發)
    3. dragend(鬆開鼠標時觸發,不管是否放置到有效目標上)
  • 當某個元素被拖動到一個有效的放置目標上時,下列事件會依次發生:
    1. dragenter(元素被拖動到放置目標上時觸發)
    2. dragover(被拖動元素還在放置目標範圍內時持續觸發)
    3. dragleave或drop(元素拖出放置目標,或元素被放置時觸發)

16.2.2 自定義放置目標

  • 全部元素默認不容許放置。
  • 能夠把任何元素變成有效的放置目標。方法是重寫dragenter和dragover事件的默認行爲。
  • 在Firefox3.5+中,放置事件的默認行爲是打開被放到放置目標上的URL。換句話說,若是是把圖像拖放到放置目標上,頁面就會轉向圖像文件;而若是是把文本拖放到放置目標上,則會致使無效URL錯誤。所以,爲了讓Firefox支持正常的拖放,還要取消drop事件的默認行爲,阻止它打開URL。

16.2.3 dataTransfer對象

  • 爲了在拖放操做時實現數據交換,IE5引入了dataTransfer對象,它是事件對象的一個屬性,用於從被拖動元素向放置目標傳遞字符串格式的數據。由於它是事件對象的屬性,因此只能在拖放事件的事件處理程序中訪問dataTransfer對象。在事件處理程序中,可使用這個對象的屬性和方法來完善拖放功能。目前,HTML5規範草案也收入了dataTransfer對象。

  • dataTransfer對象有兩個主要方法:getData()和setData()。不難想象,getData()能夠取得由setData()保存的值。setData()方法的第一個參數,也是getData()方法惟一的一個參數,是一個字符串,表示保存的數據類型,取值爲"text"或"URL"。

  • IE只定義了"text"和"URL"兩種有效的數據類型,而HTML5則對此加以擴展,容許指定各類MIME類型。考慮到向後兼容,HTML5也支持"text"和"URL",但這兩種類型會被映射爲"text/plain"和"text/uri-list"。

  • 在拖動文本框中的文本時,瀏覽器會調用setData()方法,將拖動的文本以"text"格式保存在dataTransfer對象中。相似地,在拖放連接或圖像時,會調用setData()方法並保存URL。而後,在這些元素被拖放到放置目標時,就能夠經過getData()讀到這些數據。固然,做爲開發人員,你也能夠在dragstart事件處理程序中調用setData(),手工保存本身要傳輸的數據,以便未來使用。

  • 將數據保存爲文本和保存爲URL是有區別的。若是將數據保存爲文本格式,那麼數據不會獲得任何特殊處理。而若是將數據保存爲URL,瀏覽器會將其當成網頁中的連接。換句話說,若是你把它放置到另外一個瀏覽器窗口中,瀏覽器就會打開該URL。

  • Firefox在其第5個版本以前不能正確地將"url"和"text"映射爲"text/uri-list"和"text/plain"。可是卻能把"Text"(T大寫)映射爲"text/plain"。爲了更好地在跨瀏覽器的狀況下從dataTransfer對象取得數據,最好在取得URL數據時檢測兩個值,而在取得文本數據時使用"Text"。

16.2.4 dropEffect與effectAllowed

  • 利用dataTransfer對象,可不光是可以傳輸數據,還能經過它來肯定被拖動的元素以及做爲放置目標的元素可以接收什麼操做。爲此,須要訪問dataTransfer對象的兩個屬性:dropEffect和effectAllowed。
  • 其中,經過dropEffect屬性能夠知道被拖動的元素可以執行哪一種放置行爲。這個屬性有下列4個可能的值:
    • "none":不能把拖動的元素放在這裏。這是除文本框以外全部元素的默認值。
    • "move":應該把拖動的元素移動到放置目標。
    • "copy":應該把拖動的元素複製到放置目標。
    • "link":表示放置目標會打開拖動的元素(但拖動的元素必須是一個連接,有URL)。
  • 要使用dropEffect屬性,必須在ondragenter事件處理程序中針對放置目標來設置它。

  • dropEffect屬性只有搭配effectAllowed屬性纔有用。effectAllowed屬性表示容許拖動元素的哪一種dropEffect,effectAllowed屬性可能的值以下。
    • "uninitialized":沒有給被拖動的元素設置任何放置行爲。
    • "none":被拖動的元素不能有任何行爲。
    • "copy":只容許值爲"copy"的dropEffect。
    • "link":只容許值爲"link"的dropEffect。
    • "move":只容許值爲"move"的dropEffect。
    • "copyLink":容許值爲"copy"和"link"的dropEffect。
    • "copyMove":容許值爲"copy"和"move"的dropEffect。
    • "linkMove":容許值爲"link"和"move"的dropEffect。
    • "all":容許任意dropEffect。
  • 必須在ondragstart事件處理程序中設置effectAllowed屬性。

16.2.5 可拖動

  • 默認狀況下,圖像、連接和文本是能夠拖動的,也就是說,不用額外編寫代碼,用戶就能夠拖動它們。文本只有在被選中的狀況下才能拖動,而圖像和連接在任什麼時候候均可以拖動。
  • 讓其餘元素能夠拖動也是可能的。HTML5爲全部HTML元素規定了一個draggable屬性,表示元素是否能夠拖動。圖像和連接的draggable屬性自動被設置成了true,而其餘元素這個屬性的默認值都是false。要想讓其餘元素可拖動,或者讓圖像或連接不能拖動,均可以設置這個屬性。
  • 支持draggable屬性的瀏覽器有IE10+、Firefox4+、Safari5+和Chrome。Opera11.5及以前的版本都不支持HTML5的拖放功能。另外,爲了讓Firefox支持可拖動屬性,還必須添加一個ondragstart事件處理程序,並在dataTransfer對象中保存一些信息。

16.3 媒體元素

  • HTML5 新增了兩個與媒體相關的標籤,讓開發人員沒必要依賴任何插件就能在網頁中嵌入跨瀏覽器的音頻和視頻內容。這兩個標籤就是<audio><video>

  • 這兩個標籤除了能讓開發人員方便地嵌入媒體文件以外,都提供了用於實現經常使用功能的JavaScriptAPI,容許爲媒體建立自定義的控件。這兩個元素的用法以下。

    <!--嵌入視頻-->
     <video src="conference.mpg" id="myVideo">Video player not available.</video>
     <!--嵌入音頻-->
     <audio src="song.mp3" id="myAudio">Audio player not available.</audio>
  • 使用這兩個元素時,至少要在標籤中包含src屬性,指向要加載的媒體文件。還能夠設置width和height屬性以指定視頻播放器的大小,而爲poster屬性指定圖像的URI能夠在加載視頻內容期間顯示一幅圖像。另外,若是標籤中有controls屬性,則意味着瀏覽器應該顯示UI控件,以便用戶直接操做媒體。位於開始和結束標籤之間的任何內容都將做爲後備內容,在瀏覽器不支持這兩個媒體元素的狀況下顯示。

因爲這一部份內容較多,在此僅粗略介紹,須要時再查詢<audio><video>標籤的使用方法。(或者有空另開一篇博客介紹)

16.4 歷史狀態管理

  • 歷史狀態管理是現代Web應用開發中的一個難點。在現代Web應用中,用戶的每次操做不必定會打開一個全新的頁面,所以「後退」和「前進」按鈕也就失去了做用,致使用戶很難在不一樣狀態間切換。要解決這個問題,首選使用hashchange事件(第13章曾討論過)。HTML5經過更新history對象爲管理歷史狀態提供了方便。

  • 經過hashchange事件,能夠知道URL的參數何時發生了變化,即何時該有所反應。而經過狀態管理API,可以在不加載新頁面的狀況下改變瀏覽器的URL。爲此,須要使用history.pushState()方法,該方法能夠接收三個參數:狀態對象、新狀態的標題和可選的相對URL。例如:

    history.pushState({name:" Nicholas"}, "Nicholas' page", "nicholas.html");
  • 其餘相關方法和屬性有:window.popstate(), history.replaceState(), event.state

在使用HTML5的狀態管理機制時,請確保使用pushState()創造的每個「假」URL,在Web服務器上都有一個真的、實際存在的URL與之對應。不然,單擊「刷新」按鈕會致使404錯誤。

第17章 錯誤處理與調試

17.2 錯誤處理

17.2.1 try-catch語句

  • try - catch:若是try塊中的任何代碼發生了錯誤,就會當即退出代碼執行過程,而後接着執行catch塊。此時,catch塊會接收到一個包含錯誤信息的對象。與在其餘語言中不一樣的是,即便你不想使用這個錯誤對象,也要給它起個名字。這個對象中包含的實際信息會因瀏覽器而異,但共同的是有一個保存着錯誤消息的message屬性。ECMA-262還規定了一個保存錯誤類型的name屬性;當前全部瀏覽器都支持這個屬性(Opera9以前的版本不支持這個屬性)。所以,在發生錯誤時,就能夠就能夠像下面這樣實事求是地顯示瀏覽器給出的消息。

    try { 
         window.someNonexistentFunction(); 
     } catch (error){ 
         alert(error.message); 
     }
  • 這個例子在向用戶顯示錯誤消息時,使用了錯誤對象的message屬性。這個message屬性是惟一一個可以保證全部瀏覽器都支持的屬性,除此以外,不一樣的瀏覽器都爲事件對象添加了其餘相關信息。固然,在跨瀏覽器編程時,最好仍是隻使用message屬性。
  • finally子句:不管如何也會執行,甚至不受return影響。

  • 錯誤類型:
    • Error:Error是基類型,其餘錯誤類型都繼承自該類型。所以,全部錯誤類型共享了一組相同的屬性(錯誤對象中的方法全是默認的對象方法)。Error類型的錯誤不多見,若是有也是瀏覽器拋出的;這個基類型的主要目的是供開發人員拋出自定義錯誤。
    • EvalError:EvalError類型的錯誤會在使用eval()函數而發生異常時被拋出。
    • RangeError:RangeError類型的錯誤會在數值超出相應範圍時觸發。例如,在定義數組時,若是指定了數組不支持的項數(如?20或Number.MAX_VALUE),就會觸發這種錯誤。
    • ReferenceError :在找不到對象的狀況下,會發生ReferenceError(這種狀況下,會直接致使人所共知的"object expected"瀏覽器錯誤)。一般,在訪問不存在的變量時,就會發生這種錯誤。
    • SyntaxError:至於SyntaxError,當咱們把語法錯誤的JavaScript字符串傳入eval()函數時,就會致使此類錯誤。若是語法錯誤的代碼出如今eval()函數以外,則不太可能使用SyntaxError,由於此時的語法錯誤會致使JavaScript代碼當即中止執行。
    • TypeError:因爲在執行特定於類型的操做時,變量的類型並不符合要求所致。最常發生類型錯誤的狀況,就是傳遞給函數的參數事先未經檢查,結果傳入類型與預期類型不相符。
    • URIError:在使用encodeURI()或decodeURI(),而URI格式不正確時,就會致使URIError錯誤。這種錯誤也不多見,由於前面說的這兩個函數的容錯性很是高。
  • 利用不一樣的錯誤類型,能夠獲悉更多有關異常的信息,從而有助於對錯誤做出恰當的處理。要想知道錯誤的類型,能夠在try-catch語句的catch語句中使用instanceof操做符。

  • 合理使用try-catch:當try-catch語句中發生錯誤時,瀏覽器會認爲錯誤已經被處理了,於是不會經過本章前面討論的機制記錄或報告錯誤。try-catch可以讓咱們實現本身的錯誤處理機制。使用try-catch最適合處理那些咱們沒法控制的錯誤。假設你在使用一個大型JavaScript庫中的函數,該函數可能會有意無心地拋出一些錯誤。因爲咱們不能修改這個庫的源代碼,因此大可將對該函數的調用放在try-catch語句當中,萬一有什麼錯誤發生,也好恰當地處理它們。在明明白白地知道本身的代碼會發生錯誤時,再使用try-catch語句就不太合適了。例如,若是傳遞給函數的參數是字符串而非數值,就會形成函數出錯,那麼就應該先檢查參數的類型,而後再決定如何去作。在這種狀況下,不該用使用try-catch語句。

17.2.2 拋出錯誤

  • throw操做符,用於隨時拋出自定義錯誤。拋出錯誤時,必需要給throw操做符指定一個值,這個值是什麼類型,沒有要求。
  • 在遇到throw操做符時,代碼會當即中止執行。僅當有try-catch語句捕獲到被拋出的值時,代碼纔會繼續執行。經過使用內置錯誤類型,能夠更真實地模擬瀏覽器錯誤。每種錯誤類型的構造函數接收一個參數,即實際的錯誤消息。下面是一個例子。

    throw new Error("Something bad happened.");
     throw new TypeError("What type of variable do you take me for?");
  • 在建立自定義錯誤消息時最經常使用的錯誤類型是Error、RangeError、ReferenceError和TypeError。

咱們認爲只應該捕獲那些你確切地知道該如何處理的錯誤。捕獲錯誤的目的在於避免瀏覽器以默認方式處理它們;而拋出錯誤的目的在於提供錯誤發生具體緣由的消息。

第18章 JavaScript與XML

第20章 JSON

20.1 語法

  • 關於JSON,最重要的是要理解它是一種數據格式,不是一種編程語言。雖然具備相同的語法形式,但JSON並不從屬於JavaScript。並且,並非只有JavaScript才使用JSON,畢竟JSON只是一種數據格式。不少編程語言都有針對JSON的解析器和序列化器。

  • JSON的語法能夠表示如下三種類型的值。
    • 簡單值:使用與JavaScript相同的語法,能夠在JSON中表示字符串、數值、布爾值和null。但JSON不支持JavaScript中的特殊值undefined。
    • 對象:對象做爲一種複雜數據類型,表示的是一組有序的鍵值對兒。而每一個鍵值對兒中的值能夠是簡單值,也能夠是複雜數據類型的值。
    • 數組:數組也是一種複雜數據類型,表示一組有序的值的列表,能夠經過數值索引來訪問其中的值。數組的值也能夠是任意類型——簡單值、對象或數組。
  • JSON不支持變量、函數或對象實例,它就是一種表示結構化數據的格式,雖然與JavaScript中表示數據的某些語法相同,但它並不侷限於JavaScript的範疇。

    一個JSON對象的示例:
     { 
         "name": "Nicholas", 
         "age": 29 
     }
  • 與JavaScript不一樣,JSON中對象的屬性名任什麼時候候都必須加雙引號。手工編寫JSON時,忘了給對象屬性名加雙引號或者把雙引號寫成單引號都是常見的錯誤。

  • JSON數組使用[]表示。

20.2 解析與序列化

  • JSON對象有兩個方法:stringify()parse()。在最簡單的狀況下,這兩個方法分別用於把JavaScript對象序列化爲JSON字符串和把JSON字符串解析爲原生JavaScript值。
  • 默認狀況下,JSON.stringify()輸出的JSON字符串不包含任何空格字符或縮進。
  • 在序列化JavaScript對象時,全部函數及原型成員都會被有意忽略,不體如今結果中。此外,值爲undefined的任何屬性也都會被跳過。結果中最終都是值爲有效JSON數據類型的實例屬性。

20.2.2 序列化選項

  • JSON.stringify()除了要序列化的JavaScript對象外,還能夠接收另外兩個參數,這兩個參數用於指定以不一樣的方式序列化JavaScript對象。第一個參數是個過濾器,能夠是一個數組,也能夠是一個函數;第二個參數是一個選項,表示是否在JSON字符串中保留縮進。單獨或組合使用這兩個參數,能夠更全面深刻地控制JSON的序列化。

  • 若是過濾器參數是數組,那麼JSON.stringify()的結果中將只包含數組中列出的屬性。
  • 若是第二個參數是函數,行爲會稍有不一樣。傳入的函數接收兩個參數,屬性(鍵)名和屬性值。根據屬性(鍵)名能夠知道應該如何處理要序列化的對象中的屬性。屬性名只能是字符串,而在值並不是鍵值對兒結構的值時,鍵名能夠是空字符串。爲了改變序列化對象的結果,函數返回的值就是相應鍵的值。不過要注意,若是函數返回了undefined,那麼相應的屬性會被忽略。
  • JSON.stringify()方法的第三個參數用於控制結果中的縮進和空白符。若是這個參數是一個數值,那它表示的是每一個級別縮進的空格數。最大縮進空格數爲10,全部大於10的值都會自動轉換爲10。只要傳入有效的控制縮進的參數值,結果字符串就會包含換行符。 例如,要在每一個級別縮進4個空格,能夠這樣寫代碼:

    var jsonText = JSON.stringify(book, null, 4);
    
     // 獲得結果以下
     { 
         "title": "Professional JavaScript", 
         "authors": [ "Nicholas C. Zakas" ], 
         "edition": 3, 
         "year": 2011 
     }
  • 若是縮進參數是一個字符串而非數值,則這個字符串將在JSON字符串中被用做縮進字符(再也不使用空格)。在使用字符串的狀況下,能夠將縮進字符設置爲製表符,或者兩個短劃線之類的任意字符。

  • toJSON()能夠做爲函數過濾器的補充,所以理解序列化的內部順序十分重要。假設把一個對象傳入JSON.stringify(),序列化該對象的順序以下。
    1. 若是存在toJSON()方法並且能經過它取得有效的值,則調用該方法。不然,按默認順序執行序列化。
    2. 若是提供了第二個參數,應用這個函數過濾器。傳入函數過濾器的值是第1步返回的值。
    3. 對第2步返回的每一個值進行相應的序列化。
    4. 若是提供了第三個參數,執行相應的格式化。
  • 不管是考慮定義toJSON()方法,仍是考慮使用函數過濾器,亦或須要同時使用二者,理解這個順序都是相當重要的。

20.2.3 解析選項

  • JSON.parse()方法也能夠接收另外一個參數,該參數是一個函數,將在每一個鍵值對兒上調用。在將日期字符串轉換爲Date對象時,常常要用到還原函數。例如:

    var book = { 
         "title": "Professional JavaScript", 
         "authors": [ "Nicholas C. Zakas" ], 
         edition: 3, 
         year: 2011, 
         releaseDate: new Date( 2011, 11, 1) 
     }; 
     var jsonText = JSON. stringify(book);
     var bookCopy = JSON. parse(jsonText, function(key, value){ 
         if (key == "releaseDate"){ 
             return new Date( value); 
         } else { 
             return value;
         } 
     });
     alert(bookCopy.releaseDate.getFullYear());
     // bookCopy.releaseDate被解析爲Date類型的對象,所以可以調用getFullYear()方法

第21章 AJAX和Comet

  • AJAX:經過XMLHttpRequest(XHR)對象以異步方式與服務器進行通訊,意味着無需刷新頁面便可取得新數據。

  • 實踐中使用jQuery的ajax函數較多,此筆記爲了解背後的機制。

  • IE7+、Firefox、Opera、Chrome和Safari都支持原生的XHR對象,在這些瀏覽器中建立XHR對象要像下面這樣使用XMLHttpRequest構造函數。

    var xhr = new XMLHttpRequest();

    XMLHTTPRequest對象

21.1.1 XHR的用法

  • 在使用XHR對象時,要調用的第一個方法是open(),它接受3個參數:要發送的請求的類型("get"、"post"等)、請求的URL(相對路徑或絕對路徑)和表示是否異步發送請求的布爾值。

    xhr.open("get", "example.php", false);
  • 只能向同一個域中使用相同端口和協議的URL發送請求。若是URL與啓動請求的頁面有任何差異,都會引起安全錯誤。需使用CORS。

    xhr. open("get", "example.txt", false);
     xhr.send(null);
  • 這裏的send()方法接收一個參數,即要做爲請求主體(body)發送的數據。若是不須要經過請求主體發送數據,則必須傳入null,由於這個參數對有些瀏覽器來講是必需的。調用send()以後,請求就會被分派到服務器。

  • 異步發送請求時,能夠經過readestatechange事件來監聽readyState屬性的變化,從而確認是否收到響應。當readyState爲4的時候,說明已經接收到所有響應數據,並且已經能夠在客戶端使用了。

  • 在收到響應後,響應的數據會自動填充XHR對象的屬性,相關的屬性簡介以下。
    • responseText:做爲響應主體被返回的文本。
    • responseXML:若是響應的內容類型是"text/xml"或"application/xml",這個屬性中將保存包含着響應數據的XMLDOM文檔。
    • status:響應的HTTP狀態。收到響應後第一步應該檢查status。
    • statusText:HTTP狀態的說明。
  • 默認狀況下,XHR還會發送相關的頭部信息。要自定義頭部信息,在open()以後,send()以前使用SetRequestHeader()方法。
  • 獲取響應頭部信息,使用getResponseHeader()

21.1.3 GET請求

  • 傳入open()函數的URL必須使用encodeURIComponent()進行編碼。
  • 下面是一個添加參數的輔助函數示例:

    function addURLParam(url, name, value) { 
         url += (url.indexOf("?") == -1 ? "?" : "&"); 
         url += encodeURIComponent(name) + "=" + encodeURIComponent(value); 
         return url; 
     }

21.1.4 POST請求

  • 如要使用XHR模仿表單提交,須要將Content-type頭部信息設置爲application/x-www-form-urlencoded
  • POST數據的格式與querystring相同。

21.2 XMLHTTPRequest 2級

21.2.1 FormData

  • 因爲表單序列化很是經常使用,XMLHTTPRequest2級定義了FormData類型,爲序列化提供了便利。其用法一目瞭然。

    var data = new FormData(); 
     data.append("name", "Nicholas");
  • 建立後的FormData實例能夠直接傳給XHR的send()方法。

21.2.3 overrideMimeType() 方法

  • overrideMimeType()方法能夠重寫響應的MIME類型,從而更加恰當地處理響應。

21.3 進度事件

21.3.1 load事件

  • 與onReadyStateChange監聽器相似,監聽是否完成加載。

21.3.2 progress事件

  • 這個事件會在瀏覽器接收新數據期間週期性地觸發。而onprogress事件處理程序會接收到一個event對象,其target屬性是XHR對象,但包含着三個額外的屬性:lengthComputable、position和totalSize。其中,lengthComputable是一個表示進度信息是否可用的布爾值,position表示已經接收的字節數,totalSize表示根據Content-Length響應頭部肯定的預期字節數。有了這些信息,咱們就能夠爲用戶建立一個進度指示器了。

21.4 跨域資源共享

  • Firefox3.5+、Safari4+、Chrome、iOS版Safari和Android平臺中的WebKit都經過XMLHttpRequest對象實現了對CORS的原生支持。在嘗試打開不一樣來源的資源時,無需額外編寫代碼就能夠觸發這個行爲。要請求位於另外一個域中的資源,使用標準的XHR對象並在open()方法中傳入絕對URL便可。IE瀏覽器須要使用XDR對象,須要時查詢。
  • 跨瀏覽器支持:檢測XHR是否支持CORS的最簡單方式,就是檢查是否存在withCredentials屬性。再結合檢測XDomainRequest對象是否存在,就能夠兼顧全部瀏覽器了。

21.5 其餘跨域技術

  • 圖像Ping:使用<img>標籤,實現瀏覽器到服務器的單向通訊。
  • JSONP:使用<script>標籤請求加載一個腳本,服務器返回JS函數調用形式的數據。更多請看說說JSON和JSONP
  • Comet:Ajax是一種從頁面向服務器請求數據的技術,而Comet則是一種服務器向頁面推送數據的技術。
相關文章
相關標籤/搜索