上書房之JavaScript(Part2)

第十章 變量、作用域及內存

JavaScript的變量與其他語言的變量有很大區別。JavaScript變量是松散型的(不強制類型),決定了它只是在特定時間用于保存特定值的一個名字而已。由于不存在定義某個變量必須要保存何種數據類型值的規則,變量的值及其數據類型可以在腳本的生命周期內改變。

10.1 變量及作用域

(1) 值類型和引用類型
JavaScript的變量包括兩種類型,即值類型和引用類型。
值類型:值類型指的是那些保存在內存中的簡單數據段,即這種值完全保存在內存中的一個位置。
引用類型:引用類型值則是指那些保存在內存中的對象,意思是變量中保存的實際上只是一個指針,這個指針指向內存中的另一個位置,該位置保存對象
將一個值賦給變量時,解析器必須確定這個值是值類型,還是引用類型值。值類型有以下幾種:Undefined、Null、Boolean、Number和String。這些類型在內存中分別占有固定大小的空間,他們的值保存在棧空間,我們是通過按值訪問的。(在某些語言中,字符串以對象的形式來表示,因此被認為是引用類型。JavaScript放棄這一傳統。)
如果賦值的是引用類型的值,則必須在堆內存中為這個值分配空間。由于這種值的大小不固定,因此不能把它們保存到棧內存中。但內存地址大小是固定的,因此可以將內存地址保存在棧內存中。這樣,當查詢引用類型的變量時,先從棧中讀取內存地址,然后再通過地址找到堆中的值。對于這種,我們把它叫做按引用訪問。

值類型引用類型

(2) 動態屬性
定義基本類型值和引用類型值的方式是相似的:創建一個變量并為該變量賦值。但是,當這個值保存到變量中以后,對不同類型值可以執行的操作則大相徑庭。

<script>
  //定義一個對象,即引用類型
  var car = new Object();
  car.brand = "Mercedes Benz";
  alert(car.brand);
  
  //定義一個字符串變量,即值類型
  var cellPhone = "iphone6";
  cellPhone.color = "Gold";
  alert(cellPhone.color);
</script>

(3) 變量復制
在變量復制方面,值類型和引用類型也有所不同。值類型復制的是值本身,而引用類型復制的是地址。

//值類型復制
var car1 = "Mercedes Benz"; //在棧內存生成一個car1的變量,保存的值為 'Mercedes Benz'
var car2 = car1;    //在棧內存再生成一個car2的變量,保存的值也是 'Mercedes Benz'
變量復制

car2是雖然是car1的一個副本,但從圖示可以看出,它是完全獨立的。也就是說,兩個變量分別操作時互不影響。

//引用類型復制
var car1 = new Object();
car.brand = "Mercedes Benz";
var car2 = car1;
car2.brand = "BMW";
alert(car1.brand);
變量復制

引用類型中,car2其實就是car1,因為他們指向的是同一個對象。如果這個對象中的name屬性被修改了,car2.name和car1.name輸出的值都會被相應修改掉了。
(4) 傳遞參數
JavaScript中所有函數的參數都是按值傳遞的,言下之意就是說,參數不會按引用傳遞,雖然變量有基本類型和引用類型之分。

<script>
  function sum(num){    //按值傳遞,傳遞的參數是基本類型
    num += 10;      //這里的num是局部變量
    return num;
  }
  
  var num = 10;
  var result = sum(num);
  alert(result);
  alert(num);
</script>

以上的代碼中,傳遞的參數是一個值類型。而函數里的num是一個局部變量,和外面的num沒有任何聯系。

<script>
  function setName(obj){    //按值傳遞,傳遞的參數是引用類型
    obj.name = "馬云";
    var obj = new Object();
    obj.name = "劉強東";
  }
  
  var obj = new Object();
  setName(obj);
  alert(obj.name);  //馬云
</script>

(5) 檢測類型
要檢測一個變量的類型,我們可以通過typeof運算符來判別。諸如:

<script>
  var name = "Johnny";
  alert(typeof name);
</script>

雖然typeof運算符在檢查值類型的數據的時候非常好用,但檢測引用類型的時候,它就不是那么好用了。通常,我們并不想知道它是不是對象,而是想知道它到底是什么類型的對象。因為數組也是object,null也是Object等等。對于引用類型的檢測,當需要知道這個引用類型具體是什么對象的時候,應該使用instanceof運算符。

<script>
  var obj1 = [1,2,3];
  alert(obj1 instanceof Array); //是否是數組
  var obj2 = {};
  alert(obj2 instanceof Object);    //是否是對象
  var obj3 = new String('Lee');
  alert(obj3 instanceof String);    //是否是字符串對象
</script>

注意:當使用instanceof檢查基本類型的值時,它會返回false。
(6) 執行環境及作用域
執行環境是JavaScript中最為重要的一個概念。執行環境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。
(1) 全局執行環境(全局作用域)
全局執行環境是最外圍的執行環境。在Web瀏覽器中,全局執行環境被認為是window對象。因此所有的全局變量和函數都是作為window對象的屬性和方法創建的。全局的變量和函數,都是window對象的屬性和方法。

  var color = "blue";
  function showColor(){
    alert(color);
  }
  showColor();

  var color = "blue";
  function showColor(){
    alert(window.color);
  }
  
  window.showColor();

當執行環境中的所有代碼執行完畢后,該環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀。如果是全局環境下,需要程序執行完畢,或者網頁被關閉才會銷毀。
(2) 局部執行環境(局部作用域)
函數里的局部作用域里的變量替換全局變量,但作用域僅限在函數體內這個局部環境。

<script>
  var color = "blue"; //全局變量
  function setColor(color){
    color = color; //局部變量
    alert(color);
  }
  setColor("red");
  alert(color);
</script>

(7) 沒有塊及作用域
塊級作用域表示諸如if語句等有花括號封閉的代碼塊,所以,支持條件判斷來定義變量。

<script>
  if (true) { //if語句代碼塊沒有局部作用域
    var color = 'red';
  }
  alert(color);

  for (var i = 0; i < 10; i ++){  //沒有局部作用域
    var fullName = 'Johnny';
  }
  alert(i);
  alert(fullName);
</script>

<script>
  var color = "red";
  function sum(num1, num2){
    var num = num1 + num2;
    return num;
  }
  
  sum(1, 2);
  alert(num); //報錯 num is not defined
</script>

注意:定義及初始化變量的時候一定要加上var。

10.2 內存管理

JavaScript具有自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程中使用的內存。其他語言比如C和C++,必須手工跟蹤內存使用情況,適時的釋放,否則會造成很多問題。而JavaScript則不需要這樣,它會自行管理內存分配及無用內存的回收。
JavaScript最常用的垃圾收集方式是標記清除。垃圾收集器會在運行的時候給存儲在內存中的變量加上標記。然后,它會去掉環境中正在使用變量的標記,而沒有被去掉標記的變量將被視為準備刪除的變量。最后,垃圾收集器完成內存清理工作,銷毀那些帶標記的值并回收他們所占用的內存空間。
垃圾收集器是周期性運行的,這樣會導致整個程序的性能問題。比如IE7以前的版本,它的垃圾收集器是根據內存分配量運行的,比如256個變量就開始運行垃圾收集器,這樣,就不得不頻繁地運行,從而降低的性能。
一般來說,確保占用最少的內存可以讓頁面獲得更好的性能。那么優化內存的最佳方案,就是一旦數據不再有用,那么將其設置為null來釋放引用,這個做法叫做解除引用。這一做法適用于大多數全局變量和全局對象。

