《JavaScript 高級程序設計》

第 3 章 基本概念

3.5.2 位操做符

ECMAScript 中全部數值都是以 IEEE-754 64 位格式存儲,但位操做符並不直接操做 64 位的值。而是先將 64 位的值轉換成 32 位的整數,而後執行操做,最後再將結果轉換爲 64 位。(對於開發人員 64 位存儲格式是透明的,所以整個過程像是隻存在 32 位的整數同樣)javascript

3.5.6 關係操做符

  • 比較的操做數爲對象,則調用 valueOf() 方法(沒有 valueOf() 調用 toString() 方法),用獲得的值進行比較
  • 比較的操做數爲布爾值,則轉換爲數字比較

3.7.1 理解參數

  • 修改 arguments 數組會改變形參的值(這並非說它們訪問相同的內存空間;它們的內存空間是獨立的,但它們的值會同步。好比未傳入形參時,修改了 arguments 數組形參值也不會變)
function func(para1, para2){
  console.log('修改前', arguments , para1, para2);
  arguments [0] = 'arguements_0';
  arguments [1] = 'arguements_1';
  console.log('修改後', arguments , para1, para2);
}
func('para1')
  • arguments 對象的長度是由傳入的參數個數決定的,不是由定義函數時的命名參數的個數決定的。
  • ECMAScript 中的參數都是值傳遞,不存在引用傳遞

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

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

基本類型:php

  1. Undefined
  2. Null
  3. Boolean
  4. Number
  5. String(注意 String 是基本類型)

基本類型的值沒法添加屬性(儘管這樣不會報錯),eg:css

var name = "zhang";
name.age = 17;
console.log(name.age);// undefined

引用類型:html

  1. Object

4.1.3 傳遞參數

ECMAScript 中全部函數的參數都是按值傳遞的。前端

eg:這裏的 obj 形參是建立了一個指針讓他與 person 指針指向同一個地址,obj 和 person 都指向同一個地址,但改變 obj 的指向 person 不改變html5

function setName(obj) {
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

4.2.2 沒有塊級做用域

使用 var 聲明的變量會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境;在 with 語句中,最接近的環境是函數環境。若是初始化變量時沒有使用 var 聲明,該變量會自動被添加到全局環境。java

// 1.if
if (true) {
    var color = "blue";
}
alert(color); //"blue"

// 2.for
for (var i=0; i < 10; i++){
}
alert(i); //10

// 3.function
function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}
alert(sum); //因爲 sum 不是有效的變量,所以會致使錯誤

如今使用 ES6 的let會聲明塊級做用域變量。node

4.3 垃圾收集

經常使用標記清除,不經常使用引用計數git

第 5 章 引用類型

5.2 Array 類型

若是 slice() 方法的參數中有一個負數,則用數組長度加上該數來肯定相應的位置。例如,在一個包含 5 項的數組上調用 slice(-2,-1) 與調用 slice(3,4) 獲得的結果相同。若是結束位置小於起始位置,則返回空數組。程序員

5.2.8 迭代方法

  • every():對數組中的每一項運行給定函數,若是該函數對每一項都返回 true,則返回 true。
  • filter():對數組中的每一項運行給定函數,返回該函數會返回 true 的項組成的數組。
  • forEach():對數組中的每一項運行給定函數。這個方法沒有返回值。
  • map():對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組。
  • some():對數組中的每一項運行給定函數,若是該函數對任一項返回 true,則返回 true

5.2.9 歸併方法

ECMAScript 5 還新增了兩個歸併數組的方法: reduce() 和 reduceRight()。這兩個方法都會迭代數組的全部項,而後構建一個最終返回的值。其中, reduce() 方法從數組的第一項開始,逐個遍歷到最後。而 reduceRight() 則從數組的最後一項開始,向前遍歷到第一項。

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

6.1.1 屬性類型

  1. 數據屬性
  2. 訪問器屬性(VUE 雙向綁定實現的基礎)

6.2 建立對象

  1. 工廠模式(使用函數建立對象,爲對象添加屬性和方法而後返回對象,被構造函數模式取代)

    function createPersion(name, age) {
        return {
            name,
            age
        }
    }
    
    var p1 = createPersion("zhang", 17);
    var p2 = createPersion("foo", 18);
  2. 構造函數模式(每一個實例不共用屬性和方法,JS 中函數是對象)

    function Persion(name, age) {
        this.name = name;
        this.age = age;
        this.print = function () {
            console.log(this.name);
        }
    }
    
    var p1 = new Persion("zhang", 17);
    var p2 = new Persion("foo", 18);
  3. 原型模式(每一個實例共用一套屬性和方法)

    function Persion() {}
    Persion.prototype.name = "zhang";
    Persion.prototype.age = 17;
    Persion.prototype.print = function () {
        console.log(this.name);
    }
    
    var p1 = new Persion();
    var p2 = new Persion();
  4. 組合使用構造函數模式和原型模式(既有共用又有不共用的屬性和方法)

    • 這是目前使用最普遍的建立自定義類型的方法。

    • 使用對象覆蓋原型(字面量方式修改原型)要注意已經建立的實例的constructor指針不會自動變(仍是覆蓋前的原型)。

    function Persion(name, age) {
        this.name = name;
        this.age = age;
    }
    Persion.prototype = {
        constructor: Persion,
        print: function () {
            console.log(this.name);
        }
    }
    
    var p1 = new Persion("zhang", 17);
    var p2 = new Persion("foo", 18);
  5. 動態原型模式(給原型添加屬性的操做放到構造函數內)

    function Persion(name, age) {
        this.name = name;
        this.age = age;
        // 第一次執行時初始化
        if (typeof this.print !== "function") {
            Persion.prototype.print = function () {
                console.log(this.name);
            }
        }
    }
    
    var p1 = new Persion("zhang", 17);
    var p2 = new Persion("foo", 18);
  6. 寄生構造函數模式(和工廠模式幾乎同樣,能夠爲對象建立構造函數)

    function MyArray(){
        let arr = new Array();
        arr.push.apply(arr, arguments);
        arr.toPipeString = function(){
            return this.join("|");
        }
        return arr;
    }
    
    var marr = new MyArray(1,2,3);
    console.log(marr.toPipeString());
  7. 穩妥構造函數模式

    • 穩妥對象:沒有公共屬性,方法也不引用this對象。適合用在安全環境中,或防止數據被其餘程序改動。
    function Persion(name, age) {
        var o = {};
    
        // 除了使用 print 沒有其餘方法能獲取到 name
        o.print = function () {
            console.log(name);
        }
        return o;
    }
    
    var p1 = new Persion("zhang", 17);
    var p2 = new Persion("foo", 18);

6.3 繼承

OO 語言支持兩種繼承:接口繼承和實現繼承。因爲函數沒有簽名,ES 沒法實現接口繼承。

  1. 原型鏈

    有兩個問題:1 屬性共享, 2 沒法參數傳遞

    function SuperType(para) {
        this.property = true;
        this.arr = [1, 2, 3];
        this.para = para;
    }
    
    SuperType.prototype.getSuperProp = function () {
        return this.property;
    }
    
    function SubType() {
        this.subproperty = false;
    }
    
    // 繼承(這時 SubType.prototype.constructor 屬性沒有了,SubType.[[prototype]].constructor 爲 SuperType)
    SubType.prototype = new SuperType();
    
    SubType.prototype.getSubProp = function () {
        return this.subproperty;
    }
    
    // 問題1:沒法向父類構造函數傳值
    var instance1 = new SubType();
    var instance2 = new SubType();
    
    // 問題2:instance2.arr 也改變了
    instance1.arr.push(4);
  2. 借用構造函數

    和建立對象的構造函數模式的問題同樣,每一個實例不共用屬性和方法。

    function SuperType(para) {
        this.para = para;
        this.getPara = function(){
            return this.para;
        }
    }
    
    function SubType() {
        SuperType.apply(this, arguments);
    }
    
    // 能夠向父類構造函數傳值
    var instance1 = new SubType("instance1");
    var instance2 = new SubType("instance2");
  3. 組合繼承

    原型鏈 + 借用構造函數

    JS 中最經常使用的繼承模式。

    function SuperType(para) {
        this.property = true;
        this.arr = [1, 2, 3];
        this.para = para;
    }
    
    SuperType.prototype.getSuperProp = function () {
        return this.property;
    }
    
    function SubType() {
        SuperType.apply(this, arguments);
        this.subproperty = false;
    }
    
    // 繼承
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    
    SubType.prototype.getSubProp = function () {
        return this.subproperty;
    }
    
    // 能夠向父類構造函數傳值
    var instance1 = new SubType("instance1");
    var instance2 = new SubType("instance2");
    
    // instance2.arr 不變
    instance1.arr.push(4);
  4. 原型式繼承

    有兩個問題:1 屬性會共享,2 每一個實例沒法共用方法

    function createObj(obj) {
        function f() {};
        f.prototype = obj;
        return new f();
    }
    
    var person = {
        name: "zhang",
        age: [17]
    }
    
    // person、 zi 和 ma 共用引用屬性 age
    var zi = createObj(person);
    var ma = createObj(person);
    
    zi.age.push(18);
    ma.age.push(17);
    console.log(person.age);
  5. 寄生式繼承

    和建立對象的寄生構造函數模式相似,也有屬性會共享的問題

    function createObjParasitic(obj) {
        let o = Object.create(obj);
        // o 目前是以下對象:
        // {
        //     [[prototype]] = obj
        // }
        o.sayHi = function () {
            console.log("hi")
        }
        return o;
    }
    
    var person = {
        name: "zhang",
        age: [17]
    }
    
    var zi = createObjParasitic(person);
    
    zi.sayHi();
  6. 寄生組合式繼承

    寄生式繼承 + 組合式繼承

    new SuperType()改成Object.create(SuperType.prototype)減小一次父類的調用,還避免了在SubType.prototype上添加沒必要要的屬性(如:下面的propertyarr屬性)。

    function inheritPrototype(sub, sup) {
        // 使 sub.prototype 的 [[prototype]] 「指針」指向 sup.prototype
        sub.prototype = Object.create(sup.prototype);
        sub.prototype.constructor = sub;
    }
    
    function SuperType() {
        this.property = true;
        this.arr = [1, 2, 3];
    }
    
    SuperType.prototype.getSuperProp = function () {
        return this.property;
    }
    
    function SubType() {
        SuperType.apply(this, arguments);
        this.subproperty = false;
    }
    
    inheritPrototype(SubType, SuperType);
    
    console.log((new SubType()).getSuperProp());

