javascript 學習筆記

1.0, 概述。JavaScript是ECMAScript的實現之一javascript

2.0,在HTML中使用JavaScript.php

2.1 css

3.0,基本概念html

3.1,ECMAScript中的一切(變量,函數名,操做符)都是區分大小寫的。前端

3.2,java

3.3,node

3.4,python

 

3.5,jquery

 

typeof 用於基本類型的判別,instanceof用於引用類型(Object類型)的判別。android

3.6,

3.7,

3.8,Boolean類型有兩個取值:true 和 false(區分大小寫).使用Boolean()函數以下:注意,除null對象外,其餘任何對象的布爾值都是true     n/a:not applicable

 

 Boolean(xxx) == !!xxx

3.9.0,數值類型

但16進制0x後面若超過了範圍,則會報錯。

 

3.9.1,

3.9.2,數值範圍:

      Number.MIN_VALUE      Number.MAX_VALUE

      Number.NEGTIVE_INFINITY  Number.POSITIVE_INFINITY

      isFinite() 

3.9.3,NaN (not a number)

    

 

isNaN(); //

3.13, 三個數值轉換函數。Number() / parseInt() / parseFloat()。轉型函數Number()能夠用於任何數據類型,而另兩個函數則專門用於把字符串轉換成數值。

  Number():

  parseInt(str[, radix]):  在ECMAScript 5 JavaScript引擎中,parseInt()函數已經不具有解析八進制的能力。parsetInt('070');//返回70,忽略前導0。

  parseFloat(str): 沒有第二個參數。始終忽略前導的0.

 3.14,String類型

 3.14.1,有任何區別。

3.14.2,轉義序列,用於表示非打印字符,

\x表示擴展的ascii碼錶字符,\u表示unicode字符。unicode字符是兼容ascii碼錶的。如'\u00ff'與'\xff'都是字符  ÿ。

3.14.3,轉化爲字符串方法,String()  /  obj.toString([radix])

  String()方法能夠轉換任何類型爲字符型。

  obj.toString()方法使用於除Undefined和Null以外的其餘四種類型(Number, String, Boolean,Object).其中若是是Number類型,此方法還能夠指定一個參數: 輸出數值的基數。

3.15,Object類型

【注】關於Object類型的兩種方法toString()和valueOf()。在牽涉到比較或計算時會優先使用valueOf()方法,若是沒有此方法纔會調用toString()方法。而在字符串鏈接時則會首先調用toString()方法。

3.15.1,ECMAScript中的 Object通用方法

3.16,操做符

3.16.1,一元操做符(++,--)

  前置的是先變化在計算,後置的是先計算在變化。如,

  var a=3;var b = ++a + 5;//先將a變化爲4再計算,結果爲b=9, a=4;

  var a=3;var b = a++ + 5;//先計算(用a的原值)再變化,結果爲b=8,a=4

 

 

3.16.2,關係操做符

  兩個字符串比較大小時,比較首字母(根據結果推斷的,不保證正確)的ascii碼值。"Brisk" < 'alpha' ;//true;  "23" < "3";//true

  字符串與數字比較大小時,先將字符串轉化爲數值在比較大小。「23」 < 3;//false

  NaN不等於任何數,包括它本身。NaN == NaN;//返回false;

  任何操做數與NaN進行大小比較時,結果都是false。 'a' < 3;//false;     'a'>=3;//false;    Number('a')=>NaN

  true和false在關係比較時,會先轉化爲1和0;所以,true==3;//false;  

  undefined和null在關係比較時不會轉化爲任何數。so, null==0;//false

  null == undefined; // true;  null===undefined;// false,類型不一樣;

 

[新增:]

除了null對象外,其餘對象的全部布爾值都爲true,例如空數組,空對象的布爾值都是true邏輯非操做符(!)遵循如下規則:

相等操做符(==)遵循如下規則:

例子:[] == true;//false. []是對象,調用valueOf()方法得不到基本類型,調用toString()方法獲得基本類型空字符串,true轉換爲數字0,而後空字符串轉換爲數字0.最會是比較 0 == 1,返回false

同理:[] == false;//true.

[9] == 8;//false. [9]轉換爲字符串9,在轉換爲數字9.

{name:'tong'} == true;//false

{name:'tong'} == false;//false,解釋以下:

對象{name:'tong'}調用toString()方法獲得「[object Object]", 而後這個字符串轉換爲數字NaN,而false轉換爲0,NaN == 0;//false

3.17;

,.

for (var i=1;i<10;i++){...}   alert(i) // 循環結束後,循環體外i的值爲10(儘管i是在循環體內定義的).

3,18;     相似goto語句的label語句:break和continue   與label一塊兒使用時,表示break和continue是對lable指定的地方生效。

3.19,javascript中獨具特點的switch語句:

PHP中switch語句在比較值時使用的是(==)而不是(===)

4.0變量,做用域和內存問題

4.1,javascript中的變量能夠參考python,二者能夠相同理解。JavaScript中將變量分爲基本類型(Undefined,Null,Number,String,Boolean)和引用類型(Object)【引用類型的變量是指向內存對象的一個指針】,相似於python中的不可變類型(數字,字符串,元組等)和可變類型(列表,字典等)。

參看本身總結的python變量和引用的關係。http://www.cnblogs.com/everest33Tong/p/6537583.html

再次審視時,發現二者貌似有區別:python中的對象(不管可變類型仍是不可變類型)貌似都是存儲在堆內存中的[或者理解爲,即便不可變類型是存儲在棧內存中,但其行爲和堆內存沒有區別,在棧內存中複製也是複製指針而不像js中是複製副本],變量均可視爲一個指向堆內存對象的一個指針,而JavaScript中基本類型是存儲在棧內存中的,引用類型是存儲在堆內存中的。棧內存複製值時是複製一個值的副本,而堆內存是複製一個指針!不是太肯定這些是否是正確

4.2,傳遞參數能夠看做變量的複製。也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製給另外一個變量同樣。

 

4.3,延長做用域鏈:

下面的一個例子的理解:with語句中定義的變量url是屬於buildUrl()函數執行環境的,參見4.5

4.4,JavaScript / php / python這三種腳本都沒有塊級做用域,都是函數做用域。

在其餘類C語言中,由花括號封閉的代碼塊都有本身的做用域(若是用ECMAScript的話來說,就是它們本身的執行環境),於是支持根據條件來定義變量。例如,下面的代碼在JavaScript中並不會獲得想要的結果:

 

<script>
    var a = 3; if (a == 3) { var color = 'red'; } console.log(color);//red
</script>

 

這裏是在一個if語句中定義了變量color.若是是在C,C++,或Java中,color會在if語句執行完畢後被銷燬。但在JavaScript中,if語句中的變量聲明會將變量添加到當前的執行環境(在這裏是全局環境)中。在使用for語句時尤爲要牢記這一差別,例如:

<script>
    for (var i = 0; i < 10; i++) { doSomething(i); } alert(i);//10
</script>

對於有塊級做用域的語言來講,for語句初始化變量的表達式所定義的變量,只會存在於循環的環境之中。而對於JavaScrip來講,由for建立的變量i即便在for循環結束以後,也依舊會存在於循環外部的執行環境中。

4.5,聲明變量

 4.6,垃圾收集 和 內存管理

經常使用的垃圾收集機制有兩種,

1,標記清除。JavaScript中最經常使用的垃圾收集方式是標記清除(mark-and-sweep).當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量做佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當環境離開環境時,則將其標記爲「離開環境」。能夠使用任何方式來標記變量。好比,能夠經過翻轉某個特殊的位來記錄一個變量什麼時候進入環境或者用一個「進入環境的」變量列表及一個「離開環境的」變量列表來跟蹤哪一個變量發生了變化。說到底,如何標記變量其實並不重要,關鍵在於採起什麼策略。垃圾收集器在運行的時候會給存儲在內存中的全部變量都加上標記(固然,能夠使用任何標記方式)。而後,他會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此以後在被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。最後,垃圾收集器完成內存清除工做,銷燬那些待標記的值並回收他們所佔用的內存空間。大部分的瀏覽器的JavaScript都是使用的此種方式垃圾收集策略。

2,引用計數。引用計數(reference counting)的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1.若是同一個值被賦給另外一個變量,則該值的引用次數加1.相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數減1.當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,於是就能夠將其佔用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數爲零的值所佔用的內存。這中垃圾回收機制有一個嚴重的問題:循環引用。一旦有循環引用存在則永遠不會回收這些變量。python腳本語言就是採用引用計數垃圾回收機制,python同時也會監視循環引用的變量,在合適的時機就會回收這些變量。對於循環引用問題,有一個手動解決的方法,就是在再也不使用它們的時候手工斷開它們之間的鏈接,即將它們的值設爲null。將變量設置爲null意味着切斷變量與此前引用的值之間的鏈接。當垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的內存。

內存管理:優化內存佔用的最佳方式,就是爲執行中的代碼只保存必要的數據。一旦數據再也不有用,最好經過將其值設置爲null來釋放其引用----這個作法叫作解除引用(dereferencing)。這一作法適用於大多數全局變量和全局對象的屬性。局部變量會在他們離開執行環境時自動被解除引用。以下面例子所示:

不過解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。 

4.7,小結

 

5.0,引用類型(Object,Array,Date, RegExp等等)

5.1, 幾個概念:var person = new Object();在此行代碼中,Object是引用類型,person是引用類型的值,也叫對象。JavaScript中提供了不少原生的引用類型,如Object,Array,Date, RegExp引用類型等等。若是使用typeof Object會發現這是個"function",其實Object也是個構造函數,該函數是出於建立新對象的目的而定義的。

Object.keys(obj)//取得js對象的鍵組成的數組,

Object.values(obj)//取得js對象的值組成的數組.

5.2,Array引用類型

      數組棧方法:array.push(xx)方法返回修改後數組的長度,array.pop()方法返回彈出的數組項。

      數組隊列方法:array.unshift(xx)返回修改後數組的長度,array.shift()返回彈出的數組項。

      轉爲字符串方法:array.join("separator"): 將數組鏈接成字符串。array.toString() == array.join(',')

      重排序方法:array.reverse()反序排列,返回通過排序後的數組。    


 array.sort(),返回通過排序後的數組。

            1,若是沒有參數,則元素按照ASCII字符順序排序,即會對數組每一個元素調用toString()方法。

              例如,var arr = [0,1,5,10,15];則arr.sort()返回[0,1,10,15,5].

            2,sort(func)還能夠接收一個比較函數,此函數接收兩個參數,這兩個參數表明數組中的任意兩個項。若是省略sortFunction參數,元素將按ASCII字符順序的升序進行排列。  關於sortFunction的說明:參考文章    http://www.cnblogs.com/longze/archive/2012/11/27/2791230.html

 

【新增例子】若是arr.sort(function(a,b){return 1})則等同於arr.reverse(),即直接倒序排列。 
例一:
  <script type="text/javascript"> var arrSimple2=new Array(1,8,7,6); arrSimple2.sort(function(a,b){ return b-a 或者 return a-b});//前者降序,後者升序 document.writeln(arrSimple2.join()); </script> 解釋:a,b表示數組中的任意兩個元素,若return > 0 b前a後;reutrn < 0 a前b後;return = 0時存在瀏覽器兼容. 簡化一下:a-b輸出從小到大排序,b-a輸出從大到小排序。【a-b時,若a-b>0,因爲return>0,b前a後,因爲a>b,因此是升序】

例二:
// 根據元素轉換爲字符串後的字符長度進行升序排列
function arraySortByLength(a, b){
    // 爲便於用戶理解,此處未考慮a或b爲undefined或null的狀況。
    var aStr = a.toString(), bStr = b.toString();
    // 若是是按照字符長度降序排序,則應該爲bStr.length - aStr.length
    return aStr.length - bStr.length;
}

操做方法:array.concat()方法不改變原數組。

       array.slice(start[, end])方法不改變原數組。返回截取後的數組片斷。[start,end)左閉右開.若start爲負,則start=length + start;或者理解爲最後一項爲-1,往前依次爲-2,-3...等等


array.splice()方法能夠用於 刪除,插入,替換數組中的項。原始數組會改變,返回的始終是被刪除的項組成的數組,若是沒有被刪除的項,則返回空數組具體用法以下

⊙刪除 :能夠刪除任意數量的項,只需指定兩個參數:要刪除的第一項的位置和要刪除的項數。例如,splice(0,2)會刪除數組中的前兩項。

⊙插入:能夠向指定位置插入任意數量的項,只需提供3個參數:起始位置、0(要刪除的項數)、和要插入的項。若是要插入多個項,能夠在傳入第4、第五,一直任意多個項。例如,splice(2,0,'red','green')會從當前數組的位置2開始插入字符串"red"和"green"。注意,原來位置2上的值會被擠到後面去。

⊙替換:能夠向指定位置插入任意數量的項,且同時刪除任意數量的項,只需指定3個參數:起始位置、要刪除的項數和要插入的任意數量的項。插入的項數沒必要與刪除的項數相等。例如,splice(2,1,'red','green')會刪除當前數組位置2的項,而後再從位置2開始插入字符串"red"和"green"。

 


位置方法: indexOf(needle[,start])      lastIndexOf(needle[,start]).

這兩個方法都接收兩個參數:要查找的項和(可選的)表示查找起點位置的索引。都返回要查找的項在數組中的位置,或者在沒有查找到的狀況下返回-1.在比較第一個參數與數組中的每一項時,會使用全等操做符===。

indexOf()當有第二個參數時,查找時從第二個參數指定的位置(包括)開始 從前日後 查詢,返回查找到的第一個needle所在整個數組(或字符串)中的位置.

lastIndexOf()當有第二個參數時,查找時從第二個參數指定的位置(包括)開始 從後往前 查詢,返回查找到的第一個needle所在整個數組或字符串中的位置.

 


迭代方法:以下 【every();filter();forEach();map();some()】


說明:

filter(func): 其中func是 用來測試數組的每一個元素的函數。調用時使用參數 (element, index, array)  此函數返回true則表示保留該元素(經過測試),false則不保留。

 

對於every()只要有一個值返回false就會中止進一步的動做,一樣,對於some()只要有一個值返回true則會中止進一步的動做。forEach()方法本質上和使用for循環迭代數組同樣。

例子: var arr = [1,3,4,5]; var every = arr.every(function(value, index, array){return value > 0});//every返回true.

 


縮小方法:array.reduce(callback[, first]); callback是回調函數,此回調函數接收四個參數(prev,cur,index,array),first可選,做爲縮小基礎的初始值。若是不存在first則數組的第一項做爲基礎值。index是cur的當前位置。函數的返回值會做爲第一個參數傳給下一項直至遞歸完畢。array.reduceRight()用法相同,只不過從後向前遍歷。

例子:var arr = [1,2,3,4,5]; var sum = arr.reduce(function(prev,cur,index,array){return prev+cur}, 10 );// 返回25;10+1+2+3+4+5=25;

 

5.3,Date引用類型

5.3.1, Date類型使用自UTC(Coordinated Universal Time,國際協調時間)1970年一月一日午夜零時開始通過的毫秒數來保存日期。可以精確到1970年一月一日午夜零時 以前或以後的285616年。

5.3.2,一些用法:

GMT:Greenwich Mean Time,格林威治標準時間。  本地時區GMT+0800(北京時間)

var date = new Date();// 不傳參數,新建立的對象自動獲取當前日期和時間。時間基於本地時區GMT+0800(北京時間)。

*********************************************************************************************************************************

*********************************************************************************************************************************

// 時間戳轉換爲 日期,這裏是轉換爲的日期對象,若是要轉化爲日期字符串,則再調用date.toString() 或其餘任意能夠 轉爲日期字符串的方法【5.3.4中介紹的方法均可以】

var date = new Date(3423);//傳入毫秒數參數,則根據此特定的日期和時間建立日期對象。基於本地時區。

// 日期字符串 轉換爲 時間戳

Date.parse('日期字符串');//根據日期字符串返回毫秒數。日期字符串格式要求比較寬鬆。若是不符合日期字符串要求則返回NaN。基於本地時區。

 

Date.now();//返回當前時間的毫秒數。

*********************************************************************************************************************************

*********************************************************************************************************************************

var date = new Date('日期字符串');//會自動調用Date.parse(),至關於new Date(Date.parse('日期字符串'));基於本地時區。

Date.UTC(year, month[,day,hour, minute, second,microsecond]);返回指定日期毫秒數。其中month以0表示一月份開始。日期和日期之後的參數都是可選。如,Date.UTC(2017,4,30,13,55,55);//GMT時間2017年5月30日下午1點55分55秒。注意:Date.UTC()這個時間是基於GMT的。也就是說,這個時間是北京時間30日下午9點55分55秒。

var date = new Date(2017,4,30,13,55,55);//雖然模仿的Date.UTC()格式,可是這個時間是以本地時區爲基準的。這個時間比Date.UTC(2017,4,30,13,55,55)表示的時間要早8個小時。

5.3.3

var date = new Date();//顯示 Thu Jun 01 2017 20:07:12 GMT+0800 (中國標準時間),雖然date顯示的相似字符串,但date是對象。

date.toString();//   "Thu Jun 01 2017 20:22:12 GMT+0800 (中國標準時間)",date對象的字符串表示。

date.toLocaleString();//  "2017/6/1 下午8:22:12"。

date.valueOf();// 1496319732948。此方法返回毫秒數,是個數字。【php的time()返回的數字精確到秒】

// 獲取當前毫秒級時間戳

date.valueOf() == Date.now() == +date; 

把+[注:這裏的+是一元操做符正號而非鏈接操做符加號]放在Date對象前時,等同於調用valueOf()方法。

如,var date = +new Date();//返回當前毫秒數1496317509583。

5.3.4,日期格式化方法:

date.toDateString();// "Thu Jun 01 2017"

date.toTimeString();//"20:22:12 GMT+0800 (中國標準時間)"

  //注:以上這兩個方法合起來就是date.toString()的值

=========================================

date.toLocaleDateString();// "2017/6/1"

date.toLocaleTimeString();// "下午8:22:12"

  //注:以上這兩個方法合起來就是date.toLocaleString()的值。

===========================================

date.toUTCString();//"Thu, 01 Jun 2017 12:22:12 GMT"。這是GMT時間,比北京時間早了8小時。

date.toGMTString();//  全等於date.toUTCString();目的在於向後兼容,推薦使用date.toUTCString()方法。

5.3.5,日期時間組件方法:以下圖

 

5.4,RegExp類型:正則表達式

5.4.1,JavaScript中正則表達式有兩種表達方式:字面量 和 RegExp構造函數[二者建立的都是對象]。

字面量:

var expression = /pattern/flags;  注意:這裏的expression也是Object類型

其中pattern部分能夠是任何簡單或複雜的正則表達式,flags標誌有如下三種:

※g:表示全局(global)模式,即模式將被應用於全部字符串,而非在發現第一個匹配項時當即中止。

※i:表示不區分大小寫(ignoreCase)模式

※m:表示多行(multiline)模式.即在達到一行文本末尾時還會繼續查找下一行中是否存在與模式匹配的項。

如var pattern = /.at/gi ;//匹配全部以‘at'結尾的3個字符的組合,不區分大小寫。

RegExp構造函數:

var pattern = new RegExp(".at",'gi');//等同於字面量/.at/gi.

注意點:RegExp構造函數的兩個參數都是以字符串形式傳進去的。因此若是涉及到須要轉義的字符則須要雙重轉義。如,

var pattern = new RegExp("\\.at", 'gi');// 等同於字面量/\.at/gi。能夠利用pattern.source查看轉化爲字面量時的值。更多例子:

regexp.toString();//   "/.at/g"   .不管regexp是字面量仍是構造函數,返回字面量的字符串形式。

regexp.toLocaleString();// 同上。

regexp.valueOf();//返回regexp自己。 regexp.valueOf() === regexp。【對象比較是比較其所在的內存地址是否相同】

5.4.2 RegExp實例屬性

RegExp的每一個實例都具備下列屬性:

※global: 布爾值,表示是否設置了g標誌。

※ignoreCase: 布爾值,表示是否設置了i標誌。

※lastIndex:整數,表示開始搜索下一個匹配項的字符位置,從0算起。

※source:正則表達式的字符串表示,按照字面量形式而非傳入構造函數中的字符串模式返回。

5.4.3,RegExp實例方法:

var matches = regexp.exec(input);

exec()接收一個參數,即要應用模式的字符串。

返回值:沒有匹配項的狀況下返回null,有匹配項的狀況下返回匹配項的數組,其中第一項是與整個模式匹配的字符串,其餘項是與模式種的捕獲組匹配的字符串。此數組有兩個額外屬性:index和input。index表示匹配項在字符串中的位置。而input表示應用正則表達式的字符串。

注意點:對於exec()方法而言,即便在模式中設置了全局標誌(g),它每次也只會返回一個匹配項。在不設置全局標誌的狀況下,在同一個字符串上屢次調用exec()將始終返回第一個匹配項的信息。而在設置全局標誌的狀況下,每次調用exec()則都會在字符串中繼續查找新匹配項。

var bool = regexp.test(input);

test()接收一個參數,即要應用模式的字符串。

返回值:參數與模式匹配返回true; 不然,返回false。

(下面論述對於不管是exec()方法仍是test()方法都是這樣的)在建立正則表達式對象時若是使用了「g」標識符或者設置它了的global屬性值爲ture時,那麼新建立的正則表達式對象將使用模式對要將要匹配的字符串進行全局匹配。在全局匹配模式下能夠對指定要查找的字符串執行屢次匹配。每次匹配使用當前正則對象的lastIndex屬性的值做爲在目標字符串中開始查找的起始位置。lastIndex屬性的初始值爲0,找到匹配的項後lastIndex的值被重置爲匹配內容的下一個字符在字符串中的位置索引,用來標識下次執行匹配時開始查找的位置,若是找不到匹配的項lastIndex的值會被設置爲0。當沒有設置正則對象的全局匹配標誌時lastIndex屬性的值始終爲0,每次執行匹配僅查找字符串中第一個匹配的項。能夠通下面的代碼來查看在執行匹配相應的lastIndex 屬性的值

5.4.4,RegExp構造函數屬性(相似靜態屬性):