第十一章 內置對象

內置對象的定義是:“由JavaScript實現提供的、不依賴宿主環境的對象,這些對象在JavaScript程序執行之前就已經存在了。”意思就是說,開發人員不必顯示地實例化內置對象;因為它們已經實例化了。JavaScript只定義了兩個內置對象:Global和Math。

11.1 Global對象

Global(全局)對象是JavaScript中一個特別的對象,因為這個對象是不存在的。在JavaScript中不屬于任何其他對象的屬性和方法,都屬于它的屬性和方法。所以,事實上,并不存在全局變量和全局函數;所有在全局作用域定義的變量和函數,都是Global對象的屬性和方法。
因為JavaScript沒有定義怎么調用Global對象,所以,Global.屬性或者Global.方法()都是無效的。(Web瀏覽器將Global作為window對象的一部分加以實現)
Global對象有一些內置的屬性和方法:
(1) URI編碼方法
URI編碼可以對鏈接進行編碼,以便發送給瀏覽器。它們采用特殊的UTF-8編碼替換所有無效字符,從而讓瀏覽器能夠接受和理解。
encodeURI()不會對本身屬于URI的特殊字符進行編碼,例如冒號、正斜杠、問號和#號;而encodeURIComponent()則會對它發現的任何非標準字符進行編碼。

<script>
  var parm = "http://Johnny 張";
  alert(encodeURI(parm)); //Johnny%20%E5%BC%A0
  alert(encodeURIComponent(parm)); //%2F%2FJohnny%20%E5%BC%A0
</script>

因為encodeURIComponent()編碼比encodeURI()編碼來的更加徹底,一般來說encodeURIComponent()使用頻率要高一些。
使用了URI編碼過后,還可以進行解碼,通過decodeURI()和decodeURIComponent()來進行解碼。

<script>
  var parm = "http://Johnny 張";
  var encode1 = encodeURI(parm);
  var encode2 = encodeURIComponent(parm);
  var decode1 = decodeURI(encode1);
  var decode2 = decodeURIComponent(encode2);
  alert(encode1); //Johnny%20%E5%BC%A0
  alert(encode2); //%2F%2FJohnny%20%E5%BC%A0
  alert(decode1);
  alert(decode1);
</script>

(2) eval()方法
eval()方法主要擔當一個字符串解析器的作用,他只接受一個參數,而這個參數就是要執行的JavaScript代碼的字符串。

<script>
  eval('var num = 100'); //解析了字符串代碼
  alert(num);
  eval('alert(100)'); //同上
  
  eval('function num() {return 123}'); //函數也可以
  alert(num());
</script>

eval()方法的功能非常強大,但也非常危險。因此使用的時候必須極為謹慎。特別是在用戶輸入數據的情況下,非常有可能導致程序的安全性,比如代碼注入等等。
(3) Global對象屬性
Global對象包含了一些屬性:undefined、NaN、Object、Array、Function等等。
(4) window對象
Global沒有辦法直接訪問,而Web瀏覽器可以使用window對象來實現全局訪問。

11.2 Math對象

JavaScript還為保存數學公式和信息提供了一個對象,即Math對象。與我們在JavaScript直接編寫計算功能相比,Math對象提供的計算功能執行起來要快得多。
(1) Math對象的屬性
Math對象包含的屬性大多是數學計算中可能會用到的一些特殊值。

屬性 說明
Math.E 自然對數的底數,即常量e的值
Math.LN10 10的自然對數
Math.LN2 2的自然對數
Math.LOG2E 以2為底e的對數
Math.LOG10E 以10為底e的對數
Math.PI PI的值
Math.SQRT1_2 1/2的平方根
Math.SQRT2 2的平方根

(2) min()和max()方法
Math.min()用于確定一組數值中的最小值。Math.max()用于確定一組數值中的最大值。
(3) 舍入方法
Math.ceil()執行向上舍入,即它總是將數值向上舍入為最接近的整數。
Math.floor()執行向下舍入,即它總是將數值向下舍入為最接近的整數。
Math.round()執行標準舍入,即它總是將數值四舍五入為最接近的整數。

<script>
  alert(Math.ceil(25.9)); //26
  alert(Math.ceil(25.5)); //26
  alert(Math.ceil(25.1)); //26
  
  alert(Math.floor(25.9)); //25
  alert(Math.floor(25.5)); //25
  alert(Math.floor(25.1)); //25
  
  alert(Math.round(25.9)); //26
  alert(Math.round(25.5)); //26
  alert(Math.round(25.1)); //25
</script>

(4) random()方法
Math.random()方法返回介于0到1之間一個隨機數,不包括0和1。如果想大于這個范圍的話,可以套用一下公式:
值 = Math.floor(Math.random() * 總數 + 第一個值)

<script>
  alert(Math.floor(Math.random() * 10 + 1)); //隨機產生1-10之間的任意數
</script>

(5) 其他方法

方法 說明
Math.abs(num) 返回num的絕對值
Math.exp(num) 返回Math.E的num次冪
Math.log(num) 返回num的自然對數
Math.pow(num,power) 返回num的power次冪
Math.sqrt(num) 返回num的平方根
Math.acos(x) 返回x的反余弦值
Math.asin(x) 返回x的反正弦值
Math.atan(x) 返回x的反正切值
Math.atan2(y,x) 返回y/x的反正切值
Math.cos(x) 返回x的余弦值
Math.sin(x) 返回x的正弦值
Math.tan(x) 返回x的正切值

第十二章 面向對象與原型

12.1 創建對象

12.1.1 new Object()

創建Object對象,然后給這個對象新建屬性和方法。

<script>
  var car = new Object();           //創建一個Object對象
  car.brand = "Mercedes-Benz";  //創建一個brand屬性并賦值
  car.model = "E260L";  
  car.color = "White";
  car.drive = function(){           //創建一個drive()方法并返回值
    return this.brand + " " + this.model + " " + this.color + " is driving.";
  };
  
  alert(car.drive());   
</script>

存在的問題:
這個創建了一個“Mercedes-Benz”的對象,如果需要在創建一個“BMW”的對象,則類似的代碼還要在寫一遍,如果需要再創建其他品牌車的對象,那么類似的代碼就要寫許多份。
解決辦法:
使用工廠模式創建對象。

12.1.2 工廠模式

為了解決多個類似對象聲明的問題,我們可以使用一種叫做工廠模式的方法,這種方法就是為了解決實例化對象產生大量重復代碼的問題。

