《JavaScript高級程序設計》讀書筆記

《JavaScript高級程序設計》讀書筆記

Javascript由如下三部分組成:css

  1. 核心(ECMAScript)html

  2. 文檔對象模型(DOM)前端

  3. 瀏覽器對象模型(BOM)node

ECMAScript組成部分:
語法、類型、語句、關鍵字、保留子、操做符、對象。 正則表達式

按 照慣例,外部 JavaScript 文件帶有.js 擴展名。但這個擴展名不是必需的,由於 瀏覽器不會檢查包含 JavaScript 的文件的擴展名。這樣一來,使用 JSP、PHP 或其餘 服務器端語言動態生成 JavaScript 代碼也就成爲了可能。可是,服務器一般仍是須要 看擴展名決定爲響應應用哪一種 MIME 類型。若是不使用.js 擴展名,請確保服務器能 返回正確的 MIME 類型。 編程

不管如何包含代碼,只要不存在 defer 和 async 屬性,瀏覽器都會按照<script>元素在頁面中出現的前後順序對它們依次進行解析。數組

ECMAScript 5 引入了嚴格模式(strict mode)的概念:"use strict」;嚴格模式下,JavaScript 的執行結果會有很大不一樣。瀏覽器

控制語句中使用代碼塊({...})——即便代碼塊中只有一條語句。安全

ECMAScript 中有 5 種簡單數據類型(也稱爲基本數據類型):
Undefined、Null、Boolean、Number 和 String。
還有 1 種複雜數據類型——Object,
Object 本質上是由一組無序的名值對組成的。
服務器

字面值 undefined 的主要目的是用於比較。

對於還沒有聲明過的變量,只能執行一項操做,即便用 typeof 操做符檢測其數據類型。

即使未初始化的變量會自動被賦予 undefined 值,但顯式地初始化變量依然是明智的選擇。

從邏輯角度來看,null 值表示一個空對象指針,而這也正是使用 typeof 操做符檢測 null 值時會返回"object」的緣由。

若是定義的變量準備在未來用於保存對象,那麼最好將該變量初始化爲 null 而不是其餘值。這樣 一來,只要直接檢查 null 值就能夠知道相應的變量是否已經保存了一個對象的引用。

實際上,undefined 值是派生自 null 值的:
alert(null == undefined);    //true
alert(null === undefined);    //false
相等操做符(==)出於比較的目的會轉換其操做數

只要意在保存對象的變量尚未真正保存對象,就應該明確地讓該變量保存 null 值。這樣作不只能夠 體現 null 做爲空對象指針的慣例,並且也有助於進一步區分 null 和 undefined。

八進制字面量在嚴格模式下是無效的,會致使支持的 JavaScript 引擎拋出錯誤。

在默認狀況下,ECMASctipt 會將那些小數點後面帶有 6 個零以上的浮點數值轉換爲以 e 表示法 表示的數值(例如,0.0000003 會被轉換成 3e7)。

浮點數值的最高精度是 17 位小數,但在進行算術計算時其精確度遠遠不如整數。例如,0.1 加 0.2 的結果不是 0.3,而是 0.30000000000000004。所以,永遠不要測試某個特定的浮點數值
關於浮點數值計算會產生舍入偏差的問題,有一點須要明確:這是使用基於 IEEE754 數值的浮點計算的通病,ECMAScript 並不是獨此一家;其餘使用相同數值格 式的語言也存在這個問題。

使用 isFinite()函數判斷一個數值是否是有窮的(是否是位於最小[-Infinity]和最大[Infinity]的數值之間)。

訪問 Number.NEGATIVE_INFINITYNumber.POSITIVE_INFINITY 也能夠 獲得負和正 Infinity 的值。
能夠想見,這兩個屬性中分別保存着-Infinity 和 Infinity。

NaN 自己有兩個非同尋常的特色。
首先,任何涉及 NaN 的操做(例如 NaN/10)都會返回 NaN,這 個特色在多步計算中有可能致使問題。
其次,NaN 與任何值都不相等,包括 NaN 自己。例如,下面的代 碼會返回 false:
alert(NaN == NaN); //false
針對 NaN 的這兩個特色,ECMAScript 定義了 isNaN()函數。

只有 0 除以 0 纔會返回 NaN,正數除以 0 返回 Infinity,負數除以 0 返回-Infinity。

盡 管有點兒難以想象,但 isNaN()確實也適用於對象。在基於對象調用 isNaN() 函數時,會首先調用對象的 valueOf()方法,而後肯定該方法返回的值是否能夠轉 換爲數值。若是不能,則基於這個返回值再調用 toString()方法,再測試返回值。 而這個過程也是 ECMAScript 中內置函數和操做符的通常執行流程。

有 3 個函數能夠把非數值轉換爲數值:Number()、parseInt()和 parseFloat()。
一元加操做符的操做與 Number()函數相同。

因爲 parseFloat()只解析十進制值,所以它沒有用第二個參數指定基 數的用法。最後還要注意一點:若是字符串包含的是一個可解析爲整數的數(沒有小數點,或者小數點後 都是零),parseFloat()會返回整數。

var lang = "Java」;
lang = lang + "Script";