長屬性名 短屬性名 說明
input $_ 最近一次要匹配的字符串
lastMatch $& 最近一次的匹配項[與整個正則表達式匹配],相似$0,但沒有這個表達
lastParen $+ 最近一次匹配的捕獲組
leftContext $` input字符串中lastMatch以前的文本
rightContext $' input字符串中lastMatch以後的文本
multiline $* 布爾值。表示是否全部表達式都使用多行模式
$1, $2,...$9 分別用於存儲第一,第二......第九個匹配的捕獲組

 

注意:段屬性名中除了$_, $1...$9是JavaScript中合法的標識符外,其餘的都是不合法的標識符。因此必須用方括號訪問。RegExp["$&"]

5.4.5,JavaScript的正則表達式的侷限性:

 

5.5,Function 類型:每一個函數都是Function類型的實例

5.5.1,函數也是對象。函數名(也是個變量)同其餘引用類型變量名同樣,是指向真正對象的指針。

5.5.2,函數有三種表達方式:

※函數聲明:

function sum(num1, num2){return num1+num2}

※函數表達式:

var sum = function(num1, num2){return num1+num2};

※Function構造函數:[不推薦]

var sum = new Function("num1","num2","return num1+num2");

-------------------------------------------------------------------------------

函數聲明和函數表達式只有一個區別,除此以外徹底等價。區別在於解釋器在向執行環境中加載數據時:

函數聲明會在代碼執行以前,經過一個名爲函數聲明提高(Function Declaration hoisting)的過程,讀取並將函數聲明添加到執行環境中。因此函數聲明老是在源代碼樹的頂部。即便在函數聲明以前調用函數也是沒問題的。

可是函數表達式只有在解釋器執行到此處時纔會加載函數表達式。因此在函數表達式以前調用函數會出錯。

5.5.3,做爲值的函數。

函數名自己就是變量,因此函數也能夠用來做爲值來使用(就像其餘變量同樣)。好比,能夠像傳遞參數同樣把一個函數傳給另外一個函數。又好比,能夠將一個函數做爲另外一個函數的結果返回。

5.5.4,函數內部屬性。

函數內部有兩個特殊對象:arguments 和 this。

※arguments:用來保存傳入函數中的全部參數,是個類數組對象(但它並非Array的實例)。它有一個屬性callee,該屬性是個指針,指向擁有這個arguments對象的函數(對象)。

        例子:function factorial(num){

              if (num <= 1){

                 return 1;

              } else {

                return num * arguments.callee(num -1); // 若是用num * facotorial(num-1),則就和函數名factorial高度耦合了。

              }

            }

-----------------------------------

※this:this對象引用的是函數據以執行的環境對象。當在網頁的全局做用域中調用函數時,this對象引用的就是window對象。

例子一:

var color = 'red';

function sayColor(){console.log(this);}

sayColor();//this指的是window對象。在全局做用域中調用函數sayColor至關於window.sayColor();天然this指的就是window對象。

例子二:

var o = {name:'tong'};

o.sayHi = function(){console.log(this);} 

o.sayHi();//this指對象o,即Object {name:'tong',sayHi: function}

-------------------------

※函數對象的屬性:caller。這個屬性保存着調用當前函數 的函數的引用。若是是在全局做用域中調用當前函數,則caller的值爲null。例如,

function outer(){inner();}

function inner(){console.log(inner.caller);}

outer();//會打印出function outer(){inner();},即outer函數的源碼。由於outer函數調用了inner函數,因此inner.caller屬性就指向outer函數。

5.5.5,函數(對象)屬性和方法

※length屬性:length屬性表示函數但願接收的命名參數的個數。   [函數名.length]

※prototype屬性:後面詳解,此屬性是不可枚舉的,沒法使用for-in遍歷。

----------------------------------------------------

※apply()方法:做用是指定函數的做用域,即在特定的做用域中調用函數,實際上等於設置函數體內this對象的值。apply()方法接收兩個可選參數:第一個是在其中運行函數的做用域(如需跳過此參數可設爲null,下同),第二個是參數數組,能夠是array數組,也能夠是arguments對象。

※call()方法:做用和apply()相同。不一樣之處在於接收參數的方式(也是兩個可選參數)。call()方法第一個參數是this值沒有變化,變化的是其他參數都直接傳給函數,即逐個列舉出來。

注意一點,在非嚴格模式下,使用函數的apply()或call()方法時,null或undefined值會被轉換爲全局對象。而在嚴格模式下就不會被轉換。

這兩個方法能夠擴充函數賴以運行的做用域。例如:

var color = 'red';

var o = {color:'blue'};

function sayColor(){console.log(this.color)}

sayColor(); // red

sayColor.call(this);//red

sayColor.call(window);//red

sayColor.call(o);//blue

----------------------------------------------

※bind(thisArg[,arg1,arg2,...])方法:這個方法返回一個改變了this值的[即函數的做用域]函數。第一個參數做爲this,第二個及之後的參數則做爲函數的參數調用。例子以下:

var color = 'red';

var o = {color:'blue'};

function sayColor(){console.log(this.color);}

var newSayColor = sayColor.bind(o);//在sayColor函數的基礎上建立一個新的函數,新函數的this值改變爲o,將此函數賦給變量newSayColor。

newSayColor();//調用新函數,打印blue。

其餘一些應用能夠參考:http://www.cnblogs.com/xxxxBW/p/4914567.html

着重提一下此篇文章中的一點:使用bind()方法使函數擁有預設的初始參數,這些參數會排在最前面,傳給綁定函數的參數會跟在它們後面。

--------------------------------------------------------

函數的繼承方法toString(),toLocaleString(),valueOf()方法都是始終返回函數的源代碼。

 

5.6,基本包裝類型(即3個特殊的引用類型:Boolean, Number, String)

5.6.1,基本類型的值不是對象,於是從邏輯上講它們不該該有方法,可是JavaScript爲基本類型建立了對應的基本包裝類型的對象。實際上,每當讀取一個基本類型的值的時候,後臺就會建立一個對應的基本包裝類型的對象,從而讓咱們可以調用一些方法來操做這些數據。例如:

var s1 = "some text";

var s2 = s1.substring(2);

第二行訪問s1時,訪問過程處於一種讀取模式。讀取模式時,後臺都會自動完成下列處理:

  ※建立String類型的一個實例;var s1 = new String('some text');

  ※在實例上調用指定的方法;   var s2 = s1.substring(2);

  ※銷燬這個實例。      s1 = null;

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

var s1 = 'some text';

s1.color = 'red';

console.log(s1.color); // undefined

第三行代碼會打印出undefined。緣由在於第二行建立的String對象在執行第三行代碼時已經被銷燬了。第三行代碼又建立本身的String對象,而該對象沒有color屬性。固然若是用var s1 = new String('some text')就不會出現這種狀況。注意,使用new調用基本包裝類型的構造函數與直接調用同名的轉型函數是不同的。如:

var value = '25';

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

console.log(typeof number); //"number";    基本類型值

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

console.log(typeof obj);// "object";             Number的實例對象。

5.6.3,Object構造函數會像工廠方法同樣,根據傳入值的類型返回相應基本包裝類型的實例。如:

var obj = new Object("some text"); 等同於 var obj = new String("some text");

5.6.4,基本包裝類型之 Boolean類型:

  ※除了null對象以外的任何對象在進行邏輯運算時都會轉化爲true。因此:

    var b = new Boolean(false);//建立了一個值爲false的Boolean對象,即b.valueOf() = false;

    b && true; // 返回true.  由於Boolean(b)是true.

  ※基本類型的布爾值不是Boolean類型的實例。即:

    true instanceof Boolean;//false

    b instance of Boolean; // true

5.6.5,基本包裝類型之Number類型:

  ※var num = new Number(10);

  num.toString(base); //將num轉化爲base進制的字符串形式。

  num.toFixed(小數位位數);//按照指定的小數位返回數值的字符串表示。可以自動舍入,適合處理貨幣

  num.toExponential(小數位位數);//返回以指數表示法表示的數字的字符串形式。參數指定輸出結果中的小數位數。如"1.00e+1"

  num.toPrecision(全部數字的位數,不包括指數部分);//此方法會根據要處理的數值決定是調用toFixed()仍是調用toExponential()。如,

    var num = 99;

    num.toPrecision(1); // "1e+2"

    num.toPrecision(2);// "99"

    num.toPrecison(3);// "99.0"

  ※Number對象是Number類型的實例,而基本類型的數值則不是。

    var numObj = new Number(10);

    var numVal = 10;

    numObj instanceof Number;//true

    numVal instanceof Number;//false

5.6.6,基本包裝類型之String類型:

  ※var str = new String('中國');

    str.length;//2

  ※字符方法:

    str.charAt(pos);//返回給定位置的字符

    str.charCodeAt(pos);//返回給定位置的字符編碼(unicode碼)

    str[pos];//數組索引法

  ※字符串操做方法:

    str.concat(任意多個參數);//用於將一個或多個字符串拼接起來。返回拼接獲得的新字符串。原字符串保持不變。【+號操做符鏈接字符串更經常使用】

    str.slice(start[, end]);//①返回子字符串[start, end).左閉右開。原字符串不變。②若參數爲負數,則規則爲:最後一位爲-1,往前爲-2,-3,....;③若是start > end,結果爲空。

    str.substring(start[, end]);//①返回子字符串[start, end).左閉右開。原字符串不變。②若參數爲負數,則會自動被轉化爲0;③若是start > end,則兩個參數位置調換一下。

    str.substr(start[, length]); //①返回子字符串,起始位置+長度。原字符串不變。②若參數爲負,第一個會按照最後一位爲-1的方式處理,第二個參數被轉換爲0。

  ※字符串位置方法:

    str.indexOf(needle[, 開始搜索位置]);//從前日後查找子字符串第一次出現的位置。若是沒找到則返回-1。若傳入第二個參數,則從指定位置(包括此位置)開始向後搜索而忽略以前的全部字符。

    str.lastIndexOf(needle[, 開始搜索位置]);//從後往前查找子字符串第一次出現的位置。若是沒找到則返回-1。若傳入第二個參數,則從指定位置(包括此位置)開始向前搜索而忽略以後的全部字符。

  ※trim()方法:

    str.trim();//無參數。返回刪除了 前置和後置全部空格 後的字符串。原字符串保持不變。

    str.trimLeft();

    str.trimRight();

  ※字符串大小寫轉化方法:

    str.toLowerCase();//無參數。

    str.toUpperCase();//無參數。

    str.toLocaleUpperCase();//針對地區

    str.toLocaleLowerCase();//針對地區

  ※字符串模式匹配方法:

    str.match(正則表達式對象,能夠是字面量形式或RegExp對象形式);//此方法和regexp.exec(text)方法本質上是同樣的。返回結果也同樣,返回一個數組,第一項爲與整個模式匹配的字符串,以後的每一項爲與正則表達式中的捕獲組匹配的字符串。注意:若是正則表達式帶有全局標誌g,則返回的數組只包含全部的整個模式匹配項,而不包括捕獲組。

    str.search(正則表達式對象,能夠是字面量形式或RegExp對象形式);//返回字符串中第一個匹配項的索引,若是沒有找到,返回-1。能夠視爲增強版的str.indexOf()方法。

    str.replace(arg1, arg2);//這個方法總的目標是把第一個參數指定的子字符串替換爲第二個參數指定的字符串。返回新的字符串,原字符串保持不變。用法比較複雜,詳述以下:

      ①,arg1能夠是字符串或RegExp對象。當此參數是RegExp對象而且帶有全局標誌g時,會替換全部的字符串。不然(其餘全部狀況)只會替換查到的第一個字符串。

      ②,arg2能夠是字符串或是一個函數。

          ※當arg2是字符串時,能夠用正則表達式中的一些表達,表格以下: 

 

字符序列 替換文本
$$ $
$& 匹配整個模式的子字符串。和RegExp.lastMatch值相同
$' 匹配的子字符串以後的子字符串。和RegExp.rightContext值相同
$` 匹配的子字符串以前的子字符串。和RegExp.leftContext值相同
$n 匹配第n個捕獲組的子字符串,n爲0-9.若是沒有捕獲,則使用空字符串
$nn 匹配第nn個捕獲組的子字符串,n爲01-99。若是沒有捕獲,則使用空字符串

            例如:

            var text = 「cat, bat, sat, fat」;
            result = text.replace(/(.at)/g, 「word ($1)」); //帶有全局標誌g,內部會使用循環。
            console.log(result); //word (cat), word (bat), word (sat), word (fat)

         ※arg2是函數時,這個函數應該返回一個字符串。此函數接收的參數個數與第一個參數中是否有捕獲組有關。若是沒有捕獲組,此函數接收3個參數:整個模式的匹配項,匹配項在字符串中           的位置,原始字符串。若是有捕獲組,則第一個參數是整個模式的匹配組,接下來的分別是各個捕獲組,最後兩個參數仍然是整個模式的匹配項在字符串中的位置和原始字符串。使用函數可          以實現更加精細的操做。

            例如:

    function htmlEscape(text) { return text.replace(/[<>"&]/g, function (match, pos, originalText) { switch (match) { case "<": return "&lt;"; case ">": return "&gt;"; case "&": return "&amp;"; case "\"": return "&quot;"; } }); } console.log(htmlEscape("<p class=\"greeting\">Hello world!</p>")); //"&lt;p class=&quot;greeting&quot;&gt;Hello world!&lt;/p&gt";
View Code

 

    str.split(separator[,limit]);//將字符串分割成數組。第一個參數是分隔符,能夠是字符或者RegExp對象。第二個可選參數用於指定返回的數組大小。 

    str.localeCompare(string);//若是str在字母表中排在參數string字符串以前,則返回負數(一般是-1),若是相等返回0,以後則返回1。

    靜態方法String.fromCharCode(num1,num2,....);//此方法接收一個或多個字符編碼,而後把它們轉化爲字符串。

 

5.7,單體內置對象(Global對象和Math對象):在全部代碼執行以前,做用域中就已經存在兩個內置對象:Global和Math。在大多數的ECMAScript實現中(JavaScript是其實現之一)都不能直接訪問Global對象。不過Web瀏覽器實現了承擔該角色的window對象[window對象的功能不止於此]

5.7.1,Object,Array,String等等都屬於內置對象,JavaScript還定義了兩個單獨的內置對象:Global和Math對象。

5.7.2,Global對象:

Global對象比較特殊,從表面上看,它是不存在的。其實,在ECMAScript中,不屬於其餘任何對象的屬性和方法,最終都是屬於Global對象的屬性和方法。如isNaN(), isFinite(), parseInt(), parseFloat()等等都是Global對象的方法。下面介紹一些Global對象的其餘方法。

※URI編碼方法(Uniform Resource Identifiers通用資源標識符)

encodeURI(uri);//對uri進行編碼。此方法不會編碼uri自己的特殊字符,如冒號,正斜槓,問號和井號,具體有:【;/?:@&=+$,#】。

encodeURIComponent(uri);//對uri進行編碼。此方法會編碼uri自己的特殊字符。

注:如下這些字符  【-_.!~*'()】  兩個方法都不會編碼,它們和 字母與數字同樣。

decodeURI(編碼後的uri);//只能對encodeURI()編碼的uri進行解碼。

decodeURIComponent(編碼後的uri);//能夠解碼任何字符

注:這四個URI方法用來敵對代替老版的escape()和unescape()方法。老版的方法只能用於ascii字符,因此被廢棄。

※eval()方法

此方法接收一個字符串參數。而後將此字符串做爲ECMAScript語句來解析。如:

var a = 'hello world';

eval("alert(a)");//至關於alert(a);

※Global對象的屬性:

 ※ECMAScript雖然沒有指出如何直接訪問Global對象,但Web瀏覽器都是將這個全局對象做爲window對象的一部分加以實現。所以,在全局做用域中聲明的左右變量和函數,就都成了window對象的屬性。

注意,在JavaScritp中,window對象除了扮演ECMAScript規定的Global對象的角色外,還承擔了不少別的任務。

另外一種取得Global對象的方法是使用如下代碼(ECMAScript方法):

var global = function(){return this;}();//建立了一個當即調用的函數表達式,返回this的值。在沒有給函數明確指定this值(指定方法有:把函數添加爲對象方法,調用apply()或call()方法)的狀況下,this的值就等於Global對象。

5.7.3,Math對象

※Math對象的屬性:

※Math.min()和Math.max():

這兩個方法接收任意多個數值參數,求取最小和最大值。如Math.max(1,3,4,5,9);//9

若是要找到數組中的最大值和最小值,能夠像小面這樣使用apply方法:Math.max.apply(Math, [1,2,3,4]);//4。把max函數的this值指定爲Math[其實設爲null也行]

※舍入方法:將小數值舍入爲整數

Math.ceil(float);//天花板舍入,老是往上舍入

Math.floor(float);//地板舍入,老是往下舍入

Math.round(float);//四捨五入

※Math.random();//無參數,返回介於0和1之間一個隨機數,不包括0和1。要獲得介於[m,n]之間的任意一個整數(包括m和n),有公式以下:

var value = Math.floor( Math.random() * (n-m+1) + m );如介於[5,11]之間的隨機整數:Math.floor(Math.random()*7+5);

※Math對象的其餘方法

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

 

 

6.0:面向對象的程序設計

面向對象(Object-Oriented,OO)的語言有一個標誌,那就是它們都有類的概念,而經過類能夠建立任意多個具備相同屬性和方法的對象。在ECMAScirpt中沒有類的概念,所以它的對象也與基於類的語言中的對象有所不一樣。ECMA-262把對象定義爲:「無序屬性的集合,其屬性能夠包含基本值、對象或者函數。」嚴格來說,這就至關於說對象是一組沒有特定順序的值。對象的每一個屬性或方法都有一個名字,而每一個名字都映射到一個值。正由於這樣,咱們能夠把ECMAScript的對象想象成散列表(又叫hash table) :無非就是一組名值對,其中值能夠是數據或函數。

6.1理解對象:

6.1.1,建立自定義對象的兩種方法:

①,建立一個Object的實例,而後再爲它添加屬性和方法。

②,直接建立對象字面量。

6.1.2,對象的特性

ECMAScript中的對象屬性能夠分爲兩種(各類各樣的屬性分爲兩類):數據屬性和訪問器屬性。每種屬性都有一些特性,這些特性是爲了實現JavaScript引擎用的,是"內部的",於是在JavaScript中不能直接訪問它們。具體解釋以下:

※數據屬性

數據屬性包含一個數據值的位置。在這個位置能夠讀取和寫入值。數據屬性有4個描述其行爲的特性:

①,configurable:表示1,可否經過delete刪除屬性從而從新定義屬性,2,可否修改屬性的特性,3,可否把屬性修改成訪問器屬性。此特性的默認值爲true.

②,enumerable:表示可否經過for-in循環返回屬性。此特性的默認值爲true。

③,writable:表示可否修改屬性的值。此特性的默認值爲true。

④,value:表示這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。此特性的默認值爲undefined。

例如:var person = {name:"Tong"};//這裏建立了一個person對象,它有一個數據屬性name,此屬性的configuable,enumerable,writable特性都被設置爲true,而其value特性被設置爲「Tong」。而對這個值的任何修改都將被反映在這個位置。

--------------------------------------------------------------

要修改數據屬性默認的特性,必須使用ECMAScript5的Object.defineProperty(obj, property, descriptor)方法。此方法接收三個參數:屬性所在的對象,屬性名稱(字符串形式)和一個描述符對象。其中描述符的屬性名稱必須是configurable,enumerable,writable,value中的一個或多個。例如:

    var person = new Object(); Object.defineProperty(person, 'name', { writable: false, //設置name屬性不可改寫,即name屬性是隻讀的。
        value: "tong" }); console.log(person.name);//"tong"
    person.name = "Gerrard";//嘗試改寫,沒有效果。若是在嚴格模式下,會拋出錯誤。
    console.log(person.name);//"tong"
View Code

若是是把configurable設置爲false,則①,對屬性調用delete[即delete person.name]在非嚴格模式下什麼都不會發生,在嚴格模式下會致使錯誤。②一旦把屬性定義爲不可配置(false)的,就不能再把它變回可配置的了。此時,若是再調用Object.defineProperty()方法修改特性,都會致使錯誤。

在調用Object.defineProperty()方法時,若是不指定,configurable,enumerable,writable特性的默認值都是false。

※訪問器屬性

訪問器屬性不包含數據值;它們包含一對get和set函數(不過,這兩個函數都不是必需的) 。讀取訪問器屬性時,會調用get函數,在寫入訪問器屬性時,會調用set函數。訪問器屬性有以下4個特性:

①,configurable:表示1,可否經過delete刪除屬性從而從新定義屬性,2,可否修改屬性的特性,3,可否把屬性修改成數據屬性。默認值爲true.

②,enumerable:表示可否經過for-in循環返回屬性。

③,get:在讀取屬性時調用的函數。默認值爲undefined.

④,set:在寫入屬性時調用的函數。默認值爲undefined.

和數據屬性能夠在對象中直接定義不一樣,訪問器屬性不能直接定義,只能使用Object.defineProperty()來定義。例如:

    var book = { _year: 2004,//year前的_是一種經常使用的記號,用於表示只能經過對象方法訪問的屬性[技術上能夠訪問,但約定不在對象方法以外訪問]。
        edition: 1 }; Object.defineProperty(book, "year", { /** *訪問器屬性year包含一個get和一個set函數,get函數返回_year的值,set函數經過計算肯定正確的版本。 *這是使用訪問器屬性的常見方式,即設置一個屬性的值會的致使其餘屬性發生變化。 */ get: function () { return this._year; // this即指book對象
 }, set: function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; console.log(book.edition);//2
View Code

訪問器屬性不必定同時指定get和set函數。若是隻指定get函數意味着此訪問器屬只能讀不能寫(寫操做會被忽略),而沒有get函數則此屬性亦不可讀(返回undefined)。

 -----------------------------------------------------------------------------------------------------

另外,還能夠一次定義多個屬性的特性,利用Object.defineProperties(obj, descriptor);//接收兩個參數,第一個是屬性所在對象,第二個是由各個屬性組成的對象。例如:

 

    var book = {}; Object.defineProperties(book, { _year: { value:2004 }, edition: { value: 1 }, year: { get: function () { return this._year; }, set: function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } });
View Code

-----------------------------------------------------------------------------------------------------------

讀取某個屬性的特性:Object.getOwnPropertyDescriptor(obj, property);//接收兩個參數,第一個是屬性所在對象,第二個是屬性的字符串名稱。返回一個對象。例如:

var book = {name:'xxx'};

var desc = Object.getOwnPropertyDescriptor(book, 'name');//name是個數據屬性,返回對象Object {value: "xxx", writable: true, enumerable: true, configurable: true}

 

6.2, 建立對象 

6.2.1,使用Object構造函數或對象字面量均可以建立對象,但其缺點很明顯:使用同一個接口建立多個對象,會產生大量重複的代碼!

6.2.2,工廠模式:在ECMAScript中沒有類的概念,工廠模式用函數來封裝建立對象的細節。例如:

    function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); }; return o } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
View Code

工廠模式的優勢:建立多個類似對象時,代碼不會有大量的重複。

工廠模式的缺點:對象識別的問題,即怎麼知道一個對象的類型。上面建立的person1 和person2都屬於Object對象,但沒法進一步知道其是Person對象仍是Man對象等等。

 