<script>
  function createCarObject(brand, model, color){  //集中實例化的函數
    var car = new Object();
    car.brand = brand;
    car.model = model;
    car.color = color;
    car.drive = function(){
      return this.brand + " " + this.model + " " + this.color + " is driving.";
    };
    return car;
  }
  
  var car1 = createCarObject("Mercedes-Benz", "E260L", "white");
  var car2 = createCarObject("Mercedes-Benz", "GLC260", "white");
  var car3 = createCarObject("BMW", "X5", "Red");
  alert(car1.drive());
  alert(car2.drive());
  alert(car3.drive());
</script>

存在的問題:
工廠模式解決了實例化代碼重復的問題,但還有一個問題,那就是識別問題,因為根本無法搞清楚他們到底是哪個對象的實例。

<script>
  function createCarObject(brand, model, color){    //集中實例化的函數
    var car = new Object();
    car.brand = brand;
    car.model = model;
    car.color = color;
    car.drive = function(){
      return this.brand + " " + this.model + " " + this.color + " is driving.";
    };
    return car;
  }
  
  var car1 = createCarObject("Mercedes-Benz", "E260L", "white");
  var car2 = createCarObject("Mercedes-Benz", "GLC260", "white");
  var car3 = createCarObject("BMW", "X5", "Red");
  alert(typeof car1);
  alert(car1 instanceof Object);
  alert(typeof car2);
  alert(car2 instanceof Object);
  alert(typeof car3);
  alert(car3 instanceof Object);
</script>

解決辦法:
采用構造函數可用來創建特定的對象

12.1.3 構造函數

使用構造函數的方法,即解決了實例化代碼重復的問題,又解決了對象識別的問題。

<script>
  function Car(brand, model, color){    //構造函數模式
    this.brand = brand;
    this.model = model;
    this.color = color;
    this.drive = function(){
      return this.brand + " " + this.model + " " + this.color + " is driving.";
    };
  }
  
  var car1 = new Car("Mercedes-Benz", "E260L", "white");
  alert(car1.drive());
  alert(typeof car1);
  alert(car1 instanceof Car);
  var car2 = new Car("Mercedes-Benz", "GLC260", "white");
  alert(car2.drive());
  alert(typeof car2);
  alert(car2 instanceof Car);
  var car3 = new Car("BMW", "X5", "Red");
  alert(car3.drive());
  alert(typeof car3);
  alert(car3 instanceof Car);
</script>

使用了構造函數的方法,和使用工廠模式的方法的不同之處如下:
(1) 構造函數方法沒有顯示的創建對象(new Object());
(2) 直接將屬性和方法賦值給this對象;
(3) 沒有return語句。
構造函數的方法有一些規范:
(1) 函數名和實例化構造名相同且大寫;
(2) 通過構造函數創建對象,必須使用new運算符。
構造函數和普通函數的唯一區別,就是他們調用方式不同。只不過,構造函數也是函數,必須用new運算符來調用,否則就是普通函數。

<script>
  function Car(brand, model, color){    //集中實例化的函數
    this.brand = brand;
    this.model = model;
    this.color = color;
    this.drive = function(){
      return this.brand + " " + this.model + " " + this.color + " is driving.";
    };
  }
  
  function sayHello(fullName){
    return "Hello " + fullName;
  }
  var car1 = new Car("Mercedes-Benz", "E260L", "white");
  alert(car1.drive());
  alert(sayHello("Johnny"));
</script>

探討構造函數內部的方法(或函數)的問題。

<script>
  function Car(brand, model, color){    //集中實例化的函數
    this.brand = brand;
    this.model = model;
    this.color = color;
    this.drive = function(){
      return this.brand + " " + this.model + " " + this.color + " is driving.";
    };
  }
  var car1 = new Car("Mercedes-Benz", "E260L", "white");
  var car2 = new Car("Mercedes-Benz", "GLC260", "white");
  alert(car1.brand === car2.brand); //true, 類型內容均相等
  alert(car1.model === car2.model); //false, 類型相等內容不等
  alert(car1.color === car2.color); //true, 類型內容均相等
  alert(car1.drive === car2.drive); //false, 引用地址不相等
</script>

12.2 原型

我們創建的每個函數都有一個原型屬性(prototype),這個屬性是一個對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法。使用原型的好處可以讓所有對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數中定義對象信息,而是可以直接將這些信息添加到原型中。

12.2.1 使用構造函數創建原型對象

<script>
  //使用聲明構造函數的方式,在原型中添加屬性和方法
  function SaloonCar(){}
  
  SaloonCar.prototype.carType = "家用型轎車";
  SaloonCar.prototype.drive = function(){
    return "Saloon car is driving.";
  };
  
  //在構造函數中添加屬性和方法
  function Suv(brand, model){
    this.brand = brand;
    this.model = model;
    this.drive = function(){
      return this.brand + " " + this.model + " is driving.";
    };
  }
  
  var bora = new SaloonCar();
  var mazda = new SaloonCar();
  alert(bora.drive === mazda.drive); //true
  
  var bmwX5 = new Suv("BMW", "X5");
  var benzGlc = new Suv("Benz", "GLC260");
  alert(bmwX5.drive === benzGlc.drive); //false
</script>
構造函數方式
原型方式

在原型模式聲明中,多了一個proto屬性,這個屬性都是創建對象時自動生成的。proto屬性是實例指向原型對象的指針,它的作用就是指向構造函數的原型屬性constructor。通過這個屬性,就可以訪問到原型里的屬性和方法了。
說明:IE瀏覽器在腳本訪問proto會不能識別,火狐和谷歌瀏覽器及其他某些瀏覽器均能識別。雖然可以輸出,但無法獲取內部信息。
原型模式的執行流程:
Step1:先查找構造函數實例里的屬性或方法,如果有,立刻返回;
Step2:如果構造函數實例里沒有,則去它的原型對象里找,如果有,就返回;
通過原型添加的值類型的屬性,其值不能被重寫:

    <script>
        //使用聲明構造函數的方式,在原型中添加屬性和方法
        function SaloonCar(){}
        
        SaloonCar.prototype.carType = "家用型轎車";
        SaloonCar.prototype.drive = function(){
            return "Saloon car is driving.";
        };
        
        var bora = new SaloonCar();
        bora.carType = "商務用車";
        alert(bora.carType);
        var mazda = new SaloonCar();
        alert(mazda.carType);
    </script>

內置方法和操作符:
isPrototypeOf()
判斷一個對象是否指向了該構造函數的原型對象。

    <script>
        //使用聲明構造函數的方式,在原型中添加屬性和方法
        function SaloonCar(){}
        
        SaloonCar.prototype.carType = "家用型轎車";
        SaloonCar.prototype.drive = function(){
            return "Saloon car is driving.";
        };
        
        var bora = new SaloonCar();
        alert(SaloonCar.prototype.isPrototypeOf(bora));
    </script>