變 量 lang 開始時包含字符串"Java"。而第二行代碼把 lang 的值從新定義爲"Java" 與"Script"的組合,即"JavaScript"。實現這個操做的過程以下:首先建立一個能容納 10 個字符的 新字符串,而後在這個字符串中填充"Java"和"Script",最後一步是銷燬原來的字符串"Java"和字符串"Script",由於這 兩個字符串已經沒用了。這個過程是在後臺發生的,而這也是在某些舊版本的瀏覽器(例如版本低於 1.0 的 Firefox、IE6 等)中拼接字符串時速度很慢的緣由所在。但這些瀏覽器後 來的版本已經解決了這個低效率問題。

要把一個值轉換爲一個字符串有兩種方式。第一種是使用幾乎每一個值都有的 toString()方法。數值、布爾值、對象和字符串值(沒錯,每一個字符串也都有一個 toString()方法,該方法返回字符串的一個副本)都有 toString()方法。
null 和 undefined 值沒有這個方法,由於它們沒有對應的包裝器類,從而沒有屬性和方法。

使用轉型函數 String(),這個 函數可以將任何類型的值轉換爲字符串。String()函數遵循下列轉換規則:

  1. 若是值有 toString()方法,則調用該方法(沒有參數)並返回相應的結果;

  2. 若是值是 null,則返回"null」;

  3. 若是值是 undefined,則返回"undefined"。

ECMAScript 中的對象其實就是一組數據和功能的集合
對象能夠經過執行 new 操做符後跟要建立 的對象類型的名稱來建立。

在 ECMAScript 中,若是不給構造函數傳遞參數,則可 以省略後面的那一對圓括號。

var o = new Object; // 有效,但不推薦省略圓括號

Object 的每一個實例都具備下列屬性和方法:

  1. constructor:保存着用於建立當前對象的函數。

  2. hasOwnProperty(propertyName):用於檢查給定的屬性在當前對象實例中(而不是在實例的原型中)是否存在。其中,做爲參數的屬性名(propertyName)必須以字符串形式指定。

  3. isPrototypeOf(object):用於檢查傳入的對象是不是傳入對象的原型。

  4.  propertyIsEnumerable(propertyName):用於檢查給定的屬性是否可以使用 for-in 語句。

  5.  toLocaleString():返回對象的字符串表示,該字符串與執行環境的地區對應。

  6.  toString():返回對象的字符串表示。

  7. valueOf():返回對象的字符串、數值或布爾值表示。一般與 toString()方法的返回值相同。

因爲在 ECMAScript 中 Object 是全部對象的基礎,所以全部對象都具備這些基本的屬性和方法。

在對非數值應用一元加操做符時,該操做符會像 Number()轉型函數同樣對這個值執行轉換。
一元減操做符遵循與一元加操做符相同的規則,最後再將獲得的數值轉換爲負數。

ECMAScript 中的全部數 值都以 IEEE-754 64 位格式存儲,但位操做符並不直接操做 64 位的值。而是先將 64 位的值轉換成 32 位 的整數,而後執行操做,最後再將結果轉換回 64 位。

計算一個數值的二進制補碼,須要通過下列 3 個步驟:

  1. 求這個數值絕對值的二進制碼(例如,要求-18 的二進制補碼,先求 18 的二進制碼);

  2. 求二進制反碼,即將 0 替換爲 1,將 1 替換爲 0;

  3. 獲得的二進制反碼加 1。

若是對非數值應用位操做符,會先使用 Number()函數將該值轉換爲一個數值(自動完成),而後 再應用位操做。獲得的結果將是一個數值。

按位非操做的本質:操做數的負值減 1。
例如:~25 == -25-1。
因爲按位非是在數值表示的最底層執行操做,所以速度更快。

注意,左移不會影響操做數的符號位。

有符號的右移操做符由兩個大於號(>>)表示,這個操做符會將數值向右移動,但保留符號位(即正負號標記)。
無符號右移操做符由 3 個大於號(>>>)表示,這個操做符會將數值的全部 32 位都向右移動。對正 數來講,無符號右移的結果與有符號右移相同。可是對負數來講,狀況就不同了。首先,無符號右移是以 0 來填充空位,而不是像有符號右移那 樣以符號位的值來填充空位。