6.2.3,構造函數模式:像Object和Array,都屬於原生構造函數。咱們也能夠建立自定義的構造函數,從而自定義對象類型的屬性和方法,例如: 

    /** * 構造函數模式 */
    function Person(name, age, job) { this.colors = ['red','blue']; this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
View Code

關於此構造函數,注意:

※構造函數名首字母大寫,以區別普通函數。這同其餘OO語言相似。

※沒有顯式建立對象,而是將屬性和方法直接賦給了this對象,沒有return語句。

※建立Person的新實例必須使用new操做符。以new操做符調用構造函數時,解析器會經歷以下步驟:

  ①,建立一個新對象, var tong = {};

    將該對象內置的原型對象設置爲構造函數prototype引用的那個原型對象(每一個函數在建立時就會自動得到一個prototype屬性,指向原型對象)。

  ②,將構造函數的做用域賦給(或者說設爲)新對象(所以this就指向了這個新對象),即Person.call(tong);

  ③,執行構造函數中的代碼(爲這個新對象添加屬性和方法)

  ④,返回這個新對象【返回的這個對象便是實例對象】。對象創建以後,對象上的任何訪問和操做都只與對象自身及其原型鏈上的那串對象有關,與構造函數在扯不上關係了。換句話說,構造函數只是在建立對象時起到介紹原型對象和初始化對象兩個做用。

※構造函數和普通函數並無實質區別。若是不用new操做符調用Person(),它就和普通函數同樣,當在全局做用域中調用一個函數時,this對象老是指向Global對象(在瀏覽器中就是window對象),因此它的方法和屬性都被添加給了window對象了。

※Person的實例對象,如例子中的person1和person2,有一個constructor(構造函數)屬性,該屬性指向Person(即指向實例的構造函數)。即person1.constructor == Person[實際上constructor是原型的屬性].

※ constructor屬性原本是用來檢測對象類型的,可是 使用instanof 操做符 檢測對象類型更好:

  person1 instanceof Object;//true

  person1 instanceof Person;//true

※ 構造函數的優勢和缺點:

優勢:解決了工廠模式的缺點,能夠識別對象的具體類型

缺點:它的每一個成員都沒法獲得複用,包括函數(方法)。即每實例化一個實例對象時,這個實例對象中的屬性和方法都是獨立的,好比上個例子中:對於基本屬性,如name,job,age等,兩個實例person1和person2實際上各保存一個副本(棧內存)。對於引用類型屬性以及方法屬性,如colors屬性和sayName方法,雖然看似同樣,但實際上兩個實例保存的也是不一樣的對象。能夠用以下代碼檢測:

person1.colors == person2.colors;//返回false;

person1.sayName == person2.sayName;//返回false;

對於屬性來說,這不算是構造函數模式的缺點,由於畢竟每一個實例都須要有本身獨特的屬性(實際上這是構造函數模式的優勢,結合後面的原型模式就知道了),可是對於方法屬性而言,這就是缺點了,由於建立兩個完成一樣任務的方法對象(Function實例)的確是沒有必要的,浪費空間,多個實例間應該共享方法。對於這個問題有一個很差的解決方法:即把方法單獨拿出來,即以下所示:

    /** *解決構造函數模式的方法屬性不能共享的方法 */
    function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
View Code

這樣:person1.sayName == person2.sayName;//返回true。

可是這樣一樣會有問題:sayName這個方法定義在了全局做用域中,但實際上它只是爲Person構造函數服務的,這讓全局做用域有點名存實亡。更讓人沒法接受的是,若是構造函數須要不少方法就要定義不少全局函數,因而咱們的這個自定義引用類型就毫無封裝性可言了。

6.2.4,原型模式

※ECMAScript中建立的每一個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象。這個對象是(叫作)經過調用構造函數而建立的實例對象的原型對象。原型對象的用處是讓全部實例對象共享它所包含的屬性和方法。共享原理後面有敘述。

※關於構造函數自己Person、構造函數(同時也是實例對象的)原型對象Person Prototype、實例對象person1的關係以下圖所示:

    /** * 原型模式 */
    function Person() { } Person.prototype.colors = ['red', 'blue']; Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { /** * 注意這裏的 this,若是是person1.sayName();則this指的是person1(實例對象),而不是Person.prototype(原型對象)。 * 若是Person.prototype.sayName()則this指的是Person.prototype。 */ console.log(this); console.log(this.name); }; var person1 = new Person(); console.log(Object.getOwnPropertyNames(person1));//[]
    console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "colors", "name", "age", "job", "sayName"]
    person1.sayName();//this == person1
    Person.prototype.sayName();//this == Person.prototype
    console.log(Person.prototype.constructor == person1.constructor);//true
    var person2 = new Person(); person2.sayName();//this == person2
    console.log(person1.sayName == person2.sayName);//true
    console.log(person1 == Person.prototype);//false
View Code

圖形表示以下:

建立構造函數的同時,其原型對象會自動得到一個constructor屬性。實例中有一個指向原型對象的內部指針[[Prototype]]。

三個對象Person,Person Prototype, person1的互相聯繫:

Person.prototype == (Person Prototype);

(Person Prototype).constuctor == Person;

person1.constructor == Person;//這個實際上是原型對象的屬性,可是原型對象的屬性和方法能夠共享給實例對象。

person1.__proto__ == (Person Prototype);//不是全部瀏覽器都支持

Object.getPrototypeOf(person1) == (Person Prototype);//Object的方法,ECMAScript5新增。

(Person Prototype).isPrototypeOf(person1);//true,原型對象的方法。

共享原理:每當代碼讀取某個對象的屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從實例對象自己開始,若是找到則返回該屬性的值並中止搜索;若是沒找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性。若是在原型對象中找到這個屬性,則返回該屬性的值。這就是多個對象實例共享原型對象所保存的屬性和方法的基本原理[後面繼承還會擴展搜索步驟]。

※ 實例對象屬性會覆蓋原型對象屬性

    var person1 = new Person(); console.log(person1.name);//Nicholas
    person1.name = 'tong';//實例對象屬性會覆蓋原型對象中的屬性。
    console.log(person1.name);//tong
    person1.name = null; console.log(person1.name);//null
    delete person1.name;//delete能夠恢復實例對象與原型對象屬性的鏈接。
    console.log(person1.name);//Nicholas
View Code

※hasOwnProperty()方法:用於斷定某個屬性屬於實例對象仍是原型對象,例如:

    var person1 = new Person(); console.log(Person.prototype.hasOwnProperty("name"));//true
    console.log(person1.hasOwnProperty('name'));//false
    person1.name = 'tong'; console.log(Person.prototype.hasOwnProperty("name"));//true
    console.log(person1.hasOwnProperty('name'));//true
View Code

※in操做符:

in操做符有兩種用法:單獨使用和for-in循環中使用。

單獨使用時,in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性存在於實例中仍是原型中,還包括繼承的屬性。因此同時使用 in操做符和hasOwnProperty()方法能夠肯定屬性是否是在原型對象中。!object.hasOwnProperty(name) && (name in object);

for-in循環時,返回的是全部可以經過對象訪問的、可枚舉的屬性,不管屬性是在實例對象仍是在原型對象中。另外有規定:全部開發人員定義的屬性都是可枚舉的。

注意:以上兩條都是對實例對象用in操做符,如 「name「 in person1, for(var i in person1){}等等,若是對原型對象用in操做符,則只會算在原型對象中的屬性,不會算在實例對象中的屬性。

※Object.keys()方法:接收一個對象做爲參數,取得對象上全部可枚舉的屬性,這個方法區分實例對象和原型對象,即實例對象只返回實例對象中的屬性,原型對象值返回原型對象中的屬性。

※Object.getOwnPropertyNames()方法:接受一個對象做爲參數,取得對象上全部的屬性,不管是否可枚舉(這是和Object.key()方法的惟一區別)。一樣區分實例對象和原型對象,注意理解Own的含義。

※用對象字面量表示對象原型,如此就沒必要沒添加一個屬性和方法就要敲一遍Person.prototype了,代碼以下:

    function Person() { } Person.prototype = { name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { console.log(this.name); } }; var p1 = new Person(); console.log(p1 instanceof Object);//true
    console.log(p1 instanceof Person);//true
    console.log(p1.constructor == Person);//false
    console.log(p1.constructor == Object);//true
View Code

注意點一:對象字面量本質上是Object對象的實例(至關於new Object() ),因此用 此方法定義原型對象時,此原型對象的constructor屬性就是Object實例的constructor屬性,也就是構造函數Object原型對象的constructor,因此最終指向的是構造函數Object而再也不指向Person函數【也就是說,構造函數建立時自動產生的原型對象被新的對象覆蓋掉了】。不過能夠顯示的覆蓋掉,以下:

    function Person() { } Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { console.log(this.name); } }; var p1 = new Person(); console.log(p1 instanceof Object);//true
    console.log(p1 instanceof Person);//true
    console.log(p1.constructor == Person);//true
    console.log(p1.constructor == Object);//false
View Code

注意點二:【補充:構造函數的原型對象是在構造函數建立時自動生成的(這個是默認的原型對象),實例的原型對象固然是在實例生成的時候即調用構造函數時添加的,看似廢話,須要仔細理解下!調用構造函數時會爲實例對象添加一個指向最初原型的指針(__proto__或Object.getPrototypeOf()),若是把原型對象修改成另外一個對象就等於切斷了構造函數與最初原型之間的聯繫。記住一點:實例中的指針指向的是構造函數的原型。例子以下:

情形一:

    function Person() { } /** * 最初的原型對象被修改了 */ Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { console.log(this.name); } }; var p1 = new Person();//此時實例p1的指針指向修改事後的原型對象
    p1.sayName();//Nicholas
View Code

情形二:

    function Person() { } var p1 = new Person();//此時實例p1的指針指向最初原型對象。
    /** * 最初的原型對象被修改了 */ Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName: function () { console.log(this.name); } }; p1.sayName();//會出錯,最初的原型對象中並無sayName方法。
View Code

※原生對象,如Array,String等的原型也能夠自定義屬性和方法,但不推薦這麼作。

※原型模式的優缺點:

優勢:可讓全部實例共享方法

缺點:1,省略了爲構造函數傳遞初始化參數這一環節,結果全部實例在默認狀況下都將取得相同的屬性值

   2,原型模式的共享特性也是它的缺點,對於屬性來說,每一個實例都應該有本身的獨特屬性,但原型模式卻都是共享的,對於基本類型的屬性來說還能夠經過實例屬性同名覆蓋,可是對於引用類型的屬性(一個實例改動此引用類型的屬性會影響到另外一個實例的此屬性)來說,就沒有任何辦法了。這個缺點恰好是構造函數模式的優勢,因此組合使用構造函數模式和原型模式是建立自定義類型的最多見方式。

 

6.2.5,組合使用構造函數模式和原型模式

這是用來定義引用類型的一種默認模式(即建立對象的的默認模式)。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存【即屬性是在實例對象中的,而方法是屬於原型對象的】。還能夠向構造函數傳遞參數,見以下例子:

    function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor: Person, sayName: function () { alert(this.name); } }; var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van"
    alert(person2.friends); //"Shelby,Court"
    alert(person1.friends === person2.friends); //false
    alert(person1.sayName === person2.sayName); //true
View Code

 

6.2.6,其餘一些模式:瞭解便可

※ 動態原型模式

※寄生構造函數模式

※穩妥構造函數模式

 

函數簽名是指:函數的參數類型,參數個數,參數順序。

6.3 繼承

ECMAScript中的繼承是依靠原型鏈實現的。

6.3.1,原型鏈 

若是讓某個引用類型的原型對象等於另外一個引用類型的實例,則會構成一個實例與原型的鏈條,這就是原型鏈的基本概念。繼承實現的本質是:重寫原型對象,代之以一個新類型的實例。例子以下:

    //繼承
    function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; function SubType() { this.subproperty = false; } //繼承SuperType
    SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
View Code

 

instance指向SubType的原型,subType的原型又指向SuperType的原型,層層遞進。圖解以下:

注意instance.constructor如今指向SuperType,由於instance的constructor實際是其原型對象的constructor屬性,而其原型對象是SuperType的實例,它的constructor屬性又是它的原型對象的constructor屬性,便是SupertType的原型對象的屬性,因此最終指向了SuperType構造函數。

 ※經過實現原型鏈,本質上擴展了原型搜索機制,搜索過程會沿着原型鏈一路向上,直到發現了要找的屬性。原型鏈的最上層是Object.prototype,由於全部函數的默認原型都是Object的實例,這也是全部自定義類型都會繼承toString()、valueOf()等默認方法的根本緣由。

※原型鏈上的原型與實例

    //原型鏈上的原型與實例
    alert(instance instanceof Object); //true
    alert(instance instanceof SuperType); //true
    alert(instance instanceof SubType); //true
    alert(Object.prototype.isPrototypeOf(instance)); //true
    alert(SuperType.prototype.isPrototypeOf(instance)); //true
    alert(SubType.prototype.isPrototypeOf(instance)); //true
View Code

※ 注意一點:在經過原型鏈實現繼承時,不能使用對象字面量建立原型對象,由於這麼作就會重寫原型鏈,切斷原來的原型鏈。

 

※ 原型鏈的問題:

經過原型繼承時,原型實際上會變成另外一個類型的實例,因此原來的實例屬性如今就變成了原型屬性了,若是包含有引用類型的屬性,那麼此屬性就會被全部的子類型的實例共享,一個實例改變此屬性會影響到其餘實例的此屬性.例子以下:

    function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { } //inherit from SuperType
    SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black"
    var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
View Code

 

6.3.2,借用構造函數(constructor stealing) 【注:這也是ECMAScript中的一種繼承方式,和原型鏈繼承是並列關係】

※爲了解決原型鏈對於引用類型屬性的問題,能夠用一種叫作 借用構造函數 的技術。其基本思想很簡單,即在子類型構造函數內部調用超類型的構造函數,並把子類型的執行環境賦予其(經過call或者apply方法).以下所示:

    //借用構造函數 技術
    function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } function SubType() { SuperType.call(this, 'Gerrard');//繼承了SuperType
 } var instance1 = new SubType(); instance1.colors.push('black'); console.log(instance1.colors);//["red", "blue", "green", "black"]
    var instance2 = new SubType(); console.log(instance2.colors);//["red", "blue", "green"]
View Code

如上代碼所示,在調用子類型構造函數時,就會執行SuperType()函數中定義的全部對象初始化代碼,結果就是SubType的每一個實例就都會具備本身的colors屬性的副本了。

※借用構造函數的缺點:也就是構造函數的缺點,即方法都是在構造函數中定義的,沒法作到函數複用。這裏注意一點,借用構造函數技術中,在超類型的原型中定義的方法對子類型而言是不可見的,由於這種繼承方法只是在初始化子類型時調用超類型構造函數,沒有涉及到超類型的原型對象。

 

6.3.3,組合繼承:

結合原型鏈和借用構造函數,發揮二者各自的長處。這是JavaScript中最經常使用的繼承模式。其思路是,使用原型鏈繼承 實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數複用,又能保證每一個實例都有本身的屬性。例子以下:

    /** * 組合繼承 */
    function SuperType(name) { //須要繼承的實例屬性
        this.name = name; this.colors = ["red", "blue", "green"]; } //須要繼承的共享的方法
    SuperType.prototype.sayName = function () { console.log(this.name); }; //須要繼承的共享屬性
    SuperType.prototype.xxx = 'xxx'; function SubType(name,age) { SuperType.call(this, name);//繼承實例屬性
        this.age = age; } /** * 這裏注意,SubType.prototype中一樣也保存着SuperType實例的屬性name和colors; * SubType在實例化時經過借用構造函數技術在實例中覆蓋了這些屬性, * 因此說在SubType.prototype中的name和colros屬性是多於的,這是組合繼承的一個小缺點,能夠用寄生組合式繼承解決。 */ SubType.prototype = new SuperType();//繼承須要共享的原型屬性和方法

    var instance1 = new SubType("tong",11); instance1.colors.push('black'); console.log(instance1.colors);//["red", "blue", "green", "black"]
    console.log(instance1.name);//tong
    console.log(instance1.age);//11
    console.log(instance1.xxx);//xxx

    var instance2 = new SubType("Gerrard",22); console.log(instance2.colors);//["red", "blue", "green"]
    console.log(instance2.name);//Gerrard
    console.log(instance2.age);//22
    console.log(instance2.xxx);//xxx
View Code

 

6.3.4,原型式繼承【這是個很基礎的模型,好好理解也很容易理解】

基本思想是:藉助原型,基於已有的對象建立新對象,同時也沒必要所以建立新類型。爲了達到這個目的,能夠利用以下函數:

    function object(o) {//o是傳入的對象

      function F() {} //建立一個臨時性的構造函數

      F.prototype = o;//將傳入對象做爲臨時構造函數的原型

      return new F();//返回臨時類型的一個實例,此實例有個指針指向傳入的對象o,整個函數的做用至關於對傳入對象o做了一次淺複製

    }

淺複製(淺拷貝):只是增長了一個指針指向已經存在的內存;

深複製(深拷貝):增長一個指針而且申請一個新的內存,並使這個指針指向這個內存

※ECMAScript5中經過新增Object.create(proto[, props])方法規範類原型式繼承。此方法接收兩個參數,第一個是參數是用做新對象原型的對象,第二個可選參數是爲新對象定義額外屬性的對象,在傳入一個參數的狀況下,Object.create(obj)與上面object(obj)方法的行爲相同,即建立一個以obj爲原型的實例對象【新建立的這個實例對象沒有任何屬性,因此它能夠用來解決組合繼承中子類的原型中包含多餘屬性的問題!】。Object.create()方法的第二個參數與Object.defineProperties()方法的第二個參數格式相同,形如:{xxx:{value:'xxx',writable:true}}。一樣,調用此方法,若是不指定writable等屬性就會默認爲false。

 

6.3.5, 寄生式繼承【實質仍是原型式繼承】

寄生式繼承是在原型式繼承模型的基礎上擴展來的,就是在原型式繼承的基礎上進一步封裝。即把擴展對象的屬性等代碼封裝到一個函數中,最後再返回此擴展對象,代碼以下:

functon createAnother(original) {

  var clone = object(original);//這個就是原型式繼承中的函數,建立一個新對象。這裏不必定必須使用object()函數,任何可以返回新對象的函數都適用於此。

  clone.sayHi = function(){alert('hi');};// 擴展新對象的屬性和方法

  return clone;

}

6.3.6,寄生組合式繼承(這是最理想的繼承範式)

上面提到組合繼承(即原型式繼承和借用構造函數式組合在一塊兒)是JavaScript中最經常使用的繼承模式,但它也有個小缺點(通常狀況下能夠忽略此缺點),即子類型原型中存着多餘的屬性,這是由於組合繼承不管在什麼狀況下都會調用兩次超類型的構造函數:按順序第一次是在建立子類型原型的時候,第二次是在子類型構造函數內部。

 

/** *組合繼承的缺點介紹 */
    function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { alert(this.name); }; function SubType(name, age) { SuperType.call(this, name); //第二次調用SuperType()
        this.age = age; } /** * 第一次調用超類型,超類型的實例屬性name 和colors也就存在於子類型的原型中了, * 這些屬性就是多餘的屬性,由於第二次調用超類型構造函數時,子類型實例對象也會得到這些屬性, * 從而覆蓋掉了子類型原型中的那些同名多餘屬性。因此最理想的方式就是讓子類型的原型中不保存這些多餘的屬性。 * 要作到這點,就要利用到原型式繼承模型,即**讓子類型的原型等於超類型的一個淺複製**用寄生模式封裝起來就能夠了 */ SubType.prototype = new SuperType(); //第一次調用SuperType()
    SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { alert(this.age); };

 

 因此,寄生組合式繼承能夠按以下方法實現只需改變第一次調用超類型構造函數的那一行代碼便可,兩種方式的惟一區別在於:原來的子類原型對象被設置爲SuperType的一個實例,因此包含SuperType構造函數中定義的屬性,而如今藉助於一個新的構造函數(假設爲F)將子類型的原型對象設置爲F的一個實例,同時將F的構造函數的原型設置爲SuperType.prototype,F中沒有定義任何屬性,因此子類型的的原型對象即是一個沒有任何屬性的對象,且和SuperType的實例對象同樣,此對象內部有一個指針指向SuperType.prototype,一句話歸納就是:藉助新的空構造函數將子類型的原型對象由原來的 SuperType的實例對象變化爲 新構造函數的實例對象)

function inheritPrototype(subType, superType) {

  var prototype = object(supertType.prototype);//建立對象,即超類型原型的一個淺複製

  prototype.constructor = subType;//擴展對象

  subType.prototype = prototype;//把子類型的原型指定爲超類型原型的淺複製

}

而後把第一次調用超類型構造函數的那一行代碼改成調用上面的函數:

inheritPrototype(SubType, SuperType);便可。

 

7.0函數表達式

7.1,函數表達式最多見的表現形式:

※匿名函數(也叫拉姆達函數)表達式,能夠把其視爲一個變量,適用於變量適用的一切地方,如函數表達式能夠做爲函數的返回值。

※命名函數表達式,如var a = function f(arg1,arg2){...};調用時要用a();而f會被認爲是個未定義的符號。a.name == f

一些瀏覽器會給函數一個非標準的name屬性,這個屬性值永遠是function標識符後面的那個名字,對於匿名函數來講就是空字符串。

7.2,遞歸

※遞歸函數內部中用arguments.callee代替函數名能夠減小耦合性(函數名可能會變化)。可是在嚴格模式下,callee屬性是不可用的。只是能夠利用命名函數表達式實現減小耦合性的遞歸,以下:

var factorial  = function f(num){

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

};

 

7.3,閉包 

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

 

★★★★★執行環境,做用域鏈,變量對象詳述以及它們之間的關係:

★執行環境:當某個函數第一次被調用時,會建立一個執行環境及相應的做用域鏈。能夠理解爲,執行環境是一個包含 做用域鏈和變量對象 的容器。

★做用域鏈:當某個函數被建立時(某個函數被返回也被視爲被建立),它會有一個初始化的做用域鏈,這個做用域鏈包括了除了本身以外的全部其餘包括這個函數的做用域,當這個函數被調用時,其自身的做用域被建立並被推入整個做用域鏈的前端。本質上講,做用域鏈是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。當在函數中訪問一個變量時,就會從做用域鏈中從前端依次搜索具備相應名字的變量,直到找到爲止。

★變量對象:每一個執行環境都有一個表示變量的對象,即爲變量對象。好比一個在全局做用域中定義的函數,其變量對象包括:this,arguments,命名參數。

關於三者之間的關係,見以下例子:

 

 

 

※閉包的做用域鏈:

 

    /** * 閉包的做用域鏈 * 通常函數的變量對象在函數執行完畢後就會被銷燬,但閉包不一樣。 */
    function outer(name) { //被返回的這個匿名函數便是閉包
        return function (obj) { return obj[name]; } } var obj = {name: 'tong'}; /** * 當調用outer函數後,返回了匿名閉包函數,此時匿名函數的做用域鏈被初始化爲 * 包含outer函數的變量對象和全局變量對象,因此匿名函數就能夠訪問outer變量對象 * 以及全局變量對象。並且,在調用outer函數後,outer函數的做用域鏈會被銷燬,可是 * 其變量對象並不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個變量對象。直到匿名 * 函數被銷燬後,outer函數的變量對象纔會被銷燬 * */
    var out = outer('name');//將閉包匿名函數賦值給變量out,此時其做用域鏈有兩個指針:outer和全局
    var val = out(obj);//調用閉包函數,此時其做用域鏈有三個指針:指向自身的變量對象,指向outer函數的變量對象,指向全局的變量對象
    console.log(val);//tong
    /** * 解除對匿名函數的引用,以後垃圾回收例程就會將匿名函數清除以回收內存, * 隨着匿名函數的做用域鏈被銷燬,outer函數的變量對象也能夠安全的銷燬了。 */ out = null;

 

 

※ 閉包與變量(閉包的一個反作用)

注意到一點:閉包函數的做用域鏈中指針指向的是外部包含函數的整個變量對象,這致使了一個問題,即閉包只能取得包含函數中任何一個變量的最後一個值。見下面一個例子:

function createFunction(){

  var result = new Array();

  for (var i = 0; i < 10; i++){

    result[i] = function(){

      return i;

    };

  }

  return result;

}

這個函數會返回一個函數數組,函數中每一個函數都是一個閉包。表面上看,每一個函數都會返回本身的索引值,但實際上每一個函數都返回10!!這是由於每一個閉包函數的的做用域鏈中都保存着createFunction函數的變量對象,因此它們引用的都是同一個變量i。當createFunction函數返回後,變量i的值是10,因此每一個函數內部i的值都是10(沿着各自的做用域鏈搜索i的值,結果發現都是10)。

這個問題能夠以下解決:

    function createFunction() { var result = new Array(); for (var i = 0; i < 10; i++){ result[i] = function (num) { return function () { return num; }; }(i); } return result; }

 

※ 關於this對象

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

 

    var o = { name: "mY", getName: function () { var a = this; return function () { console.log(a);//對象o
                console.log(this);//window對象
 } } }; o.getName()();//o.getName()返回閉包函數,把o.getName()替換掉,就至關於在全局中調用閉包,所以閉包this指的就是window對象

 

 

 