hasOwnProperty()
判斷一個屬性是構造函數中的屬性而非原型中的屬性。

    <script>
        //使用聲明構造函數的方式,在原型中添加屬性和方法
        function SaloonCar(){}
        
        SaloonCar.prototype.carType = "家用型轎車";
        SaloonCar.prototype.drive = function(){
            return "Saloon car is driving.";
        };
        
        //在構造函數中添加屬性和方法
        function Suv(brand, model){
            this.brand = brand;
            this.model = model;
            this.drive = function(){
                return this.brand + " " + this.model + " is driving.";
            };
        }

        var bora = new SaloonCar();
        alert(bora.hasOwnProperty("carType")); //false
        
        var benzGlc = new Suv("Benz", "GLC260");
        alert(benzGlc.hasOwnProperty("brand")); //true
    </script>

in
判斷指定屬性是否在對象中,無論是構造函數中定義的還是原型定義的屬性。

    <script>
        //使用聲明構造函數的方式,在原型中添加屬性和方法
        function SaloonCar(){}
        
        SaloonCar.prototype.carType = "家用型轎車";
        SaloonCar.prototype.drive = function(){
            return "Saloon car is driving.";
        };
        
        //在構造函數中添加屬性和方法
        function Suv(brand, model){
            this.brand = brand;
            this.model = model;
            this.drive = function(){
                return this.brand + " " + this.model + " is driving.";
            };
        }

        var bora = new SaloonCar();
        alert("carType" in bora);//true
        
        var benzGlc = new Suv("Benz", "GLC260");
        alert("brand" in benzGlc);//true
    </script>

12.2.2 使用字面量創建原型對象

為了讓屬性和方法更好的體現封裝的效果,并且減少不必要的輸入,原型的創建可以使用字面量的方式。使用構造函數創建原型對象和使用字面量創建原型對象在使用上基本相同,但還是有一些區別,字面量創建的方式使用constructor屬性不會指向實例,而會指向Object,構造函數創建的方式則相反。

    <script>
        //使用字面量方式聲明原型屬性和方法
        function SaloonCar(){}
        SaloonCar.prototype = {
            carType: "家用型轎車",
            drive: function(){
                return "Saloon car is driving.";
            }
        };
        
        var bora = new SaloonCar();
        alert(bora instanceof SaloonCar);
        alert(bora instanceof Object);
        alert(bora.constructor === SaloonCar); //字面量方式,返回false,否則,true
        alert(bora.constructor === Object); //字面量方式,返回true,否則,false
    </script>

如果想讓字面量方式的constructor指向實例對象,那么可以這么做:

        SaloonCar.prototype = {
            constructor: SaloonCar,
            carType: "家用型轎車",
            drive: function(){
                return "Saloon car is driving.";
            }
        };

說明:使用字面量方式創建的原型對象為什么constructor會指向Object?那是因為SaloonCar.prototype={};這種寫法其實就是創建了一個新對象。而每創建一個對象,就會同時創建它prototype屬性,這個對象也會自動獲取constructor屬性。所以,新對象的constructor重寫了SaloonCar原來的constructor,因此會指向新對象,那個新對象沒有指定構造函數,那么就默認為Object。
原型的聲明是有先后順序的,所以,重寫的原型會切斷之前的原型,所以不要重復聲明原型。

    <script>
        //使用字面量方式聲明原型屬性和方法
        function SaloonCar(){}
        SaloonCar.prototype = {
            constructor: SaloonCar,
            carType: "家用型轎車",
            drive: function(){
                return "Saloon car is driving.";
            }
        };
        
        SaloonCar.prototype = {
            color: "White"
        };
        var bora = new SaloonCar();
        alert(bora.drive());//報錯
    </script>

原型模式創建對象也有自己的缺點,它省略了構造函數傳參初始化這一過程,帶來的缺點就是初始化的值都是一致的。而原型最大的缺點就是它最大的優點,那就是共享。
原型中所有屬性是被很多實例共享的,共享對于函數非常合適,對于包含值類型的屬性也還可以。但如果屬性包含引用類型,就存在一定的問題:

    <script>
        function Student(){};
        Student.prototype = {
            constructor: Student,
            family: ["父親", "母親"],
            showFamily: function(){
                return this.family;
            }
        };
        
        var s1 = new Student();
        s1.family.push("哥哥");
        alert(s1.showFamily()); //父親,母親,哥哥
        var s2 = new Student();
        alert(s2.showFamily()); //父親,母親,哥哥
    </script>

為了解決構造傳參和共享問題,可以使用構造函數+原型模式:

    <script>
        function Student(fullName, age) { //不共享的使用構造函數
            this.fullName = fullName;
            this.age = age;
            this.family = ['父親', '母親'];
        };
        Student.prototype = { //共享的使用原型模式
            constructor : Student,
            showFamily : function () {
                return this.fullName + " " + this.age + "歲,家庭成員:" + this.family;
            }
        };
        var s1 = new Student("佟大為", 32);
        s1.family.push("哥哥");
        alert(s1.showFamily()); //父親,母親, 哥哥
        
        var s2 = new Student("馬伊琍", 20);
        alert(s2.showFamily()); //父親,母親
        
        var s3 = new Student("趙麗穎", 19);
        s3.family.push("姐姐");
        alert(s3.showFamily()); //父親,母親, 姐姐
    </script>

說明:這種構造函數+原型的模式很好的解決了傳參和引用共享的大難題。是創建對象比較好的方法。
原型模式,不管你是否調用了原型中的共享方法,它都會初始化原型中的方法,并且在聲明一個對象時,構造函數+原型部分讓人感覺又很怪異,最好就是把構造函數和原型封裝到一起。為了解決這個問題,我們可以使用動態原型模式(實際項目中使用的模式)。

    <script>
        function Student(fullName, age) { //不共享的使用構造函數
            this.fullName = fullName;
            this.age = age;
            this.family = ['父親', '母親'];
            
            if(typeof this.showFamily != "function"){
                Student.prototype.showFamily = function(){ //共享的使用原型模式
                    return this.fullName + " " + this.age + "歲,家庭成員:" + this.family;
                };
            }
        };
        
        var s1 = new Student("佟大為", 32);
        s1.family.push("哥哥");
        alert(s1.showFamily()); //父親,母親, 哥哥
        
        var s2 = new Student("馬伊琍", 20);
        alert(s2.showFamily()); //父親,母親
        
        var s3 = new Student("趙麗穎", 19);
        s3.family.push("姐姐");
        alert(s3.showFamily()); //父親,母親, 姐姐
    </script>

說明:

  1. 當第一次調用構造函數時,showFamily()方法發現不存在,然后初始化原型。當第二次調用,就不會初始化,并且第二次創建新對象,原型也不會再初始化了。這樣及得到了封裝,又實現了原型方法共享,并且屬性都保持獨立。
  2. 不可以再使用字面量的方式重寫原型,因為會切斷實例和新原型之間的聯系。

12.2.3 繼承

繼承是面向對象中一個比較核心的概念。其他正統面向對象語言都會用兩種方式實現繼承:一個是接口實現,一個是繼承。而JavaScript只支持繼承,不支持接口實現,而實現繼承的方式依靠原型鏈完成。

    <script>
        function Product(){
            this.color = "White";
        }
        
        function Cellphone(){
            this.name = "iphone"
        }
        
        Cellphone.prototype = new Product();
        
        var cellphone = new Cellphone();
        alert(cellphone.name);
        alert(cellphone.color);
    </script>