邏輯非操做符也能夠用於將一個值轉換爲與其對應的布爾值。
使用兩個邏輯非操做符,實際 上就會模擬 Boolean()轉型函數的行爲
其中,第一個邏輯非操做會基於不管什麼操做數返回一個布爾值,而第二個邏輯非操做則對該布爾值求反,因而就獲得了這個值真正對應的布爾值。
alert(!!"blue」);  //true
alert(!!NaN); //false

不能在邏輯與操做中使用未定義的值。

利用邏輯或的短路特性來避免爲變量賦 null 或 undefined 值。
例如:var myObject = preferredObject || backupObject;

兩組操做符:相等和不相等——先轉換再比較,全等和不全等——僅比較而不轉換

因爲相等和不相等操做符存在類型轉換問題,而爲了保持代碼中數據類型的完整性,咱們推薦使用全等和不全等操做符。

for-in 語句是一種精準的迭代語句,能夠用來枚舉對象的屬性(非原生屬性)。

ECMAScript 對象的屬性沒有順序。所以,經過 for-in 循環輸出的屬性名的順序是不可預測的。 具體來說,全部屬性都會被返回一次,但返回的前後次序可能會因瀏覽器而異。在使用 for-in 循環以前,先檢測確認該對象的值不是 null 或 undefined。

 

with(location){

    var qs = search.substring(1);

    var hostName = hostname;

    var url = href;

} 在 with 語句的代碼塊 內部,每一個變量首先被認爲是一個局部變量,而若是在局部環境中找不到該變量的定義,就會查詢 location 對象中是否有同名的屬性。若是發現了同名屬性,則以 location 對象屬性的值做爲變量的值。因爲大量使用 with 語句會致使性能降低,同時也會給調試代碼形成困難,所以在開發大型應用程序時,不建議使用 with 語句。

switch 語句中使用任何數據類型(在不少其餘語言中只能使用數值),不管是字符串,仍是對象都沒有 問題。其次,每一個 case 的值不必定是常量,能夠是變量,甚至是表達式

switch 語句在比較值時使用的是全等操做符,所以不會發生類型轉換(例如,字符串"10"不等於數值 10)。

arguments 的值永遠與對應命名參數的值保持同步。

function doAdd(num1, num2) {

    arguments[1] = 10;

    alert(arguments[0] + num2);

}
每 次執行這個 doAdd()函數都會重寫第二個參數,將第二個參數的值修改成 10。由於 arguments 對象中的值會自動反映到對應的命名參數,因此修改 arguments[1],也就修改了 num2,結果它們的 值都會變成 10。不過,這並非說讀取這兩個值會訪問相同的內存空間;它們的內存空間是獨立的,但它們的值會同步。另外還要記住,若是隻傳入了一個參數,那麼爲 arguments[1]設置的值不會反應到命名參數中。這是由於 arguments 對象的長度是由傳入的參數個數決定的,不是由定義函數時的命名 參數的個數決定的。

嚴 格模式對如何使用 arguments 對象作出了一些限制。首先,像前面例子中那樣的賦值會變得無效。也就是說,即便把 arguments[1]設置爲 10,num2 的值仍然仍是 undefined。其次,重寫 arguments 的值會致使語法錯誤(代碼將不會執行)。

ECMAScript 變量可能包含兩種不一樣數據類型的值: 基本類型值引用類型值
基本類型值指的是 簡單的數據段,而引用類型值指那些可能由多個值構成的對象。

若是從一個變量向另外一個變量複製基本類型的值,會在變量對象上建立一個新值,而後把該值複製 到爲新變量分配的位置上。
當從一個變量向另外一個變量複製引用類型的值時,一樣也會將存儲在變量對象中的值複製一份放到 爲新變量分配的空間中。不一樣的是,這個值的副本其實是一個指針,而這個指針指向存儲在堆中的一 個對象。複製操做結束後,兩個變量實際上將引用同一個對象。所以,改變其中一個變量,就會影響另 一個變量。

ECMAScript 中全部函數的參數都是按值傳遞。也就是說,把函數外部的值複製給函數內部的參 數,就和把值從一個變量複製到另外一個變量同樣。基本類型值的傳遞如同基本類型變量的複製同樣,而 引用類型值的傳遞,則如同引用類型變量的複製同樣。有很多開發人員在這一點上可能會感到困惑,因 爲訪問變量有按值和按引用兩種方式,而參數只能按值傳遞。在向參數傳遞基本類型的值時,被傳遞的值會被複制給一個局部變量(即命名參數,或者用 ECMAScript 的概念來講,就是 arguments 對象中的一個元素)。在向參數傳遞引用類型的值時,會把 這個值在內存中的地址複製給一個局部變量,所以這個局部變量的變化會反映在函數的外部。

function setName(obj) { 
    obj.name = "Nicholas"; 
    obj = new Object(); 
    obj.name = "Greg";

}

var person = new Object();

setName(person);

alert(person.name);    //"Nicholas"

如 果 person 是按引用傳遞的,那麼 person 就會自動被修改成指向其 name 屬性值 爲"Greg"的新對象。可是,當接下來再訪問 person.name 時,顯示的值仍然是"Nicholas"。這說明 即便在函數內部修改了參數的值,但原始的引用仍然保持未變。實際上,當在函數內部重寫 obj 時,這 個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後當即被銷燬。

 

能夠把 ECMAScript 函數的參數想象成局部變量

 

肯定一個值是哪一種基本類型可使用 typeof 操做符,而肯定一個值是哪一種引用類型可使用 instanceof 操做符。

 

JavaScript 沒有塊級做用域

 

垃圾收集機制的原理其實很簡單:找出那些再也不繼續使用的變 量,而後釋放其佔用的內存。爲此,垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間), 週期性地執行這一操做。

兩個策略:

  1. JavaScript 中最經常使用的垃圾收集方式是標記清除(mark-and-sweep)。主流瀏覽器都是標記清除式的 垃圾收集策略(或相似的策略),只不過垃圾收集的時間間隔互有不一樣。

  2. 另外一種不太常見的垃圾收集策略叫作引用計數(reference counting)。例如Objective-C。

將變量設置爲 null 意味着切斷變量與它此前引用的值之間的鏈接。

分 配給 Web 瀏覽器的可用內存數量一般要比分配給桌面應用程序的少。這樣作的目的主要是出於安全方面的考慮, 目的是防止運行 JavaScript 的網頁耗盡所有系統內存而致使系統崩潰。內存限制問題不只會影響給變量 分配內存,同時還會影響調用棧以及在一個線程中可以同時執行的語句數量。

一旦數據再也不有用,最好經過將其值設置爲 null 來釋放其引用——這個作法叫作解除引用(dereferencing)。不過,解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。

 全部變量(包括基本類型和引用類型)都存在於一個執行環境(也稱爲做用域)當中,這個執行環境決定了變量的生命週期,以及哪一部分代碼能夠訪問其中的變量。如下是關於執行環境的幾 點總結:

  1. 執行環境有全局執行環境(也稱爲全局環境)和函數執行環境之分;

  2. 每次進入一個新執行環境,都會建立一個用於搜索變量和函數的做用域鏈;

  3. 函數的局部環境不只有權訪問函數做用域中的變量,並且有權訪問其包含(父)環境,乃至全局環境;

  4. 全局環境只能訪問在全局環境中定義的變量和函數,而不能直接訪問局部環境中的任何數據;

  5. 變量的執行環境有助於肯定應該什麼時候釋放內存。

 