7.4,模仿塊級做用域

※Javascript(以及python,php)都是沒有塊級做用域的,它們都屬於函數做用域。好比:

function output(count){

  for(var i = 0; i < count; i++){

    console.log(i);

  }

  alert(i);//這裏在for循環體外,依然能夠訪問到i的值

}

在Java,C++等語言中,變量i只會在for循環的語句快中有定義,循環一旦結束,變量i就被銷燬。但在Javascript中,變量i的值在for循環外依然能夠訪問到。

※既然JS是函數做用域,那麼就能夠利用函數做用域來模擬塊級做用域,即把循環體放在一個匿名函數內,以下:

 

    function output(count) { var tmp = function () { for (var i = 0; i < count; i++) { console.log(i); } }();//當即調用此匿名函數 alert(i);//這裏會報錯,i未定義。
 } output()

 

 上面的代碼中tmp變量其實沒有任何用處,可是若是直接把(var tmp = )去除的話會報錯,由於以function關鍵字開頭會被認爲是一個函數聲明的開始(函數聲明必需要有函數名的),而函數聲明後面不能跟圓括號。然而,函數表達式的後面能夠跟圓括號。要將函數聲明轉換成函數表達式只要把這個聲明用一對圓括號括起來便可。以下:

 

    function output(count) { (function () { for (var i = 0; i < count; i++) { console.log(i); } })(); alert(i);//這裏會報錯,i未定義。
 } output();  

所以,塊級做用域形式以下:

    (function () { /*這裏是塊級做用域,出了這塊區域,這裏的全部局部變量就會被銷燬
      但注意,若是是一個不帶var表示的變量,表示這是一個全局變量,      能夠在塊級做用域以外訪問。
      */
    b = 3;//這是個全局變量,能夠在塊級做用域以外訪問到。 })()

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

 

※私有變量,靜態私有變量

模塊模式(單例模式)

實質上就是塊級做用域的應用。建立並當即調用一個函數,此函數的內部屬性和方法都是屬於私有的,而後返回一個可以訪問這些私有屬性和方法的對象。例子以下:

 

    //single是個全局變量,保存着公有屬性和方法,這些公有方法能夠訪問私有屬性和方法
    var single = function () { var pri = 'pri';//私有屬性,外部沒法直接訪問

        //私有函數,外部沒法直接訪問
        function priFun() { return pri; } //返回的對象能夠用對象字面量,也能夠用new構造函數模式
        return { pubPro: 'pubPro', pubFun: function () { return priFun(); } } }();

 

 

 

本章總結:

 

 

8.0,BOM(Browser Object Module)

8.1,window對象--BOM的核心

※全局變量與window屬性關係:

聯繫:全局變量、函數都是window對象的屬性和方法。

區別:

1,全局變量不能經過delete操做符刪除,而在window對象上定義的屬性能夠,以下:

var age = 22;

window.color = 'red';

delete age;//false

delete window.age;//false

delete window.color;//true

console.log(window.age);//22

console.log(window.color);//undefined

經過Object.getOwnPropertyDescriptors(window,'age')查看可知age屬性的[[configrable]]特性爲false,因此不可刪除。

2,對於未定義的變量,

var val = xx;//拋出錯誤,由於xx未定義

var val = window.xx;//val爲undefined而不會出錯,由於這是一次屬性查詢

 

※框架和窗口關係:

1,每一個框架都有本身的window對象。每一個window對象都一個name屬性,值爲框架的名稱。最上層window對象的name爲空字符串。

2,全部的框架都保存在frames【即window.frames】集合中(注意是隻包含框架,不包含最上層的window)。frames.length獲得框架數量。

3, top對象始終指向最高(最外)層的窗口,也就是瀏覽器窗口。

  parent對象始終指向當前框架的直接上層框架。

  self對象始終指向window;實際上window和self能夠互換使用。

4,defaultView;//返回活動文檔的Window對象

5,window[index];//返回指定索引位置的框架的window對象。注意,只包含框架,即window[0]指的是第一個框架,而不是最外層的window

6,window['name'];//返回指定名稱的內嵌框架的window對象。同上,也是隻包含框架。

※窗口位置

window.screenTop / window.screenLeft;//獲取

window.screenY / window.screenX;//獲取

window.moveTo(11,111) / window.moveBy(33,44);//只對window.open()的窗口有效!且不適用於框架,只能對最外層window對象使用

※窗口大小

window.innerWidth / window.innerHeight ;//獲取

window.outerWidth / window.outerHeight;//獲取

document.documentElement.clientWidth;//獲取

document.body.clientWidth;//獲取

window.resizeTo(100,100) / window.resizeBy(11,11);//只對window.open()窗口有效,且不適用於框架,只能對最外層window對象使用。

※導航和打開窗口

var newWin = window.open(url,name,features,replace);//打開新的標籤或窗口,四個參數都是可選的:

  :要打開的新的網址

  name:和<a>標籤的target屬性同樣

  features:新窗口的特性字符串,如:"width=300,height=300,left=50,top=50"

  replace: 新打開的url是在瀏覽歷史中新增一個條目,仍是替換當前的條目。true爲替換,false爲新增。

此函數返回一個指向新窗口的引用。

newWin.close();//此方法只對window.open()方法打開的窗口或標籤有效。

newWin.closed;//檢查窗口是否關閉了

newWin.opener;//此屬性指向調用window.open()的窗口或框架 的window對象。此屬性只在彈出窗口的最外層window對象上有定義。

在chrome中,若是把opener屬性設置爲null,則表示在單獨的進程中運行新標籤頁,若是這樣,兩個窗口將沒法通訊。

※超時調用和間歇調用

var timeoutId = window.setTimeout(code, ms);

第一個參數表示要執行的代碼,能夠是包含js代碼的字符串(不推薦,由於會有性能損失),也能夠是函數。

第二個參數表示等待多長時間的毫秒數,但通過該時間後,指定的代碼不必定會執行。JavaScript是一個單線程序的解釋器,所以一段時間內只能執行一段代碼。爲了控制要執行的代碼,就要有一個JavaScript任務隊列。這些任務會按照它們被添加到隊列的順序執行。setTimeout的第二個參數告訴JavaScript解釋器再過多長時間把當前任務添加到隊列中。若是隊列是空的,那麼添加的代碼會當即執行,若是隊列不是空的,就要等前面的代碼執行完了之後纔會執行

另外注意:setTimeout()中用到的函數的環境老是window,因此若是須要使用某個特定的this對象,須要將this保存到一個變量中。例如:

 

    var o = { nm: 'tong', sayName: function () { var that = this;//this指的是對象o,保存在變量that中,以便在setTimeout中使用。
            setTimeout(function () { console.log(this);//儘管在對象o裏面,這個this仍指的是window對象
                console.log(this.nm);//undefined;
                console.log(that.nm);//tong
            }, 5000) } }; o.sayName();

 

 

 

window.clearTimeout(timeoutId);

----

var intervalId = window.setInterval();//能不用間歇調用就不用,由於間歇的下一次可能在上一次結束以前就啓動,通常間歇調用均可以用超時調用來模擬。

window.clearInterval(intervalId);

※系統對話框

調用alert(str) / confirm(str) / prompt(str1, str2)方法能夠顯示系統對話框。系統對話框外觀由操做系統或瀏覽器決定,而不是由CSS決定。系統對話框都是同步和模態的,同步即打開對話框代碼中止執行,關掉就會繼續執行。模態是指要想對其餘地方進行操做,必須先響應系統對話框。

prompt(str1,str2);//str1是用於提示用戶的文本,str2是輸入域的默認值。返回輸入值或者null(沒有輸入)。

 

8.2, location對象

window.location === document.location。二者是同一個對象。

location對象的屬性列表:

 ※瀏覽器地址位置操做:

location.assign(URL);

location.href = URL;//會調用assign()方法

window.location = URL;//會調用assign()方法

location.hash/search/host/.... = ...;//修改地址一部分

上述方法在修改URL以後,瀏覽器的歷史記錄中就會生成一條新的記錄,所以能夠經過後退按鈕導航到前一個頁面。

locatioin.replace(URL);//以新的URL替換當前頁面,此方法不會在歷史記錄中生成新的記錄,所以沒法回到前一個頁面。

location.reload();//重載當前頁面,若是頁面沒變則會從瀏覽器緩存中加載。

location.reload(true);//強制從服務器從新加載當前頁面。

 

8.3,navigator對象

navigator對象主要提供與用戶代理(即瀏覽器)相關的信息。比較有用的信息有:

navigator.cookieEnabled ;//cookie是否啓用

navigator.platform;//瀏覽器所在的操做系統平臺

navigator.plugins;//瀏覽器中安裝的插件信息的數組

navigator.userAgent;//用戶代理信息字符串

 

8.4,screen對象【用處不大】

8.5,history對象

history.go(num);//整數表示前進num頁,負數表示後退,0表示刷新

history.forward();//前進一頁

history.back();//後退一頁

history.length;//歷史記錄的數量

 

9.0客戶端檢測:

※爲了不在全局做用域中定義太多變量,能夠採用 模塊模式 :

代碼以下:

 

    var client = function () { //呈現引擎,私有屬性,外部不可直接訪問
        var engine = { ie: 0, gecko: 0, webkit: 0, khtml: 0, opera: 0, //完整的版本號
            ver: null }; /** * 檢測呈現引擎(五大呈現引擎:IE,Gecko[firefox],WebKit[chrome,safari],KHTML,Opera) * 檢測呈現引擎關鍵是檢測順序 */
        var ua = navigator.userAgent; if (window.opera) { //首先檢測opera
            engine.ver = window.opera.version(); engine.opera = parseFloat(engine.ver); } else if (/AppleWebKit\/(\S+)/.test(ua)) { //檢測WebKit中獨一無二的【AppleKit/版本號】
            engine.ver = RegExp["$1"]; engine.webkit = parseFloat(engine.ver); } else if (/KHTML\/(\S+)/.test(ua)) { engine.ver = RegExp["$1"]; engine.khtml = parseFloat(engine.ver); } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) { //檢測Gecko,Gecko的版本號在「rv:」的後面
            engine.ver = RegExp["$1"]; engine.gecko = parseFloat(engine.ver); } else if (/MSIE ([^;]+)/.test(ua)) { //檢測IE,IE版本號位於字符串 MSIE 後面。
            engine.ver = RegExp["$1"]; engine.ie = parseFloat(engine.ver); } //返回對象
        return { engine: engine } }();

 考慮到檢測瀏覽器,操做系統平臺,移動設備和遊戲系統,更具體的用戶代理字符串檢測腳本以下:

    var client = function () {
        //rendering engines 呈現引擎
        var engine = {
            ie: 0,
            gecko: 0,
            webkit: 0,
            khtml: 0,
            opera: 0,
            //complete version完整的版本號
            ver: null
        };
        //browsers 檢測瀏覽器類型
        var browser = {
            //browsers
            ie: 0,
            firefox: 0,
            safari: 0,
            konq: 0,
            opera: 0,
            chrome: 0,
            //specific version具體版本號
            ver: null
        };
        //platform/device/OS 平臺 移動設備 操做系統
        var system = {
            win: false,
            mac: false,
            x11: false,
            //mobile devices移動設備
            iphone: false,
            ipod: false,
            ipad: false,
            ios: false,
            android: false,
            nokiaN: false,
            winMobile: false,
            //game systems遊戲系統
            wii: false,
            ps: false
        };
        //detect rendering engines/browsers檢測呈現引擎和瀏覽器
        var ua = navigator.userAgent;
        if (window.opera) {
            engine.ver = browser.ver = window.opera.version();
            engine.opera = browser.opera = parseFloat(engine.ver);
        } else if (/AppleWebKit\/(\S+)/.test(ua)) {
            engine.ver = RegExp["$1"];
            engine.webkit = parseFloat(engine.ver);
            //figure out if it’s Chrome or Safari肯定是chrome仍是safari
            if (/Chrome\/(\S+)/.test(ua)) {
                browser.ver = RegExp["$1"];
                browser.chrome = parseFloat(browser.ver);
            } else if (/Version\/(\S+)/.test(ua)) {
                browser.ver = RegExp["$1"];
                browser.safari = parseFloat(browser.ver);
            } else {
                //approximate version近似肯定版本號
                var safariVersion = 1;
                if (engine.webkit < 100) {
                    safariVersion = 1;
                } else if (engine.webkit < 312) {
                    safariVersion = 1.2;
                } else if (engine.webkit < 412) {
                    safariVersion = 1.3;
                } else {
                    safariVersion = 2;
                }
                browser.safari = browser.ver = safariVersion;
            }
        } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
            engine.ver = browser.ver = RegExp["$1"];
            engine.khtml = browser.konq = parseFloat(engine.ver);
        } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) {
            engine.ver = RegExp["$1"];
            engine.gecko = parseFloat(engine.ver);
            //determine if it’s Firefox 肯定是否爲firefox
            if (/Firefox\/(\S+)/.test(ua)) {
                browser.ver = RegExp["$1"];
                browser.firefox = parseFloat(browser.ver);
            }
        } else if (/MSIE ([^;]+)/.test(ua)) {
            engine.ver = browser.ver = RegExp["$1"];
            engine.ie = browser.ie = parseFloat(engine.ver);
        }
        //detect browsers 檢測IE和opera瀏覽器
        browser.ie = engine.ie;
        browser.opera = engine.opera;
        //detect platform   檢測平臺
        var p = navigator.platform;
        system.win = p.indexOf("Win") == 0;
        system.mac = p.indexOf("Mac") == 0;
        system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
        //detect windows operating systems檢測具體windows系統版本
        if (system.win) {
            if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)) {
                if (RegExp["$1"] == "NT") {
                    switch (RegExp["$2"]) {
                        case "5.0":
                            system.win = "2000";
                            break;
                        case "5.1":
                            system.win = "XP";
                            break;
                        case "6.0":
                            system.win = "Vista";
                            break;
                        case "6.1":
                            system.win = "7";
                            break;
                        default:
                            system.win = "NT";
                            break;
                    }
                } else if (RegExp["$1"] == "9x") {
                    system.win = "ME";
                } else {
                    system.win = RegExp["$1"];
                }
            }
        }
        //mobile devices移動設備
        system.iphone = ua.indexOf("iPhone") > -1;
        system.ipod = ua.indexOf("iPod") > -1;
        system.ipad = ua.indexOf("iPad") > -1;
        system.nokiaN = ua.indexOf("NokiaN") > -1;
        //windows mobile
        if (system.win == "CE") {
            system.winMobile = system.win;
        } else if (system.win == "Ph") {
            if (/Windows Phone OS (\d+.\d+)/.test(ua)) {
                system.win = "Phone";
                system.winMobile = parseFloat(RegExp["$1"]);
            }
        }
        //determine iOS version
        if (system.mac && ua.indexOf("Mobile") > -1) {
            if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) {
                system.ios = parseFloat(RegExp.$1.replace("_", "."));
            } else {
                system.ios = 2; //can’t really detect - so guess
            }
        }
        //determine Android version
        if (/Android (\d+\.\d+)/.test(ua)) {
            system.android = parseFloat(RegExp.$1);
        }
        //gaming systems
        system.wii = ua.indexOf("Wii") > -1;
        system.ps = /playstation/i.test(ua);
        //return it
        return {
            engine: engine,
            browser: browser,
            system: system
        };
    }();
View Code

 

10,DOM(Document Object Module)

10.1,Node類型

每一個dom元素均可視爲一個node節點,節點屬於節點類型,節點類型的屬性和方法以下:

var div = document.getElementById('xxx');//獲取一個id爲xxx的div節點。

div.nodeType;//1;  

nodeType表示節點的類型,必屬於下面12中之一:

Node.ELEMENT_NODE (1)
Node.ATTRIBUTE_NODE (2)
Node.TEXT_NODE (3)
Node.CDATA_SECTION_NODE (4)
Node.ENTITY_REFERENCE_NODE (5)
Node.ENTITY_NODE (6)
Node.PROCESSING_INSTRUCTION_NODE (7)
Node.COMMENT_NODE (8)
Node.DOCUMENT_NODE (9)
Node.DOCUMENT_TYPE_NODE (10)
Node.DOCUMENT_FRAGMENT_NODE (11)
Node.NOTATION_NODE (12)

div.nodeName;//DIV

div.nodeValue;//null

div.childNodes;//

  childNodes屬性是一個NodeList對象,類數組對象。有length屬性。它是基於DOM結構動態執行查詢的結果,所以DOM結構的變化可以自動反應在NodeList對象中。能夠經過方括號或item()方法訪問此對象中的節點。如:

  div.childNodes[0];

  div.childNodes.item(0);

  div.childNodes.length;

  另外注意,換行符和空白符會被有些瀏覽器認爲是一個text節點,能夠利用nodeType屬性過濾掉。

div.parentNode;

div.previousSibling;

div.nextSibling;

div.firstChild;

div.lastChild;

div.hasChildNodes();//無參數,有子節點返回true,無則返回false

div.ownerDocument;//該屬性指向頂端的表示整個文檔的 文檔節點#document

操做節點的方法:

div.appendChild(newNode);//在div的childNodes列表的末尾添加一個節點。返回值爲新增的節點newNode。若是newNode已是文檔的一部分了,那麼節點將從原來的位置轉移到新的位置。

div.insertBefore(要插入的節點, 做爲參照的節點);//若是參照節點爲null,新插入的節點位於div的childNodes的末尾。返回新插入的節點。

div.replaceChild(要插入的節點,被替換的節點);//返回被替換的節點。

div.removeChild(要移除的節點);//返回被移除的節點。

div.cloneNode(bool);//返回一個徹底相同的副本。參數爲true時執行深複製,爲false時執行淺複製。深複製複製節點以及整個子節點樹;淺複製只複製節點自己。

 

10.2, Document類型

document對象表示整個HTML頁面。

document.nodeType;//9

document.nodeName;//'#document'

document.nodeValue;//null

document.parentNode;//null

document.ownerDocument;//null

document.childNodes;//chrome中返回 [<!DOCTYPE html>, <!--html標籤前的一個註釋-->, <html>​…​</html>​]

document.documentElement;//始終指向頁面中的<html>元素。

document.body;//指向<body>元素。

document.doctype;// 指向 <!DOCTYPE html>

document.title;//獲取或設置title

document.URL;//獲取頁面完整的URL,不可設置

document.domain;//獲取頁面的域名,能夠設置,但有限制,以下:

document.referrer;//獲取鏈接到當前頁面的那個頁面的URL,不可設置

 查找元素

var hc = document.getElementsByTagName('div');//返回值類型爲HTMLCollection對象。訪問HTMLCollection對象中的元素方法有:

①hc[0];   ②hc['name'];  ③hc.item(1);    ④hc.namedItem('name');

如下屬性都返回HTMLCollection對象:

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

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

document.forms;//包含文檔中全部的<form>元素。

document.images;//包含文檔中全部的<img>元素。

document.write()/writeln();//在頁面呈現過程當中調用此方法則直接向網頁輸出內容。若是在文檔加載完畢後再調用此方法,那麼輸出的內容將會重寫整個頁面。

 

10.3, Element類型

全部的HMTL元素都是HTMLElement類型或其更具體的子類型來表示的。下表列出了全部的HTML元素以及與之關聯的類型,其中斜體表示已經不推薦使用了。

 

var div = document.getElementById('div');//獲取一個Element類型元素

Element類型的元素屬性:

div.nodeType;//1

div.nodeName;//元素標籤名

div.nodeValue;//null

div.tagName;//和div.nodeName同樣返回元素標籤名,語義更清晰

---------------------------------------

HTML元素的公認特性均可以做爲HTMLElement類型的屬性直接訪問或設置,如:

div.id;//獲取或設置元素的id

div.title;//說明

div.lang;//語言

div.dir;//方向

div.className;//類名,由於class是保留字

div.align;//對齊方式

-----------------------------------------------------------

div.getAttribute(屬性名);//獲取屬性,一般用來獲取自定義屬性。有兩類特殊的特性,經過屬性名直接訪問1和經過getAttribute()方法訪問結果不一樣:

一是:style特性。div.style返回的是一個對象,div.getAttribute('style')返回的是style特性值中包含的文本。

二是:相似onclick這樣的事件屬性。div.onclick返回一個JavaScript函數,div.getAttribute('onclick')返回代碼字符串。

div.setAttribute(屬性名,屬性值);//設置屬性,一般用來設置自定義屬性,而固有屬性直接用屬性名設置。

div.removeAttribute(屬性名);//移除屬性

div.attributes;//返回一個NameNodeMap對象,相似於NodeList,是一個‘動態’集合。包含div的全部屬性。

※建立元素

var div = document.createElement(tagName);//只有一個參數,即標籤名。返回新建的元素,能夠進一步添加屬性等,而後在添加到文檔樹中。

getElementById()只能在document對象上使用,其餘的好比getElementsByTagName();能夠用於任意的HTML元素,搜索時只會搜索此元素的後代元素(不包括此元素自己)

 

10.4,Text類型

設變量text爲一個Text類型的節點,則:

text.nodeType;//3

text.nodeName;//"#text"

text.nodeValue;//包含文本,能夠獲取或設置

text.data;//等於text.nodeValue;

text.length == text.nodeValue.length == text.data.length;節點中字符的數目

text.appendData(text);//將text添加到節點末尾

text.deleteData(offset, count);//從offset指定的位置開始,刪除count個字符

text.insertData(offset, text);//在offset指定的位置插入text

text.replaceData(offset, count, text);//用text替換從offset開始的count個字符

text.splitText(offset);//從offset指定的位置將當前文本節點分紅兩個文本節點。

text.substringData(offset, count);//提取從offset位置開始的count個字符。

建立文本節點:

var textNode = document.createTextNode(text);//接收一個參數:要插入節點的文本

若是連續插入兩個文本節點,它們不會合並依然是兩個節點,可是顯示時它們會連起來,中間不會有空格。

連續的兩個文本節點只會人爲生成,瀏覽器永遠不會解析出兩個兩個連續的文本節點。在文本節點的父節點上調用normalize()方法能夠合併相鄰的文本節點,此方法無參數。

 

10.5,Comment類型

註釋在DOM中經過Comment類型表示,其屬性以下:

cmt.nodeType;// 8

cmt.nodeName;// "#comment"

cmt.nodeValue;//註釋內容,不包括註釋符號<!---->

cmt.data;//等於cmt.nodeValue

cmt除了沒有splitText()方法以外,和Text類型屬性和方法徹底同樣。

var cmtNode = document.createComment("這是一條註釋");//建立註釋節點。沒什麼卵用

 

10.6,CDATASection類型

CDATASection類型只針對基於XML的文檔,表示的是CDATA區域。相似於Text類型,擁有除splitText()方法以外的全部文本節點類型的屬性和方法。

 

10.7, DocumentType:並不經常使用

var dt = document.doctype;//<!DOCTYPE html>,只能獲取不能設置

dt.nodeType;//10

dt.nodeName;//值爲doctype的名稱即:"html"

dt.nodeValue;//null

dt.name;//等於dt.nodeName,返回「html」

 

10.8,DocumentFragment類型

 

 

 10.9,Attr類型【沒什麼用】

說明一點:Attr類型就是html的屬性,也就是存在於元素的attributes屬性中的節點,如id,class,align等等。它們也是節點,但不被認爲是dom文檔樹的一部分。

----------------------------------------------------------------------------

10.10,動態添加腳本