第 7 章 函數表達式

定義函數有兩種形式:函數聲明(會函數聲明提高)和函數表達式。

7.2 閉包

閉包指有權訪問另外一個函數做用域中的變量的函數。

7.2.2 關於 this 對象

每一個函數在被調用時都會自動取得兩個特殊變量: this 和 arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。不過,把外部做用域中的 this 對象保存在一個閉包可以訪問到的變量裏,就可讓閉包訪問該對象了。

若是想訪問做用域中的 this 和 arguments 對象必須把她們保存在閉包能夠訪問的變量中。

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        return function () {
            // this指向window
            return this.name;
        };
    }
};
console.log(object.getNameFunc()()); //"The Window"(在非嚴格模式下)

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        var that = this;
        return function () {
            // 閉包能夠訪問that
            // this指向window,that指向object
            return that.name;
        };
    }
};
console.log(object.getNameFunc()()); //"My Object"

7.4 私有變量

JS 中沒有使用成員的概念,全部對象屬性都是共有的。但在函數中定義的變量均可以認爲是私有比阿尼浪,由於不能在函數外部訪問這些變量(包括:參數、局部變量和函數內部定義的其餘函數)。

7.4.2 模塊模式

JS 以字面量的方式建立單例對象。

var singleton = {
    name: "xxx",
    method: function () {
        // ...
    }
}

7.4.3 加強的模塊模式

// 這時 function(){...}() 是當作語句解析的
var application = function(){
    //私有變量和函數
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //建立 application 的一個局部副本
    var app = new BaseComponent();
    //公共接口
    app.getComponentCount = function(){
        return components.length;
    };
    app.registerComponent = function(component){
        if (typeof component == "object"){
             components.push(component);
        }
    };
    //返回這個副本
    return app;
}();

第 8 章 BOM

8.1 window 對象

BOM 的核心是 window 對象,它表示瀏覽器的一個實例,同時也是 ES 中規定的 Global 對象。

8.1.2 窗口關係及框架

若是頁面中包含框架,則每一個框架都擁有本身的 window 對象,而且保存在 frames 集合中。在 frames 集合中,能夠經過數值索引(從 0 開始,從左至右,從上到下)或者框架名稱來訪問相應的 window 對象。每一個 window 對象都有一個 name 屬性,其中包含框架的名稱。

在使用框架的狀況下,瀏覽器中會存在多個 Global 對象。在每一個框架中定義的全局變量會自動成爲框架中 window 對象的屬性。因爲每一個 window 對象都包含原生類型的構造函數,所以每一個框架都有一套本身的構造函數,這些構造函數一一對應,但並不相等。例如,top.Object並不等於top.frames[0].Object。這個問題會影響到對跨框架傳遞的對象使用instanceof操做符。

  • window.top對象始終指向最外層框架,也就是瀏覽器窗口
  • window.parent指向父窗口
  • window.self指向 window

8.1.3 窗口位置

窗口相對屏幕的位置:window.screenLeftwindow.screenTop

窗口位置移動(默認是禁用的):window.moveTo()window.moveBy()

8.1.4 窗口大小

瀏覽器窗口大小(不一樣瀏覽器對這些屬性的解釋不一樣):window.innerHeightwindow.innerWidthwindow.outerWidthwindow.outerWidth

頁面視口信息:window.document.documentElement.clientWidthwindow.document.documentElement.clientHeight

瀏覽器窗口大小改變(默認是禁用的):window.resizeTo()window.resizeBy()

8.1.6 間歇調用和超時調用

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

// 超時調用模擬間歇調用
var num = 0;
var max = 10;
function incrementNumber() {
    num++;
    //若是執行次數未達到 max 設定的值,則設置另外一次超時調用
    if (num < max) {
        setTimeout(incrementNumber, 500);
    } else {
        alert("Done");
    }
}
setTimeout(incrementNumber, 500);

8.2 location 對象

8.2.2 位置操做

// 基本
location.assign("https://developer.mozilla.org")
// 下面兩種和和顯示調用 assign 方法同樣
window.location = "https://developer.mozilla.org"
location.href = "https://developer.mozilla.org"
// 其餘
location.hash = "#123"
location.pathname = "zh-CN"
// replace 的 location 不記錄在歷史記錄中
location.replace("https://developer.mozilla.org")

獲取瀏覽器信息、主語言、插件、設置等信息。

8.4 screen 對象

用來獲取瀏覽器窗口外部的顯示器信息,如像素寬度、高度等。

8.5 history 對象

// 跳轉到最近的 mdn 界面
history.go("developer.mozilla.org");

第 9 章 客戶端檢測

  • 能力(特性)檢測:檢測瀏覽器是否支持某項功能
function isHostMethod(object, property) {
    var t = typeof object[property];
    return t === 'function' || (!!(t === 'object' && object[property])) || t === 'unknown';
}
console.log(isHostMethod([], 'find'));
  • 怪癖檢測:檢測瀏覽器某項功能是否有 bug
// 是否有將屬性不列舉的bug
var hasDontEnumQuirk = function() {
    var o = {
        // 這裏新的 toString 應該要列舉出來的,由於新的 toString 已經覆蓋了舊的 [[Enumerable]] false 的 toString
        // 但 IE8 及更低版本的瀏覽器會不將 toString 列舉。
        toString: function () {}
    };
    for (var prop in o) {
        if (prop === 'toString') {
            return false;
        }
    }
    return true;
}();
console.log(hasDontEnumQuirk());
  • 用戶代理檢測:檢測呈現引擎、瀏覽器、平臺、設備和操做系統

    由於用戶代理字符串有很長的發展歷史,在此期間瀏覽器供應商試圖在用戶代理字符串中添加一些欺騙信息,讓網站相信本身是另外一種瀏覽器,使得用戶代理檢測比較複雜。但經過navigator.userAgent(大部分信息在這個字符串中),navigator.platformwindow的屬性仍是能夠檢測出來呈現引擎、瀏覽器、平臺、設備和操做系統這些信息的。

第 10 章 DOM

10.1 節點層次

HTML 頁面中,文檔元素始終是<html>元素,XML 中沒有預約義的元素,任何元素均可能成爲文檔元素。

10.1.1 Node 接口

  • nodeType屬性

每一個節點都有nodeType屬性,用於表示節點類型。節點類型在 Node 類型中定義的下列 12 個數值常量表示,任何節點的類型必居其一。

參見:Node.nodeType - Web APIs | MDN

  • childNodes屬性

childNodes屬性中保存着一個NodeList對象。

NodeList對象是一種類數組對象,但不是 Array 實例。

DOM 結構變化會自動反映在NodeList對象中,也就是說NodeList對象是動態變化的,而不是一張快照。

NodeList對象轉爲數組:

function convertToArray(nodes){
    return Array.prototype.slice.call(nodes, 0);
}
  • clonNode()方法

參數爲 true 或 false,true 執行深複製(克隆子節點),false 執行淺複製(不克隆子節點)。

clonNode()方法不會複製 JS 的屬性(IE 存在 bug,會複製時間處理程序)。

  • normalize()方法

    處理文檔樹中的文本節點。

10.1.2 Document 接口

Document 接口繼承自 Node 接口,Document 接口描述了任何類型文檔的公共屬性和方法。

document 對象是 HTMLDocument 的一個實例。

  • dcoumentElement屬性

取得<html>的引用

  • doctype屬性

取得<!DOCTYPE>的引用

  • titel屬性

修改titel屬性不會改變<title>元素

  • domain屬性

因爲跨域安全限制,來自不一樣子域的頁面沒法經過 JS 通訊。而將每一個document.domain設置爲相同的值,這些頁面就能夠互相訪問對方包含的 JS 對象了。

domain屬性修改有兩個限制:1. 同源,2. 低級(eg:三級子域名p2p.wrox.com)向高級(eg:二級子域名wrox.com)。

  • write方法

頁面加載過程調用會動態加入內容,頁面加載結束後調用會重寫整個頁面內容。

10.1.3 Element 接口

全部 HTML 元素都是由 HTMLElement 類型或她的子類型表示。

  • getAttribute()方法

getAttribute()方法返回屬性,例如:使用屬性訪問 style 和 onclick 時分別返回對象和方法,使用getAttribute()方法返回字符串。

  • attributes屬性

attributes屬性包含一個 NamedNodeMap 對象,與 NodeList 相似是一個動態集合。

10.1.4 Text 接口

修改文本節點時,字符串會根據文檔類型進行編碼(將預留字符轉義)。

  • splitText()方法

調用這個方法原來的文本節點變爲從開始到指定位置以前的內容,返回剩下的內容。

10.1.8 DocumentFragment 類型

在全部節點類型中,只有 DocumentFragment 在文檔中沒有對應的標記。 DOM 規定文檔片斷(document fragment)是一種 「輕量級」 的文檔,能夠包含和控制節點,但不會像完整的文檔那樣佔用額外的資源。

經過apppendChild()insertBefore()將文檔片斷添加到文檔中時,只會將文檔片斷的子節點添加到響應位置上,文檔片斷自己不會成爲文檔的一部分。例如:

let fragment = document.createDocumentFragment();
let ul = document.createElement("ul");
let li = null;
for (let i = 0; i < 3; i++) {
  li = document.createElement("l1");
  li.appendChild(document.createTextNode("Item " + (i + 1)));
  fragment.appendChild(li);
}
document.body.appendChild(ul);
ul.appendChild(fragment);

10.2 DOM 操做技術

通常來講,儘可能減小訪問 NodeList 的次數。由於每次訪問 NodeList,都會運行一次基於文檔的查詢。因此能夠考慮將從 NodeList 中取得的值緩存起來。

第 11 章 DOM 拓展

11.2 元素遍歷

對於元素間的空格,瀏覽器會當作文本節點(除了 IE9 以前的瀏覽器)。Element Traversal Specification定義了一組新屬性能夠只訪問 Element 節點。

遍歷子元素:

// 新
var child = document.body.firstElementChild;
while(child != document.body.lastElementChild){
    console.log(child);
    child = child.nextElementSibling;
}
// 舊
var child = document.body.firstChild;
while(child != document.body.lastChild){
    if(child.nodeType == 1){
        console.log(child);
    }
    child = child.nextSibling;
}

11.3 HTML5

11.3.2 交點管理

HTML5 也添加了輔助管理 DOM 焦點的功能。

首先就是 document.activeElement 屬性,這個屬性始終會引用 DOM 中當前得到了焦點的元素。元素得到焦點的方式有頁面加載、用戶輸入(一般是經過按 Tab 鍵)和在代碼中調用focus()方法。

另外是新增了document.hasFocus()方法,用於判斷文檔是否獲取焦點。

11.3.4 HTMLDocument 的變化

  1. readyState 屬性:判斷頁面加載狀況(舊方法是在觸發 onload 事件時記錄頁面加載完成)。
  2. compatMode 屬性:判斷頁面是標準(Standards)模式仍是混雜(Quirks)模式(混雜模式下瀏覽器會嘗試模擬很是舊的瀏覽器的行爲)。
  3. head 屬性:引用文檔的 head 元素。
  4. charset 屬性:獲取和設置字符集。(MDN 中是Document.characterSet

11.3.4 scrollIntoView()方法

經過滾動瀏覽器窗口或某個容器元素,調用元素就能夠出如今視口中。

第 12 章 DOM2 和 DOM3

12.1 DOM 變化

  • DOM Level 2 Core:爲大多數 DOM1 方法提供特定與命名空間的版本(方法名中帶有 NS)。
  • DOM Level 2 View
  • DOM Level 2 HTML

12.2 樣式

12.2.1 訪問元素的樣式

多數狀況下,均可以經過簡單地轉換屬性名的格式來實現轉換。其中一個不能直接轉換的 CSS 屬性就是 float。因爲 float 是 JavaScript 中的保留字,所以不能用做屬性名。「DOM2 級樣式」 規範規定樣式對象上相應的屬性名應該是 cssFloat; Firefox、 Safari、 Opera 和 Chrome 都支持這個屬性,而 IE 支持的則是 styleFloat。

實踐中,最好始終指定度量單位(標準模式下全部的度量值必須指定單位,混雜模式下能夠不指定單位)。

<div id="myDiv">myDiv</div>
<script>
    var myDiv = document.getElementById("myDiv");
    //浮動
    myDiv.style.cssFloat = "left";
    //背景顏色
    myDiv.style.backgroundColor = "red";
    //改變大小
    myDiv.style.width = "100px";
    myDiv.style.height = "200px";
    //指定邊框
    myDiv.style.border = "1px solid black";
</script>

12.2.2 操做樣式表

12.2.3 元素大小

  1. 偏移量 offsetTop/Left/Width/Height(Width 和 Height 包括邊框)。
  2. 客戶區大小 clientTop/Left/Width/Height(width 和 height 不包括邊框)。
  3. 滾動大小 scrollTop/Left/Width/Height。
  4. 肯定元素大小Element.getBoundClientRect()

12.3 遍歷

NodeIterator 和 TreeWalker。IE 不支持!

12.4 範圍

爲了讓開發人員更方便地控制頁面,「DOM2 級遍歷和範圍」 模塊定義了 「範圍」(range)接口。經過範圍能夠選擇文檔中的一個區域,而沒必要考慮節點的界限(選擇在後臺完成,對用戶是不可見的)。在常規的 DOM 操做不能更有效地修改文檔時,使用範圍每每能夠達到目的。

第 13 章 事件

13.1 事件流

因爲老版本瀏覽器不支持,所以不多有人使用事件捕獲。建議放心使用事件冒泡,特殊須要時使用事件捕獲。

13.1.1 事件冒泡

IE 提出的事件流叫事件冒泡(event bubling),事件由最具體(深)的節點向不具體(文檔)節點傳播。

13.1.2 事件捕獲

Netscape 提出的事件流叫事件捕獲(event capturing),思想是不太具體的節點應先接收到事件,具體的節點後接收到事件。

13.1.3 DOM 事件流

UI Events-event-flow

「DOM2 級事件」 規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。

  • 事件捕獲:爲截獲事件提供機會(實際目標在捕獲階段不會接收到事件)
  • 目標階段:事件目標接收到事件
  • 冒泡階段:對事件做出響應

PS:多數瀏覽器都實現了在捕獲階段觸發事件對象上的事件這種特定行爲。即便 「DOM2 級事件」 規範明確要求捕獲階段不會涉及事件目標。

13.2 事件處理程序

諸如 click、load 和 mouseover 叫事件的名字。響應某個事件的函數叫作事件處理程序,事件處理程序的名字以‘’on「開頭。

13.2.1 HTML 事件處理程序

某個元素支持的每種事件,均可以用一個與之相應事件處理程序同名的 HTML 特性來指定。

這樣指定事件處理程序:首先會建立一個封裝着元素屬性值的函數,其次這種方式動態建立的函數會拓展做用域,例如:

<!-- 1. 查看屬相和參數 -->
<input type="button" value="click" onclick="console.log(arguments, event, this, value)">

<!-- 2. 拓展做用域 -->
<form>
    <input type="text" name="uname" value="zzz">
    <input type="button" value="click" onclick="console.log(uname.value)">
</form>
<!--
這個動態建立的函數像這樣拓展做用域:
function(){
    with(document){
        with(this,form){
            with(this){
                console.log(uname.value)
            }
        }
    }
}
-->

13.2.2 DOM0 級事件處理程序

使用 DOM0 級方法指定的事件處理程序被認爲是元素的方法。所以,這時候的事件處理程序是在
元素的做用域中運行;換句話說,程序中的 this 引用當前元素。來看一個例子。

<button id="myBtn">myBtn</button>
<script>
var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert(this.id); //"myBtn"
};
</script>

將事件處理程序屬性的值設置爲 null 能夠刪除經過 DOM0 級方法指定的事件處理程序:
btn.onclick = null;

13.2.3 DOM2 級事件處理程序

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

<button id="myBtn">myBtn</button>
<script>
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id);
}, false);
btn.addEventListener("click", function(){
    alert("Hello world!");
}, false);
</script>

刪除事件時必須傳入綁定的事件的 「指針」。

13.2.4 IE 事件處理程序

IE 實現了與 DOM 中相似的兩個方法:attachEvent()detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。因爲 IE8 及更早版本只支持事件冒泡,因此經過 attachEvent()添加的事件處理程序都會被添加到冒泡階段。

13.3 事件對象

13.3.1 DOM 中的事件對象

兼容 DOM 的瀏覽器會將一個 event 對象傳入到事件處理程序中。不管指定事件處理程序時使用什麼方法(DOM0 級或 DOM2 級),都會傳入 event 對象。

event 對象的屬性參見:Event - Web APIs | MDN

<button id="myBtn">myBtn</button>
<script>
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
  alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
  alert(event.type); //"click"
}, false);
</script>

13.2.2 IE 中的事件對象

DOM0 事件處理程序:event 對象做爲 window 對象的一個屬性存在。

attachEvent()添加事件處理程序:event 對象做爲參數傳入事件處理函數中,也能夠經過 window 對象的 event 屬相獲得。

13.4 事件類型

DOM3 級事件類型:DOM-Level-3-Events

  • User Interface Events debugger
  • Focus Events
  • Mouse Events
  • Wheel Events
  • Input Events
  • Keyboard Events
  • Composition Events:用於處理 IME 輸入序列。IME(Input Method Editor)可讓用戶輸入在物理鍵盤上找不到的字符。例如,使用拉丁文鍵盤的用戶經過 IME 能夠輸入日文字符。
  • 變更(mutation)事件:底層 DOM 結構發生變化時觸發。變更事件是爲 XML 或 HTML DOM 設計的,並不特定於某種語言。

13.4.1 UI 事件

HTML 中沒法訪問 widow 元素,因此通常在 window 上面發生的任何事件均可以在<body>元素中經過相應特性指定。

13.4.3 鼠標與滾輪事件

mousedown、mouseup、click 和 dbclick 順序:

  1. mousedown
  2. mouseup
  3. click
  4. mousedown
  5. mouseup
  6. dbclick

若是 mousedown 或 mouseup 中的一個被取消,click 事件就不會觸發。

  1. 客戶區座標位置:clientX|Y,鼠標指針在瀏覽器視口的水平和垂直位置
  2. 頁面座標位置:pageX|Y,鼠標在頁面中的位置
  3. 屏幕座標位置:screenX|Y,鼠標在電腦屏幕的位置
  4. 相關元素:在執行 mouseover 和 mouseout 時 relatedTarget 提供了涉及到的(移入、移出的)元素的信息。
  5. 觸摸設備:
    iOS 和 Android 設備的實現很是特別,由於這些設備沒有鼠標。在面向 iPhone 和 iPod 中的 Safari 開發時,要記住如下幾點。
    • 不支持 dblclick 事件。雙擊瀏覽器窗口會放大畫面,並且沒有辦法改變該行爲。
    • 輕擊可單擊元素會觸發 mousemove 事件。若是此操做會致使內容變化,將再也不有其餘事件發生;若是屏幕沒有所以變化,那麼會依次發生 mousedown、 mouseup 和 click 事件。輕擊不可單擊的元素不會觸發任何事件。可單擊的元素是指那些單擊可產生默認操做的元素(如連接),或者那些已經被指定了 onclick 事件處理程序的元素。
    • mousemove 事件也會觸發 mouseover 和 mouseout 事件。
    • 兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發 mousewheel 和 scroll 事件。

13.4.4 鍵盤與文本事件

  • 鍵盤事件:keydown、keypress、keyup

    按下字符鍵時:會先觸發 keydown 而後觸發 keypress 最後觸發 keyup,按住不放時 keydown、keypress 會重複觸發。

    按下非字符鍵時:會觸發 keydown 最後觸發 keyup,按住不放時 keydown 會重複觸發。

  • 文本事件:textInput