對象字面量也是向函數傳遞大量可選參數的首選方式。

 

JavaScript訪問對象屬性:

  1. 點表示法;

  2. 方括號法。主要優勢是能夠經過變量來訪問屬性,

    例如:var propertyName = "name";alert(person[propertyName]); //"Nicholas"

    若是屬性名中包含會致使語法錯誤的字符,或者屬性名使用的是關鍵字或保留字,也可使用方括 號表示法。一般,除非必須使用變量來訪問屬性,不然咱們建議使用點表示法。

 

在使用 Array 構造函數時也能夠省略 new 操做符。

var colors = Array(3); // 建立一個包含 3 項的數組

與對象同樣,在使用數組字面量表示法時,也不會調用 Array 構造函數。

 

數組的 length 屬性頗有特色——它不是隻讀的。
所以,經過設置這個屬性,能夠從數組的末尾移除項或向數組中添加新項。請看下面的例子:

var colors = ["red", "blue", "green"]; // 建立一個包含 3 個字符串的數組

colors.length = 2;

alert(colors[2]); //undefined

若是將其 length 屬性設置爲大於數組 項數的值,則新增的每一項都會取得 undefined 值,以下所示:

colors.length = 4;

alert(colors[3]); //undefined

利用 length 屬性也能夠方便地在數組末尾添加新項,以下所示:

var colors = ["red", "blue", "green"];// 建立一個包含 3 個字符串的數組

colors[colors.length] = "black」;  //(在位置3)添加一種顏色

colors[colors.length] = "brown」; //(在位置4)再添加一種顏色

 

sort()方法會調用每一個數組項的 toString()轉型方法,而後比較獲得的字符串,以 肯定如何排序。即便數組中的每一項都是數值,sort()方法比較的也是字符串。

var values = [0, 1, 5, 10, 15];

values.sort();

alert(values);     //0,1,10,15,5

 

正則表達式中的元字符包括: 11 ( [ { \ ^ $ | ) ? * + .]}

這些元字符在正則表達式中都有一或多種特殊用途,所以若是想要匹配字符串中包含的這些字符,

就必須對它們進行轉義

 

因爲函數是對象,所以函數名實際上也是一個指向函數對象的指針,不會與某個函數綁定。

 

最後一種定義函數的方式是使用 Function 構造函數。Function 構造函數能夠接收任意數量的參數, 但最後一個參數始終都被當作是函數體,而前面的參數則枚舉出了新函數的參數。

var sum = new Function("num1", "num2", "return num1 + num2"); // 不推薦

函數名想象爲指針,也有助於理解爲何 ECMAScript 中沒有函數重載的概念。

兩個同名函數,而結果則是後面的函數覆蓋了前面的函數。

 

解析器在向執行環境中加載數據時,對函數聲明函數表達式並不是一視同仁。
解析器會率先讀取函數聲明,並使其在執行任何代碼以前可用(能夠訪問);
至於函數表達式,則必須等到解析器執行到它所在的代碼行,纔會真正被解釋執行。

在函數內部,有兩個特殊的對象:arguments 和 this

雖然 arguments 的主要用途是保存函數參數, 但這個對象還有一個名叫 callee 的屬性,該屬性是一個指針,指向擁有這個 arguments 對象的函數。

function factorial(num){

    if (num <=1) {

        return 1;

    } else {

        return num * arguments.callee(num-1)

} }

 

this 引用的是函數據以執行的環境對象——或者也能夠說是 this 值(當在網頁的全局做用域中調用函數時, this 對象引用的就是 window)。

 

必定要牢記,函數的名字僅僅是一個包含指針的變量而已。所以,即便是在不一樣的環境中執行,全局的 sayColor()函數與 o.sayColor()指向的仍然是同一 個函數。

 

ECMAScript 5 也規範化了另外一個函數對象的屬性:caller。這個屬性中保存着調用當前函數的函數的引用, 若是是在全局做用域中調用當前函數,它的值爲 null。

每一個函數都包含兩個屬性:length 和 prototype
其中,length 屬性表示函數但願接收的命名參數的個數, 

每一個函數都包含兩個非繼承而來的方法:apply()和 call()

這兩個方法的用途都是在特定的做用域中調用函數,實際上等於設置函數體內 this 對象的值。

首先,apply()方法接收兩個參數:
一個是在其中運行函數的做用域,另外一個是參數數組。
其中,第二個參數能夠是 Array 的實例,也能夠是 arguments 對象。

 

call()方法與 apply()方法的做用相同,它們的區別僅在於接收參數的方式不一樣。對於 call() 方法而言,第一個參數是 this 值沒有變化,變化的是其他參數都直接傳遞給函數。換句話說,在使用 call()方法時,傳遞給函數的參數必須逐個列舉出來。

 

傳遞參數並不是 apply()和 call()真正的用武之地;

它們真正強大的地方是可以擴充函數賴以運行的做用域

 

使用 call()(或 apply())來擴充做用域的最大好處,就是對象不須要與方法有任何耦合關係