建立動態腳本有兩種方法:插入外部文件 和 直接插入JavaScript代碼

①,插入外部文件,代碼以下:

<script>
  //能夠封裝起來 var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'xxx.js';//指定外部js文件 document.body.appendChild(script);//執行此句代碼以後,纔會下載外部文件 </script>

②,直接插入JavaScript代碼,代碼以下:

<script>
    /** * 以這種方式加載的代碼會在全局做用域中執行,腳本執行後當即可用。實際上, * 這樣執行代碼和在全局做用域中把相同的字符串傳遞給eval()是同樣的。 */
    var script = document.createElement('script'); script.type = 'text/javascript'; var code = "alert('hi');"; try { script.appendChild(document.createTextNode(code));//IE無效
    } catch (ex) {
     //針對IE的代碼
     //注意這裏的text屬性貌似是script標籤獨有的,像style標籤就沒有這個屬性。可是textContent屬性貌似有。 script.text
= code;//Safari3.0以前無效. } document.body.appendChild(script); </script>

 

10.11, 動態添加樣式:

和動態添加腳本相似,不一樣的地方是針對id的代碼

try{}catch(){

style.styleSheet.cssText = 「body{backgrouond-color:red}」;

}

10.12,建立表格

10.13,關於NodeList、NamedNodeMap、HTMLCollection

這三個集合都是「動態的」;換句話說,每當文檔結構發生變化時,它們都會更新。所以,它們始終都會保存着最新、最準確的信息。從本質上說,全部NodeList對象都是在訪問DOM文檔時 實時運行的查詢。例如,下面的代碼將致使無限循環:

var divs = document.getElementsByTagName('div'),

  i,

  div;

//divs是一個HTMLCollection,假設開始長度爲1.

for (var i = 0; i < divs.length; i++){

  //循環體中的divs.length每次都會更新

  div = document.createElement('div');

  document.body.appendChild(div);

}

 

11,DOM擴展

三部分擴展:Selectors API、HTML5 DOM擴展、專有DOM擴展

11.1,Selectors API:

querySelector(css選擇符);//經過document類型調用此方法時,會在文檔元素的範圍內查找匹配的元素。而經過Element類型調用此方法時,只會在該元素後代元素範圍內(並不包括該元素)查找匹配的元素。

querySelectorAll(css選擇符);//返回一個NodeList對象。

matchesSelector(css選擇符);//調用元素與該選擇符匹配,返回true,不然返回false。目前沒有瀏覽器支持此方法,但IE9+支持msMatchesSelector(),chrome和Safari5+支持webkitMatchesSelector(),FireFox3.6+支持mozMathcesSelector()。

11.2,Element Traversal 元素遍歷

爲解決元素間的空格也會返回文本節點的問題(<=IE9的不會,其他都會),Element Traversal API爲DOM元素新增5個屬性:

firstElementChild;//指向第一個子元素(不包括文本節點和註釋),firstChild的元素版。

lastElementChild;//指向最後一個子元素,lastChild的元素版。

previousElementSibling;//指向前一個同輩元素,previousSibling的元素版。

nextElementSibling;//指向後一個同輩元素,nextSibling的元素版。

childElementCount;//返回子元素的個數,length屬性的元素版。

 

11.3,HTML5 DOM擴展

11.3.1,與類(即class屬性)相關的擴充

getElementsByClassName("class1 class2");//參數爲一個或多個類名字符串,順序無所謂,返回同時包含多個類名的元素。能夠在document對象上調用,也能夠在元素上調用(只在其後代元素中查找).

classList屬性;//返回一個包含全部類名的集合,要取得每一個元素能夠使用item()方法也能夠使用方括號語法。此集合有以下屬性和方法:

  length;//類名的個數

  add(value);//將給定的字符串值添加到列表中。若是值已經存在,就不添加了

  remove(value);//從列表中刪除給定的字符串。

  toggle(value);//若是列表中存在給定的值,刪除它並返回false;若是沒有,添加它並返回true。

  contains(value);//給定列表中是否存在給定的值,存在返回true,不存在返回false.

11.3.2, 焦點管理

document.activeElement;//此屬性始終引用DOM中當前得到了焦點的元素。默認狀況下,文檔剛剛加載完成時,此屬性保存的是document.body元素的引用。

HTMLElement.focus()方法;//只能對HTML元素類型調用此方法,爲此元素獲取焦點。

document.hasFocus();//只能對document對象使用。用於肯定文檔是否得到了焦點。如此即可知道用戶是否是正在與頁面交互。

11,3,3; HTMLDocument :

※document.readyState;//可能的值有:

  loading:表示正在加載和解析文檔。

  interactive:文檔已被解析,但瀏覽器正在加載其中連接的資源(如圖像和媒體文件等)

  complete:文檔已被解析,全部資源也被加載完畢。

配合document.onreadystatechange事件使用。

※document.compatMode;//檢測頁面的渲染模式,有兩個值:

  "CSS1Compat":標準模式,即文檔最開始有<!DOCTYPE html>

  "BackCompat":混雜模式  / 怪異模式。即最開始沒有<!DOCTYPE html>

※document.head;//引用文檔的<head>元素。等同於document.getElementsByTagName('head')[0];

11,3,4,字符集屬性
document.charset;//獲取或設置文檔的字符集編碼

document.characterSet;//返回文檔的字符集編碼。這是一個只讀屬性。

document.defaultCharset;//獲取瀏覽器所使用的默認字符編碼

11.3.5,自定義數據屬性

<div data-name='tong'>myDiv</div>

div.dataset;//返回一個DOMStringMap對象 {name:"tong"}。能夠獲取或設置自定義屬性。

11.3.6,插入標記

div.innerHTML;//獲取或設置

div.outerHTML;//包含div自己,獲取或設置

div.insertAdjacentHTML(插入位置,要插入的HTML文本);//具體見下圖:

※性能與內存問題

 

 11.3.7,scrollIntoView(bool)方法:

此方法能夠在任何HTML元素上調用,效果是讓調用元素出如今窗口中。參數爲true則調用元素頂部和視口頂部儘量齊平;參數爲false則調用元素會盡量出如今視口中。

當頁面發生變化時,通常用此方法來吸引用戶的注意力。另外,爲某個元素設置焦點也會致使瀏覽器改動並顯示出得到焦點的元素。須要注意的是,對於input button等元素,默認是能夠得到焦點的,但通常元素如div等默認並不能得到焦點,須要給這些元素設置tabindex屬性以後才能獲取焦點

 

11.4,專有擴展: 還沒有被標準化的一些擴展,但支持的瀏覽器可能並很多。。

11.4.1,IE獨有的文檔模式 X-UA-Compatible,略

11.4.2,children屬性:

  和childNodes屬性相比,children屬性只包含元素子節點,而不包含文本和註釋節點.其餘都同樣。

11.4.3,contains(node)方法:

  祖先節點調用此方法,若是此節點包含給定node節點,返回true,不然返回false;

  還有一個方法能夠肯定節點間的關係:compareDocumentPosition();//此方法是在DOM level3(第三版)中定義的。

  refNode.compareDocumentPosition(otherNode);//參考節點refNode; 給定節點otherNode,返回一個表示兩個節點間關係的位掩碼(bitmask),位掩碼值以下:

11.4.4,插入文本:innerText 和 outerText 

innerText屬性:將元素子節點的全部文本拼接起來。能夠經過innerText屬性過濾HTML標籤,以下:

  div.innerText = div.innerText;//如此即可過濾掉HTML標籤。

  相似屬性有textContent屬性(DOM Level3定義的)。

outerText屬性:讀取時和innerText返回值同樣,設置時則不同。

 

 

 12,DOM2 和 DOM3 

12.1,DOM變化

※document.importNode(須要引入的節點[, 是否複製子節點]);//此方法的用途是從一個文檔中取得一個節點,而後將其導入到另外一個文檔,使其成爲這個文檔結構的一部分。

※要肯定文檔的歸屬窗口,能夠使用一下代碼:

var parentWindow =  document.defaultView || document.parentWindow; IE只支持後者

※要訪問內嵌框架的文檔對象,除了使用以前的frames集合以外,還能夠經過元素直接取得這個文檔對象,使用如下代碼:

var iframe = document.getElementById('myIframe');

var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

 

12.2,樣式[CSS]

12.2.1,訪問元素樣式

※設置HTML元素的樣式用三種方式:外部樣式表、內嵌樣式表、行內樣式表。

※var div = document.getElementById('myDiv');

div.style;//style屬性包含着行內樣式表中指定的全部樣式信息,但不包括與外部樣式表或內嵌樣式表經層疊而來的樣式。此屬性是CSSStyleDeclaration的實例。

div.style.color;//能夠獲取或設置div的樣式。注意css的float屬性,因爲float本是javascript中的保留字。DOM2規定其在樣式對象上相應的屬性名是:cssFloat。IE支持的則是styleFloat.不過經測試如今的瀏覽器也支持直接的float屬性。

div.style還有一些屬性和方法,詳述以下:

 

※計算的樣式

style屬性只包含行內樣式,若是要取得元素從其餘樣式表(內嵌的或外部的)層疊的樣式,能夠使用document.defaultView對象的getComputedStyle()方法。

document.defaultView.getComputedStyle(元素[,僞元素字符串]);//接收兩個參數:要取得計算樣式的元素和一個可選的僞元素字符串(如「:after」)。若是不須要僞元素信息,能夠設置爲null。同style屬性同樣,此方法返回一個CSSStyleDeclartion對象。

IE不支持getComputedStyle()方法,但它有一個相似的概念。在IE中,每一個具備style屬性的元素還有一個currentStyle屬性。這個屬性是CSSStyleDeclaration的實例,包含當前元素所有計算後的樣式。var comptedStyle = div.currentStyle;

注意:全部的計算樣式都是隻讀的,不能作修改

12.2.2,操做樣式表

CSSStyleSheet類型表示的是樣式表,包括經過<link>元素包含的外部樣式表和經過<style>元素定義的內嵌樣式表。注意,不包含行內樣式。每一個<link>元素或<style>元素對應一個CSSStyleSheet對象。對於HTMLLinkElement或HTMLStyleElement而言,能夠直接經過sheet屬性獲取此元素對應的CSSStyleSheet對象。IE不支持sheet而是支持styleSheet屬性。

  var link = document.getElementsByTagName('link')[0];

  var sheet = link.sheet || link.styleSheet;

document.styleSheets;//返回文檔的全部樣式表的集合。集合中包含全部的CSSStyleSheet類型。

CSSStyleSheet中的屬性除了disabled是可讀可寫的以外(將此屬性設置爲true能夠禁用此樣式表),其餘的屬性都是隻讀的。

12.2.3,元素大小

※偏移量

能夠經過HTMLElement的如下屬性獲取元素的偏移量:這些屬性都是隻讀的。

 

注: offsetParent屬性最外層是<body>元素, 若是對body元素使用offsetParent屬性則返回null

※,客戶區大小

元素的客戶區大小指的是元素內容及其內邊距padding所佔空間的大小。能夠經過HTMLElement元素的如下屬性獲取客戶區大小:這些屬性是隻讀的。

clientWidth;//內容寬度加上左右內邊距

clientHeight;

cilentTop;//實際指的是上邊框的大小。

clientLeft;//實際指 的是左邊框的大小。

註釋:若經過此屬性獲取document.body或document.documentElement元素的大小,由於有滾動條的存在,返回值並非咱們想要的結果。請看下面的滾動大小。

※,滾動大小

注意:對於不包含滾動條的頁面而言,scrollWidth / scrollHeight 和 clientWidth / clientHeight之間的關係並不清晰。沒有什麼規律可言。在肯定文檔的總高度時,必須取得scrollWidth/clientWidth 和 scrollHeight/clientHeight中的最大值。

※,肯定元素大小:

HTMLElement.getBoundingClinetRect();//無參數,返回一個矩形對象,包含6個屬性:

width: 元素的寬度,包含邊框。

height:元素的高度,包含邊框。

top: 上邊框距離座標原點的垂直距離。

bottom:下邊框距離座標原點的垂直距離。

left:左邊框距離座標原點的水平距離。

right:有邊框距離 座標原點的水平距離。

座標原點通常都是指(0,0)座標點,但IE8及以前設爲(2,2)。另外這些屬性會受到滾動的影響。

12.3 遍歷 Traversal

"DOM2級遍歷和範圍"模塊定義了兩個能夠遍歷DOM結構的類型:NodeIterator 和 TreeWalker。這兩個類型都能基於給定起點對DOM結構執行深度優先(depth-first)的遍歷操做。

※NodeIterator:

var iterator = document.createNodeIterator();//4個參數

關於whatToShow參數:

關於filter參數:filter參數能夠是個對象或一個函數,具體以下:

    //filter 爲對象時:
    var filer = { acceptNode: function (node) { return node.tagName.toLowerCase() == 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } }; //filter 也能夠是一個與acceptNode()方法相似的函數:
    var filter = function (node) { return node.tagName.toLowerCase() == 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; };

 

iterator有兩個方法:nextNode() 和 previousNode()。這裏這個指針有點很差理解。

※TreeWalker

var walker = document.createTreeWalker();// 同上4個參數。惟一不一樣的是第三個參數,見下說明:

walker除了有nextNode()和previousNode()方法以外,還有以下屬性和方法:

currentNode屬性:獲取或設置當前所在節點位置。

parentNode();//返回當前節點的父節點

firstChild();//

lastChild();//

nextSibling();//下一個同輩節點

previousSibling();//

 

12.4,範圍 Range

"DOM級遍歷和範圍「模塊定義了」範圍「接口。

因爲比較繁瑣,估計用到的也少,就不記錄了。

 

13,事件 Event

13.1,事件處理程序:

相應某個事件的函數叫作事件處理程序(又叫事件偵聽器)。事件處理程序的名字以"on"開頭。事件處理程序有如下幾種:

①,HTML事件處理程序,如,

<div id='div' onclick="console.log(event);">Hello World!</div>

注意:

  這裏onclick的值是函數調用,函數也能夠是在<script>標籤或外部文件中定義的。

  this值等於事件的目標元素。

  事件對象event保存在變量event中,能夠直接訪問,不用本身定義,也不用從函數的參數列表中讀取。

②,DOM0級事件處理程序,如

div.onclick = function(){do something};

注意:

  以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。

  此時,事件處理程序是在元素的做用域中運行的,即this引用的是當前元素

  若是對同一元素添加多個相同類型(如都爲click事件)的事件處理程序,則後面會覆蓋前面。

  能夠經過以下方法刪除事件處理程序:div.onclick = null;

DOM2級事件處理程序

"DOM2級事件"定義了兩個方法,用於指定和刪除事件處理程序:addEventListener()和removeEventListener().

div.addEventListener('click', 匿名函數或函數名,boolean);//boolean爲false表示表示在冒泡階段調用此事件處理程序,爲true則表示在捕獲階段調用事件處理程序。

經過addEventListener()添加的事件處理程序只能使用removeEventListener()來刪除。移除時傳入的參數與添加時使用的參數必須相同。也就意味着若是addEventListener()中是經過匿名函數處理的,則沒法被移除。

注意:

  DOM2級事件處理程序也是在其依附的元素的做用域中運行。

  此種方法能夠爲同一元素添加多個相同類型(如都爲click事件)的事件處理程序,事件會以它們被添加的順序依次觸發。

④,IE的事件處理程序,

IE實現了DOM中相似的兩個方法:attachEvent()和detachEvent()。兩個方法接收相同的兩個參數:事件處理程序名稱,如'onclick'和事件處理程序函數。是在冒泡階段調用。刪除事件時也要求參數必須相同,匿名函數也沒法被刪除。

注意:  

  此時事件處理程序是在全局做用域中運行的,所以this的值爲window。

  此種方法也能夠爲同一個元素添加多個事件處理程序,可是觸發的順序和上面相反。

13.2, 事件對象: event對象 【IE的事件對象有所不一樣】

各類不一樣事件的 事件對象 所共有的屬性和方法以下:

其中:對象this始終等於currentTarget的值,而target則只包含事件的實際目標(也就是同心圓中最中間的那個元素)。

13.4,事件類型

13.4.1,UI事件(User Interface 用戶界面)

※load事件(5種觸發條件):

①當頁面徹底加載後(包括全部圖像、javascript文件、css文件等外部資源)在window上觸發,onload事件處理程序有兩種方式:

  window.onload = function(event){}

  <body onload='alert("hello");'>//在window 上發生的任何事件均可以在<body>元素中經過相應的特性來指定(下同)。這只是爲了向後兼容的一種權宜之計(由於在HTML中沒法訪問到window對象)

②在圖像加載完畢後在圖像上觸發。

  注意一點:當建立新的<img>元素時,能夠爲其指定一個事件處理程序,以便圖像加載完畢後給出提示。此時,最重要的是要在指定src屬性以前先指定事件。新圖像元素不是在將元素添加到文檔後纔開始下載,而是在設置了src屬性就會開始下載。若是在圖片已經下載好了以後解析器纔讀到img的onload事件,則這個onload事件不會起任何做用。能夠以下驗證:在window.onload函數中寫img.onload事件是沒有做用的! 【若是load事件放在以後,問題也不大,只要不打斷點cpu的解析頻率確定大於圖片加載速度,不過仍是按規範理論走的好。】

③<script>元素也會觸發load事件,以便肯定動態加載的JavaScript文件是否加載完畢。與圖像不一樣,只有在設置了<script>元素的屬性並將該元素添加到文檔後,纔會開始下載JavaScript文件。

④,當全部框架都加載完畢後,在框架集上面觸發。

⑤,當嵌入內容加載完畢後,在<object>元素上面觸發。

 

※unload事件:window.onunload

關閉窗口 或者 刷新窗口 或者 在當前窗口打開超連接(在新標籤中打開超連接不會觸發unload),就會發生unload事件。能夠用以下代碼驗證:

 

    window.addEventListener('unload', function (e) {
        localStorage.clear();
    });

 

而利用這個事件最多的狀況就是清除引用,以免內存泄露。關於unload事件注意一點:

既然unload事件是在一切都被卸載以後才觸發,那麼在頁面加載後存在的那些對象,此時就不必定存在了。此時,操做DOM節點或者元素的樣式就會致使錯誤。好比下面的代碼:

 

    window.addEventListener('unload', function (e) {
        alert("Unloaded");//並不會有效果,刷新很快時,能夠看到瀏覽器提示Block alert(...) during unload;
     console.log(333);//刷新很快時,能夠看見333 });

 

 

 

※resize事件: 在窗口或框架上觸發。window.onresize

※scroll事件:能夠在帶有滾動條的元素上使用,也能夠在window對象上使用。

13.4.2,焦點事件

注意一點:像div等這些元素默認不會獲得焦點,設置了tabindex屬性以後才能夠獲取焦點。

※focus / blur事件:不會冒泡。

※focusin / focusout 事件:會冒泡。

13.4.3,鼠標事件

DOM3級事件中定義了9個鼠標事件,以下

鼠標事件對象中保存着一些有用的屬性:

※鼠標指針位置屬性:

event.clientX: 鼠標指針在視口中的水平座標。視口即意味着不考慮滾動。

event.clinetY:

event.pageX:鼠標光標在頁面中的位置。頁面而非視口,即意味着考慮了滾動。

event.pageY:

event.screenX:鼠標光標距離左側屏幕的座標。

event.screenY

※ 修改鍵屬性,即按下鼠標時是否按了shift,ctrl, alt, meta鍵。

event.shiftKey / ctrlKey / altKey/ metaKey;//按了則返回true,不然返回false.

※鼠標按鈕

event.button ;// 0:鼠標左鍵, 1:鼠標中鍵,2:鼠標右鍵

13.4.4,滾輪事件

mousewheel事件【firefox不支持】:這個事件能夠在任何元素上觸發,最終會冒泡到document(IE)或window對象。

滾輪事件的event對象中有一個特殊的wheelDelta屬性,當用戶向前滾動滾輪時,wheelDelta是120的倍數;當用戶向後滾動鼠標滾輪時,wheelDelta是-120的倍數。

firefox支持一個相似事件:DOMMouseScroll,經測試只能經過addEventListener()添加有效。當向前滾動時,其event.detail是3的倍數,不然是-3的倍數。

13.4.5,鍵盤事件

說明一點:當元素可以獲取焦點時,在獲取焦點的狀況下,鍵盤事件會生效。

keydown和keypress的區別是:

按下一個按鍵時,首先會觸發keydown事件,而後是keypress事件。

keydown事件能夠由鍵盤上的任意一個鍵觸發,其event.keyCode返回的是鍵盤的代碼,每一個按鍵都對應惟一一個代碼,包括esc鍵。

keypress事件只能由字符鍵觸發,keyCode返回的是ASCII碼。event.charCode是此事件獨有的屬性,返回值也是ASCII碼。

 

鍵盤事件的event對象的屬性:

※鍵碼

event.keyCode

對於keyup和keydown事件,每一個按鍵的對應鍵碼以下:

※event.shiftKey / ctrlKey / altKey / metaKey;//若是按下的是這些鍵,返回true,不然返回fasle.

 

13.4.6, 文本事件

「DOM3級事件"引入了一個新的事件: textInput。[firefox不支持]

此事件只能在可編輯區域才能觸發。

event.data;// 具體的字符,如"s", "S"

 

13.4.7,複合事件  【DOM3級】

用於處理IME(Input Method Editor輸入法編輯器)輸入序列。好比當開啓中文輸入法後輸入字符則會觸發這些事件。

13,.4,8,變更事件 mutation 【DOM2級】

當文檔的結構發生變化時會觸發這些事件。如增長 移除節點等等

13.4,9,HTML5事件

※contextmenu 事件

能夠設置點擊鼠標右鍵時彈出的菜單。能夠在任何元素上設置,最終會冒泡到document。此事件屬於鼠標事件,其event對象包含與光標位置有關的全部屬性。一般使用onclick事件處理程序來隱藏該菜單(瀏覽器默認就是如此)。 

一個例子: 

<!DOCTYPE html>
<html>
<head>
    <title>ContextMenu Event Example</title>
</head>
<body>
<div id="myDiv">Right click or Ctrl+click me to get a custom context menu.
    Click anywhere else to get the default context menu.
</div>
<ul id="myMenu" style="position:absolute;visibility:hidden;background-color:
silver">
    <li><a href="http://www.nczonline.net">Nicholas's site</a></li>
    <li><a href="http://www.wrox.com">Wrox site</a></li>
    <li><a href="http://www.yahoo.com">Yahoo!</a></li>
</ul>
</body>
<script>
    window.addEventListener("load", function (event) {
        var div = document.getElementById("myDiv");
        document.addEventListener("contextmenu", function (event) {
            event.preventDefault();
            var menu = document.getElementById("myMenu");
            menu.style.left = event.clientX + "px";
            menu.style.top = event.clientY + "px";
            menu.style.visibility = "visible";
        }, false);
        document.addEventListener("click", function (event) {
            document.getElementById("myMenu").style.visibility = "hidden";
        }, false);
    }, false);
</script>
</html>
View Code

※beforeunload事件

當刷新或離開頁面以前會觸發此事件,能夠告訴用戶此頁面將被卸載,詢問用戶是否真的要關閉頁面。爲了顯示這個彈出對話框,必須將event.returnValue的值設置爲要顯示給用戶的字符串,同時做爲函數的值返回。例子以下:

    window.addEventListener('beforeunload', function (event) { var msg = 'you wanna leave?'; event.returnValue = msg; return msg; }, false);

※DOMContentLoaded事件

window的load事件會在頁面中的一切都加載完畢時觸發。而DOMContentLoaded事件則在造成完整的DOM樹以後就會觸發,不理會圖像,JavaScript文件,CSS文件或其餘資源是否已經下載完畢。此事件能夠在window或document上添加。

※readystatechange事件

document.onreadystatechange = function(e){alert(document.readyState);}// loading  interactive complete

※pageshow 和 pagehide事件

※hashchange事件

window.onhashchange;

event.oldURL;//舊的網址

event.newURL;//新的網址

13.4.10,設備事件[手機等]

※orientationchange事件

window.onoritationchange = function(){};

window.orientation;//此屬性有三個值:0,90,-90

※觸摸事件

在document或window上。

※手勢事件

貌似只有ios的safari支持。

13.5,內存和性能

 

13.5.1,事件委託

事件委託利用了事件冒泡原理。能夠減小時間處理程序的數量。對於同一類型的事件,事件委託技術只須要爲整個頁面指定一個事件處理程序,原則是在DOM樹中儘可能最高的層次上添加一個事件處理程序,常常是在document對象上添加。經過event.target識別具體元素。

13.5.2,移除事件處理程序

在不須要的時候移除事件處理程序,也能夠回收內存。有兩種狀況會形成「空事件處理程序」:

①,從文檔中移除帶有事件處理程序的元素時。好比,removeChild() 和 replaceChild(),更多的是使用innerHTML替換頁面某一部分的時候。好比:

②卸載頁面的時候。

13.6,模擬事件

即不經過用戶操做交互就能夠觸發事件。以前的作法是經過 createEvent() 和 dispatchEvent()來模擬[IE有本身的方法],如今是經過事件構造器如 var event = new KeyboardEvent(typeArg, KeyboardEventInit);

https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent

 

 

14.0表單腳本

※document.forms;//取得頁面中全部的表單。能夠經過數值索引或name值取得特定的表單。

※document.forms[0].elements;//取得頁面中某個表單的全部控件。

※HTMLFormElement獨特的屬性和方法以下:

form.submit();//不會觸發onsubmit事件

 

form.reset();//會觸發onreset事件

※表單獨有事件

form.onsubmit

form.onreset

form.onchange;//

 

14.1,表單字段,如input button select 等等。

※提交按鈕能夠使用圖像,以下:

<input type='image' src='xxx.gif' />

※表單字段(如input ,button, select等)共有的屬性和方法

 

HTML5表單字段中新增了一個autofocus屬性。

※,默認狀況下,只有表單字段能夠得到焦點。對於其餘元素而言,若是先將其tabIndex屬性設置爲-1,而後在再調用focus()方法,也可讓這些元素得到焦點。只有Opera不支持這種技術。

※表單字段的共有事件:

14.2,文本框腳本:<input> 和 <textarea>

※<input type='text' size='11' maxlength='20' />;//size表示能夠顯示的字符數,maxlength表示最多接收的字符數。

※textbox.select();//表單元素的select方法,用於選中文本框中的全部文本。

※textbox.onselect = function(){};//選擇事件,在選擇了文本框中的文本時觸發。

※textbox.selectionStart / textbox.selectionEnd;//兩個屬性,用於取得所選文本 。

※textbox.setSelectionRange(start,end);//

14.3, 過濾輸入

※屏蔽字符:響應向文本框中插入字符的操做是keypress事件,所以能夠經過阻止這個事件的默認行爲來屏蔽某些字符。代碼以下:

 

    var textbox = document.getElementById("textbox");
    textbox.onkeypress = function (e) {
//        console.log(e.charCode);
        //只容許數字輸入,其餘可輸入字符都屏蔽掉
        if (!/\d/.test(String.fromCharCode(e.charCode))) {
            e.preventDefault();
        }
    }

※操做剪切板[注意:這個不僅是 針對文本框,其餘元素也能用這些事件]

6個剪切板事件:

 

※一些用於驗證的屬性和方法

textbox.checkValidity();//檢測字段是否有效

textbox.validity;

form.novalidate;//設置表單不進行驗證。能夠在HTML元素中添加這個字段,另外在提交按鈕上添加formnovalidate屬性能夠在此按鈕提交時不進行驗證,但在其餘按鈕上提交正常驗證。

14.4,選擇框腳本:<select>與<option>

※HTMLSelectElement類型有以下屬性和方法:

 

※HTMLOptionElement有以下屬性和方法:

 

14.5,表單序列化

表單字段數據是如何發送給服務器的:

 

14.6,富文本編輯

※ 所謂富文本編輯就是在網頁中編輯內容,就像博客園的隨筆功能同樣。有兩種方法實現富文本編輯:

①,使用html元素的contenteditable屬性[布爾屬性]。這個屬性能夠給頁面中的任何元素,而後就能夠編輯此元素。經過javaScript能夠設置這個屬性的值,如,div.contentEditable = true / false / inherit

②,在頁面中嵌入一個包含空HTML頁面的的iframe。經過設置designMode屬性[注:designMode屬性是屬於document對象的。],這個空白的HTML頁面能夠被編輯,而編輯對象則是該頁面<body>元素的HTML代碼。designMode有兩個可能的值:"off"[默認值] 和 "on"。在設置爲‘on'時,整個文檔就能夠編輯。能夠爲空白頁面應用CSS樣式。但要注意一點,designMode屬性只有在頁面徹底加載以後才能設置,所以須要使用onload事件處理程序來設置designMode。以下:

window.onload = function(){frames['iframe'].document.designMode = 'on';}

※操做富文本

操做富文本通常是經過按鈕執行的(固然,不經過按鈕也能夠,可是須要先把焦點聚焦到須要執行編輯的地方)。就像博客園上面的編輯按鈕同樣。主要是經過document.execCommand()方法執行預約義的命令。此方法接收三個參數:命令名稱,表示瀏覽器是否應該爲當前命令提供用戶界面的一個布爾值,和執行命令必須的一個值(若是不須要能夠設爲null)。通常第二個參數都設爲false。下圖列出了被多數瀏覽器支持的命令(注,cut / copy / paste並無被支持,通常瀏覽器都是用快捷鍵操做的):

案例代碼以下:

 

   <iframe src="test.html" name="xxx">iframe</iframe>
    <button id="myBtn">按鈕紐</button>
<script>
    var btn = document.getElementById("myBtn"); window.onload = function (e) { var xx = frames['xxx'].document; xx.designMode = 'on'; btn.onclick = function () { xx.execCommand('bold', false, null) } } </script>

 

另外:此方法一樣也適用於頁面中contenteditable屬性爲true的區塊,只要在當前窗口的document對象上應用execCommand()方法便可。

其餘一些與富文本操做命令有關的的方法:

document.queryCommandEnabled(「bold");//此方法接收一個參數,即要檢測的命令。此方法用於檢測傳入的命令是否可用(即瀏覽器是否支持此命令)。

document.queryCommandState("bold");//同上,接收一個命令參數,用於肯定是否已經將指定命令應用到了選擇的文本。能夠利用這個方法更新按鈕的狀態。

document.queryCommandValue('bold');//同上,接收一個命令參數,用於取得執行命令時傳入的值。

※富文本選區:略

※富文本做爲表單提交:

富文本內容是用iframe實現的,因此富文本編輯器中的HTML不會被自動提交給服務器。可是咱們能夠變相提交。即提交表單以前,從iframe中提取出HTML,並將其插入到隱藏的字段中。代碼以下:textbox.value = frames['xxx'].document.body.innerHTML;

 

15,使用canvas畫圖

var cvs = document.getElementById('canvas');

var ctx = document.getContext('2d');

var imgURI = cvs.toDataURL("image/png");//傳入一個MIME類型。用於導出在canvas元素上繪製的圖像。

15.1,2d 省略

15.2, WebGL:是針對canvas的3D上下文。非W3C指定的標準,而是由Khronos Group指定的。WebGL是基於OpenGL ES 2.0制定的。www.opengl.org  www.learningwebgl.com  http://www.hiwebgl.com/?p=42  {這是中文教程,很好。想學能夠看看這個網站}

※ArrayBuffer:數組緩衝類型。每一個ArrayBuffer對象表示的是內存中指定的字節數,但不會指定這些字節用於保存什麼類型的數據。經過ArrayBuffer所能作的,就是爲了未來使用而分配必定數量的字節。例如,var buffer = new ArrayBuffer(20);//在內存中分配20B。

※var gl = cvs.getContext('webgl');//獲取3d上下文。不只瀏覽器要支持,顯卡也要支持。

 

16,HTML5腳本編程

16.1,跨文檔消息傳遞 cross-document messaging

※只要 協議,主機,端口三者有一個不一樣,則就是跨文檔。

※window.postMessage(msg, origin);//第一個參數是消息字符串,第二個參數表示消息接收方來自哪一個域的字符串。關於此方法說明以下:

①,此方法只能在dom加載完畢後使用,不然會出現意想不到的錯誤。即在window.onload 或btn.onclick之類的事件中使用。

②調用此方法的是window對象,並且須要向哪一個window傳遞消息就在哪一個window上調用此方法

③接收消息的文檔須要使用window對象的message事件,此事件是異步觸發的。事件對象event包含三個重要屬性:

  ■data:接收到的消息字符串,便是postMessage的第一個參數

  ■origin:發送消息的文檔所在域

  ■source:發送消息的文檔的window對象的代理。注意這個只是window對象的代理,並不是實際的window對象。也就是說,不能經過這個代理對象訪問window對象的其餘任何信息。這個代理對象主要用於向發送消息的文檔反饋信息,即在此window代理對象上調用postMessage()方法。

一個例子以下:

http://localhost/   下的發送消息的文檔代碼:

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<iframe src="http://tong/html/sss.html" name="ifr" id="ifr">iframe</iframe>
<button id="btn">GOO</button>
<div id="div">這裏顯示反饋消息</div>
</body>
<script type="text/javascript">
    var btn = document.getElementById('btn'); var div = document.getElementById('div'); //鼠標點擊事件來發送消息
    btn.onclick = function (e) { //postMessage()的第二個參數和iframe中的src必須一致。
        frames['ifr'].postMessage('send a message to iframe["ifr"]', 'http://tong'); }; // //或者在DOM加載完畢後就發送消息 // window.onload = function () { // frames['ifr'].postMessage('send a message to iframe["ifr"]', 'http://tong'); // };

    //接收反饋信息
    window.onmessage = function (e) { div.innerHTML = e.data; } </script>
</html>

 

http://tong/  下接收消息的文檔sss.html代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body> 這是框架sss.html </body>
<script>
    //接收消息
    window.onmessage = function (e) { document.body.innerHTML = e.data; //向發送消息的文檔反饋信息
        e.source.postMessage('Received', 'http://localhost') } </script>
</html>

 16.2,原生拖放

16.3,媒體元素:<video>和<audio>標籤

16,4,歷史狀態管理:[HTML權威指南中27.6.2章節]

主要做用是改變同一個頁面的狀態(如查詢字符串的改變,hash的改變等等不會新增頁面的頁面內部的變化)時,能夠使用後退或前進按鈕在不一樣的狀態間切換。具體細節略。

pushState() / replaceState()

 

17,錯誤處理和調試

※try{...} catch(error){...} finally{...}

catch和finally只要有一個便可,不管如何finally子句都會執行,即便在try或catch語句中包含return語句都不會阻止finally子句的執行。

此函數返回0,若是finally子句中沒有return語句則返回2.

※錯誤類型:7種

①Error;//基類型,其它錯誤都繼承自該類型。此類錯誤不多見,主要目的是工開發人員拋出自定義錯誤。

②EvalError;//和eval()函數有關的異常。瀏覽器並無按標準執行,遇到這類錯誤的可能性極小。

③RangeError;//數值超出相應範圍時觸發。如:var a = new Array(-2);

④ReferenceError;//在找不到對象的狀況下觸發。一般在訪問不存在的變量時就會發生這類錯誤。

⑤SyntaxError;//語法錯誤。把語法錯誤的JavaScript字符串傳入eval()函數時,就會致使此類錯誤。在eval()函數以外的語法錯誤會致使JavaScript代碼當即中止執行。

⑥TypeError;//

⑦URIError;///使用encodeURI()或decodeURI()函數時,若是URI格式不正確,就會致使URIError。

利用不一樣的錯誤類型:

※拋出錯誤

throw操做符,用於隨時拋出自定義錯誤。throw的值能夠是任何類型。在遇到throw操做符時,代碼會當即中止執行。僅當有try-catch語句捕獲到被拋出的值時,代碼纔會繼續執行。

throw 1234;//1234

throw true;//true

經過使用某種內置錯誤類型,能夠模擬瀏覽器錯誤。每種錯誤類型接收一個參數,即實際的錯誤消息。

throw new RangeError('error occured');//模擬瀏覽器拋出錯誤

還能夠經過原型鏈建立自定義錯誤類型,此時須要爲新建立的錯誤類型指定name和message屬性。例子以下:

    function CustomError(msg) { this.name = 'CustomeError'; this.message = msg; } CustomError.prototype = new Error(); throw new CustomError('My Message');//Uncaught CustomeError: My Message 

 

※錯誤事件

window.onerror = function(msg,url,line){...};//這種DOM0級事件處理程序處理錯誤事件時,參數不是event對象,而是三個分別表示錯誤消息,文檔位置和行號的參數。可是若是用DOM2級事件處理程序(即addEventListener()),就只傳入一個event對象了。

錯誤事件對圖像也適用。不管是DOM0仍是DOM2級事件處理程序都是隻傳入一個event對象。

※ 記錄錯誤日誌(一個技術:利用img對象)

 

※錯誤調試

console對象的一些方法:

 

20,JSON

JSON:JavaScript Object Notation。JSON是一種數據格式,而不是一種編程語言。不少語言都有針對JSON的解析器和序列化器。

20.1,語法

20.1.1,簡單值:

JSON的簡單值如:5,「hello world」,true,null等等。注意一點,JSON的字符串必須用雙引號(單引號會致使語法錯誤)

20.1.3,對象:

JSON中的對象,如:{"name":"tong","age":11}。注意一點:JSON對象中的屬性必須加雙引號【JavaScript中的對象字面量中的屬性能夠加也能夠不加】。

20.1.4,數組:

JSON中的數組,如:[25, "hi", true]。

 

20.2,解析與序列化

※,早期JSON解析器基本是使用eval()函數。ECMAScript 5對解析JSON的行爲進行了規範,定義了全局對象JSON

20.2.1,JSON對象:

※序列化:JSON.stringify();//接收一個JavaScript對象,返回序列化以後的JSON字符串。此方法還能夠接收另外兩個參數,具體見下面序列化選項。

※解析:JSON.parse();//接收一個JSON字符串,將其解析爲原生的JavaScript值。注意,若是不是把JSON字符串保存在變量中傳給此函數,而是直接把JSON字符串傳進去,則須要注意,如:JSON.parse('"Hello World"');//JSON字符串是」hello world「,可是傳進去的時候還須要用單引號把它圍起來。又如,JSON.parse("[1,3,4]");//數組須要用單引號或雙引號圍起來,對象也是。此方法還能夠接收另一個參數,具體見下面解析選項。

※序列化選項:

JSON.stringify();//除了第一個參數以外,還能夠接收另外兩個可選參數。第二個參數是個過濾器,能夠是一個數組,也能夠是個函數。第三個參數是個選項,表示是否在JSON字符串中保留縮進。

①過濾器

若是過濾器參數是個數組,那麼JSON.stringify()的結果中將值包含數組中列出的屬性。例子以下:

 

    var o = {"a": "aa", b: 'bb', c: 'cc'}; var jsonText = JSON.stringify(o,["a",'b']); console.log(jsonText);//{"a":"aa","b":"bb"}

 

若是第二個參數是函數,此函數接收兩個參數:屬性名和屬性值。具體用法見例子:

    var o = {"a": "aa", b: 'bb', c: 'cc'};
    var ss = JSON.stringify(o, function (key, value) {
        /**
         * 這裏注意,對象o中的鍵和值會依次傳給此函數,可是第一次傳的鍵爲空字符串,
         * 而值爲整個對象o。
         */
//        console.log('GOD',value);
        switch (key) {
            case "a":
                return value.toUpperCase();
                break;//無關緊要
            case "b":
                return undefined;//無效的JSON值,返回的JSON字符串中將跳過此屬性(b)。
            default:
                /**第一次傳值給函數時直接到這裏,而後原始的整個對象(o)就會被替換爲這裏的返回值,
                 * 而後再用這個返回值依次傳值給函數。因此這裏的返回值不能亂用,直接用value就行
                 */
                return value;
        }
    });
    console.log(ss);//{"a":"AA","c":"cc"}

②字符串縮進

第三個參數用於控制結果中的縮進和空白符。此參數能夠是數值或字符串。若是是數值,表示結果字符串中每一個級別縮進的空格數。若是是字符串,則這個字符串將在JSON字符串中被用做縮進字符(再也不使用空格)。另外注意,不管數值仍是字符串,最大都是10,超過10都會被視爲10。例子以下:

 

    var o = {"a": "aa", b: {bb:'bbb',bbb:"bbbb"}, c: 'cc'};
    var jsonText1 = JSON.stringify(o,null,4);
    var jsonText2 = JSON.stringify(o,null,'----');
    console.log(jsonText1);
    console.log(jsonText2);

 

③toJSON()方法:

toJSON()方法也是JSON.stringify()方法的一部分。能夠爲任何對象添加toJSON()方法,而後調用對象上的toJSON()方法,返回其自身的JSON數據格式。具體見以下描述:

一個例子:

    var book = { "title": "Professional JavaScript", "authors": [ "Nicholas C. Zakas" ], edition: 3, year: 2011, toJSON: function () { return this.title; } }; var jsonText = JSON.stringify(book); console.log(jsonText);//"Professional JavaScript"

※,解析選項

JSON.parse()方法也但是接收另外一個參數,這個 參數和stringify()的第二個參數爲函數時相似,被稱爲還原函數。

日期字符串被還原成了日期對象。

 

21,Ajax 和 Comet 

※,ajax技術就是 無需刷新頁面便可從服務器取得數據,但數據不必定是XML數據,ajax通訊與數據格式無關。

 

22,高級技巧

22.1,高級函數

22.1.1, 安全的類型檢測

22.1.2,做用域安全的構造函數

22.1.3,惰性載入函數(兩種方法)

22.1.4,函數綁定(以前講到過,就是bind()函數的使用)

22.1.5,函數柯里化

 

22.2,防篡改對象

對於對象屬性,前面介紹過如何手工設置每一個屬性的[[Configrable]]、[[Writable]]等等特性,以改變屬性的行爲。一樣,ECMAScript5也增長了幾個方法用以指定對象的行爲。

注意:一旦把對象定義爲防篡改,就沒法撤銷了。若是不是這樣,定義防篡改也就沒什麼意義了。

22.2.1,不可擴展對象

Object.preventExtensions(obj);//obj對象將不能被添加新的屬性和方法,但已有屬性仍能夠修改和刪除。

Object.isExtensible(obj);//肯定對象是否能夠擴展。

22.2.2,密封的對象[sealed object]

密封對象不可擴展,並且已有成員的[[Configurable]]特性會被設置爲false。這就意味着不能刪除屬性和方法, 也不能使用Object.defineProperty()把數據屬性修改成訪問器屬性,或者把訪問器屬性修改成數據屬性。可是密封對象的屬性值是能夠修改的。

Object.seal(obj);//將對象密封。

Object.isSealed(obj);//肯定對象是否被密封了。密封對象也是不可擴展的。

22.2.3,凍結的對象[frozen object]

最嚴格的防篡改級別是凍結對象。凍結對象即不可擴展,又是密封的。並且對象數據屬性的[[Writable]]特性會被設置爲false。若是定義[[Set]]函數,訪問器屬性仍然是可寫的。

Object.freeze(obj);//凍結對象。

Object.isFrozen(obj);//檢測對象是否被凍結。凍結對象既是密封的又是不可擴展的。因此用Object.isExtensible()和Object.isSealed()檢測凍結對象分別返回false和true.

 

22.3, 高級定時器

setInterval()和setTimeout()能夠建立定時器。人們對於JavaScript的定時器存在廣泛的誤解,認爲它們是線程,其實JavaScript是運行於單線程的環境中的,而定時器僅僅只是計劃代碼在將來的某個時間執行。執行的時機是不能保證的,由於在頁面的生命週期中,不一樣時間可能有其餘代碼在控制JavaScript進程。

除了主JavaScript執行進程外,還有一個須要在進程下一次空閒執行的代碼隊列。關於定時器最重要的事情是,指定的時間間隔表示什麼時候將定時器的代碼添加到隊列,而不是什麼時候實際執行代碼。由於添加到隊列的代碼不意味着它會被當即執行,而只能表示它會盡快執行。好比,設定一個150ms後執行的定時器不表明他會在150s後馬上執行,而是表示代碼會在150ms後被加入到隊列中。若是在這個時間點上,隊列中沒有其餘東西,那麼這段代碼就被當即執行,不然代碼須要等待更長時間才能執行。

22.3.1, 重複的定時器

setInterval()建立的重複定時器有一些缺點,因此一般使用鏈式setTimeout()調用來模擬setInterval()。代碼以下:

    setTimeout(function () { //這裏作一些處理
 setTimeout(arguments.callee, 2000) },2000)

 

22.3.2,柔和的處理(Yielding Processes)

桌面應用每每可以隨意控制它們要的內存大小和處理器時間,可是javaScript被嚴格限制了,以防止惡意的Web程序員把用戶的計算機搞掛了。其中一個限制就是長時間運行腳本的制約。若是代碼運行時間過長,瀏覽器就會中止運行報錯。腳本長時間運行每每是如下兩個緣由形成的:過長的、過深嵌套的函數調用 或者是 進行大量處理的循環。後者是較爲容易解決的問題,能夠使用定時器分割這個循環。這是一種叫作數組分塊(array chunking)的技術,小塊小塊地處理數組。在數組分塊模式中,array變量本質上就是一個「待辦事宜」列表,它包含了要處理的項目。具體代碼以下:

 

    function chunk(array, process, context) { setTimeout(function () { var item = array.shift(); process.call(context, item); if (array.length > 0) { setTimeout(arguments.callee, 100); } }, 100); } var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]; function printValue(item) { var div = document.getElementById("myDiv"); div.innerHTML += item + "<br > "; } chunk(data, printValue);//調用chunk函數處理數據的同時,data數組中的條目也在改變。若是想保持原數組不變,能夠將該數組的克隆傳遞給chunk(),即chunk(data.concat(),printValue);

 

運行後會有一個延遲出現的效果。

 

22.3.3,函數節流

瀏覽器中的DOM操做比起非DOM操做交互須要更多的內存和CPU時間。連續嘗試進行過多的DOM相關操做可能會致使瀏覽器掛起,甚至崩潰,在使用onresize事件處理程序時容易發生。爲類繞開這個問題,能夠使用定時器對該函數進行節流。

函數節流的基本思想是指,某些代碼不能夠在沒有間斷的狀況下連續重複執行。代碼以下:

 

    //節流函數throttle
    function throttle(method, context) { clearTimeout(method.tId);//method是個函數,函數的本質也是對象,能夠爲其設置屬性。此屬性初次執行時並不存在。
        method.tId = setTimeout(function () { method.call(context); }, 100) } function resizeDiv() { var div = document.getElementById('myDiv'); div.style.height = div.offsetWidth + 'px'; } window.onresize = function () { /**不管onresize被觸發的有多頻繁,經過節流函數throttle均可以保證 * resizeDiv函數只在最後一次resize被觸發後的100ms以後執行一次 */ throttle(resizeDiv); }

 

22.4,自定義事件

 事件是與DOM交互最多見的方式,但事件也能夠用於非DOM代碼中--經過自定義事件。自定義事件背後的概念是 建立一個管理事件的對象,讓其餘對象監聽那些事件。具體代碼以下:

<script type='text/javascript'>
    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); } //create a new object var target = new EventTarget(); //add an event handler target.addHandler("message", handleMessage); //fire the event target.fire({type: "message", message: "Hello world!"}); //remove the handler target.removeHandler("message", handleMessage); //try again - there should be no handler target.fire({ type: "message", message: "Hello world!"}); </script>

 使用自定義事件有助於將不一樣部分的代碼相互之間解耦,

22.5,拖放(未看)

 

23,離線應用和客戶端存儲

23.1,離線檢測

navigator.onLine;//表示設備可否上網。

window.ononline事件:從離線變爲在線時觸發

window.onoffline事件:從在線變爲離線時觸發

23.2,應用緩存[application cache或appcache]

HTML5的應用緩存是專門爲開發離線web應用而設計的。簡單說來就是在網絡可用時,下載一些必要的文件以便離線時仍然能夠使用這些資源。具體細節省略。

23.3,數據存儲

數據存儲指的是直接在客戶端上存儲用戶的信息,如登錄信息,偏好設定或其餘數據。有以下實現方式:

23.3.1,cookie

※全稱爲HTTP Cookie。服務器對任意HTTP請求發送Set-Cookie HTTP頭做爲響應的一部分。cookie的性質是綁定在特定的域名下的。當設定了一個cookie後,再給建立它的域名發送請求時,都會包含這個cookie(而其餘域名沒法訪問到這個cookie中的信息)。

※cookie的構成:

cookie有瀏覽器保存的一下幾塊信息構成:

①名稱:cookie的名稱必須是通過URL編碼的。關於URL編碼,簡單說來就是在十六進制ascii碼前面加上百分號。對於普通字符如字母數字等能夠編碼也能夠不編碼,但對於特殊字符(特殊字符包含部分ascii碼中的內容如&=等,也有非ascii字符,如中文字符等等)則必須編碼。三個字節的中文字符的URL編碼方法是在每一個字節的十六進制前面加上百分號。具體內容見以下網址:http://www.cnblogs.com/jerrysion/p/5522673.html 

②值:值也必須被URL編碼

③域:代表cookie對於哪一個域是有效的。全部項該域發送的請求中都會包含這個cookie信息。若是沒有明確設定,那麼這個域 會被認爲來自設置cookie的那個域。這個值能夠包含子域(如,www.xxx.com),也能夠不包含(如,.xxx.com,則對於xxx.com的全部子域都有效)。

④路徑:路徑是對域的補充。對於指定了路徑的cookie,這隻有此路徑才能訪問cookie,如http://www.xxx.com/books/下設定了cookie,則cookie只會向此路徑發送cookie,而http://www.xxx.com則不會被髮送cookie,即便請求都是來自同一個域的。

⑤失效日期:表示cookie什麼時候應該被刪除的時間戳(也就是,什麼時候應該中止向服務器發送這個cookie)。默認是繪話結束時即將全部cookie刪除。若是設置的失效時期是當前時間之前的時間,這cookie會被當即刪除。日期格式爲GMT格式,如:Mon, 22-Jan-07 07:10:22 GMT。

⑥安全標誌:指定後,cookie只有在使用SSL鏈接的時候才發送到服務器。例如,cookie信息只能發送給https://www.xxx.com,而http://www.xxx.com的請求則不能發送cookie。

服務器端設置一個完整的cookie的例子以下,分號加空格分割每一段:

Set-Cookie: name=value; expires=Mon, 22-Jan-2017 11:11:11 GMT; domain=.xxx.com; path=/; secure

※JavaScript中的cookie

document.cookie能夠獲取cookie值,也能夠設置cookie值

①,獲取cookie時,獲得一系列由分號隔開的鍵值對,全部的名字和值都是通過URL編碼的因此必須使用decodeURIComponent()來解碼。

②,設置cookie時,這個cookie字符串會被添加到現有的cookie集合中,設置document.cookie不會覆蓋cookie,除非設置的cookie的名稱已經存在。設置cookie的格式和Set-Cookie頭中使用的格式是同樣的,例如:

document.cookie = "name=tong; domain=.xxx.com; path=/; secure";

實際操做中,最好把名稱和值經過encodeURIComponent()編碼下,以下:

document.cookie=encodeURIComponent('name') + '=' + encodeURIComponent('tong') + "; domain=.xxx.com; path=/; secure"。

③因爲JavaScript中讀寫cookie不是很直觀,因此能夠用一些函數來簡化cookie的功能,代碼以下:

<script type="text/javascript">
    var CookieUtil = { get: function (name) { var cookieName = encodeURIComponent(name) + "=", cookieStart = document.cookie.indexOf(cookieName), cookieValue = null; if (cookieStart > -1) { var cookieEnd = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1) { cookieEnd = document.cookie.length; } cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd)); } return cookieValue; }, set: function (name, value, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value); if (expires instanceof Date) { cookieText += "; expires=" + expires.toGMTString(); } if (path) { cookieText += "; path=" + path; } if (domain) { cookieText += "; domain=" + domain; } if (secure) { cookieText += "; secure"; } document.cookie = cookieText; }, unset: function (name, path, domain, secure) { this.set(name, "", new Date(0), path, domain, secure); } };