在JavaScript里,被繼承的函數稱為超類型(其他語言成為父類),繼承的函數稱為子類型(其他語言成為派生類)。繼承也有之前問題,比如字面量重寫原型會中斷關系,使用引用類型的原型,并且子類型還無法給超類型傳遞參數。
為了解決引用共享和超類型無法傳參的問題,我們可以采用對象冒充(偽造對象、經典繼承)的方法來解決這兩種問題。

    <script>
        function Product(name){
            this.manufacturers = ["Apple", "Sumsung"];
            this.name = name;
        }
        
        function Cellphone(name, brand, model){
            Product.call(this, name)
            this.brand = brand;
            this.model = model;
        }
        
        var product1 = new Cellphone("cellphone", "letv", "1Pro");
        product1.manufacturers.push("letv");
        alert(product1.name + ": " + product1.brand + product1.model + "\n" + product1.manufacturers);
        
        var product2 = new Cellphone("cellphone", "meizu", "魅藍3");
        product2.manufacturers.push("meizu");
        alert(product2.name + ": " + product2.brand + product2.model + "\n" + product2.manufacturers);
    </script>

借用構造函數雖然解決了剛才兩種問題,但沒有原型,復用則無從談起。所以,我們需要原型鏈+借用構造函數的模式,這種模式成為組合繼承。

    <script>
        function Product(name){
            this.manufacturers = ["Apple", "Sumsung"];
            this.name = name;
            if(typeof this.show != "function"){
                Product.prototype.show = function(){//方法放到原型中,使其每次實例化時方法地址保持一致
                    alert("The product is:" + this.name);
                }
            }
        }
        function Cellphone(name, brand, model){
            Product.call(this, name)
            this.brand = brand;
            this.model = model;
        }
        function Pad(name, brand, model){
            Product.call(this, name);
            this.brand = brand;
            this.model = model;
        }
        Cellphone.prototype = new Product();
        Pad.prototype = new Product();
        var product1 = new Cellphone("cellphone", "iphone", "6puls");
        product1.show();
        var product2 = new Pad("pad", "ipad", "air");
        product2.show();
        alert(product1.show === product2.show);//true
    </script>

組合式繼承是JavaScript最常用的繼承模式。

第十三章 匿名函數和閉包

13.1 匿名函數

匿名函數就是沒有名字的函數。
(1)普通函數(有名字的函數)

function showCarInfo(car){
    var carInfo = "Car brand:" + car.brand + "\n" + 
                    "Car model:" + car.model + "\n" +
                    "Car color:" + car.color;
    alert(carInfo);
}

(2)匿名函數(沒有名字的函數)

function(){
    alert("hello.");
}

如何執行匿名函數:
方法1:通過表達式自我執行

(function(){
    alert("hello.");
})();

方法2:把匿名函數賦值給變量

var show = function(){
    alert("hello.");
};

show();

方法3:函數里的匿名函數

function show(){
    return function(){
        return "hello.";
    }
}

alert(show()());

實際項目中使用場景:
場景一、回掉函數
場景二、閉包

13.2 閉包

閉包是指有權訪問另一個函數作用域中的變量的函數,創建閉包的常見的方式,就是在一個函數內部創建另一個函數,通過另一個函數訪問這個函數的局部變量。
使用閉包有一個優點,也是它的缺點:就是可以把局部變量駐留在內存中,可以避免使用全局變量。(全局變量污染導致應用程序不可預測性,每個模塊都可調用必將引來災難,所以推薦使用私有的,封裝的局部變量)。

function show(){
    var num = 0;
    return function(){
        num++;
        return num;
    }
}

var s = show();
alert(s()); //1
alert(s()); //2
alert(s()); //3

說明:由于閉包里作用域返回的局部變量資源不會被立刻銷毀回收,所以可能會占用更多的內存。過度使用閉包會導致性能下降,建議在非常有必要的時候才使用閉包。

第十四章 BOM操作

BOM(Browser Object Model)也叫瀏覽器對象模型,它提供了很多對象,用于訪問瀏覽器的功能。BOM缺少規范,每個瀏覽器提供商又按照自己想法去擴展它,那么瀏覽器共有對象就成了事實的標準。所以,BOM本身是沒有標準的或者還沒有哪個組織去標準它。

14.1 window對象

BOM的核心對象是window,它表示瀏覽器的一個實例。window對象處于JavaScript結構的最頂層,對于每個打開的窗口,系統都會自動為其定義 window 對象。

window對象

(1) window對象的屬性

屬性 含義
closed 當窗口關閉時為真
defaultStatus 窗口底部狀態欄顯示的默認狀態消息
document 窗口中當前顯示的文檔對象
frames 窗口中的框架對象數組
history 保存有窗口最近加載的URL
length 窗口中的框架數
location 當前窗口的URL
name 窗口名
offscreenBuffering 用于繪制新窗口內容并在完成后復制已存在的內容,控制屏幕更新
opener 打開當前窗口的窗口
parent 指向包含另一個窗口的窗口(由框架使用)
screen 顯示屏幕相關信息,如高度、寬度(以像素為單位)
self 指示當前窗口。
status 描述由用戶交互導致的狀態欄的臨時消息
top 包含特定窗口的最頂層窗口(由框架使用)
window 指示當前窗口,與self等效

(2) window對象的方法

方法 功能
alert(text) 創建一個警告對話框,顯示一條信息
blur() 將焦點從窗口移除
clearInterval(interval) 清除之前設置的定時器間隔
clearTimeOut(timer) 清除之前設置的超時
close() 關閉窗口
confirm() 創建一個需要用戶確認的對話框
focus() 將焦點移至窗口
open(url,name,[options]) 打開一個新窗口并返回新window對象
prompt(text,defaultInput) 創建一個對話框要求用戶輸入信息
scroll(x,y) 在窗口中滾動到一個像素點的位置
setInterval(expression,milliseconds) 經過指定時間間隔計算一個表達式
setInterval(function,millisenconds,[arguments]) 經過指定時間間隔后調用一個函數
setTimeout(expression,milliseconds) 在定時器超過后計算一個表達式
setTimeout(expression,milliseconds,[arguments]) 在定時器超過時后計算一個函數
print() 調出打印對話框
find() 調出查找對話框

window下的屬性和方法,可以使用window.屬性、window.方法()或者直接屬性、方法()的方式調用。例如:window.alert()和alert()是一樣的。

14.2 location對象

location是BOM對象之一,它提供了與當前窗口中加載的文檔有關的信息,還提供了一些導航功能。事實上,location對象是window對象的屬性,也是document對象的屬性;所以window.location和document.location等效。
(1) location對象的屬性

屬性 描述的URL內容
hash 如果該部分存在,表示錨點部分
host 主機名:端口號
hostname 主機名
href 整個URL
pathname 路徑名
port 端口號
protocol 協議部分
search 查詢字符串

(2) location對象的方法

方法 功能
assign() 跳轉到指定頁面,與href等效
reload() 重載當前URL
repalce() 用新的URL替換當前頁面

14.3 history對象