13.4.7 HTML5 事件

  1. contextmenu 事件:用戶打開上下文菜單時觸發(Windows 下是鼠標右鍵單擊,Mac 下時 Ctrl + 單擊),能夠阻止默認的上下文菜單。

  2. beforeunload 事件:在頁面卸載前觸發,並彈出確認框讓用戶確認是否離開頁面。這個事件不能完全取消,由於那樣就至關於讓用戶就沒法離開當前頁面了。。

  3. DOMContentLoaded 事件 :

    window 的 load 事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會由於要加載的外部資源過多而頗費周折。而 DOMContentLoaded 事件則在造成完整的 DOM 樹以後就會觸發,不理會圖像、 JavaScript 文件、 CSS 文件或其餘資源是否已經下載完畢。與 load 事件不一樣,DOMContentLoaded 支持在頁面下載的早期添加事件處理程序,這也就意味着用戶可以儘早地與頁面進行交互。

  4. readystatechange 事件:提供與文檔或元素加載狀態有關的信息。

  5. pageshow 和 pagehide 事件:瀏覽器會緩存前進和後退的頁面(bfcache, before-forward chache),頁面位於 bfcache 中,load 事件不會觸發,pageshow 事件會觸發。

  6. hashchange 事件:頁面 URL 中 "#" 後面的字符串發生變化時觸發。

13.4.8 設備事件

智能手機和平板相關的事件,能夠監測橫豎屏切換、方向改變、移動。

13.4.9 觸摸與手勢事件

  1. 觸摸事件

    事件發生順序入下

    1. touchstart
    2. mouseover
    3. mousemove(一次)
    4. mousedown
    5. mouseup
    6. click
    7. touchend
  2. 手勢事件

13.5 內存和性能

13.5.1 事件委託

對 「事件處理程序過多」 問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。例如, click 事件會一直冒泡到 document 層次。也就是說,咱們能夠爲整個頁面指定一個 onclick 事件處理程序,而沒必要給每一個可單擊的元素分別添加事件處理程序。

13.5.2 移除事件處理程序

經過手動去除事件處理程序的引用防止內存泄露。

13.6 模擬事件

13.6.1 DOM 中的事件模擬

步驟(1,2 步的方法已通過時,推薦直接用 new 建立 event 對象):

  1. 建立事件對象:在 documnet 對象上使用createEvent()方法建立 event 對象。
  2. 初始化事件對象:調用 event 對象的initXXXEvent()方法。
  3. 觸發事件:目標元素調用dispatchEvent()方法。

例,模擬鼠標事件:

<!-- 新 -->
<button id="btn" onclick="alert('clicked')">click me</button>
<script>
    var btn = document.querySelector("#btn");
    var event = new  MouseEvent("click", {button: 0});
    btn.dispatchEvent(event);
</script>

<!-- 過期 -->
<button id="btn2" onclick="alert('clicked Deprecated')">click me</button>
<script>
    var btn = document.querySelector("#btn2");
    var event = document.createEvent("MouseEvents");
    event.initMouseEvent("click", true, true, document.defaultView);
    btn.dispatchEvent(event);
</script>

第 14 章 表單腳本

14.1 表單基礎知識

<form>
    input1: <input type="text" name="i1">
    input2: <input type="text" name="i2" autofocus>
    <select name="s">
        <option>zzz</option>
        <option>jjj</option>
        <option>fff</option>
    </select>
</form>
<script>
    // 經過document.forms能夠取得全部表單
    var forms = document.forms;

    // form 的 submit() 方法*不會*觸發 submit 事件
    forms[0].addEventListener("submit", () => alert('submit'));
    // 取消下面這個註釋會很是鬼畜
    // forms[0].submit();

    // form 的 reset() 方法*會*觸發 reset 事件
    forms[0].addEventListener("reset", () => alert('reset'));
    forms[0].reset();

    // form 的 elements 屬性是表單中元素的集合
    var filed = forms[0].elements[1];
    // 自動將焦點移動到輸入框
    window.addEventListener("load", () => {
        // autofocus 屬性在支持她的瀏覽器中應該爲 true,不支持的爲空字符串
        if(filed.autofocus !== true){
            filed.focus();
        }
    });

    // change 事件對於輸入元素失去焦點(blur)觸發,對於選擇元素修改選項後觸發
    forms[0].elements["i2"].addEventListener("change", () => {
        console.log("input change");
    });
    forms[0].elements["s"].addEventListener("change", () => {
        console.log("select change");
    });
</script>

14.2 文本框腳本

<form>
    <!-- size : 能夠顯示的字符數 -->
    <input type="text" name="i1" value="initial value" size="4">
    <br>
    <!-- 第一個文本節點爲初始值 -->
    <textarea name="t">initial value</textarea>
    <br>
    <!-- 綁定限制輸入類型的事件 -->
    <input type="text" name="onlyNum">
    <br>
    <!-- 正則驗證 -->
    <input type="text" pattern="\w+">
</form>
<script>
    var forms = document.forms;
    var input = forms[0].elements[0];
    var textarea = forms[0].elements[1];
    var onlyNumInput = forms[0].elements[2];

    // 不建議用 DOM 方法修改文本框的值(例如:修改 <textarea> 的第一個子節點,或使用 setAttribute() 來設置 value 屬性)。
    // 由於這些修改不必定會反映在DOM中。
    // 建議像下面這樣使用 value 屬性讀取和設置文本框的值。
    textarea.value = "new initial value";
    console.log(input.value);


    // 選擇文本
    input.addEventListener("focus", (e) => {
        e.target.select();
    });

    // 取得選擇文本
    console.log(input.value.substring(input.selectionStart, input.selectionEnd));
    
    // 選擇部分文本
    textarea.setSelectionRange(3, 9);
    textarea.focus();

    // keydown 屏蔽字符(keypress 已經棄用了)
    onlyNumInput.addEventListener("keydown", (e) => {
        if(!/\d/.test(e.key) && !e.ctrlKey){
            e.preventDefault();
        }
    });

    // 屏蔽粘貼的字符
    onlyNumInput.addEventListener("paste", (e) => {
        if(!/\d/.test(e.clipboardData.getData("text"))){
            e.preventDefault();
        }
    });
</script>

