第二章 (數據存取)php
一.四種基本的數據存取位置css
(1)字面量java
字面量只表明自身,不存儲在特定的位置。javaScript中的字面量有:String,Number,Boolean,Object,Array,Function,Regex,null,nudefined值。node
(2)本地變量算法
使用關鍵字定義變量。例:var定義的數據存儲單元。數組
(3)數組元素瀏覽器
存儲在javaScript數組對象內部,以數字做爲索引。緩存
(4)對象成員安全
存儲在javaScript對象內部,以字符串做爲索引。閉包
二.管理做用域
1.做用域鏈 ([[Scope]])
[[Scope]]:js一切萬物皆對象,Function也是對象,[[Scope]]是Function的一個內部屬性,該內部屬性包含了函數被建立的做用域中對象的集合。它決定了哪些數據能被函數訪問。
function add(){ //建立add函數過程當中,它就產生了內部做用域,而後它的做用域鏈中也插入了一個對象變量,這個全局對象表明着全部在全局範圍內定義的變量,該全局對象包含了window,docunment,等。 }
而後在執行add這個函數時,會產生一個稱爲執行上下文(執行環境)的內部對象。
每次執行add函數都會建立一個獨一無二的執行環境,因此屢次調用一個函數就會致使建立多個執行環境,當函數執行完畢,執行環境也隨之銷燬。
每一個執行環境都有本身的做用域鏈,用於解析標識符。
2.標識符解析的性能
(1)解析標識符是有代價的,一個標識符所在的位置越深那麼解析的速度則越慢。所以函數中局部變量的讀寫老是最快的,而讀寫全局變量一般是最慢的。
function add (){ var a = document.body; var b = document.getElementId('id'); var c = document.getElementId('abc'); } //該函數引用了三次document,而document是全局對象,搜索該對象的過程必需要遍歷整個做用域鏈,直到最後在全局變量對象中找到。
(2)優化:
function add (){ var doc = doucment; var a = doc .body; var b = doc .getElementId('id'); var c = doc .getElementId('abc'); } //先將一個全局變量的引用儲存在一個局部變量中, 而後使用整個局部變量替代全局變量doucment。因此執行時只執行一次搜索全局變量的過程。
3.改變做用域鏈(with,try-catch)
(1):with語句
function initUI(){ //當使用with語句時,執行環境的做用域就會被臨時改變。 with(document){ //避免屢次書寫document。可是卻生成能夠個包含了document對象全部屬性的可變變量。致使這個可變變量被置於做用域鏈頭部了。 var bd = body; //因此訪問局變量會變慢,訪問doucment卻很快。 } }
(2):try-catch語句
try{ // do something }catch (ex){ // 把異常對象推入一個變量對像而且置於做用域鏈的首位。 alert(ex.message)//做用域在此處被改變 var a = "123"; //局部變量都存在於第二個做用域鏈中了。 handleError(ex) //因此咱們要委託給錯誤處理器函數。 因爲只執行一條語句,而且沒有局部變量的訪問,因此做用域的臨時改變就不會影響代碼性能。
}
4.閉包,做用域,內存
(1)閉包:它容許函數訪問局部做用域以外的數據。
function assing(){ var id = '123'; //執行assing函數時,就建立了包含id這個變量以及其餘數據的活動對象。它成爲了做用域鏈的第一個對象。 saveDom(id); //當閉包被建立的時候,它的[[scope]]屬性被初始化爲這些對象。也就是說 閉包的[[scope]]屬性包含了與執行環境做用域鏈相同的對象的引用。所以會產生反作用。 } //一般只要執行環境執行完畢,活動對象便可銷燬,可是因爲閉包的引入,因此激活對象沒法被銷燬。因此所以會形成性能的損耗。甚至內存泄漏。 //想要減輕閉包對執行速度的影響:將經常使用的跨做用域變量存儲在局部變量中,而後直接訪問局部變量。例:上面的id。
5.原型
(1)js一切皆對象,對象都是基於原型的。
(2)_proto_:每個對象都有一個_proto_屬性,經過它去綁定對象的原型。
(3)對象有兩種成員:原型成員,實例成員。實例成員直接存在於對象實例中,原型成員則從對象原型繼承而來。
var book = { title:'對象', //實例成員 } alret(book.toString) // [object object] 對象繼承而來的原型成員
(4)使用hasOwnProperty()方法判斷對象是否含有某實例成員,參數:成員名稱。也可使用 in 操做符 判斷對象是否包含特性的屬性。
var book = { title:'對象', //實例成員 } //經過 hasOwnProperty() 方法 能夠判斷對象是否包含實例成員。 alret(book.hasOwnProperty('title')) // true alret(book.hasOwnProperty('toString')) // false //經過in操做符 能夠判斷對象是否包含特定的屬性,固然包括實例成員 alret('title' in book) // true alret('toString' in book) // true
6.原型鏈
(1)對象的原型決定了對象的類型!默認狀況下,全部的對象都是對象(Object)的實例,並繼承了全部的基本方法。
(2)經過構造函數建立另一種類型的原型。
function Book (title){ this.title = title; } Book.prototype.sayTitle =function(){ alert(this.title); } var book1 = new Book ('world');
var book2 = new Book ('hello');
alert(book1 instanceof Book) // true alert(book1 instanceof Object) // true book1.sayTitle(); // "hello"; alert(book1.toString) // [object object]
//使用新構造函數 Book 來建立一個新的 Book 實例。
//實例 book1 的原型(_proto_)是Book.prototype,而 Book.prototype的原型(_proto_)是Object。這就是原型鏈的過程。
//book1繼承了原型鏈中的全部成員。
(3)注意:這兩個 Book 實例共享着同一個原型鏈,它們有着本身的屬性 'title' ,而其餘部分都繼承自原型。
(4)搜索原型鏈:例如 當調用 book1.toStting() 時,搜索過程會深刻到原型鏈中直到找到這個方法爲止。因此對象在原型鏈中存在的位置越深,則找到它越慢。
(5)請記住:搜索實例成員比從字面量或者局部變量中讀取數據代價更高,再加上遍歷原型鏈帶來的開銷,性能問題會更加嚴重。
第三章 (文檔對象模型DOM)
一:瀏覽器世界中的DOM
(1)dom是一個獨立於語言的,使用XML,HTML文檔操做的應用程序接口(API)
(2)主要是與HTML文檔打交道。
(3)儘管dom是與語言無關的API,可是在瀏覽器中的接口倒是以javaScript實現的。
二:天生就慢
(1)簡單來講,兩個相互獨立的部分以功能接口鏈接就會帶來性能消耗。例如:把DOM和javaScript大當作兩個島嶼,二者之間以一座收費橋鏈接,那麼每次js訪問dom都須要給過橋費,訪問的越屢次,過橋費就越高。因此建議儘可能減小過橋費。
三:DOM修改和訪問
(1)正如上方所說,訪問一個DOM元素的代價是支付一次過橋費,那麼修改元素的費用可能會更貴。由於它常常致使瀏覽器從新計算頁面的幾何變化。
(2)for循環執行dom的修改和訪問是最爲嚴重的。
function innerHTMLLoop() { for (var count = 0; count < 15000; count++) { document.getElementById('here').innerHTML += 'a'; } } //此代碼每次循環都對dom元素訪問兩次,一次讀取innerHTML屬性內容,另外一次是寫入它。
(3)解決:
function innerHTMLLoop2() { var content = ''; for (var count = 0; count < 15000; count++) { content += 'a'; } document.getElementById('here').innerHTML += content; } //將循環的內容賦值給局部變量,而後一次性寫入到dom元素中,減小dom的操做。
四:innerHTML 和 DOM方法 比較
(1)老式瀏覽器innerHTML快,新式瀏覽器DOM方法快。
五:節點克隆
(1)使用DOM方法更新頁面內容的另一個方法就是克隆已有的DOM元素,效率會高一些。
六:HTML集合
(1)HTML集合是用於存放DOM節點應用的類數組對象,
(2)下列函數的返回值就是一個集合
document.getElementsByName() document.getElementsByClassName() document.getElementsByTagName_r() //返回頁面全部的img元素
document.images //返回頁面全部的a標籤
document.links //返回頁面中全部的form表單
document.forms //返回頁面中第一個表單的全部字段
document.forms[0].elements
(3)以上的這些方法和屬性返回的對象,是一種類數組列表,它們不是數組,可是提供length屬性。
(4)HTML集合實際上就是在查詢文檔,當更新信息時,每次都要重複執行這種查詢操做。例如讀取集合中元素的length索引。這正是低效率的來源。
七:昂貴的集合
//意外的無限循環
var alldivs = document.getElementsByTagName_r('div'); for (var i = 0; i < alldivs.length; i++) { document.body.appendChild(document.createElement('div')) } //每次迭代過程訪問集合的length屬性時,它致使集合器更新,在全部的瀏覽器中會產生明顯的性能損失。
//優化:將集合的長度存進一個局部變量,而後循環判斷條件中使用這個變量。
function loopCacheLengthCollection() { var coll = document.getElementsByTagName_r('div'), len = coll.length; for (var count = 0; count < len; count++) { //do Something
} }
八:訪問集合元素時使用局部變量
//最快的方法(將全部全局變量存儲在局部變量) function collectionNodesLocal() { var coll = document.getElementsByTagName_r('div'), len = coll.length, name = '', el = null; for (var count = 0; count < len; count++) { el = coll[count]; name = el.nodeName; name = el.nodeType; name = el.tagName; } return name; };
九:重繪和迴流
(1)重繪:改變一個元素的背景色不影響它的高度和寬度。只要元素的佈局沒有改變,稱之爲重繪。
(2)迴流:
1.元素的尺寸發生改變,
2.添加和刪除可見的DOM元素
3.元素的位置改變
4最初的頁面渲染
5.頁面的尺寸改變
十:最小化重繪和迴流
(1)每一次瀏覽器都要刷新渲染隊列並回流。由於computed的風格被查詢而引起。
var computed, tmp = '',
bodystyle = document.body.style;
if (document.body.currentStyle) { // IE, Opera
computed = document.body.currentStyle;
} else { // W3C
computed = document.defaultView.getComputedStyle(document.body, '');
}
//
bodystyle.color = 'red'; tmp = computed.backgroundColor; bodystyle.color = 'white'; tmp = computed.backgroundImage; bodystyle.color = 'green'; tmp = computed.backgroundAttachment;
(2)優化:不要在佈局信息改變時查詢它。等到佈局信息改變完成後再查詢。
bodystyle.color = 'red'; bodystyle.color = 'white'; bodystyle.color = 'green'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment;
(3)改變風格:
//此代碼改變了三個風格屬性,每次改變都影響元素的幾何屬性,它致使瀏覽器迴流了三次。
var el = document.getElementById('mydiv'); el.style.borderLeft = '1px'; el.style.borderRight = '2px'; el.style.padding = '5px';
(4)優化:
//將全部改變風格合併在一塊兒執行,只修改DOM一次。
var el = document.getElementById('mydiv'); el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;'; //固然也能夠追加屬性
el.style.cssText += '; border-left: 1px;'; //另一個就是改變元素的class值。
var el = document.getElementById('mydiv'); el.className = 'active';
十一:批量修改DOM
<ul id="mylist"> <li><a href="http://phpied.com">Stoyan</a></li> <li><a href="http://julienlecomte.com">Julien</a></li> </ul> var data = [ { "name": "Nicholas", "url": "http://nczonline.net" }, { "name": "Ross", "url": "http://techfoolery.com" } ]; function appendDataToElement(appendToElement, data) { var a, li; for (var i = 0, max = data.length; i < max; i++) { a = document.createElement('a'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li = document.createElement('li'); li.appendChild(a); appendToElement.appendChild(li); } };
(1)批量操做DOM時,如下三種方法能夠減小重繪和迴流的次數。
1.隱藏元素,進行修改,而後再顯示它
var ul = document.getElementById('mylist'); ul.style.display = 'none'; //先隱藏 appendDataToElement(ul, data); ul.style.display = 'block' //再顯示
2.使用一個文檔片斷在已存DOM以外建立一個子樹,而後將它拷貝到文檔中。
var fragment = document.createDocumentFragment(); //建立文檔片斷 appendDataToElement(fragment, data); document.getElementById('mylist').appendChild(fragment);
3.將原始元素拷貝到一個脫離文檔的節點中,修改副本,而後覆蓋原始元素。
var old = document.getElementById('mylist'); var clone = old.cloneNode(true); appendDataToElement(clone,data); console.log(old.parentNode) //body //replaceChild將某個子節點替換掉 old.parentNode.replaceChild(clone,old);
十二:緩衝佈局信息
(1)說白了儘可能減小對佈局信息的查詢次數,查詢時將它賦值給局部變量,而且使用局部變量進行計算,例如偏移量,滾動條等。
//低效率 myElement.style.left = 1 + myElement.offsetLeft + 'px'; myElement.style.top = 1 + myElement.offsetTop + 'px'; if (myElement.offsetLeft >= 500) { stopAnimation(); } //高效率 var current myElement.offsetLeft; current++ myElement.style.left = current + 'px'; myElement.style.top = current + 'px'; if (current >= 500) { stopAnimation(); }
十三:總結
(1)最小化DOM訪問,在javaScript端作儘量多的事情。
(2)在反覆訪問的地方使用局部變量存放DOM引用。
(3)當心地處理HTML集合,覺得他們表現出 ‘存在性’ ,老是對底層文檔從新查詢。將集合的length存儲到一個變量中,在迭代中使用這個變量,若是常常操做這個集合,能夠將它拷貝到數組中。
(4)若是能夠,使用速度更快的API。 例如:querySelectorAll()....
(5)注意重繪和迴流:批量修改風格,離線操做DOM樹,緩存並減小對佈局信息的訪問。
(6)動畫中使用絕對座標,使用拖放代理。
(7)使用事件託管技術最小化事件句柄數量。
第四章:算法與流程控制
一:四種循環類型
//1.for循環
for(var i=0;i<10;i++){ //do....
} //2.while循環(簡單的預測循環,由預測條件和循環體組成)
var i=0; while(i<10){ //do...
i++; } //3.do-while後測試的循環。
var i =0; do{ // do...
}while(i++<10); //for-in對象循環。(相比以上,它是最慢的循環)
for(var props in boject){ //do...
}
二:優化循環性能
(1):減小迭代的工做量
//1.將length長度存儲在局部變量中
for(var i=0,len<items.length;i<len;i++ ){ //do...
} //2.倒序法
for(var i=itmes.length;i--){ //do...
}
(2):減小迭代次數
達夫設備:限制循環迭代次數的模式.(元素總數超過1000個以上使用)
var iterations = Math.floor(items.length / 8), startAt = items.length % 8, i = 0; do { switch(startAt){ case 0: process(items[i++]); case 7: process(items[i++]); case 6: process(items[i++]); case 5: process(items[i++]); case 4: process(items[i++]); case 3: process(items[i++]); case 2: process(items[i++]); case 1: process(items[i++]); } startAt = 0; } while (--iterations); //達夫設備基本原理:每次循環中最多可8次process(),循環迭代次數爲元素的總數除以8,由於總數不必定是8的倍數,因此須要用startAt 變量存放餘數,指出第一次循環中應當執行多少次方法;
//比方現有12個元素,那麼第一次循環調用process()4次,第二次循環調用process()8次。用兩次循環代替了12次循環。
//此算法是一個比較快的方法,取消了switch表達式,將餘數處理與主循環分開。
var i = items.length % 8; while(i){ process(items[i--]); } i = Math.floor(items.length / 8); while(i){ process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); }
三:基於函數的迭代
(1)forEach() 函數在火狐,谷歌,Safari中爲原生函數。
(2)forEach()仍是基於循環的迭代要慢一些。每一個數組項要關聯額外的函數調用是形成速度慢的緣由。
四:if-else與switch比較
(1)基於可讀性:條件數量大傾向switch,而不是if-else。
(2)性能速度:事實證實,大多數狀況下switch表達式比if-else更快。條件數量越大越明顯。
五:查表法
(1)與if-else和switch相比,查表法不只很是快,並且當須要測試的離散值數量很是大時,也有助於保持代碼的可讀性。
//switch
switch(value){ case 0: return result0; case 1: return result1; case 2: return result2; } //查表法(必須消除全部判斷條件)操做轉換成一個數組查詢或者對象查詢
var results = [result0, result1, result2] return results[value]
六:遞歸
(1)使用遞歸能夠將複雜的算法變得簡單。例如以下:階乘
function factorial(n){ if(n==0){ return 1; }else{ return n*factorial(n-1); } }
(2)遞歸函數存在的問題
1.終止條件不明確或缺乏終止條件會致使函數長時間運行,並使得用戶界面出現假死狀態。
2.還可能出現 "調用棧大小限制" 問題.
(3)調用棧限制
1.js引擎支持的遞歸數量與js調用棧大小直接相關。只有IE例外:它的調用棧與系統空閒內存有關。
//當遞歸函數超過瀏覽器的調用棧容量時的報錯信息 IE:'Stack overflow at line x'; Firefox:'Too much recursion' Safari :"Maximum call stack size exceeded" Opera : 'Abort (control stack overflow)' Chrome是惟一不顯示調用棧溢出錯誤的瀏覽器 咱們能夠這樣捕獲它 try{ recurse(); }catch(ex){ alert('Too much recursion!') }
七.遞歸模式
//1.調用自身 function recurse(){ recurse() } recurse() //2.兩個函數相互調用(造成一個無限循環,很難定位問題) funcion first(){ second() } function second(){ first() } first(); 爲了能在瀏覽器中更安全的工做建議使用:迭代,Memoization 或者結合二者一塊兒使用
//並歸排序法 function merge(left, right) { var tmp = []; while(left.length && right.length) { if(left[0] < right[0]) { tmp.push(left.shift()); } else { tmp.push(right.shift()); } return tmp.concat(left, right); } } function mergeSort(a) { if (a.length === 1){ return a; } var mid = Math.floor(a.length / 2) , left = a.slice(0, mid) , right = a.slice(mid); return merge(mergeSort(left), mergeSort(right)); } console.log(mergeSort([5,1,4,3])) //[1,3,4,5]
以上的排序法 因爲頻繁調用mergeSort()函數,這意味着若是一個長度超過1500的數組就會發生棧溢出錯誤。
如下用迭代實現。
function merge(left, right) { var result = []; while (left.length && right.length) { if (left[0] < right[0]) result.push(left.shift()); else result.push(right.shift()); } return result.concat(left, right); } function mergeSort(a) { if (a.length === 1){ return a; } var work = []; for (var i = 0, len = a.length; i < len; i++){ work.push([a[i]]); } work.push([]); // 若是數組長度爲奇數,避免下面work[k+1]越界 for (var lim = len; lim > 1; lim = Math.floor((lim + 1) / 2)) { for (var j = 0, k = 0; k < lim; j++, k += 2) { work[j] = merge(work[k], work[k + 1]); } work[j] = []; //見下面註釋 } return work[0]; }
第六章:快速響應的用戶界面
一:瀏覽器UI線程
什麼是瀏覽器UI線程:用於執行JavaScript和更新用戶界面的進程一般被稱爲‘瀏覽器UI線程’。
UI線程的工做:UI線程的工做基於一個簡單的隊列系統,任務會被保存到隊列中直到進程空閒,一旦空閒,隊列中的下一個任務就被從新提取出來而且運行,這些任務包括js,頁面更新,重繪和迴流等。
二:瀏覽器的限制
什麼是瀏覽器限制:瀏覽器限制了JavaScript任務的運行時間,這種限制是有必要的,它確保某些惡意代碼不能經過永不中止的密集操做鎖住用戶的瀏覽器或計算機。
瀏覽器限制的種類:分兩種,1.調用棧大小限制,2.長時間運行腳本限制。
三:定時器使用
不管發生何種狀況,建立一個定時器會形成UI線程暫停,定時器代碼會重置全部相關的瀏覽器限制,包括長時間運行腳本定時器。此外,調用棧也在定時器的代碼中重置爲0,這一特性使得定時器成爲長時間運行腳本代碼的理想跨瀏覽器解決方案。
四:使用定時器處理數組
1.常見的一種形成長時間運行腳本的原由就是耗時過長的循環。可使用定時器將循環的工做分解到一系列的定時器中。
//典型的簡單循環模式 for(var i=0,len=items.length;i<len;i++){ process(items[i]); } //這類循環結構運行時間過長的緣由主要是process()的複雜度或items的大小,或者二者兼有。
2.是否可使用定時器的兩個決定性因素。
(1)處理過程是否必須同步?
(2)數據是否必須按順序處理?
若是答案都是‘否’,那麼代碼將適用於定時器分解任務。一種基本的一部代碼模式以下:
function processArray(items,process,callback){ var todo = items.concat(); //克隆原數組 setTimeout(function(){ //取得數組的下個元素並進行處理 process(todo.shit()); //若是還有須要處理的元素,建立另一個定時器 if(todo.length>0){ setTimeout(arguments.callee,25); }else{ callback(items); } },25); } var items=[123,456,789,147,258,369]; function outputValue(value){ console.log(value); } processArray(items,outputValue,function(){ consloe.log('Done!'); }) //processArray()函數三個參數,待處理的數組,對每一項數組調用的函數,處理完成後運行的回調函數。 //只要todo數組中還有條目,那麼就再啓動一個定時器,由於下一個定時器須要運行相同的代碼,因此第一個參數是arguments.callee,該值指向當前正在運行的匿名函數。
後續持續更新!!!