history對象是window對象的屬性,它保存著用戶上網的記錄,從窗口被打開的那一刻算起。
(1) history對象的屬性

屬性 描述的URL內容
length history對象中的記錄數

(2) history對象的方法

方法 功能
back() 前往瀏覽器歷史條目前一個URL,類似后退
forward() 前往瀏覽器歷史條目下一個URL,類似前進
go(num) 瀏覽器在history對象中向前或向后

第十五章 DOM操作

DOM(Document Object Model)即文檔對象模型,針對HTML和XML文檔的API(應用程序接口)。DOM描繪了一個層次化的節點樹,運行開發人員添加、移除和修改頁面的某一部分。

15.1 DOM介紹

(1) 節點
加載HTML頁面時,Web瀏覽器生成一個樹型結構,用來表示頁面內部結構。DOM將這種樹型結構理解為由節點組成。

DOM結構

從上圖的樹型結構,我們理解幾個概念,html標簽沒有父輩,沒有兄弟,所以html標簽為根標簽。head標簽是html子標簽,meta和title標簽之間是兄弟關系。如果把每個標簽當作一個節點的話,那么這些節點組合成了一棵節點樹(后面我們經常把標簽稱作為元素,是一個意思)。
(2) 節點種類
節點種類分為三種,元素節點、文本節點、屬性節點。
<div title="屬性節點">測試div</div>

元素節點

DOM操作請參照后續jQuery課程。

第十六章 JavaScript事件

16.1 事件介紹

JavaScript事件是由訪問Web頁面的用戶引起的一系列操作,例如:用戶點擊。當用戶執行某些操作的時候,再去執行一系列代碼。事件一般是用于瀏覽器和用戶操作進行交互。最早是IE和Netscape Navigator中出現,作為分擔服務器端運算負載的一種手段。直到幾乎所有的瀏覽器都支持事件處理。而DOM2級規范開始嘗試以一種復合邏輯的方式標準化DOM事件。IE9、Firefox、Opera、Safari和Chrome全都已經實現了“DOM2級事件”模塊的核心部分。IE8之前瀏覽器仍然使用其專有事件模型。
JavaScript有三種事件模型:內聯模型、腳本模型和DOM2模型。

16.2 內聯模型

這種模型是傳統且單一的處理事件的方法。在內聯模型中,事件處理函數是HTML標簽的一個屬性,用于處理指定事件。雖然內聯在早期使用較多,但它是和HTML混寫的,并沒有與HTML分離,現在已很少使用。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JavaScript事件</title>
    </head>
    <script>
        function hello(n){
            alert("hello " + n);
        }
    </script>
    <body>
        <button onclick="alert('hello.')">內聯事件(嵌入內容)</button>
        <button onclick="hello('Johnny')">內聯事件(調用函數)</button>
    </body>
</html>

16.3 腳本模型

由于內聯模型違反了HTML與JavaScript代碼層次分離的原則。為了解決這個問題,我們可以在JavaScript中處理事件。這種處理方式就是腳本模型。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JavaScript事件</title>
    </head>
    <script>
        window.onload = function(){
            var btn = document.getElementById("btnOnclick");
            btn.onclick = function(){
                alert("hello.");
            };
        };
    </script>
    <body>
        <button id="btnOnclick">腳本模型</button>
    </body>
</html>

16.4 事件處理函數
JavaScript可以處理的事件類型為:鼠標事件、鍵盤事件、HTML事件。
JavaScript事件處理函數及其使用列表

事件處理函數 影響的元素 何時發生
onabort 圖像 當圖像加載被中斷時
onblur 窗口、框架、所有表單對象 當焦點從對象上移開時
onchange 輸入框,選擇框和文本區域 當改變一個元素的值且失去焦點時
onclick 鏈接、按鈕、表單對象、圖像映射區域 當用戶單擊對象時
ondblclick 鏈接、按鈕、表單對象 當用戶雙擊對象時
ondragdrop 窗口 當用戶將一個對象拖放到瀏覽器窗口時
onError 腳本 當腳本中發生語法錯誤時
onfocus 窗口、框架、所有表單對象 當單擊鼠標或者將鼠標移動聚焦到窗口或框架時
onkeydown 文檔、圖像、鏈接、表單 當按鍵被按下時
onkeypress 文檔、圖像、鏈接、表單 當按鍵被按下然后松開時
onkeyup 文檔、圖像、鏈接、表單 當按鍵被松開時
onload 主題、框架集、圖像 文檔或圖像加載后
onunload 主體、框架集 文檔或框架集卸載后
onmouseout 鏈接 當圖標移除鏈接時
onmouseover 鏈接 當鼠標移到鏈接時
onmove 窗口 當瀏覽器窗口移動時
onreset 表單復位按鈕 單擊表單的reset按鈕
onresize 窗口 當選擇一個表單對象時
onselect 表單元素 當選擇一個表單對象時
onsubmit 表單 當發送表格到服務器時

實際項目中一般使用jQuery來實現腳本模式添加事件,詳細請參照jQuery課程。

第十七章 表單處理

為了分擔服務器處理表單的壓力,JavaScript提供了一些解決方案,從而大大打破了處處依賴服務器的局面。
實際項目中一般使用jQuery來處理表單,詳細請參照jQuery課程。

第十八章 錯誤處理與調試

良好的錯誤處理機制可以及時的提醒用戶,知道發生了什么事,而不會驚慌失措。為此,作為開發人員,我們必須理解在處理JavaScript錯誤的時候,都有哪些手段和工具可以利用。我們可以使用try-catch語句來捕獲可能會發生的錯誤并進行處理。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>原型</title>
    </head>
    <script>
        window.onload = function(){
            var btn = document.getElementById("btnOnclick");
            btn.onclick = function(){
                try{
                    var arr = new Array[-5];
                }catch(err){
                    alert(err.message);
                } 
            };
        };
    </script>
    <body>
        <input type="text" id="input-number" placeholder="請輸入日期" />
        <button id="btnOnclick">腳本模型</button>
    </body>
</html>

善用try-catch
在明明知道某個地方會產生錯誤,可以通過修改代碼來解決的地方,是不適合用try-catch的。或者是那種不同瀏覽器兼容性錯誤導致錯誤的也不太適合,因為可以通過判斷瀏覽器或者判斷這款瀏覽器是否存在此屬性和方法來解決。
常規錯誤和這種瀏覽器兼容錯誤,我們都不建議使用try-catch。因為常規錯誤可以修改代碼即可解決,瀏覽器兼容錯誤,可以通過普通if判斷即可。并且try-catch比一般語句消耗資源更多,負擔更大。所以,在萬不得已,無法修改代碼,不能通過普通判斷的情況下才去使用try-catch,比如后面的Ajax技術。

第十九章 Cookie

19.1 Cookie介紹