//使用
CookieUtil.set('name','Tong',new Date('2017-7-19'),null,'.xxx.com');
</script>

※子cookie:

因爲某個域名下的cookie有數量限制(不一樣瀏覽器限制不一樣,通常是30個-50個),若是擔憂數量不夠用,能夠使用子cookie,也就是使用單個cookie記錄多個cookie的鍵值對,如:

documen.cookie="data=name=tong&age=11&address=china";

※cookie信息越大,完成對服務器請求的時間也越長,因此最好儘量在cookie中少存儲信息,避免影響性能。另外,cookie數據並不是存儲在一個安全環境中,因此cookie中的保存的都是不重要的數據。

23.3.2,IE用戶數據(略)

23.3.3,Web存儲機制

Web Storage的目的是克服由cookie帶來的一些限制。

具體內容參見html學習筆記第25條記錄。 

23.3.4,IndexedDB (Indexed Database API,索引數據庫API)

雖然 Web Storage 對於存儲較少許的數據頗有用,但對於存儲更大量的結構化數據來講,這種方法不太有用。IndexedDB提供了一個解決方案。

參見下面兩篇文章:

 

 http://www.tfan.org/using-indexeddb/  [很詳細,下面代碼主要就是看這篇文章]

 

 http://web.jobbole.com/81793/ 

 
    var request;