每一個函數繼承的 toLocaleString()和 toString()方法始終都返回函數的代碼。返回代碼的格式則因瀏覽器而異。

 

每當讀取一個基本類型值的時候,後臺就會建立一個對應的基本包裝類型的對象,從而讓咱們可以調用一些方法來操做這些數據。

引用類型與基本包裝類型的主要區別就是對象的生存期。使用 new 操做符建立的引用類型的實例, 在執行流離開當前做用域以前都一直保存在內存中。而自動建立的基本包裝類型的對象,則只存在於一 行代碼的執行瞬間,而後當即被銷燬。這意味着咱們不能在運行時爲基本類型值添加屬性和方法。來看 下面的例子:

    var s1 = "some text";

    s1.color = "red";

    alert(s1.color);   //undefined

 

使用 new 調用基本包裝類型的構造函數,與直接調用同名的轉型函數是不同的。

var value = "25";

var number = Number(value); //轉型函數 

alert(typeof number); //「number"

 

var obj = new Number(value); //構造函數

alert(typeof obj); //"object"

 

基 本類型與引用類型的布爾值還有兩個區別。首先,typeof 操做符對基本類型返回"boolean", 而對引用類型返回"object"。其次,因爲 Boolean 對象是 Boolean 類型的實例,因此使用 instanceof 操做符測試 Boolean 對象會返回 true,而測試基本類型的布爾值則返回 false。

 

ECMAScript 提供了三個基於子字符串建立新字符串的方法:slice()、substr()和 substring()。

 

encodeURI()主要用於整個 URI,而 encodeURIComponent()主要用於對 URI 中的某一段。

它們的主要區別在於,encodeURI()不會對自己屬於 URI 的特殊字符進行編碼,例如冒號、正斜槓、 問號和井字號;而 encodeURIComponent()則會對它發現的任何非標準字符進行編碼。

通常來講,咱們使用 encodeURIComponent()方法的時候要比使用 encodeURI()更多,由於在實踐中更常見的是對查詢字符串參數而不是對基礎 URI 進行編碼。

與 encodeURI()和 encodeURIComponent()方法對應的兩個方法分別是 decodeURI()和 decodeURIComponent()。其中,decodeURI()只能對使用 encodeURI()替換的字符進行解碼。

decodeURIComponent()可以解碼使用 encodeURIComponent()編碼的全部字符,即它能夠解碼任何特殊字符的編碼。

 

在 eval()中建立的任何變量或函數都不會被提高,由於在解析代碼的時候,它們被包含在一個字 符串中;它們只在 eval()執行的時候建立。

 

可以解釋代碼字符串的能力很是強大,但也很是危險。所以在使用 eval()時必 須極爲謹慎,特別是在用它執行用戶輸入數據的狀況下。不然,可能會有惡意用戶輸 入威脅你的站點或應用程序安全的代碼(即所謂的代碼注入)。

 

舍入爲整數的幾個方法:Math.ceil()、Math.floor()和 Math.round()。 這三個方法分別遵循下列舍入規則:

  1. Math.ceil()執行向上舍入,即它老是將數值向上舍入爲最接近的整數;

  2. Math.floor()執行向下舍入,即它老是將數值向下舍入爲最接近的整數;

  3. Math.round()執行標準舍入,即它老是將數值四捨五入爲最接近的整數。

 利用 Math.random() 從某個整數範圍內隨機選擇一個值。
值 = Math.floor(Math.random() * 可能值的總數 + 第一個可能的值)
舉例來講,若是你想選擇一個 1 到 10 之間的數值,能夠像下面這樣編寫代碼:
var num = Math.floor(Math.random() * 10 + 1);
或 num = Math.random() * 10 + 1|0;

ECMA-262 把對象定義爲:「無序屬性的集合,其屬性能夠包含基本值、對象或者函數。」咱們能夠把 ECMAScript 的對象想象成散列表:無非就是一組名值對,其中值能夠是數據或函數。

咱們建立的每一個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象, 而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。

使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。換句話說,沒必要在構造函數中定義對象實例的信息,而是 能夠將這些信息直接添加到原型對象中。

 不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個 prototype 屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個 constructor (構造函數)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指針。

當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部 屬性),指向構造函數的原型對象。ECMA-262 第 5 版中管這個指針叫[[Prototype]]。雖然在腳本中 沒有標準的方式訪問[[Prototype]],但 Firefox、Safari 和 Chrome 在每一個對象上都支持一個屬性 __proto__;而在其餘實現中,這個屬性對腳本則是徹底不可見的。不過,要明確的真正重要的一點就 是,這個鏈接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間。

使用 delete 操做符則能夠徹底刪除實例屬性,從而讓咱們可以從新訪問原型中的屬性。

使用 hasOwnProperty()方法能夠檢測一個屬性是存在於實例中,仍是存在於原型中。

有兩種方式使用 in 操做符單獨使用和在 for-in 循環中使用。
在單獨使用時,in 操做符會在經過對象可以訪問給定屬性時返回 true,不管該屬性存在於實例中仍是原型中。

要取得對象上全部可枚舉的實例屬性,可使用 ECMAScript 5 的 Object.keys()方法。這個方法接收一個對象做爲參數,返回一個包含全部可枚舉屬性的字符串數組。

當使用字面量來寫原型對象時constructor 屬性默認指向Object,因此要重置。

調用構造函數時會爲實例添加一個指向最初原型的 [[Prototype]]指針,
把原型修改成另一個對象就等於切斷了構造函數與最初原型之間的聯繫。
請記住:實例中的指針僅指向原型,而不指向構造函數。

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