隨著Web越來越復雜,開發者急切的需要能夠本地化存儲的腳本功能。這個時候,第一個出現的方案:cookie誕生了。cookie的意圖是,在本地的客戶端的磁盤上以很小的文件形式保存數據。
cookie也叫HTTP Cookie,最初是客戶端與服務器端進行會話使用的。比如,會員登錄,下次回訪網站時無須登錄了;或者是購物車,購買的商品沒有及時付款,過兩天發現購物車里還有之前的商品列表。
HTTP Cookie要求服務器對任意HTTP請求發送Set-Cookie,因此,Cookie的處理原則上需要在服務器環境下進行。當然,現在大部分瀏覽器在客戶端也能實現Cookie的生成和獲取。(目前Chrome不可以在客戶端操作,其他瀏覽器均可)
cookie的組成:
cookie由名/值(鍵值對)形式的文本組成:name=value。完整格式為:
name=value; [expires=date]; [path=path]; [domain=somewhere.com]; [secure]
中括號是可選,name=value是必選。
expires=date 失效時間,如果沒有聲明,則為瀏覽器關閉后即失效。聲明了失效時間,那么時間到期后方能失效。
path=path 訪問路徑,當設置了路徑,那么只有設置的那個路徑文件才可以訪問cookie。
domain=domain 訪問域名,用于限制只有設置的域名才可以訪問,那么沒有設置,會默認限制為創建cookie的域名。
secure 安全設置,指明必須通過安全的通信通道來傳輸(HTTPS)才能獲取cookie。
具體Cookie的操作一般使用jQuery來實現,請參照jQuery課程。

19.2 Cookie的局限性
Cookie雖然在持久保存客戶端用戶數據提供了方便,分擔了服務器存儲的負擔。但是還有很多局限性的。
(1) 每個特定的域名下最多生成20個cookie(根據不同的瀏覽器有所區別)

  • IE7和之后的版本最多可以50個cookie。IE7最初也只能20個,之后因被升級不定后增加了。
  • Firefox最多50個cookie
  • Opera最多30個cookie
  • Safari和Chrome沒有做硬性限制。
    為了更好的兼容性,所以按照最低的要求來,也就是最多不得超過20個cookie。當超過指定的 cookie時,瀏覽器會清理掉早期的cookie。IE和Opera會清理近期最少使用的cookie,Firefox會隨機清理cookie。

(2) Cookie的最大大約為4096字節(4k),為了更好的兼容性,一般不能超過4095字節即可。
(3) Cookie存儲在客戶端的文本文件,所以特別重要和敏感的數據是不建議保存在Cookie的。比如銀行卡號,用戶密碼等。

第二十章 XML

20.1 XML簡介

XML是可擴展標記語言,被設計用來傳輸和存儲數據。XML與HTML的區別如下:

  • XML用來傳輸和存儲數據,HTML被設計用來顯示數據。
  • XML被設計為具有自我描述性,通過XML可以發明自己的標簽。
  • XML是W3C(World Wide Web Consortium),即萬維網聯盟的推薦標準。
  • XML是沒有任何行為的,僅僅是純文本。

20.2 XML用途

XML 應用于 web 開發的許多方面,常用于簡化數據的存儲和共享。XML主要用途如下:
(1) XML把數據從 HTML 分離
如果你需要在HTML文檔中顯示動態數據,那么每當數據改變時將花費大量的時間來編輯HTML。通過XML,數據能夠存儲在獨立的XML文件中。這樣你就可以專注于使用 HTML進行布局和顯示,并確保修改底層數據不再需要對 HTML 進行任何的改變。通過使用幾行 JavaScript,你就可以讀取一個外部 XML 文件,然后更新 HTML 中的數據內容。
(2) XML簡化數據共享
在真實的世界中,計算機系統和數據使用不兼容的格式來存儲數據。XML數據以純文本格式進行存儲,因此提供了一種獨立于軟件和硬件的數據存儲方法。這讓創建不同應用程序可以共享的數據變得更加容易。
(3) XML簡化數據傳輸
通過 XML,可以在不兼容的系統之間輕松地交換數據。對開發人員來說,其中一項最費時的挑戰一直是在因特網上的不兼容系統之間交換數據。由于可以通過各種不兼容的應用程序來讀取數據,以XML交換數據降低了這種復雜性。
(4) XML簡化平臺的變更
升級到新的系統(硬件或軟件平臺),總是非常費時的。必須轉換大量的數據,不兼容的數據經常會丟失。
XML 數據以文本格式存儲。這使得 XML 在不損失數據的情況下,更容易擴展或升級到新的操作系統、新應用程序或新的瀏覽器。
(5) XML使您的數據更有用
由于XML獨立于硬件、軟件以及應用程序,XML使數據更可用,也更有用。不同的應用程序都能夠訪問您的數據,不僅僅在HTML頁中,也可以從XML數據源中進行訪問。通過XML您的數據可供各種閱讀設備使用(手持的計算機、語音設備、新聞閱讀器等),還可以供盲人或其他殘障人士使用。

20.3 XML樹形結構

XML文檔形成了一種樹結構,它從“根部”開始,然后擴展到“枝葉”。

<?xml version="1.0" encoding="utf-8"?>
<student>
  <id>10001</id>
  <name>Johnny</name>
  <sex>female</sex>
  <major>Software engineering</major>
</student>

XML 文檔必須包含根元素。該元素是所有其他元素的父元素。所有元素均可擁有子元素。

<?xml version="1.0" encoding="utf-8"?>
<bookstore>
  <book category="Cooking">
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book category="Children">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book category="Web">
    <title lang="en">Learning XML</title>
    <author>Erik T. Ray</author>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>

20.4 XML語法

XML 的語法規則很簡單,且很有邏輯。
(1) 所有XML元素都須有關閉標簽

<title lang="en">Everyday Italian</title>

(2) XML 標簽對大小寫敏感

<Message>這是錯誤的。</message>
<message>這是正確的。</message>

(3) XML 必須正確地嵌套

<b><i>This text is bold and italic</b></i> <!--錯誤-->
<b><i>This text is bold and italic</i></b> <!--正確-->

(4) XML 文檔必須有根元素

<?xml version="1.0" encoding="utf-8"?>
<root>
  <child>
    <subchild>.....</subchild>
  </child>
</root>

(5) XML 的屬性值必須加引號

<!--錯誤-->
<note date=08/08/2008>
  <to>George</to>
  <from>John</from>
</note>
<!--正確-->
<note date="08/08/2008">
  <to>George</to>
  <from>John</from>
</note>

(6) 實體引用
在 XML 中,一些字符擁有特殊的意義。如果你把字符 "<" 放在 XML 元素中,會發生錯誤,因為解析器會把它當作新元素的開始。

<message>if salary < 1000 then</message> <!--錯誤-->

為了避免這個錯誤,請用實體引用來代替 "<" 字符:

<message>if salary &lt; 1000 then</message>

在 XML 中,有 5 個預定義的實體引用(專業字符):

轉義字符 代表字符 說明
< < 小于
> > 大于
& & and符
' ' 單引號
" " 雙引號

(7) XML中的注釋
在XML中編寫注釋的語法與 HTML 的語法很相似。

<!-- 注釋內容 -->

(8) 在XML中,空格會被保留
HTML會把多個連續的空格字符裁減(合并)為一個,在XML中,文檔中的空格不會被刪節。