14.3 選擇框腳本

  • 添加 / 刪除選項:使用 DOM 方法(appendChild()removeChild())或選擇框的方法(add()remove()
  • 移動選項:使用appendChild()將一個選擇框中的選項移動到另外一個選擇框
  • 重排選項:使用insertBefore()移動選項的位置

14.4 表單序列化

目前尚未內置的表單序列化的方法,參見:HTMLFormElement - Web APIs | MDN。造序列化的輪子要注意制定表單字段的顯示隱藏、是否禁用、按鈕、單選多選等狀況的處理。

14.5 富文本編輯

設置 iframe 的 designMode 屬性可使這個 iframe 可編輯。

14.5.1 使用 contenteditable 屬性

經過設置元素的 contenteditable 屬性控制該元素是否可編輯。

14.5.2 操做富文本

使用document.execCommand()執行預約義的命令。

14.5.3 富文本選區

使用 Selection 和 Range 的相關的屬性和方法,參見:Selection - Web APIs | MDNRange - Web APIs | MDN

設置選擇的文本背景爲黃色:

<div id="rechedit" contenteditable="true" style="background: #ddd;">
    Hello world!
</div>
<button onclick="setbg()">setbg</button>
<script>
    function setbg(){
        var selection = document.getSelection(),
            range = selection.getRangeAt(0);
            span = document.createElement("span");
        // 設置背景
        span.style.backgroundColor = "yellow";
        // 設置到選區
        range.surroundContents(span);
    }
</script>

14.5.4 表單與富文本

因爲富文本編輯是使用 iframe 而非表單控件實現的,所以從技術上說,富文本編輯器並不屬於表單。換句話說,富文本編輯器中的 HTML 不會被自動提交給服務器,而須要咱們手工來提取並提交 HTML。爲此,一般能夠添加一個隱藏的表單字段,讓它的值等於從 iframe 中提取出的 HTML。具體來講,就是在提交表單以前,從 iframe 中提取出 HTML,並將其插入到隱藏的字段中。

第 15 章 使用 Canvas 繪圖

15.1 基本用法

在使用<canvas>前首先要檢測getContext()方法是否存在。

使用toDataURL()方法能夠導出在<canvas>上繪製的圖形。

15.2 2D 上下文

15.2.5 變換

若是你知道未來還要返回某組屬性與變換的組合,能夠調用 save() 方法。調用這個方法後,當時的全部設置都會進入一個棧結構,得以妥善保管。而後能夠對上下文進行其餘修改。等想要回到以前保存的設置時,能夠調用 restore() 方法,在保存設置的棧結構中向前返回一級,恢復以前的狀態。連續調用 save() 能夠把更多設置保存到棧結構中,以後再連續調用 restore() 則能夠一級一級返回。

<canvas id="drawing" width=" 200" height="200">A drawing of something.</canvas>
<script>
var drawing = document.getElementById("drawing");
//肯定瀏覽器支持<canvas>元素
if (drawing.getContext){
    var context = drawing.getContext("2d");
    context.fillStyle = "#ff0000";
    context.save(); //save1
    context.fillStyle = "#00ff00";
    context.translate(100, 100);
    context.save(); //save2
    context.fillStyle = "#0000ff";
    context.fillRect(0, 0, 100, 200); //從點(100,100)開始繪製藍色矩形
    context.restore(); //返回save2
    context.fillRect(10, 10, 100, 200); //從點(110,110)開始繪製綠色矩形
    context.restore(); //返回save2
    context.fillRect(0, 0, 50, 50); //從點(0,0)開始繪製紅色矩形
}
</script>

15.2.10 使用圖像數據

使用getImageData能夠獲取<canvas>ImageData實例(包括圖像的寬高和每一個像素的信息)。
使用putImageData()能夠將ImageData實例設置到<canvas>

15.3 WebGL

15.3.1 類型化數組

WebGL 涉及的複雜計算須要提早知道數值的精度,而標準的 JS 數值沒法知足。爲此 WebGL 引入了類型化數組。

15.3.2 WebGL 上下文

  • 視口與座標:WebGL 中的蛇口座標和網頁座標不同,其左下角爲原點。
  • 着色器:使用 GLSL 語言編寫。以字符串的形式傳給 WebGL 上下文對象glcompileShader()方法進行編譯。

WebGL: 2D and 3D graphics for the web - Web APIs | MDN

第 16 章 HTML5 腳本編程

16.1 跨文檔消息傳遞

跨文檔消息傳送(cross-document messaging),有時候簡稱爲 XDM,指的是在來自不一樣域的頁面間傳遞消息。例如, www.wrox.com 域中的頁面與位於一個內嵌框架中的 p2p.wrox.com 域中的頁面通訊。在 XDM 機制出現以前,要穩妥地實現這種通訊須要花不少工夫。 XDM 把這種機制規範化,讓咱們能既穩妥又簡單地實現跨文檔通訊。

XDM 的核心是 postMessage() 方法。在 HTML5 規範中,除了 XDM 部分以外的其餘部分也會提到這個方法名,但都是爲了同一個目的:向另外一個地方傳遞數據。對於 XDM 而言, 「另外一個地方」 指的是包含在當前頁面中的<iframe>元素,或者由當前頁面彈出的窗口。

發送消息:iframe 的 contentWindow 的postMessage() 方法

處理消息:window 的onmessage事件

16.2 原生拖放

拖動某元素時,將依次觸發下列事件:

  1. dragstart
  2. drag
  3. dragend

當某個元素被拖動到一個有效的放置目標上時,下列事件會依次發生:

  1. dragenter
  2. dragover
  3. dragleave 或 drop

使用DragEventdataTransfer屬性的setData()getData()方法能夠實現拖放的數據交換。

默認狀況下圖片、連接文本能夠拖動,能夠經過設置draggable屬性爲true使其餘元素可拖動。

<div 
    id="destination" 
    style="width: 200px; height: 200px; background: green;"
    ondrop="alert(event.dataTransfer.getData('text'))"
>destination</div>

<div 
    id="box" 
    style="display: inline; background: lightyellow; cursor: all-scroll;"
    draggable="true"
    ondragstart="event.dataTransfer.setData('text/plain', 'test text')"
>drag me to destination</div>

16.3 媒體元素

<video><audio>這兩個媒體元素都有一個canPlayType()方法檢測瀏覽器是否支持某種格式和解碼器。

Audio 不用像 Image 那樣必須插入到文檔中,只要建立一個實例並傳入音頻便可使用。

16.4 歷史狀態管理

  • 使用history.pushState()方法增長曆史狀態
  • 用戶點擊後退後觸發popState事件

注意:請確保每一個pushState()創造的假的 URL,服務器上都有一個真的 URL 與之對應, 否側用戶點擊刷新會致使 404 錯誤。

第 17 章 錯誤處理與調試

17.2 錯誤處理

17.2.1 try-catch 語句

ECMA-262 第 3 版引入了 try-catch 語句,做爲 JavaScript 中處理異常的一種標準方式。這與 Java 中的 try-catch 語句是徹底相同的。

try{
    // 可能會致使錯誤的代碼
} catch(error){
    // 在錯誤發生時怎麼處理
    alert(error.message) // message 屬性是惟一一個可以保證全部瀏覽器都支持的屬性
}

只要代碼中包含 finally 子句,則不管 try 或 catch 語句塊中包含什麼代碼——甚至 return 語句,都不會阻止 finally 子句的執行。

<script>
alert(testFinally());// 0
function testFinally(){
    try {
        return 2;// 不執行
        alert('try');// 不執行
    } catch (error){
        return 1;
    } finally {
        return 0;
    }
}
</script>

只要代碼中包含 finally 子句,不管 try 仍是 catch 語句塊中的 return 語句都會被忽視。

17.2.2 拋出錯誤

與 try-catch 語句相匹配的還有 throw 操做符,用於拋出自定義錯誤。

遇到 throw 操做符時代碼會當即中止,僅當有 try-catch 語句捕獲到拋出的值時,代碼纔會繼續執行。

利用原型鏈能夠經過繼承 Error 來建立自定義錯誤類型。

17.2.3 錯誤(error)事件

<!-- 圖像 -->
<img src="xxx" onerror="console.log('img not loaded')">
<!-- window -->
<script>
    window.onerror = function(message, source, lineno, colno, error) {
        console.log(message);
    }
    throw "test";
</script>

GlobalEventHandlers.onerror - Web APIs | MDN

17.2.5 常見的錯誤類型

  1. 類型轉換錯誤:注意類型的自動轉換引發的錯誤,尤爲是在做爲判斷條件時
  2. 數據類型錯誤:使用參數和變量前應該保證其類型是正確的(這點利用 TS 能夠很是好地解決)
  3. 通訊錯誤:對於查詢字符串要使用encodeURIComponent()處理

17.2.6 區分致命錯誤和非致命錯誤

非致命錯誤,能夠根據下列一或多個條件來肯定:

  • 不影響用戶的主要任務;
  • 隻影響頁面的一部分;
  • 能夠恢復;
  • 重複相同操做能夠消除錯誤。

eg:非致命錯誤添加 try-catch 可使非致命錯誤發生後後續代碼繼續執行,後面的模塊繼續加載

致命錯誤,能夠經過如下一或多個條件來肯定:

  • 應用程序根本沒法繼續運行;
  • 錯誤明顯影響到了用戶的主要操做;
  • 會致使其餘連帶錯誤。

17.2.7 把錯誤記錄到服務器

推薦將 JS 錯誤寫回服務器,把先後端的錯誤集中起來可以極大地方便對數據的分析。

創建這樣一種 JavaScript 錯誤記錄系統,首先須要在服務器上建立一個頁面(或者一個服務器入口點),用於處理錯誤數據。這個頁面的做用無非就是從查詢字符串中取得數據,而後再將數據寫入錯誤日誌中。這個頁面可能會使用以下所示的函數:

function logError(sev, msg){
    var img = new Image();
    img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" + encodeURIComponent(msg);
}

這個 logError() 函數接收兩個參數:表示嚴重程度的數值或字符串(視所用系統而異)及錯誤消息。其中,使用了 Image 對象來發送請求,這樣作很是靈活,主要表現以下幾方面。

  • 全部瀏覽器都支持 Image 對象,包括那些不支持 XMLHttpRequest 對象的瀏覽器。
  • 能夠避免跨域限制。一般都是一臺服務器要負責處理多臺服務器的錯誤,而這種狀況下使用 XMLHttpRequest 是不行的。
  • 在記錄錯誤的過程當中出問題的機率比較低。大多數 Ajax 通訊都是由 JavaScript 庫提供的包裝函數來處理的,若是庫代碼自己有問題,而你還在依賴該庫記錄錯誤,可想而知,錯誤消息是不可能獲得記錄的。

第 18 章 JavaScript 與 XML

  • DOMParser 和 XMLSerializer 接口提供的 XML 和 DOM 文檔的轉換
  • XPath 查找 XML 節點
  • XSLT 轉換 XML 文檔

第 19 章 E4X

E4X 已經廢棄了(E4X - Archive of obsolete content | MDN),大概看了一下感受 JSX 和 XML 字面量有點像啊(PS:確實只是有點像,JSX | XML-like syntax extension to ECMAScript)。

第 20 章 JSON

JSON 的語法能夠表示如下三種類型的值。

  • 簡單值:使用與 JavaScript 相同的語法,能夠在 JSON 中表示字符串、數值、布爾值和 null。但 JSON 不支持 JavaScript 中的特殊值 undefined。
  • 對象:對象做爲一種複雜數據類型,表示的是一組無序的鍵值對兒。而每一個鍵值對兒中的值能夠是簡單值,也能夠是複雜數據類型的值。
  • 數組:數組也是一種複雜數據類型,表示一組有序的值的列表,能夠經過數值索引來訪問其中的值。數組的值也能夠是任意類型——簡單值、對象或數組。

與 JavaScript 的對象字面量相比, JSON 對象有兩個地方不同。首先,沒有聲明變量(JSON 中沒有變量的概念)。其次,沒有末尾的分號(由於這不是 JavaScript 語句,因此不須要分號)。再說一遍,對象的屬性必須加雙引號,這在 JSON 中是必需的。屬性的值能夠是簡單值,也能夠是複雜類型值。

20.2 序列化和解析

與 XML 數據結構解析成 DOM 文檔提取數據很麻煩,而 JSON 解析爲 JS 對象提取數據很是簡單。

20.2.1 JSON 對象

早期 JSON 解析器基本上就是使用 JS 的eavl()函數,因爲 JSON 是 JS 語法的子集,所以eavl()函數能夠解析並返回數據。ES5 對即系解析 JSON 的行爲進行規範,定義了全局對象 JSON。

JSON 對象有兩個方法:stringify()parse()

20.2.2 序列化選項

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

能夠爲對象添加toJSON()方法,這個方法會返回自身的 JSON 數據格式。

例如:

var purple = {
    name: "zzz",
    age: 17,
    toJSON() {
        return {
            age: this.age,
            fans: []
        }
    }
};
JSON.stringify(purple, (k, v) => k === "age" ? 99999 : v, 4);
// '{\n    "age": 99999,\n    "fans": []\n}'

20.2.3 解析選項

JSON.parse()方法也能夠接收另外一個參數,該參數是一個函數,將在每一個鍵值對上調用。

例如:

var purple = {
    name: "zzz",
    age: 17,
    birthday: new Date(0)
};
var str = JSON.stringify(purple);
var obj = JSON.parse(str, (k, v) => k === "birthday" ? new Date(v) : v);
console.log(obj.birthday.getFullYear());// 1970

第 21 章 Ajax 與 Comet

21.1 XMLHttpRequest 對象

XMLHttpRequest - Web APIs | MDN

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("GET", "example.txt", true);
xhr.send(null);

21.1.3 GET 請求

查詢字符串中每一個參數的名稱和值必須使用encodeURIComponent()進行編碼而後放到 URL 的末尾。

  1. 使用open()方法時第一個參數爲 GET(書上是小寫,應該是打錯了)
  2. 構建帶查詢字符串的 URL

21.1.4 POST 請求

POST 請求應該把數據做爲請求主體提交。

POST 請求的主體能夠包含很是多的數據,並且格式不限。

  1. 使用open()方法時第一個參數爲 POST(書上是小寫,應該是打錯了)
  2. 使用send()方法發送數據

21.2 XMLHttpRequest 2 級

21.2.1 FormData

FormData - Web APIs | MDN

<form>
    <input type="text" name="inp">
    <input type="radio" name="ra" value="male" checked>
    <input type="radio" name="ra" value="famale">
    <select name="sel">
        <option>t1</option>
        <option>t2</option>
    </select>
</form>

<script>
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                alert("Request was unsuccessful: " + xhr.status);
            }
        }
    };
    xhr.open("POST", "example.php");
    xhr.send(new FormData(document.forms[0]));