構造函數、原型和實例的關係:
每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。

無論怎樣,給原型添加方法的代碼必定要放在替換原型的語句以後。

在經過原型鏈實現繼承時,不能使用對象字面量建立原型方法。由於這樣作就會重寫原型鏈。

組合繼承:

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

 

寄生組合式繼承,即 經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。其背 後的基本思路是:沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們所須要的無 非就是超類型 原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型 的原型。

寄生組合式繼承的基本模式以下所示。

function inheritPrototype(subType, superType){

    var prototype = object(superType.prototype);//建立對象

    prototype.constructor = subType;//加強對象

    subType.prototype = prototype;//指定對象

}

YUI 的 YAHOO.lang.extend()方法採用了寄生組合繼承,從而讓這種模式首次 出如今了一個應用很是普遍的 JavaScript 庫中。

 

關於函數聲明,它的一個重要特徵就是函數聲明提高(function declaration hoisting),意思是在執行代碼以前會先讀取函數聲明。這就意味着能夠把函數聲明放在調用它的語句後面。

 

在編寫遞歸函數時,使用 arguments.callee 總比使用函數名更保險。

但在嚴格模式下,不能經過腳本訪問 arguments.callee,訪問這個屬性會致使錯誤。不過,可 以使用命名函數表達式來達成相同的結果。例如:

var factorial = (function f(num){

        if (num <= 1){

            return 1;

        } else {

            return num * f(num-1);

} });

以上代碼建立了一個名爲 f()的命名函數表達式,而後將它賦值給變量 factorial。即使把函數 賦值給了另外一個變量,函數的名字 f 仍然有效,因此遞歸調用照樣能正確完成。這種方式在嚴格模式和 非嚴格模式下都行得通。

當 某個函數被調用時,會建立一個執行環境(execution context)及相應的做用域鏈。 而後,使用 arguments 和其餘命名參數的值來初始化函數的活動對象(activation object)。但在做用域 鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位,......直至做爲做用域鏈終點的全 局執行環境。

 

因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。
過分使用閉包可能會致使內存佔用過多,咱們建議讀者只在絕對必要時再考慮使用閉 包。
雖然像 V8 等優化後的 JavaScript 引擎會嘗試回收被閉包占用的內存,但請你們 仍是要慎重使用閉包。

 

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

 

閉包只能取得包含函數中任何變量的最 後一個值。別忘了閉包所保存的是整個變量對象,而不是某個特殊的變量。咱們能夠經過建立另外一個匿名函數強制讓閉包的行爲符合預期:

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 和 arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。不過,把外部做用域中的 this 對象保存在一個閉包可以訪問 到的變量裏,就可讓閉包訪問該對象了。

 

用做塊級做用域(一般稱爲私有做用域)的匿名函數的語法以下所示。

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

})();

函數表達式的後面能夠跟圓括號。

這種作法能夠減小閉包占用的內存問題,由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈了。

 

任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數的外部訪問這些變量。 私有變量包括函數的參數、局部變量和在函數內部定義的其餘函數。

 

在 JavaScript 編程中,函數表達式是一種很是有用的技術。使用函數表達式能夠無須對函數命名, 從而實現動態編程。匿名函數,也稱爲拉姆達函數,是一種使用 JavaScript 函數的強大方式。

 

在後臺執行環境中,閉包的做用域鏈包含着它本身的做用域包含函數的做用域全局做用域

當函數返回了一個閉包時,這個函數的做用域將會一直在內存中保存到閉包不存在爲止。

 

建立並當即調用一個函數,這樣既能夠執行其中的代碼,又不會在內存中留下對該函數的引用。

 

先設計最通用的方案,而後再使用特定於瀏覽器的技術加強該方案。

 

通常應優先考慮使用能力檢測怪癖檢測是肯定應該如何處理 代碼的第二選擇。而用戶代理檢測則是客戶端檢測的最後一種方案,由於這種方法對用戶代理字符串具 有很強的依賴性。

 

對 arguments 對象使用 Array.prototype.slice()方法能夠 將其轉換爲數組
而採用一樣的方法,也能夠將 NodeList 對象轉換爲數組。

function convertToArray(nodes){

    var array = null;

try {

        array = Array.prototype.slice.call(nodes, 0); //針對非 IE 瀏覽器
 } catch (ex) {

            array = new Array();

            for (var i=0, len=nodes.length; i < len; i++){

                array.push(nodes[i]);

            }

}

        return array;

}

 

Document 類型爲此提供了兩個方 法:getElementById()和 getElementsByTagName()。

第三個方法,也是隻有 HTMLDocument 類型纔有的方法,是 getElementsByName()。

 

document 對象還有一些特殊的集合。這些集合都是 HTMLCollection 對象, 爲訪問文檔經常使用的部分提供了快捷方式,包括:

  1. document.anchors,包含文檔中全部帶 name 特性的<a>元素;

  2. document.forms,包含文檔中全部的<form>元素,與 document.getElementsByTagName("form")獲得的結果相同;

  3. document.images,包含文檔中全部的<img>元素,與document.getElementsByTagName("img")獲得的結果相同;

  4. document.links,包含文檔中全部帶 href 特性的<a>元素。

 

var style = document.createElement("style"); style.type = "text/css";

try{

style.appendChild(document.createTextNode("body{background-color:red}"));

    } catch (ex){

        style.styleSheet.cssText = "body{background-color:red}";

}

    var head = document.getElementsByTagName("head")[0];

    head.appendChild(style);