<message>Hello   my name is David.</message>

20.5 XML元素

XML 元素指的是從(且包括)開始標簽直到(且包括)結束標簽的部分。元素可包含其他元素、文本或者兩者的混合物。元素也可以擁有屬性。

<?xml version="1.0" encoding="utf-8"?>
<bookstore>
  <book category="Cooking">
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book category="Children">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book category="Web">
    <title lang="en">Learning XML</title>
    <author>Erik T. Ray</author>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>

在上例中,<bookstore> 和 <book> 都擁有元素內容,因為它們包含了其他元素。<author> 只有文本內容,因為它僅包含文本。<book> 元素擁有屬性 (category=" Children ")。
XML命名規則

  • 名稱可以含字母、數字以及其他的字符
  • 名稱不能以數字或者標點符號開始
  • 名稱不能以字符“xml”(或者 XML、Xml)開始
  • 名稱不能包含空格
  • 可使用任何名稱,沒有保留的字詞。

20.6 XML屬性

XML元素可以在開始標簽中包含屬性,類似HTML。屬性 (Attribute) 提供關于元素的額外(附加)信息。

<?xml version="1.0" encoding="utf-8"?>
<bookstore>
  <book category="Cooking">
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book category="Children">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book category="Web">
    <title lang="en">Learning XML</title>
    <author>Erik T. Ray</author>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>

20.7 XML驗證

XML Schema是W3C支持的基于XML的驗證方式,它名為XML Schema。

<?xml version="1.0" encoding="utf-8"?>
<xs:element name="note">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="to" type="xs:string"/>
      <xs:element name="from" type="xs:string"/>
      <xs:element name="heading" type="xs:string"/>
      <xs:element name="body" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>

第二十一章 JSON

21.1 JSON簡介

盡管有許多宣傳關于 XML 如何擁有跨平臺,跨語言的優勢,然而,除非應用于Web Services,否則,在普通的 Web 應用中,開發者經常為 XML 的解析傷透了腦筋,無論是服務器端生成或處理 XML,還是客戶端用JavaScript解析XML,都常常導致復雜的代碼,極低的開發效率。實際上,對于大多數Web應用來說,他們根本不需要復雜的XML來傳輸數據,XML 的擴展性很少具有優勢,許多AJAX應用甚至直接返回HTML片段來構建動態 Web 頁面。和返回XML并解析它相比,返回HTML片段大大降低了系統的復雜性,但同時缺少了一定的靈活性。
現在,JSON為Web應用開發者提供了另一種數據交換格式。JSON即JavaScript Object Natation,它是一種輕量級的數據交換格式,非常適合于服務器與JavaScript的交互。

21.2 JSON語法

和XML一樣,JSON也是基于純文本的數據格式。由于JSON天生是為JavaScript準備的,因此,JSON的數據格式非常簡單,您可以用JSON傳輸一個簡單的String,Number,Boolean也可以傳輸一個數組,或者一個復雜的 Object 對象。
JSON語法是JavaScript對象表示法語法的子集。

  • 數據在名稱/值對中(鍵值對);
  • 數據由逗號分隔;
  • 花括號保存對象;
  • 方括號保存數組。

(1) JSON名稱/值對(鍵值對)
JSON數據的書寫格式是:名稱/值的鍵值對對。鍵值對對包括字段名稱(在雙引號中),后面寫一個冒號,然后是值。

"name": "Johnny"

(2) JSON值
JSON值可以是:

  • 數字(整數或浮點數)
  • 字符串(在雙引號中)
  • 邏輯值(true 或 false)
  • 數組(在方括號中)
  • 對象(在花括號中)
  • null

(3) JSON對象
JSON對象在花括號中書寫,對象可以包含多個名稱/值對。

{
    "id": 10001,
    "name": "Johnny",
    "sex": "female",
    "major": "Software engineering"
}

(4) JSON數組
JSON 數組在方括號中書寫,數組可包含多個對象。

{
    "employees": [
        {
            "firstName":"John",
            "lastName":"Doe"
        },
        {
            "firstName":"Anna",
            "lastName":"Smith"
        },
        {
            "firstName":"Peter",
            "lastName":"Jones"
        }
    ]
}

(5) JSON使用JavaScript語法
因為JSON使用JavaScript語法,所以無需額外的軟件就能處理 JavaScript 中的 JSON。通過 JavaScript,您可以創建一個對象數組,并像這樣進行賦值。

{
    "employees": [
        {
            "firstName":"John",
            "lastName":"Doe"
        },
        {
            "firstName":"Anna",
            "lastName":"Smith"
        },
        {
            "firstName":"Peter",
            "lastName":"Jones"
        }
    ]
}

可以像這樣訪問JavaScript對象數組中的第一項:

employees[0].lastName;

可以像這樣修改數據:

employees[0].lastName = "Jobs";

(6) JSON文件
JSON文件的文件類型是 ".json"
JSON文本的MIME類型是 "application/json"

21.3 JSON使用

JSON最常見的用法之一,是從web服務器上讀取JSON數據(作為文件或作為 HttpRequest),將JSON數據轉換為JavaScript對象,然后在網頁中使用該數據。
(1) 將json字符串解析為json對象

    <script>
        var res = "{\"id\": 10001,\"name\": \"Johnny\",\"sex\": \"female\",\"major\": \"Software engineering\"}";
        var obj = JSON.parse(res);//將json字符串解析為json對象
        alert(obj.name);
    </script>

(2) 將json對象轉為json字符串

    <script>
        var obj = {
            id: 10001,
            name: "Johnny",
            sex: "female",
            major: "Software engineering"
        };
        var str = JSON.stringify(obj); //將json對象轉為json字符串
        alert(str);
    </script>

第二十二章 Ajax

Ajax,是Asynchronous JavaScript + XML的簡寫。這種技術能夠向服務器請求額外的數據而無刷新頁面,會帶來更好的用戶體驗。Ajax技術核心是XMLHttpRequest對象(簡稱XHR),這是由微軟首先引入的一個特性,其他瀏覽器提供商后來都提供了相同的實現。
在實際項目中,Ajax的實現是由jQuery來實現的,請參照jQuery課程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 劇透、圖片預警,慎入! 去看了《拆彈專家》在這個熱鬧的五一檔捧了港片的場。電影我打7分。分享一下自己的觀影感受。 ...
    超懷觀道閱讀 1,405評論 3 14
  • 一年多過去了,再提起你已經沒有滿滿的恨和痛苦了。畢竟第一次那么認真用力的去愛過一個人,那么天真的認為一輩子可以只愛...
    懶豆閱讀 544評論 4 4
  • 到今天,我已經堅持每天跑步至少2公里整整30天了,這對于一個做事容易半途而廢,很難堅持的人來說,真的是具有里程碑意...
    小草禾刀閱讀 1,683評論 9 15
  • 清晨,陽光擠進窗簾的縫隙,輕輕的散落在地上,試圖將沉睡中的藺琳叫醒,卻無濟于事,床上的人依舊睡的很香甜。突然,一陣...
    余美魚閱讀 777評論 0 0