//第一個參數爲數據庫名稱,第二個參數可選,爲數據庫版本
request = indexedDB.open('database', 40);
/**
* onblock事件主要處理併發問題(好幾個標籤打開了這個數據庫)。後面還有一個onversionchange事件處理程序也是處理併發問題的.
*/
request.onblocked = function (e) {
alert('請先關閉已經打開的本網站,而後從新加載此網頁');
};
/**
* 在打開數據庫時常見的可能出現的錯誤之一是 VER_ERR。這代表存儲在磁盤上的數據庫的版本高於你試圖打開的版本。這是一種必需要被錯誤處理程序處理的一種出錯狀況。
*/
request.onerror = function (e) {
alert('error: ' + e.target.error.message);
};
request.onsuccess = function (e) {
alert('on success');
var database = e.target.result;//獲取打開的數據庫
console.log(database.version,"&&&&&&&&&&&&");
/**成功打開數據庫後,要對數據表進行操做(增刪改查),首先要開啓一個事務.
*transaction()函數接收兩個參數,第一個參數是涉及到的數據表(以數組形式傳入),若是是空數組表示涉及全部數據表;
* 第二個參數能夠是readonly(只讀事務),readwrite,versionchange,默認是隻讀事務
*事務能夠接收三種不一樣類型的DOM事件:error,abort以及complete
*/
var transaction = database.transaction(['table'], 'readwrite');
transaction.oncomplete = function (e) {
alert('all done!')
};
transaction.onerror = function (e) {
alert('事務錯誤')
};
transaction.onabort = function (e) {
alert('transaction abort!')
};

/**
* 有了事務以後,須要從其獲取一個數據表引用,此數據表必須是建立事務時已經指定過的數據表。
*/
var tb = transaction.objectStore('table');
//tb.indexNames能夠查看錶的全部索引
//tb.deleteIndex(索引名稱);//刪除索引。由於刪除索引不會影響數據表中的數據,因此這個操做沒有任何回調函數。
/**
* 獲取了數據表以後就能夠利用此數據表引用的方法來增刪改查數據表了.
* 增長數據方法:add()或put()。區別是當指定的鍵值(本例爲id)已經存在時,add()方法會報錯,put()方法則會更新
* 刪除方法:delete().傳入主鍵(id).
* 查詢方法: get(). 傳入主鍵.
* clear()方法: 刪除全部對象
*/
var pput = tb.put({id: "ID10088", name: 'tong', age: 22});
pput.onsuccess = function (e) {
alert('pput success')
};
var del = tb.delete(3);
del.onsuccess = function (e) {
alert('dddelte success')
};
var gget = tb.get(2);
gget.onsuccess = function (e) {
alert('gget.success');
console.log(gget.result);
};

/**
* 使用遊標:使用get()獲取須要知道想要檢索哪個鍵。若是想遍歷數據表中的全部值,就必須使用遊標
* openCursor()方法能夠接收兩個參數,具體見下面
*/
//這裏是對主鍵使用遊標,下面還有對索引使用遊標的例子
tb.openCursor().onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
/**
* 使用遊標能夠更新個別的記錄,用update()方法,也能夠刪除某些記錄,用delete()方法。
* 二者都返回一個請求,能夠爲這個請求指定onsuccess和onerror事件處理程序,這樣即可知道更新或刪除的結果
*/
if (cursor.key == 'foo'){
value = cursor.value;
value.name = 'magic';
updateRequest = cursor.update(value);
updateRequest.onsuccess = function (e) {
alert('使用遊標更新成功');
};
updateRequest.onerror = function (e) {
alert('遊標更新失敗');
};
}
console.log(cursor.key + "--", cursor.value);
/**
* continue([key])方法能夠接收一個可選參數。指定這個參數,遊標會移動到指定鍵的位置
* 調用continue()會觸發另外一次請求,進而再次調用onsuccess事件處理程序。
* 還有另一種方法發起另外一次請求:
* advance(count);//向前移動count指定的項數
*/
cursor.continue();
} else {
alert('no more entries');
}
};
// //getAll()方法不是標準,是mozilla本身的實現。
// tb.getAll().onsuccess = function (e) {
// console.log(e.target.result[0]);
// };

/**
* 使用索引:
*/
var index = tb.index('name');
//index.getKey('tong');//能夠取得索引值tong對應的主鍵(id)
index.get('tong').onsuccess = function (e) {
//因爲索引name=tong的記錄可能有多條,可是獲得的老是鍵值(id)最小的那個
console.log(e.target.result);
};
//若是想訪問全部索引name=tong的記錄,能夠對索引使用標.索引遊標有兩種方式,區別在於返回的值不一樣,能夠分別打印出兩個cursor查看區別
index.openCursor().onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
//區別:cursor.value就是整條記錄的對象
console.log(cursor.key, cursor.value, '$$$');
cursor.continue();
}
};
index.openKeyCursor().onsuccess = function (e) {
var cursor = e.target.result;
if (cursor) {
//區別:沒有cursor.value,沒法獲得記錄的其餘字段信息
console.log(cursor.key, cursor.primaryKey, "###");
cursor.continue();
}
};
/**
* 指定遊標的範圍和方向:
* openCursor()和openKeyCursor()還能夠接收兩個可選參數,第一個參數指定遊標範圍,第二個參數指定方向
* 第一個參數有以下用法:
* // 只匹配 "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");

// 指定結果集的下界(即遊標開始的位置).例如,遊標從鍵爲Bill的對象開始,而後繼續向前移動,直至最後一個對象
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");

//若是想忽略Bill,從它的下一個對象開始,那麼能夠傳入第二個參數true.
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

// 指定結果集的上界。例如,遊標從頭開始,直到取得鍵爲"Donna"的對象終止,若是傳入第二個參數則不包含"Donna"。
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);

//同時指定上下界的bound()方法。接收四個參數:下界鍵值,上界鍵值,是否跳過下界的布爾值,是否跳過上界的布爾值
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
* 第二個參數能夠是:'next', 'nextunique', 'prev', or 'prevunique'
*/

};
/**
*在數據庫第一次被打開或者當指定的版本號高於當前存儲的數據庫的版本號時,會觸發這個upgradeneeded事件,而後能夠在此事件中更新數據庫。
*版本號是unsigned long long型,浮點數會被地板轉換。
* onupgradeneeded 是咱們惟一能夠修改數據庫結構的地方。在這裏面,咱們能夠建立和刪除對象存儲空間(即數據表)以及構建和刪除索引。
*/
request.onupgradeneeded = function (e) {
alert('upgradeneeded');
var db = e.target.result;//將獲得的數據庫保存到變量中。
/**
* 處理併發問題,onversionchange事件處理程序,當即關閉數據庫從而保證版本更新順利完成!
*/
db.onversionchange = function (e) {
alert('檢測到版本變化!')
db.close();
};
//在數據庫中建立數據表,ObjectStore能夠理解爲數據表, 第二個對象參數(可選)中的keyPath能夠理解爲主鍵.往數據表中添加記錄時,必需要有主鍵,不然報錯
var table = db.createObjectStore('table', {keyPath: 'id'});
//爲數據表添加索引,參數一爲索引名稱,參數二爲鍵名,參數三可選,指定一些選項,如此索引是否惟一等等。
table.createIndex('name', 'name', {unique: false});
table.add({id: 11, name: 'tong', age: 33});//能夠向表中添加記錄
};

 本章總結:本章介紹了在客戶端存儲數據的幾種方法:cookie, web storage(localStorage/sessionStorage), indexedDB。在客戶端存儲數據須要注意一點:不要存儲敏感數據,由於客戶端數據緩存不會加密。

 

 24,最佳實踐

24.1,可維護性

一些常見的作法(合理的註釋,命名有意義等,合理的命名,如變量用名詞,函數名用動詞,返回布爾類型的函數通常以is開頭。)就不提了。下面是一些保持代碼可維護性的方法:

①,變量類型透明:因爲JavaScript中變量是鬆散類型的,很容易就忘記變量所應包含的數據類型,有如下三種方式能夠表示變量數據類型

※初始化。缺點是沒法用於函數中的參數。

※使用匈牙利標記法來指定變量類型。缺點是使代碼變的難以閱讀。

※使用類型註釋。缺點是沒法再將這些代碼大段註釋,只能一行一行註釋(不少編輯器均可以完成此工做)

②,鬆散耦合(即低耦合)

只要代碼 的某一部分過於依賴另外一部分,就是耦合過緊,難以維護。能夠參考原文,頗有價值。

※解耦 HTML/JavaScript

  1,不要使用內嵌<script>標籤在html中書寫js代碼,不要使用html屬性分配事件處理程序。這些狀況都屬於耦合過於緊密。應該經過外部文件來包含javascript

  2,反過來,js中的代碼也儘可能不要包含html代碼。例如,在js中用innerHTML插入一段html文本到頁面。這就屬於緊密耦合了。通常能夠以下解決:能夠先在頁面中直接包含並隱藏html標記,而後等整個頁面渲染好了以後在用javascript顯示它而非生成它。另外一種方法是使用ajax獲取更多要顯示的html,這個方法可讓一樣的渲染層(php,ruby,jsp等等)來輸出標記,而不是直接嵌在javascript中。

※解耦 CSS/JavaScript

  1,最多見的css和javascript耦合的例子是使用javascript來更改某些樣式,如element.style.color='red'。因爲經常須要使用js更改樣式,因此不可能將css和js徹底解耦,但仍是可以讓耦合儘可能低的。如,能夠經過動態更改樣式類而非特定樣式。如element.className='edit';如此即可使大部分樣式信息嚴格保留在css中。

※解耦 應用邏輯/事件處理程序:事件處理程序值處理事件,由事件獲得的信息須要處理則放到另外的應用邏輯層處理。

 

③,編程實踐

※尊重對象全部權。意思是你不能修改不屬於你的對象,包括別人的對象以及原生對象。若是須要擴展某個對象能夠建立本身的對象並繼承須要擴展的對象。

※避免全局量。最多建立一個全局變量,讓其餘對象和函數存在其中。單一的全局變量的延伸即是命名空間的概念。YAHOO.util.Event / YAHOO.util.Dom / YAHOO.lang

※避免與null進行比較。與null比較經常因爲不充分而致使錯誤。應該使用更充分的類型比較。

※使用常量

JavaScript中沒有常量的正式概念,但它仍是頗有用的。將數據從應用邏輯分離出來的思想,能夠用以下方式表示:

var Constants = {

   CONSTANT1: "value1",

      CONSTANT2: "value2"

}

 

24.2, 性能

注意如下方面,能夠改進代碼的總體性能。

※注意做用域:

  訪問全局變量老是要比訪問局部變量慢,由於須要遍歷做用域鏈。只要能減小花費在做用域鏈上的時間,就能增長腳本的整體性能。

  1,避免全局查找:將一個函數中屢次用到的全局對象(好比document)存儲爲局部變量老是沒錯的。

  2,避免with語句:由於with語句會建立本身的做用域,所以增長了其中的代碼的做用域鏈的長度。一樣能夠用一個局部變量來代替with語句中的表達式。

※選擇正確的方法:

  計算機科學中,算法的複雜度是用符號O來表示的,最簡單、最快捷的的算法是常數值即O(1)。

  

  如下是javascript中的複雜度:

  O(1):  字面值、存儲在變量中的值、訪問數組元素

  O(n):訪問對象上的屬性(必須在原型鏈中對擁有該名稱的屬性進行一次搜索)。

  

  1,避免沒必要要的屬性查找

  如:var query = window.location.href.substring(window.location.href.indexOf("?"));  這段代碼中有6此屬性查找,能夠優化。

  一旦屢次用到對象屬性,應該將其存儲在局部變量中。第一次訪問是O(n),後續訪問都會是O(1)。上述代碼可優化爲:

  var url = window.location.href;

  var query = url.substring(url.indexOf("?")); 這段代碼中只有4次屬性查找,相對於原來節省了33%.

  2,優化循環

  例以下面一個循環:

  for (var i = 0; i < values.length; i++) { process(values[i])}

  這裏每次循環都要判斷的終止條件values.length是O(n)的,所以能夠單獨拿出來存在一個變量裏,或者使用減值迭代,以下

  for (var i = values.length - 1; i >= 0; i--){process(values[i])}. 這兩種方法均可以使終止條件簡化爲O(1).

  3,避免雙重解釋

  所謂雙重解釋意思是,出現了須要按照JavaScript解釋的字符串。好比,當時用eval()函數,或者使用Function構造函數,或者使用setTimeout() / setInterval()傳一個字符串參數時都會發生這種狀況。以下例子;

eval("alert('hello world')");// 避免!!

var sayHi = new Function("alert('hello world')"); 或者

var sum = new Function("a","b","c=a+b;return c");

setTimeout("alert('hello world')", 1000);

以上這些例子中,都要解析包含了JavaScript代碼的字符串。這個操做是不能在初始化的解析過程當中完成的,由於代碼是包含在字符串中的,也就是說在JavaScript代碼運行的同時必須新啓動一個解析器來解析新的代碼。實例化一個新的解析器有不容忽視的開銷,因此這種代碼比直接解析要慢的多。

以上的例子都有另外的辦法,見下圖。除了極少的狀況下eval()是絕對必要的,其餘狀況都要避免使用雙重解釋。

  4,性能的其餘注意事項:

  如下並不是主要的問題,不過若是使用得當也會有至關大的提高。

※最小化語句數

JavaScript代碼中的語句數量也影響所執行的操做的速度。完成多個操做的單個語句要比完成單個操做的多個語句速度快,因此就要找出能夠組合在一塊兒的語句,以減小腳本總體的執行時間。如下幾個模式能夠參考:

1,多個變量聲明

//4個語句---很浪費

var count  = 5;

var color = 'red';

var arr = [1,3,4];

var now = new Date();

在強類型語言中,不一樣的數據類型的變量必須在不一樣的語句中聲明。然而,在JavaScript中全部的變量均可以使用單個var語句來聲明。以上代碼能夠以下優化重寫:

//一個語句,用逗號隔開

var count = 5,

  color = 'red',

  arr = [1,3,4],

  now = new Date();

2,插入迭代值(即自增自減),以下例子:

var name = values[i];

i++;

上述兩個語句能夠合併成一個語句,var name = values[i++];這樣效果更優。

3,使用數組和對象字面量:

建立數組或對象有兩種方法:使用構造函數或者使用字面量。前者用到的語句要比後者多,因此通常儘量用字面量建立數組或對象。

//4個語句建立和初始化數組----浪費!

var values = new Array();

values[0] = 123;

values[1] = 345;

values[2] = 456;

改成:var values = [123,345,456];//一條語句完成建立

//4個語句建立和初始化對象----浪費!

var person = new Object();

person.name = 'Tong';

person.age = 11;

person.sayName = function(){alert(this.name);}

改成:var person = {

      name : "Tong",

      age: 11,

      sayName : function(){alert(this.name);}

    };//一條語句完成對象的建立

※優化DOM交換

在JavaScript的各個方面中,DOM毫無疑問是最慢的一部分。理解如何優化與DOM的交互能夠極大的提升腳本完成的速度。 

1,最小化現場更新

若是你正在更改的DOM已是顯示的頁面的一部分,那麼你就是在進行一個現場更新。每個更改,無論是插入單個字符仍是移除整個片斷,都有一個性能懲罰,由於瀏覽器要從新計算無數尺寸以進行更新。例子以下:

var list = document.getElementById('myList'),

  item,

  i;

for (i = 0; i < 10; i++){

  item = document.createElement("li");

  item.appendChild(document.createTextNode("item" + i));

  list.appendChild(item);

}

這段代碼爲列表添加了10個項目,添加每一個項目時,都有兩個現場更新:一個添加<li>元素,另外一個給他添加文本節點。總共進行了20次現場更新。要修正這個性能瓶頸,能夠使用文檔碎片來構建DOM結構【在本頁搜索{10.8,DocumentFragment類型}】,而後一次性添加到list元素中。改進以下:

var list = document.getElementById("myList"),

  fragment = document.createDocumentFragment(),

  item,

  i;

for (i = 0; i< 10; i++){

  item = document.createElement("li");

  item.appendChild(document.createTextNode("item" + i));

  fragment.appendChild(item);  

}

list.appendChild(fragment); //這樣只會進行一次現場更新,大大提升性能。

2,使用innerHTML

有兩種在頁面上建立DOM節點的方法:使用諸如createElement()和appendChild()之類的DOM方法,或者使用innerHTML。當把innerHTML設置爲某個值時,後臺會建立一個HMML解析器,而後使用內部的DOM調用來建立DOM結構,而不是基於JavaScript的DOM調用。因爲內部方法是編譯好的而非解釋執行的,因此執行得快的多。上面的例子還能夠用下面的方法優化:

var list = document.getElementById("list"),

  html = '',

  i;

for( i = 0; i < 10; i++){

  html += "<li>item " + i + "</li>";

}

list.innerHTML = html;

另外注意,使用一次innerHTML就是進行一次現場更新,因此也要儘可能減小innerHTML的使用次數,先把字符串拼接好而後再一次性更新而不是在for循環體裏每次更新。