使用了 try-catch 語句來捕獲 IE 拋出的錯誤,而後再 使用針對 IE 的特殊方式來設置樣式。

 

理 解 DOM 的關鍵,就是理解 DOM 對性能的影響。DOM 操做每每是 JavaScript 程序中開銷最大的 部分,而因訪問 NodeList 致使的問題爲最多。NodeList 對象都是「動態的」,這就意味着每次訪問 NodeList 對象,都會運行一次查詢。有鑑於此,最好的辦法就是儘可能減小 DOM 操做。

 

Selectors API Level 1 的核心是兩個方法:querySelector()和 querySelectorAll()。在兼容的瀏 覽器中,能夠經過 Document 及 Element 類型的實例調用它們。

 

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

配的元素,返回 null。

經過 Document 類型調用 querySelector()方法時,會在文檔元素的範圍內查找匹配的元素。而 經過 Element 類型調用 querySelector()方法時,只會在該元素後代元素的範圍內查找匹配的元素。

 

querySelectorAll()方法接收的參數與 querySelector()方法同樣,都是一個 CSS 選擇符,但 返回的是全部匹配的元素而不只僅是一個元素。這個方法返回的是一個 NodeList 的實例。

具體來講,返回的值其實是帶有全部屬性和方法的 NodeList,而其底層實現則相似於一組元素 的快照,而非不斷對文檔進行搜索的動態查詢。這樣實現能夠避免使用 NodeList 對象一般會引發的大多數性能問題。

 

要強制瀏覽器以某種模式渲染頁面,可使用 HTTP 頭部信息 X-UA-Compatible,或經過等價的 <meta>標籤來設置:

<meta http-equiv="X-UA-Compatible" content="IE=IEVersion」>

Edge:始終以最新的文檔模式來渲染頁面。忽略文檔類型聲明。對於 IE8,始終保持以 IE8 標 準模式渲染頁面。對於 IE9,則以 IE9 標準模式渲染頁面。

div.innerText = "Hello & welcome, <b>\"reader\"!</b>";

運行以上代碼以後,會獲得以下所示的結果。

<div id="content">Hello &amp; welcome, &lt;b&gt;&quot;reader&quot;!&lt;/b&gt;</div>

將 innerText 設置爲等於 innerText,這樣就能夠去掉全部 HTML 標籤,好比:

div.innerText = div.innerText;

執行這行代碼後,就用原來的文本內容替換了容器元素中的全部內容(包括子節點,於是也就去掉 了 HTML 標籤)。

 

function getInnerText(element){

    return (typeof element.textContent == "string") ?

        element.textContent : element.innerText;

}

function setInnerText(element, text){

    if (typeof element.textContent == "string"){

        element.textContent = text;

    } else {

        element.innerText = text;

    }

}

 

不管在哪一個瀏覽器中,最重要的一條是要記住全部計算的樣式都是隻讀的;不能修改計算後樣式對 象中的 CSS 屬性。此外,計算後的樣式也包含屬於瀏覽器內部樣式表的樣式信息,所以任何具備默認值 的 CSS 屬性都會表如今計算後的樣式中。

 

全部這些偏移量屬性都是隻讀的,並且每次訪問它們都須要從新計算。所以,應 該儘可能避免重複訪問這些屬性;若是須要重複使用其中某些屬性的值,能夠將它們保 存在局部變量中,以提升性能。

 

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

 

「DOM2 級事件」定義了兩個方法,用於處理指定和刪除事件處理程序的操做:

addEventListener() 和 removeEventListener()。

全部 DOM 節點中都包含這兩個方法,而且它們都接受 3 個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值參數若是是 true,表示在捕獲 階段調用事件處理程序;若是是 false,表示在冒泡階段調用事件處理程序。

其中第二個參數還能夠是對象: element.addEventListener(event, object [,capture] );
事件會自動在傳入對象中尋找handleEvent方法,也就是 object.handleEvent。
這樣,在 element 觸發event事件後,調用的是handleEvent 方法,
注意這裏面的 this 是指向對象自己, 而普通的函數,this傳入函數裏面的this 是指向事件的。


IE 實現了與 DOM 中相似的兩個方法:

attachEvent()和 detachEvent()。

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

 

在 IE 中使用 attachEvent()與使用 DOM0 級方法的主要區別在於事件處理程序的做用域。在使 用 DOM0 級方法的狀況下,事件處理程序會在其所屬元素的做用域內運行;在使用 attachEvent()方 法的狀況下,事件處理程序會在全局做用域中運行,所以 this 等於 window。

 

對「事件處理程序過多」問題的解決方案就是事件委託
事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。

另外,在不須要的時候移除事件處理程序,也是解決這個問題的一種方案。

 

最適合採用事件委託技術的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。 雖然 mouseover 和 mouseout 事件也冒泡,但要適當處理它們並不容易,並且常常須要計算元素的位置。

 

在使用事件時,須要考慮以下一些內存與性能方面的問題

  1. 有必要限制一個頁面中事件處理程序的數量,數量太多會致使佔用大量內存,並且也會讓用戶感受頁面反應不夠靈敏。

  2. 創建在事件冒泡機制之上的事件委託技術,能夠有效地減小事件處理程序的數量。

  3. 建議在瀏覽器卸載頁面以前移除頁面中的全部事件處理程序。

 

解決重複提交表單的辦法有兩個:

在第一次提交表單後就禁用提交按鈕,