</script>

21.3 進度事件

XMLHttpRequest: progress event - Web APIs | MDN

當請求接收到數據這個事件會按期觸發。

21.4 跨源資源共享

CORS(Cross-Origin Resource Sharing,跨源資源共享)是 W3C 的一個工做草案,定義了在必須訪問跨源資源時,瀏覽器與服務器應該如何溝通。 CORS 背後的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,仍是應該失敗。

好比一個簡單的使用 GET 或 POST 發送的請求,它沒有自定義的頭部,而主體內容是 text/plain。在發送該請求時,須要給它附加一個額外的 Origin 頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。下面是 Origin 頭部的一個示例:

Origin: http://www.nczonline.net

若是服務器認爲這個請求能夠接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源信息(若是是公共資源,能夠回發 "*")。例如:

Access-Control-Allow-Origin: http://www.nczonline.net

若是沒有這個頭部,或者有這個頭部但源信息不匹配,瀏覽器就會駁回請求。正常狀況下,瀏覽器會處理請求。注意,請求和響應都不包含 cookie 信息。

瀏覽器對 CORS 的實現:

要請求位於另外一個域中的資源,使用標準的 XHR 對象並在open()方法中傳入絕對 URL 便可:

var xhr = new XMLHttpRequest();
xhr.onload = function(){
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
        alert(xhr.responseText);
    } else {
        alert("Request was unsuccessful: " + xhr.status);
    }
};
xhr.open("GET", "http://www.somewhere-else.com");
xhr.send(null);

跨越 XHR 的限制:

  1. 不能使用setRequestHeader()設置自定義請求頭
  2. 不能發送和接收 cookie
  3. 調用getAllResponseHeaders()總會返回空字符串

21.4.3 Preflighted Request

Preflight request - MDN Web Docs Glossary: Definitions of Web-related terms | MDN

一個 CORS 預檢請求是用於檢查服務器是否支持 CORS 即跨域資源共享。

它通常是用瞭如下幾個 HTTP 請求首部的 OPTIONS 請求:Access-Control-Request-MethodAccess-Control-Request-Headers,以及一個 Origin 首部。

當有必要的時候,瀏覽器會自動發出一個預檢請求;因此在正常狀況下,前端開發者不須要本身去發這樣的請求。

21.4.4 帶憑據請求

默認狀況下,跨域請求不提供憑據(cookie,HTTP 認證,及客戶端 SSL 證實等)。經過將 withCredentials 設置爲 true,能夠指定某個請求應該發送憑據。

IE10 及更早版本都不支持!

21.5 其餘跨域技術

21.5.1 圖像 Ping

沒法處理相應的信息,只能使用 onload 和 onerror 肯定是否接收到相應,所以只能用於瀏覽器與服務器單向通訊。

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";

21.5.2 JSONP