3,使用事件代理(又叫事件委託):

頁面上的事件處理程序的數量越多,頁面的響應速度就越慢。爲減輕這種懲罰,最好使用事件代理。事件代理利用到了事件冒泡,即在儘量高層的元素上添加一個事件處理程序來處理下層的多個同類型的事件,經過event.target.id來分別不一樣的下層元素。【參見13.5.1,事件委託】

4,最小化訪問HTMLCollection

任什麼時候候要訪問HTMLCollection,無論它是一個屬性仍是一個方法,都是在文檔上進行一個查詢,這個查詢開銷很昂貴。因此要儘可能最小化HTMLCollection訪問(尤爲在循環中)。

在JavaScript中,發生一下狀況時會返回HTMLCollection對象:

  □ 進行了對 getElementsByTagName()的調用;

  □ 獲取了元素的childNodes屬性;

  □ 獲取了元素的attributes屬性;

  □ 訪問了特殊的集合,如document.forms、document.images等;

 

24.3,部署

24.3.1,構建過程

24.3.2,驗證

24.3.3,壓縮

JavaScript中的壓縮涉及到兩個方面:代碼長度和配重(Wire weight)。代碼長度指的是瀏覽器所需解析的字節數,配重是指實際從服務器傳送到瀏覽器的字節數。前者能夠經過文件壓縮來減小文件字節數,後者能夠HTTP壓縮來減小傳輸字節數。

※文件壓縮:能夠經過一些壓縮工具(如YUI)進行壓縮,壓縮器通常進行以下一些步驟:

□ 刪除額外的空白(包括換行);

□刪除全部註釋;

□縮短變量名。

※HTTP壓縮:文件壓縮後,還能夠繼續經過HTTP壓縮減小傳輸字節數。瀏覽器接收到壓縮文件後再解壓縮。

 

25,新興的API

25.1,requestAnimationFrame() 

HTML5/CSS3時代,咱們要在web裏作動畫選擇其實已經不少了:

你能夠用CSS3的animation+keyframes;

你也能夠用css3的transition;

你還能夠用經過在canvas上做圖來實現動畫,也能夠藉助jQuery動畫相關的API方便地實現;

固然最原始的你還能夠使用window.setTimout()或者window.setInterval()經過不斷更新元素的狀態位置等來實現動畫,前提是畫面的更新頻率要達到每秒60次才能讓肉眼看到流暢的動畫效果。

如今又多了一種實現動畫的方案,那就是還在草案當中的window.requestAnimationFrame()方法。下面介紹一個這個方法:

mozilla開發手冊中的介紹:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

其實這個方法和setTimeout用法很是類似,能夠說requestAnimationFrame就是一個性能優化版、專爲動畫量身打造的setTimeout,不一樣的是requestAnimationFrame不是本身指定回調函數運行的時間,而是跟着瀏覽器內建的刷新頻率來執行回調,這固然就能達到瀏覽器所能實現動畫的最佳效果了。下面是requestAnimationFrame的一個例子:進度條的實現

0%

代碼以下:

<div id="test" style="width:1px;height:17px;background:#0f0;">0%</div>
<input type="button" value="Run" id="run"/>
<script type="text/javascript">
    //requestAnimationFrame的用法和setTimeout及其類似
    window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
    var start = null;
    var ele = document.getElementById("test");
    var progress = 0,
            startTime;
    function step(timestamp) {
//        console.log(timestamp);
        /**timestamp是自動傳給回調函數的,打印的結果並非普通的時間戳,感受像是
        /*頁面被加載後開始計時的時間戳
         */
        if (startTime) {
            console.log(timestamp - startTime);//打印兩次時間間隔
        }
        startTime = timestamp;
        progress += 1;
        ele.style.width = progress + "%";
        ele.innerHTML = progress + "%";
        if (progress < 100) {
            start = requestAnimationFrame(step);
        }
    }
    //    requestAnimationFrame(step);
    document.getElementById("run").addEventListener("click", function () {
        ele.style.width = "1px";
        progress = 0;
        cancelAnimationFrame(start);
        requestAnimationFrame(step);
    }, false);
</script>

 

 

 

25.2,Page Visibility API(頁面可見性API)

此API由三部分組成:

說明一點:經測試,在chromium37中,visibilitychange事件只能經過DOM2級事件處理程序[addEventListener]觸發,DOM0級事件處理程序[onclick]無效 !

具體參見很詳細的一篇文章:http://www.zhangxinxu.com/wordpress/2012/11/page-visibility-api-introduction-extend/ 

下面的代碼是整理自上述文章中:跨瀏覽器兼容的Page Visibility API,

 

    var pageVisibility = (function () { var prefixSupport, keyWithPrefix = function (prefix, key) { if (prefix !== '') { return prefix + key.substring(0, 1).toUpperCase() + key.substring(1); } return key; }; var isPageVisibilitySupport = (function () { var support = false; ['webkit', 'moz', 'o', ''].forEach(function (prefix) { if (document[keyWithPrefix(prefix, 'hidden')] != undefined) { prefixSupport = prefix; support = true; } }); return support; })(); var visibilityState = function () { if (isPageVisibilitySupport) { return document[keyWithPrefix(prefixSupport, 'visibilityState')] } return undefined; }; var hidden = function () { if (isPageVisibilitySupport) { return document[keyWithPrefix(prefixSupport, 'hidden')]; } return undefined; }; return { hidden: hidden(), visibilityState: visibilityState(), visibilitychange: function (fn) { if (isPageVisibilitySupport && typeof fn == 'function') { return document.addEventListener(prefixSupport + 'visibilitychange', function (e) { this.hidden = hidden(); this.visibilityState = visibilityState(); fn.call(this, e); }.bind(this)) }
          return undefined; } } })(); (
function () { pageVisibility.visibilitychange(function (e) { console.log((new Date).toLocaleString() + ": " + this.visibilityState); }) })();
 

應用一:網頁視頻播放時,當此網頁不可見時暫停視頻播放,當再次可見時繼續播放,代碼以下:

 

HTML代碼: <video id="eleVideo" width="50%" src="res/feather.mp4" controls muted> JS代碼: (function () { var pauseByVisibility = false, eleVideo = document.querySelector('#eleVideo'); if (pageVisibility.hidden != undefined) { //視頻元素的時間更新事件 eleVideo.addEventListener('timeupdate', function (e) { document.title = "第" + Math.floor(this.currentTime) + "秒"; }, false); //視頻元素的暫停事件 eleVideo.addEventListener('pause', function (e) { if (pageVisibility.hidden) { pauseByVisibility = true; } }, false);  //視頻元素的播放事件  eleVideo.addEventListener('play', function (e) { pauseByVisibility = false; }); pageVisibility.visibilitychange(function (e) { if (this.hidden) { eleVideo.pause(); } else if (pauseByVisibility) { eleVideo.play(); } }) } else { alert('您的瀏覽器不支持Page Visibility API!!!') } })();

 

應用二:先打開一個未登陸的網頁,此網頁顯示未登陸。在另外一個網頁中登陸以後在返回原來的那個未登陸的網頁,實時顯示登陸狀態。代碼以下:

<div id="msg">您還沒有登陸,請<a href="./d.html" target="_blank">登陸</a></div>
<script type="text/javascript"> (function () { if (pageVisibility.hidden != undefined) { var msg = document.querySelector('#msg'), loginFun = function () { /** * sessionStorage特色:1,即便同源的不一樣網頁信息也不共享;2,關閉此窗口,信息即消失; * localStorage特色:1,同源(協議,主機,端口都同)的不一樣網頁信息共享;2,關閉窗口,信息仍然存在。  */
                        var name = sessionStorage.name || localStorage.name; var pwd = sessionStorage.pwd || localStorage.pwd; if (name) { sessionStorage.name = name; sessionStorage.pwd = pwd; msg.innerHTML = "<span>歡迎回來," + name + "。您的密碼爲" + pwd + "</span>"; } }; loginFun(); pageVisibility.visibilitychange(function (e) { if (!this.hidden) { loginFun(); } }); /** * 注意:unload事件在 關閉此窗口刷新此窗口在當前窗口打開超連接 都會觸發!!! */ window.addEventListener('unload', function (e) { localStorage.clear(); }) } })(); </script>

 

25.3,Geolocation API

參見html學習筆記記錄: http://www.cnblogs.com/everest33Tong/p/6706497.html

25.4,File API

1,文件處理經過文件表單元素:<input type='file' />。用法以下:

<input type="file" id="eleFile" multiple/>
<script type="text/javascript">
    var eleFile = document.getElementById('eleFile'); /**文件表單元素有一個files集合,files集合中包含一組File對象,每一個File對象對應着一個文件。 * 每一個File對象都有下面的只讀屬性(更新的瀏覽器支持的屬性應該還支持其餘一些屬性,可本身嘗試看看): * name:本地文件系統中的文件名, * size:文件的字節大小, * type:字符串,文件的MIME類型, * lastModifiedDate: 文件上一次被修改的日期 */ console.log(eleFile.files); //文件表單元素有一個change事件,當選擇的文件發生變化時觸發:
 eleFile.onchange = function (e) { alert('選擇的文件發生了變化'); }; </script>

2,FileReader 類型

FileReader類型實現的是一種異步文件讀取機制。能夠把FileReader想象成XMLHttpRequest,區別只是它讀取的是文件系統,而不是遠程服務器。爲了讀取文件中的數據,FileReader提供了以下方法:

 

FileReader有個屬性readyState標誌着當前讀取狀態:

EMPTY : 0 : 表示還沒有開始讀取數據,

LOADING : 1 : 表示正在加載數據,

DONE : 2 : 表示數據已讀取完畢。

 

因爲讀取過程是異步的,所以FileReader提供了幾個事件(按照事件的依次觸發順序):

loadstart事件:最早被觸發。

progress事件:表示是否又讀取了新數據。每過50ms左右就會觸發一次progress事件,經過事件對象event能夠獲取與XML的progress事件相同的屬性(信息):lengthComputable, total, loaded。每次progress事件中均可以經過FileReader的result屬性獲取已經讀取到的內容。

load事件:文件加載成功以後就會觸發load事件。

loadend事件最終總會被觸發的事件,loadend事件發生意味着已經讀取完整個文件,或者讀取時發生了錯誤,或者讀取過程被中斷。

另外幾個事件:

error事件:因爲種種緣由沒法讀取文件,就會觸發error事件。觸發error事件時,相關的信息會被保存在FileReader的error屬性中。error屬性是個對象,有以下三個屬性:message, code, name.

abort事件:若是調用了FileReader的abort()方法就會觸發abort事件。

 

測試時發現的一個小點(關鍵點:異步讀取!!),注意下,以下:

<script type="text/javascript">
    var eleFile = document.getElementById('eleFile'); eleFile.onchange = function (e) { var reader = new FileReader(), file = this.files[0]; reader.onload = function (e) { console.log(e.target.result);//正確的讀取位置,在load事件中讀取。
 }; reader.readAsText(file, 'gbk'); /** * 注意若是在下面打印reader對象和其result屬性,此時reader.readyState仍爲1,result會顯示爲undefined, * 可是若是在瀏覽器控制檯中展開打印的reader對象,會發現readyState是2,result屬性也有結果,這是由於展開
      * 看時顯示的是已經讀取完畢時的狀態。
*/ console.log(this.result,reader); }; </script>

 

3,讀取部份內容:

只讀文件的一部份內容能夠節省時間,很是適合只關注數據中某個特定部分(如文件頭部)的狀況。具體用法以下:

 

<body><a id='rtobottom' href='#blog_post_info_block'>底部</a>
<input type="file" id="eleFile"/>
<div id="output"></div>
<!--<video id="videoElement" src="./res/home.mp4" controls></video>-->
<script type="text/javascript" src="../amomeshoe/js/jquery2.1.4.js"></script>
<script type="text/javascript"> (function () { var eleFile = document.querySelector('#eleFile'), output = document.querySelector('#output'); eleFile.onchange = function (e) { var file = eleFile.files[0], reader = new FileReader, html = ''; /** * file屬於File類型,blob屬於Blob類型,Blob是File類型的父類型。 * File對象支持slice(起始字節數,要讀取的字節數)方法來實現部分讀取,其父類型Blob類型也支持slice()方法以進一步切割數據 */
            var blob = file.slice(0,10); var blob1 = blob.slice(1,3); console.log(file,blob); reader.readAsText(blob1);//此處參數爲File或Blob類型,便可以從File類型或Blob類型中讀取數據
            reader.onload = function (e) { // html = "<img src='" + reader.result + "'/>";
                html = reader.result; output.innerHTML = html; }; } })(); </script>
</body>

 

4,對象URL(和Data URI不一樣,Data URI是將內容自己存儲在字符串中,放在任何地方均可以使用,而對象URL是將內容存儲在內存中)

對象URL也叫blob URL,是指引用了  存儲在File或Blob中的數據   的URL。使用對象URL的好處是能夠沒必要把文件內容讀取到JavaScript中而直接使用文件內容,爲此只要在須要文件內容的地方提供對象URL便可。要建立對象URL,能夠使用window.URL.createObjectURL()方法,並傳入File或Blob對象。這個函數的返回值是一個字符串,指向一塊內存的地址【理解方式(關鍵):這個字符串就是個URL,和普通URL同樣指向一個地方,這裏是指向了一塊內存(而普通的URL指向的是文件夾中的文件)】。這個字符串 就是[保存在內存中的]文件內容的URL。只要這塊內存還在,就能夠使用這個URL來指向文件。好比,能夠用以下代碼顯示一個圖像文件:

<body><a id='rtobottom' href='#blog_post_info_block'>底部</a>
<input type="file" id="eleFile"/>
<div id="output"></div>
<!--<video id="videoElement" src="./res/home.mp4" controls></video>-->
<script type="text/javascript" src="../amomeshoe/js/jquery2.1.4.js"></script>
<script type="text/javascript"> (function () { var eleFile = document.querySelector('#eleFile'), output = document.querySelector('#output'); eleFile.onchange = function (e) { var file = eleFile.files[0], reader = new FileReader, html = '',  url = window.URL.createObjectURL(file); if (/image/.test(file.type)) { /** * 直接把對象URL放在img標籤中,就省去了把數據讀到JavaScript中的麻煩。img標籤會直接從URL中讀取數據並顯示圖像 */ html = "<img src='" + url +"' />"; } else { html = url; } output.innerHTML = html;
       window.URL.revokeObjectURL(url); } })();
</script> </body>

若是再也不須要相應的數據,最好釋放它佔用的內存。當頁面卸載時會自動釋放對象URL佔用的內存。不過最好在不須要某個對象URL時,就立刻釋放其佔用的內存。要手工釋放內存,能夠把對象URL傳給window.URL.revokeObjectURL()方法。

5,讀取拖放的文件:【能夠參見 HTML學習筆記      http://www.cnblogs.com/everest33Tong/p/6706497.html  中的{使用拖放}】

結合使用HTML5的拖放API和文件API,能夠讀取拖放的文件內容。下列代碼能夠將拖放到頁面指定位置的文件信息顯示出來。

 

<body><a id='rtobottom' href='#blog_post_info_block'>底部</a>
<input type="file" id="eleFile"/>
<div id="target"></div>
<div id="output"></div>
<!--<video id="videoElement" src="./res/home.mp4" controls></video>-->
<script type="text/javascript" src="../amomeshoe/js/jquery2.1.4.js"></script>
<script type="text/javascript"> (function () { var eleFile = document.querySelector('#eleFile'), output = document.querySelector('#output'), target = document.querySelector("#target"), files, i, len, info = '<table border="1">'; target.ondragover = handledrag; target.ondragenter = handledrag; function handledrag(e) { e.preventDefault();//dragenter 和 dragover事件的默認操做爲阻止文件拖放,因此要取消默認操做
 } target.ondrop = function (e) { e.preventDefault();//drop事件的默認操做是打開文件的URL,因此也要取消drop的默認操做
            files = e.dataTransfer.files;//這裏讀取到的文件列表和文件輸入字段取得的文件列表對象是同樣的,都是由一個個File對象(即對應一個文件)組成。
            len = files.length; for (i = 0; i < len; i++) { info += "<tr>" + "<td>" + files[i].name + "</td>" + "<td>" + files[i].type + "</td>" + "<td>" + files[i].size + "Bytes</td><tr/>"; } output.innerHTML = info + "</table>"; }; })(); </script>
</body>

 

6,利用 XHR 和 拖放 上傳文件

 

<body>
<form action="test.php" method="post" id="form">
    <table id="data" border="1">
    </table>
    <input type="submit" value="GO" id="sub" />
</form>
<div id="target">拖放至此</div>
<div id="re"></div>
<script>
    var target = document.getElementById('target'); var ff = document.getElementById('ff'); var sub = document.getElementById('sub'); var form = document.getElementById('form'); var re = document.getElementById('re'); var files; target.ondragenter = handleDrag; //若要釋放區事件drop生效,必須首先阻止釋放區的dragenter和dragover事件的默認行爲
    target.ondragover = handleDrag; function handleDrag(e) { e.preventDefault(); } target.ondrop = function (e) { files = e.dataTransfer.files; e.preventDefault(); var formData = new FormData(form); var hr = new XMLHttpRequest(); hr.onreadystatechange = function (e) { if (e.target.readyState == XMLHttpRequest.DONE && e.target.status == 200) { re.innerHTML = e.target.responseText; } }; hr.open('post', form.action); if (files) { /** * 服務器端只須要像處理表單提交的文件同樣處理便可,能夠打印$_FIELS看下詳情。 */ formData.append('fileee',files[0]);//上傳拖放的文件
 } hr.send(formData); }; </script>
</body>

 

25.5, Web Timing API

1,這個API的功能是分析頁面性能。Web Timing API機制的核心是window.performance對象。對頁面的全部度量信息,都包括在這個對象中。最新的瀏覽器中performance有三個屬性,navigation、timing、memory。其中前兩個屬性的具體信息以下截圖:

25.6,Web Workers

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

2,如何使用Worker

參考網站,很詳細:【 http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html 】 

一個詳細的例子,代碼以下:

---- 主線程頁面

<!DOCTYPE HTML>
<html>
<head>
    <title>a.html</title>
    <meta charset="utf-8">
</head>
<body>
<div id="msg"></div>
<script>
    //這個是主線程
    var work = new Worker('a.js'); /** * 向分支線程傳遞數據,與XDM(cross-document message)的postMessage()不一樣,這裏能夠傳遞對象參數 */
        //work.postMessage([1,3,5,2]);//傳數組測試

    /** * 因爲javascript是單線程執行的,在求數列的過程當中瀏覽器不能執行其它javascript腳本, * UI渲染線程也會被掛起,從而致使瀏覽器進入僵死狀態。使用web worker將數列的計算過程 * 放入一個新線程裏去執行將避免這種狀況的出現 */ work.postMessage(10);//在本身的電腦上測試,40大概耗時16秒,50大概耗時30分鐘。
 console.time('worker'); //接收分支線程傳回的數據,注意是在work上調用message事件
 work.onmessage = function (e) { console.timeEnd('worker');//能夠打印出大概的計算耗時
 console.log(e.data); }; /** * Worker不能完成給定的任務時會觸發error事件。即分支線程內部的JavaScript在執行中只要遇到錯誤,就會觸發error事件。 * error事件對象中包含三個屬性:filename[文件名]、lineno[錯誤代碼行號]、message[錯誤消息]. * 最好始終使用onerror處理程序,不然,Worker在發生錯誤時就會悄無聲息的失敗了 */ work.onerror = function (e) { console.log("ERROR: " + e.filename + " (" + e.lineno + "): " + e.message) }; console.log('hello WORLD"');//驗證JavaScript是否被阻塞,也但是設置個setTimeout函數驗證
    /** * 任什麼時候候,只要調用terminate()方法就能夠中止Worker工做。並且分支線程Worker中的代碼會當即中止執行, * 後續的全部過程都不會發生(包括error和message事件也不會再觸發); */
    //work.terminate();
</script>
</body>
</html>

分支線程

/** * fibonacci.js, 分支線程 * 關於Web Worker,最重要的是要知道它所執行的JavaScript代碼徹底在另外一個做用域中,與當前網頁(即主線程)中的代碼不共享做用域。 * 在Web Worker中,一樣有一個全局對象和其餘對象以及其餘方法。可是Web Worker中的代碼不能訪問DOM,也沒法經過任何方式影響頁面 * 的外觀。 * Web Worker中的全局對象是worker對象自己(注意和主線程中的worker對象不是同一個對象)。在這個特殊的全局做用域中,this 和 self * 引用的都是worker對象。爲便於處理數據,Web Worker自己也是一個最小化的運行環境。具體說來以下: * ■ 最小化的navigator對象 * ■ 只讀的location對象 * ■ setTimeout(),setInterval(),clearTimeout(),clearInterval()方法 * ■ XMLHttpRequest構造函數 *顯然Web Worker的運行環境和頁面環境相比,功能是至關有限的。(將來可能會擴展) */ self.onmessage = function (e) { //接收數組測試
    // var d = e.data;
    // d.sort(function (a, b) {
    // return a-b;//升序排列
    // });
    // postMessage(d);

    //Fibonacci
    var d = parseInt(e.data, 10); postMessage(fibonacci(d));//向主線程返回數據
}; //計算fibonacci數列的函數
function fibonacci(n) { return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2); } /** * 在Worker內部,調用close()方法也能夠中止工做。就像在主線程中調用terminate()同樣。 * 可是貌似close()方法只會阻止「下一級別的代碼」執行,同一級別的代碼即便在close()以後 * 也會被執行。好比close()放在onmessage內部postMessage()以前,postMessage()方法依然會發回消息! */ //self.close(); //如下代碼非必要,作一些雜亂的驗證
console.log(this, self, this===self);//DedicatedWorkerGlobalScope(專用Worker), true
console.log("YYYYYYYY"); setTimeout(function () { console.log("Delayyyyyyed"); }, 2000);

3,在Worker中包含其餘腳本

Worker 中沒法動態建立新的<script>元素,可是Worker的全局做用域提供了包含其餘腳本的功能,即調用importScripts()方法。這個方法接收一個或多個指向JavaScript文件的URL。每一個加載過程都是異步的,所以全部腳本加載並執行以後,importScripts()纔會執行。例如:

importScripts('file1.js', 'file2.js');

即便file2.js限於file1.js下載完,執行的時候仍然會按照前後順序執行。並且須要注意一點:這些腳本是在Worker的全局做用域中執行的。

|§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

=========||||||||||||||||||||=====附錄======|||||||||||||||||||||||=========

§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

JavaScript之後可能會增長的功能,好可能是借鑑了python

1,常量 const PI = 3.14;

2,  塊級做用域,關鍵字let

3,  函數

3.1,剩餘參數和分佈參數

3.2,默認參數值(才發現JavaScript居然不支持函數參數的默認值)

3.3,生成器,python中有相似概念

4,數組和其餘結構

4.1,迭代器,python中有

4.2,數組領悟(array comprehensions)。這個也是借鑑了python。如,var s = [ i for each (i in numbers) ];

4.3, 解構賦值。借鑑了python,如[a, b] = ['hello', 'world'];那麼a='hello', b='world';

5, 新對象類型

5.1,代理對象

5.2,代理函數

5.3,映射和集合

5.4,WeakMap

5.5, StructType

5.6, ArrayType

6, 類

6.1,私有成員

6.2,getter 和 setter

6.3, 繼承

6.4,模塊

 

附錄B:嚴格模式下的一些變化(略)

附錄C:JavaScript庫,如jQuery等等

相關文章
相關標籤/搜索