或者利用 onsubmit 事件處理程序取消後續的 表單提交操做。

 

跨文檔消息傳送(cross-document messaging),有時候簡稱爲 XDM,指的是在來自不一樣域的頁面間傳遞消息

 

使用 try-catch 最適合處理那些咱們沒法控制的錯誤。

JSONP 是 JSON with padding(填充式 JSON 或參數式 JSON)的簡寫,是應用 JSON 的一種新方法, 在後來的 Web 服務中很是流行。JSONP 看起來與 JSON 差很少,只不過是被包含在函數調用中的 JSON, 就像下面這樣。

callback({ "name": "Nicholas" });

JSONP 由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調 函數的名字通常是在請求中指定的。而數據就是傳入回調函數中的 JSON 數據。

 

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

第一種就是在函數被調用時再處理函數。

第二種實現惰性載入的方式是在聲明函數時就指定適當的函數。

 

與函數綁定緊密相關的主題是函數柯里化(function currying),它用於建立已經設置好了一個或多 個參數的函數。函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。二者的區別 在於,當函數被調用時,返回的函數還須要設置一些傳入的參數。

 

JavaScript 中的柯里化函數和綁定函數提供了強大的動態函數建立功能。

 

數組分塊(array chunking)的技術,小塊小塊地處理數組,通 常每次一小塊。基本的思路是爲要處理的項目建立一個隊列,而後使用定時器取出下一個要處理的項目

進行處理,接着再設置另外一個定時器。

setTimeout(function(){

//取出下一個條目並處理

var item = array.shift(); process(item);

//若還有條目,再設置另外一個定時器 
if(array.length > 0){

        setTimeout(arguments.callee, 100);

    }

}, 100);

 

一旦某個函數須要花 50ms 以上的時間完成,那麼最好看看可否將任務分割爲一系列能夠使用定時器的小任務。

 

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

節流在 resize 事件中是最經常使用的。

 

function throttle(method, context) {

clearTimeout(method.tId);

method.tId= setTimeout(function(){

        method.call(context);

    }, 100);

}

 

只要應用的某個部分過度依賴於另外一部分,代碼就是耦合過緊,難於維護。
典型的問題如:對象直接引用另外一個對象,而且當修改其中一個的同時須要修改另一個。
緊密耦合的軟件難於維護而且須要常常重寫

  1. 解耦 HTML/JavaScript

  2. 解耦 CSS/JavaScript

  3. 解耦應用邏輯/事件處理程序

 

如下是要牢記的應用和業務邏輯之間鬆散耦合的幾條原則:

  1. 勿將 event 對象傳給其餘方法;只傳來自 event 對象中所需的數據;

  2. 任何能夠在應用層面的動做都應該能夠在不執行任何事件處理程序的狀況下進行;

  3. 任何事件處理程序都應該處理事件,而後將處理轉交給應用邏輯。

 

雖然命名空間會須要多寫一些代碼,可是對於可維護的目的而言是值得的。

 

顯示在用戶界面上的字符串應該以容許 進行語言國際化的方式抽取出來。URL 也應被抽取出來,由於它們有隨着應用成長而改變的傾向。基本 上,有着可能因爲這樣那樣緣由會變化的這些數據,那麼都會須要找到函數並在其中修改代碼 。而每次 修改應用邏輯的代碼,均可能會引入錯誤。能夠經過將數據抽取出來變成單獨定義的常量的方式,將應用邏輯與數據修改隔離開來。

 

關鍵在於將數據和使用它的邏輯進行分離。要注意的值的類型以下所示。

  1. 重複值——任何在多處用到的值都應抽取爲一個常量。這就限制了當一個值變了而另外一個沒變 的時候會形成的錯誤。這也包含了 CSS 類名。

  2. 用戶界面字符串 —— 任何用於顯示給用戶的字符串,都應被抽取出來以方便國際化。

  3. URLs —— 在 Web 應用中,資源位置很容易變動,因此推薦用一個公共地方存放全部的 URL。

  4. 任意可能會更改的值 —— 每當你在用到字面量值的時候,你都要問一下本身這個值在將來是否是會變化。若是答案是「是」,那麼這個值就應該被提取出來做爲一個常量。

 

性能:

注意做用域

  1. 避免全局查找:使用全局變量和函數確定要比局部的開銷更大,由於要涉及做用域鏈上的查找。將在一個函數中會用到屢次的全局對象存儲爲局部變量老是沒錯的。

  2. 避免 with 語句:會增長其中執行的代碼的做用域鏈的長度。

選擇正確方法

  1. 避免沒必要要的屬性查找:一旦屢次用到對象屬性,應該將其存儲在局部變量中。第一次訪問該值會是 O(n),然然後續的訪問 都會是 O(1),就會節省不少。

  2. 優化循環:減值迭代、簡化終止條件、簡化循環體、使用後測試循環。

  3. 展開循環:Duff 裝置技術(針對大數據集)

  4. 避免雙重解釋

  5. 性能的其餘注意事項:原生方法較快、Switch 語句較快、位運算符較快

最小化語句數

  1. 多個變量聲明

  2. 插入迭代值

  3.  使用數組和對象字面量

優化DOM交互

  1. 最小化現場更新

  2. 使用 innerHTML

  3. 使用事件代理

  4. 注意 HTMLCollection:記住,任什麼時候候要訪問 HTMLCollection,無論它是一個屬性仍是一個方法,都是在文檔上進 行一個查詢,這個查詢開銷很昂貴。

相關文章
相關標籤/搜索