例:經過查詢地理定位服務來顯示你的 IP 地址和位置信息。(接口已經廢棄:apilayer/freegeoip: IP geolocation web server

function handleResponse(response){
    alert("You’ re at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

問題:

  1. 執行其餘域的代碼不安全
  2. 肯定 JSONP 請求是否失敗不容易(目前除了 FireFox 和 Chrome,其餘瀏覽器的對 onerror 事件的支持都是未知。GlobalEventHandlers.onerror - Web APIs | MDN

Comet

Ajax 是一種從頁面向服務器請求數據的技術,而 Comet 則是一種服務器向頁面推送數據的技術。 Comet 可以讓信息近乎實時地被推送到頁面上,很是適合處理體育比賽的分數和股票報價。

實現方式:

  1. 使用長輪詢:頁面發起請求等待服務器回覆,服務器回覆後開啓發起新請求
  2. 使用 HTTP 流:瀏覽器發送一個請求,服務器保持鏈接打開,而後週期性地像瀏覽器發送數據,瀏覽器經過 readystatechange 事件檢測 readyState 是否爲 3 就能夠利用 HTTP 流。

SSE 與 Web Sockets

面對某個具體的用例,在考慮是使用 SSE 仍是使用 Web Sockets 時,能夠考慮以下幾個因素。

首先,你是否有自由度創建和維護 Web Sockets 服務器?由於 Web Socket 協議不一樣於 HTTP,因此現有服務器不能用於 Web Socket 通訊。 SSE 卻是經過常規 HTTP 通訊,所以現有服務器就能夠知足需求。

第二個要考慮的問題是到底需不須要雙向通訊。若是用例只需讀取服務器數據(如比賽成績),那麼 SSE 比較容易實現。若是用例必須雙向通訊(如聊天室),那麼 Web Sockets 顯然更好。別忘了,在不能選擇 Web Sockets 的狀況下,組合 XHR 和 SSE 也是能實現雙向通訊的。

21.6 安全

對於未被受權系統有權訪問某個資源的狀況,咱們稱之爲 CSRF(Cross-Site Request Forgery,跨站點請求僞造)。

爲了確保經過 XHR 訪問的 URL 安全,通行的作法就是驗證發送請求者是否有權訪問相應的資源。有如下方法可供選擇:

  • 經過 SSL 鏈接來訪問能夠經過 XHR 請求的資源
  • 每次請求都攜帶 token

第 22 章 高級技巧

22.1 高級函數

22.1.1 安全類型檢測

Object.prototype.toString.call(JSON); // '[object JSON]'
JSON = function(){};
Object.prototype.toString.call(JSON); // '[object Function]'

22.1.2 做用域安全的構造函數

瀏覽器控制檯運行下面的例子,你會神奇地發現頁面竟然跳轉了(好神奇 =_=!!!)

function Persion(loc){
    this.location = loc;
}

var zi = Persion('zi');// 不當心忘記使用 new

這時您須要用做用域安全的構造函數,而後你會神奇地發現頁面又跳轉了(好神奇 =_=!!!)

function Persion(loc){
    if(this instanceof Persion){
        this.location = loc;
    } else {
        return new Persion(loc)
    }
}

var zi = Persion('zi');// 又不當心忘記使用 new

使用做用域安全的構造函數後您就必需要用原型鏈了。

function Persion(loc){
    if(this instanceof Persion){
        this.location = loc;
    } else {
        return new Persion(loc)
    }
}

function ZM(loc){
    Persion.call(this, loc);
    this.age = 17;
}

ZM.prototype = new Persion(); // 不將原型鏈連上就沒法繼承
ZM.prototype.constructor = ZM;

var zi = new ZM('zi');

22.1.3 惰性載入函數

惰性載入表示函數執行的分支僅會發生一次。有兩種實現惰性載入的方式

第一種就是在函數被調用時再處理函數。在第一次調用的過程當中,該函數會被覆蓋爲另一個按合適方式執行的函數,這樣任何對原函數的調用都不用再通過執行的分支了。

第二種實現惰性載入的方式是在聲明函數時就指定適當的函數。這樣,第一次調用函數時就不會損失性能了,而在代碼首次加載時會損失一點性能。

// 第一種方式
function flatArr(arr){
    if(typeof Array.prototype.flat === "function"){
        flatArr = arr => arr.flat();
    }else{
        flatArr = arr => {
            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
            const stack = [...arr];
            const res = [];
            while (stack.length) {
                // pop value from stack
                const next = stack.pop();
                if (Array.isArray(next)) {
                    // push back array items, won't modify the original input
                    stack.push(...next);
                } else {
                    res.push(next);
                }
            }
            //reverse to restore input order
            return res.reverse();
        }
    }
    return flatArr(arr);
}

flatArr([1, 2, 3, [4, 5, 6]]);
// 第二種方式
var flatArr = (function(){
    if(typeof Array.prototype.flat === "function"){
        return arr => arr.flat();
    }else{
        return arr => {
            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
            const stack = [...arr];
            const res = [];
            while (stack.length) {
                // pop value from stack
                const next = stack.pop();
                if (Array.isArray(next)) {
                    // push back array items, won't modify the original input
                    stack.push(...next);
                } else {
                    res.push(next);
                }
            }
            //reverse to restore input order
            return res.reverse();
        }
    }
})();

flatArr([1, 2, 3, [4, 5, 6]]);

22.1.4 函數綁定

另外一個日益流行的高級技巧叫作函數綁定。函數綁定要建立一個函數,能夠在特定的 this 環境中以指定參數調用另外一個函數。該技巧經常和回調函數與事件處理程序一塊兒使用,以便在將函數做爲變量傳遞的同時保留代碼執行環境。

使用函數的bind()方法。被綁定函數與普通函數相比須要更多開銷,最好只在必要時使用。

var handler = {
    msg: "text",
    handleClick: function(){
        alert(this.msg)
    }
}

// not bind
document.addEventListener('click', handler.handleClick);

// bind
document.addEventListener('click', handler.handleClick.bind(handler));

22.1.5 函數柯里化

函數柯里化(function currying),用於建立已經設置好一個或多個參數的函數。

function curry(fn, ...args){
    return function(...innerArgs){
        return fn(...args, ...innerArgs);
    }
}
function add(a, b){
    return a + b;
}
var curriedAdd = curry(add, 3);
console.log(curriedAdd(5)); // 8

// 精簡版
var add2 = a => b => a + b;
console.log(add2(3)(5)); // 8

22.2 防篡改對象

注意:一旦將對象定義爲防篡改就沒法撤銷了。

22.2.1 不可拓展對象

禁止添加屬性。

Object.preventExtensions() - JavaScript | MDN

22.2.2 密封對象

禁止添加屬性,且已有成員的[[Configurable]]特性設爲爲false(不能刪除和修改訪問器屬性)。

Object.seal() - JavaScript | MDN

22.2.3 凍結的對象

禁止添加屬性,且已有成員的[[Configurable]]特性設爲爲false(不能刪除和修改訪問器屬性),且已有成員的[[[Writable]]特性設爲false(若是定義[[Set]]函數,訪問器依然是可寫的)。

Object.freeze() - JavaScript | MDN

22.3 高級定時器

JS 是單線程的,setTimeout()setInterval()只是在它們開始執行後過了設置的時間,將回調函數添加到事件隊列。若是尚未到設置的事件就清除了,這個回調函數就不會添加到事件隊列了。

22.3.1 重複的定時器

使用setTimeout()代替setInterval()能夠解決setInterval()在任務隊列已有該回調函數時不會重複添加的問題。

22.3.2 Yielding Processes

運行在瀏覽器中的 JavaScript 都被分配了一個肯定數量的資源。不一樣於桌面應用每每可以隨意控制他們要的內存大小和處理器時間, JavaScript 被嚴格限制了,以防止惡意的 Web 程序員把用戶的計算機搞掛了。其中一個限制是長時間運行腳本的制約,若是代碼運行超過特定的時間或者特定語句數量就不讓它繼續執行。若是代碼達到了這個限制,會彈出一個瀏覽器錯誤的對話框,告訴用戶某個腳本會用過長的時間執行,詢問是容許其繼續執行仍是中止它。全部 JavaScript 開發人員的目標就是,確保用戶永遠不會在瀏覽器中看到這個使人費解的對話框。定時器是繞開此限制的方法之一。

不需同步和按順序完成的循環可使用定時器進行分割,這時一種叫數組分塊(array chunking)的技術。

function chunk(arr, process, context){
    setTimeout(function(){
        let item = arr.shift();
        process.call(context, item);
        if(arr.length){
            setTimeout(arguments.callee, 100)
        }
    }, 100)
}
chunk([1, 2, 3], d => console.log(d));

22.3.3 函數節流(throttle)和防反彈 (debounce)

瀏覽器中某些計算和處理要比其餘的昂貴不少。例如, DOM 操做比起非 DOM 交互須要更多的內存和 CPU 時間。連續嘗試進行過多的 DOM 相關操做可能會致使瀏覽器掛起,有時候甚至會崩潰。尤爲在 IE 中使用 onresize 事件處理程序的時候容易發生,當調整瀏覽器大小的時候,該事件會連續觸發。在 onresize 事件處理程序內部若是嘗試進行 DOM 操做,其高頻率的更改可能會讓瀏覽器崩潰。爲了繞開這個問題,你可使用定時器對該函數進行節流。

函數節流背後的基本思想是指,某些代碼不能夠在沒有間斷的狀況連續重複執行。第一次調用函數,建立一個定時器,在指定的時間間隔以後運行代碼。當第二次調用該函數時,它會清除前一次的定時器並設置另外一個。若是前一個定時器已經執行過了,這個操做就沒有任何意義。然而,若是前一個定時器還沒有執行,其實就是將其替換爲一個新的定時器。目的是隻有在執行函數的請求中止了一段時間以後才執行。

PS:書中的函數節流應該叫防反彈比較合適。

Underscore.js - throttle

Underscore.js - debounce

// 書中將下面的函數叫作 throttle
function debounce(callback){
    let timeoutID;
    function wrapper () {
        clearTimeout(timeoutID);
        timeoutID = setTimeout(callback, 1000);
    }
    return wrapper;
}

function throttle(callback){
    let lastExec = 0;
    function wrapper () {
        if(Date.now() - lastExec > 1000){
            lastExec = Date.now();
            setTimeout(callback, 1000);
        }
    }
    return wrapper;
}

var resizeThrottle = throttle(() => console.log('throttle'));
var resizeDebounce = debounce(() => console.log('debounce'));

window.onscroll = () => {
    resizeThrottle();
    resizeDebounce();
}

22.4 自定義事件

事件是一種叫作觀察者的設計模式,這是一種建立鬆散耦合代碼的技術。對象能夠發佈事件,用來表示在該對象生命週期中某個有趣的時刻到了。而後其餘對象能夠觀察該對象,等待這些有趣的時刻到來並經過運行代碼來響應。

觀察者模式由兩類對象組成:主體和觀察者。主體負責發佈事件,同時觀察者經過訂閱這些事件來觀察該主體。該模式的一個關鍵概念是主體並不知道觀察者的任何事情,也就是說它能夠獨自存在並正常運做即便觀察者不存在。從另外一方面來講,觀察者知道主體並能註冊事件的回調函數(事件處理程序)。涉及 DOM 上時, DOM 元素即是主體,你的事件處理代碼即是觀察者。

EventTarget - Web APIs | MDN

function EventTarget(){
  this.handlers = {};
}
EventTarget.prototype = {
  constructor: EventTarget,
  addHandler: function(type, handler){
    if (typeof this.handlers[type] == "undefined"){
      this.handlers[type] = [];
    }
    this.handlers[type].push(handler);
  },
  fire: function(event){
    if (!event.target){
      event.target = this;
    }
    if (this.handlers[event.type] instanceof Array){
      var handlers = this.handlers[event.type];
      for (var i=0, len=handlers.length; i < len; i++){
        handlers[i](event);
      }
    }
  },
  removeHandler: function(type, handler){
    if (this.handlers[type] instanceof Array){
      var handlers = this.handlers[type];
      for (var i=0, len=handlers.length; i < len; i++){
        if (handlers[i] === handler){
          break;
        }
      }
      handlers.splice(i, 1);
    }
  }
};

function handleMessage(event){
  alert("Message received: " + event.message);
}
//建立一個新對象
var target = new EventTarget();
//添加一個事件處理程序
target.addHandler("message", handleMessage);
//觸發事件
target.fire({ type: "message", message: "Hello world!"});
//刪除事件處理程序
target.removeHandler("message", handleMessage);
//再次,應沒有處理程序
target.fire({ type: "message", message: "Hello world!"});

22.5 拖放

使用 DragEvent - Web APIs | MDN 或者使用鼠標事件進行模擬。

第 23 章 離線應用與客戶端存儲

支持離線 Web 應用開發是 HTML5 的另外一個重點。

開發離線 Web 應用:

  1. 檢測設備是否能夠上網
  2. 能訪問資源(圖片、CSS、JS 等)
  3. 本地空間用於保存數據

23.1 離線檢測

經過navigator.onLine屬性檢測是否離線。

在線離線狀態切換時會觸發 window 的 online 和 offline 事件。

23.2 應用緩存(application cache)

書中的方法已經廢棄!!!如今應該這樣實現:經過 Service workers 讓 PWA 離線工做 - 漸進式 Web 應用(PWA) | MDN

使用描述文件(manifest file)例如出要下載和緩存的資源。

能夠在<html>標籤的 manifest 屬性中指定這個文件的路徑,例如<html mainfest="/offline.manifest">

23.3 數據存儲

cookies.Cookie - Mozilla | MDN

  1. 限制:50 個之內,總長度 4095B 之內
  2. 構成:名稱、值域、路徑、失效時間、安全標誌、只供 HTTP 使用等
  3. 子 cookie:使用一個 cookie 的值存過個鍵值對兒

23.3.3 Web 存儲機制

Storage - Web API 接口參考 | MDN

  1. sessionStorage 對象:存儲到頁面關閉,同會話共享(新標籤或窗口中打開頁面會建立新的會話)
  2. localStorage 對象:永久存儲,同域共享

23.3.4 IndexedDB

IndexedDB 是一個數據庫,和 MySQL 等數據庫相似。

IndexedDB 最大的特色是使用對象保存數據,而不是表來保存數據。(和 MongoDB 有點像)

一個 IndexedDB 數據庫,就是一組位於相同命名空間下的對象的集合。

IndexedDB API - Web APIs | MDN

留坑。。

第 24 章 最佳實踐

24.1 可維護性

編寫可維護的代碼很重要,由於大部分開發人員都花費大量時間維護他人代碼。

24.1.1 可維護的代碼的特徵

  • 可理解性
  • 直觀性
  • 可適應性
  • 可擴展性
  • 可調試性

24.1.2 代碼約定

  1. 可讀性:通常要在函數和方法、大段代碼、複雜的算法、Hack 這些地方加上註釋
  2. 變量和函數名:變量應爲名詞,函數名應該以動詞開頭(返回布爾類型值得函數通常以 is 開頭)
  3. 變量類型透明:使用TypeScript匈牙利標記法等方法。

24.1.3 鬆散耦合

  1. 解耦 HTML/JS:不要混這寫,好比:若是使用 JSX 就儘可能不要直接操做 DOM,若是直接操做 DOM 就儘可能不要使用 JS 生成 HTML 標籤。
  2. 解耦 CSS/JS:儘可能不用 JS 直接改 CSS,而是經過修改 class 間接修改樣式。
  3. 解耦應用邏輯 / 事件處理程序:事件處理程序應處理事件、而後將數據轉交給應用邏輯處理。

24.1.4 編程實踐

  1. 尊重對象全部權:也許是企業環境中總重要的實踐。
    • 不要爲實例或原型添加屬性和方法。
    • 不要重定義已經存在的方法。
  2. 避免全局量:使用命名空間,雖然使用命名空間須要多寫一些代碼,但命名空間有助於確保代碼和頁面的其餘代碼以無害的方式一塊兒工做。
  3. 使用常量:如下狀況能夠考慮使用常量。
    • 重複值:多處用到的值應抽取爲常量,包括 CSS 類名等。
    • 用戶界面字符串:方便國際化。
    • URLs:Web 應用中資源位置很容易變,推薦用一個公共的地方存 URL。
    • 任意可能會更改的值:當您用到字面量時,先考慮一下這個值在將來是否是會變化,若是會變化就應該提取出來做爲常量。

24.2 性能

24.2.1 注意做用域

  1. 避免全局查找:將在一個函數中屢次用到的全局對象存儲爲局部變量老是沒錯的。
  2. 避免 with 語句:with 會建立本身的做用域鏈,會增長其中執行的代碼的做用域鏈長度。

24.2.2 選擇正確的方法

數據量大的循環能夠考慮:Duff's device

24.2.4 優化 DOM 交互

  1. 最小化現場更新:經過文檔片斷(fragment)將 DOM 一塊兒更新。

    一旦你須要訪問的 DOM 部分是已經顯示的頁面的一部分,那麼你就是在進行一個現場更新。之因此叫現場更新,是由於須要當即(現場)對頁面對用戶的顯示進行更新。每個更改,無論是插入單個字符,仍是移除整個片斷,都有一個性能懲罰,由於瀏覽器要從新計算無數尺寸以進行更新。現場更新進行得越多,代碼完成執行所花的時間就越長;完成一個操做所需的現場更新越少,代碼就越快。

  2. 使用 innerHTML:對於大量 DOM 更改,innerHTML 比 DOM 方法快。

    有兩種在頁面上建立 DOM 節點的方法:使用諸如 createElement() 和 appendChild() 之類的 DOM 方法,以及使用 innerHTML。對於小的 DOM 更改而言,兩種方法效率都差很少。然而,對於大的 DOM 更改,使用 innerHTML 要比使用標準 DOM 方法建立一樣的 DOM 結構快得多。

  3. 使用事件代理:將事件處理程序附加到更高層的地方負責多個目標的事件處理。

    大多數 Web 應用在用戶交互上大量用到事件處理程序。頁面上的事件處理程序的數量和頁面響應用戶交互的速度之間有個負相關。爲了減輕這種懲罰,最好使用事件代理。

  4. 注意 HTMLCollection

    HTMLCollection 對象的陷阱已經在本書中討論過了,由於它們對於 Web 應用的性能而言是巨大的損害。記住,任什麼時候候要訪問 HTMLCollection,無論它是一個屬性仍是一個方法,都是在文檔上進行一個查詢,這個查詢開銷很昂貴。最小化訪問 HTMLCollection 的次數能夠極大地改進腳本的性能。

24.3 部署

24.3.1 構建過程

軟件開發典型模式:寫代碼 -> 編譯 -> 測試

JS 是非編譯型語言,模式變成:寫代碼 -> (語法轉換) -> 測試

如今一般是使用 ES6 和更新的語法,一些語法測試和生產環境還不支持,須要用進行語法轉換(通常使用 Babe)。

24.3.2 驗證

XXLint(常見的有 JSLint、TSLint、ESLint) 能夠查找代碼中的語法錯誤以及常見的編碼錯誤。

24.3.3 壓縮

當談及 JavaScript 文件壓縮,其實在討論兩個東西:代碼長度和配重(Wire weight)。代碼長度指的是瀏覽器所需解析的字節數,配重指的是實際從服務器傳送到瀏覽器的字節數。在 Web 開發的早期,這兩個數字幾乎是同樣的,由於從服務器端到客戶端原封不動地傳遞了源文件。而在今天的 Web 上,這二者不多相等,實際上也不該相等。

  1. 文件壓縮:刪除額外的空白、刪除註釋、縮短變量名等
  2. HTTP 壓縮

    配重指的是實際從服務器傳送到瀏覽器的字節數。由於如今的服務器和瀏覽器都有壓縮功能,這個字節數不必定和代碼長度同樣。全部的五大 Web 瀏覽器(IE、 Firefox、 Safari、 Chrome 和 Opera)都支持對所接收的資源進行客戶端解壓縮。這樣服務器端就可使用服務器端相關功能來壓縮 JavaScript 文件。一個指定了文件使用了給定格式進行了壓縮的 HTTP 頭包含在了服務器響應中。接着瀏覽器會查看該 HTTP 頭肯定文件是否已被壓縮,而後使用合適的格式進行解壓縮。結果是和原來的代碼量相比在網絡中傳遞的字節數量大大減小了。

第 25 章 新興的 API

25.1 requestAnimationFrame()

<style>
  div {
    width: 200px;
    height: 20px;
  }
  #bar {
    border: 1px solid #000;
    position: relative;
    background-color: #666;
    overflow: hidden;
  }
  #progress {
    position: absolute;
    background-color: #fff;
    top: 0;
    left: 0;
  }
</style>

<div id="bar">
  <div id="progress"></div>
</div>

<script>
  var start = null;
  var element = document.getElementById("progress");
  element.style.position = "absolute";

  function step(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    element.style.left = Math.min(progress / 10, 200) + "px";
    if (progress < 2000) {
      window.requestAnimationFrame(step);
    }
  }

  window.requestAnimationFrame(step);
</script>

25.2 Page Visibility API

不知道用戶是否是正在與頁面交互,這是困擾廣大 Web 開發人員的一個主要問題。若是頁面最小化了或者隱藏在了其餘標籤頁後面,那麼有些功能是能夠停下來的,好比輪詢服務器或者某些動畫效果。而 Page Visibility API(頁面可見性 API)就是爲了讓開發人員知道頁面是否對用戶可見而推出的。

  • document.hidden:頁面是否隱藏
  • document.visibilityState:表示當前頁面的可視狀態

25.3 Geolocation API

地理定位(geolocation)是最使人興奮,並且獲得了普遍支持的一個新 API。 經過這套 API, JavaScript 代碼可以訪問到用戶的當前位置信息。固然,訪問以前必須獲得用戶的明確許可,即贊成在頁面中共享其位置信息。若是頁面嘗試訪問地理定位信息,瀏覽器就會顯示一個對話框,請求用戶許可共享其位置信息。

使用navigator.geolocation對象

25.4 File API

不能直接訪問用戶計算機中的文件,一直都是 Web 應用開發中的一大障礙。 2000 年之前,處理文件的惟一方式就是在表單中加入<input type="file">字段,僅此而已。 File API(文件 API)的宗旨是爲 Web 開發人員提供一種安全的方式,以便在客戶端訪問用戶計算機中的文件,並更好地對這些文件執行操做。支持 File API 的瀏覽器有 IE10+、 Firefox 4+、 Safari 5.0.5+、 Opera 11.1 + 和 Chrome。

HTML5 在 DOM 中爲文件輸入元素添加了一個 files 集合,裏面包含着一組帶有文件信息的 File 對象。

25.4.1 FileReader

FileReader 類型實現的是一種異步文件讀取機制。能夠把 FileReader 想象成 XMLHttpRequest,區別只是它讀取的是文件系統,而不是遠程服務器。

25.4.2 讀取部份內容

調用 blob 的 slice() 方法返回一個 Blob 實例,而後使用 FileReader 讀取這個 Blob 實例。

只讀文件的一部分能夠節省時間,適合只關注文件特定部分的狀況。

25.4.3 對象 URL

對象 URL 也被稱爲 blob URL,指的是引用保存在 File 或 Blob 中數據的 URL。使用對象 URL 的好處是能夠沒必要把文件內容讀取到 JavaScript 中而直接使用文件內容。爲此,只要在須要文件內容的地方提供對象 URL 便可。要建立對象 URL,可使用window.URL.createObjectURL()方法,並傳入 File 或 Blob 對象。

注意:要在不須要某個對象 URL 是手動釋放內存。

25.4.4 讀取拖放的內容

經過 event.dataTransfer.files獲取文件信息。

25.4.5 使用 XHR 上傳文件

可使用 File API 讀取文件內容而後 post 給後端(這樣後端收到的是問價的內容,還要再將她們保存),但更方便的作法是以提交表單的方式上傳文件(這樣後端就像接收到常規表單同樣處理就行):

  1. 建立 21 章介紹的 FormData 的對象
  2. 調用 FormData 對象的append()方法添加文件
  3. 調用 XHR 對象的send()方法發送 FormData 對象

25.5 Web 計時

Performance:用於查看頁面加載的各個階段的時間(13 位的時間戳,精確到毫秒)

25.6 Web Workers

隨着 Web 應用複雜性的與日俱增,愈來愈複雜的計算在所不免。長時間運行的 JavaScript 進程會致使瀏覽器凍結用戶界面,讓人感受屏幕 「凍結」 了。 Web Workers 規範經過讓 JavaScript 在後臺運行解決了這個問題。瀏覽器實現 Web Workers 規範的方式有不少種,可使用線程、後臺進程或者運行在其餘處理器核心上的進程,等等。具體的實現細節其實沒有那麼重要,重要的是開發人員如今能夠放心地運行 JavaScript,而沒必要擔憂會影響用戶體驗了。

很是消耗時間的操做轉交給 Worker 就不會阻塞用戶界面了,例如:使用 Worker 排序數組 web worker example - CodeSandbox

Worker 可使用importScripts()方法導入其餘腳本。

Worker 分爲專用 Worker(dedicated worker)和共享 Worker(shared worker),前者不能在頁面間共享,後者能夠在多個窗口,iframe 或 Worker 間共享。

附錄 A ECMAScript Harmony

書中好多特性(例如,迭代器對象、數組領悟等)都沒有歸入 ES6 標準。

代理對象

Proxy 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)。

另外一種描述:Proxy 對象能夠處理(捕捉)原生功能,並用本身的函數處理。

映射與集合

用普通對象保存鍵值對融合與原生屬性混淆,使用Map類型能夠避免混淆。

WeakMap是 ES 中惟一一個可以讓你知道對象何時已經徹底解除引用的類型。例:Babel 將類的私用屬性轉換爲 WeakMap,這樣私有屬性就不會強引用實例化的對象(強引用實例化的對象會致使對象內存沒法釋放)。

class Test {
  #t1 = 500
  #t2 = 600
}

// 轉換後
var Test = function Test() {
  _classCallCheck(this, Test);

  _t.set(this, {
    writable: true,
    value: 500
  });

  _t2.set(this, {
    writable: true,
    value: 600
  });
};

var _t = new WeakMap();

var _t2 = new WeakMap();

附錄 B 嚴格模式

變量

嚴格模式下不容許:

  1. 意外建立全局變量
  2. 對變量使用delete操做符
  3. 不能使用保留字做爲變量名

對象

嚴格模式下錯誤地操做對象更容易報錯(如,爲只讀屬性賦值等)。

函數

嚴格模式下不能使用arguments.callerarguments.callee

抑制this

非嚴格模式下使用函數的call()apply()方法時nullundefined會轉爲爲全局對象(這很是危險)。

嚴格模式下this就是制定的值

function C1(a){
    this.val1 = a;
}
C1.call(null, 123);// globalThis.val1 變爲 123

function C2(a){
    "use strict"
    this.val2 = a;
}
C.call(null, 456);// 報錯

附錄 C JavaScript 庫

通用庫

互聯網應用

動畫和特效

附錄 D JavaScript 工具

校驗器

模塊打包

語法轉換

單元測試

壓縮器

文檔生成器

(安全執行環境?)

ADsafe 和 Caja

相關文章
相關標籤/搜索