2.5 Ajax是什么? 有什么作用?
Ajax這個東西,很多童鞋在學習的時候都很茫然,聽起來很高大上的趕腳,而且也看不懂的樣子。其實則不然,Ajax很簡單,AJAX 就是一個與服務器交換數據并更新部分網頁的技術,并且是在不重新加載整個頁面的情況下。如果您學過計算機網絡基礎的話,或者學過TCP/IP協議的話,網絡通訊對于你來說輕而易舉,分分鐘搞定(因為http協議就是在TCP的基礎上寫的應用級協議)。
先將Ajax原生代碼貼出來,使用了的XMLHttpRequest接口(目前應該都使用了xmlhttprequest level 2的接口了吧)
window.onload = function() {
function myajax (method, url, st, success, fail) {
// 1.創建一個Ajax的對象
var ajax = new XMLHttpRequest();
// 2.鏈接服務器
ajax.open(method, url, st);
// 3.發送請求
ajax.send();
// 4. 請求回調函數(當服務器給出響應時執行)
ajax.onreadystatechange = function() {
if(ajax.readyState == 4) {
if(ajax.status == 200) {
success(ajax.responseText);
}else if(ajax.status == 404) {
fail("請求文件不存在");
}
}
}
}
myajax('GET', 'a.txt', true, function(str) {
console.log(eval(str));
},function(str) {
});
}
這個就是原生js的Ajax的請求,這里要說明一下的是Ajax的請求的方式有很多,目前只學習POST和GET這兩種方式來完成。至于兩者區別,你們自行研究(So easy),下面咱們來說一下HTTP的返回狀態碼的總結。
2.6 HTTP返回狀態碼總結
狀態碼 | 描述
------------- | -------------
1xx | 請求收到,繼續處理
2xx | 操作成功收到,分析、接受
3xx | 完成此請求必須進一步處理
4xx | 請求包含一個錯誤語法或不能完成
5xx | 服務器執行一個完全有效請求失敗
1xx的狀態碼
狀態碼 | 描述
------------- | -------------
100(繼續) | 請求者應當繼續提出請求。 服務器返回此代碼表示已收到請求的第一部分,正在等待其余部分。
101(切換協議)| 請求者已要求服務器切換協議,服務器已確認并準備切換。
2xx的狀態碼
狀態碼 | 描述
------------- | -------------
200(成功) | 請求者應當繼續提出請求。 服務器返回此代碼表示已收到請求的第一部分,正在等待其余部分。
201(已創建) | 請求成功并且服務器創建了新的資源。
202(已接受) | 服務器已接受請求,但尚未處理。
203(非授權信息)| 服務器已成功處理了請求,但返回的信息可能來自另一來源。
204(無內容) | 服務器成功處理了請求,但沒有返回任何內容。
205(重置內容) | 服務器成功處理了請求,但沒有返回任何內容。 與 204 響應不同,此響應要求請求者重置文檔視圖(例如,清除表單內容以輸入新內容)。
206(部分內容) | 服務器成功處理了部分 GET 請求。
3xx的狀態碼
狀態碼 | 描述
------------- | -------------
300(多種選擇) | 針對請求,服務器可執行多種操作。 服務器可根據請求者(用戶代理)選擇一項操作,或提供操作列表供請求者選擇。
301(永久移動) | 請求的網頁已永久移動到新位置。 服務器返回此響應(對 GET 或 HEAD 請求的響應)時,會自動將請求者轉到新位置。 您應使用此代碼告訴 Googlebot 某個網頁或網站已永久移動到新位置。
302(暫時移動) | 服 務器目前從不同位置的網頁響應請求,但請求者應繼續使用原有位置來進行以后的請求。 此代碼與響應 GET 或 HEAD 請求的 301 代碼類似,會自動將請求者轉到不同的位置,但您不應使用此代碼來告訴 Googlebot 某個網頁或網站已經移動,因為 Googlebot 會繼續抓取原有位置并編入索引。
303(查看其他位置) | 請求者應當對不同的位置使用單獨的 GET 請求來檢索響應時,服務器返回此代碼。 對于除 HEAD 之外的所有請求,服務器會自動轉到其他位置。
304(未修改) | 自從上次請求后,請求的網頁未修改過。服務器返回此響應時,不會返回網頁內容。如果網頁自請求者上次請求后再也沒有更改 過,您應當將服務器配置為返回此響應(稱為 If-Modified-Since HTTP 標頭)。 由于服務器可以告訴 Googlebot 自從上次抓取后網頁沒有更改過,因此可節省帶寬和開銷。
305(使用代理) | 請求者只能使用代理訪問請求的網頁。 如果服務器返回此響應,還表示請求者應使用代理。
307(暫時重定向) | 服務器目前從不同位置的網頁響應請求,但請求者應繼續使用原有位置來進行以后的請求。 此代碼與響應 GET 和 HEAD 請求的 301 代碼類似,會自動將請求者轉到不同的位置,但您不應使用此代碼來告訴 Googlebot 某個頁面或網站已經移動,因為 Googlebot 會繼續抓取原有位置并編入索引。
4xx狀態碼
狀態碼 | 描述
------------- | -------------
400(錯誤請求) | 服務器不理解請求的語法。
401(未授權) | 請求要求身份驗證。 對于需要登錄的網頁,服務器可能返回此響應。
403(禁止) | 服務器拒絕請求。 如果您看到 Googlebot 在嘗試抓取您網站上的有效網頁時收到此狀態代碼(可以在 Google 網站管理員工具診 斷 下的網絡抓取 頁面上看到此信息),可能是您的服務器或主機拒絕 Googlebot 訪問。
404(未找到) | 服務器找不到請求的網頁。 例如,如果請求服務器上不存在的網頁,服務器通常會返回此代碼。
405(禁用的方法) | 禁用請求中指定的方法。
406(不可接受) | 無法使用請求的內容特性響應請求的網頁。
407(需要代理授權) | 此狀態代碼與 401(未授權)類似,但指定請求者應當授權使用代理。 如果服務器返回此響應,還會指明請求者應當使用的代理。
408(請求超時) | 服務器等候請求時發生超時。
409(沖突) | 服務器在完成請求時發生沖突。 服務器必須在響應中包含有關沖突的信息。 服務器在響應與前一個請求相沖突的 PUT 請求時可能會返回此代碼,同時會附上兩個請求的差異列表。
410(已刪除) | 如果請求的資源已永久刪除,服務器就會返回此響應。 該代碼與 404(未找到)代碼相似,但在資源以前存在而現在不存在的情況下,有時會用來替代 404 代碼。 如果資源已永久刪除,您應當使用 301 指定資源的新位置。
411(需要有效長度) | 服務器不接受不含有效內容長度標頭字段的請求。
412(未滿足前提條件)| 服務器未滿足請求者在請求中設置的其中一個前提條件。
413(請求實體過大) | 服務器無法處理請求,因為請求實體過大,超出服務器的處理能力。
414(請求的 URI 過長) | 請求的 URI(通常為網址)過長,服務器無法處理。
415(不支持的媒體類型) | 請求的格式不受請求頁面的支持。
416(請求范圍不符合要求) | 如果頁面無法提供請求的范圍,則服務器會返回此狀態代碼。
417(未滿足期望要求) | 服務器未滿足”期望”請求標頭字段的要求。
5xx狀態碼
狀態碼 | 描述
------------- | -------------
500(服務器內部錯誤) | 服務器遇到錯誤,無法完成請求。
501(尚未實施) | 服務器不具備完成請求的功能。 例如,服務器無法識別請求方法時可能會返回此代碼。
502(錯誤網關) | 服務器充當網關或代理,從上游服務器收到無效響應。
503(服務不可用) | 服務器目前無法使用(由于超載或停機維護)。 通常,這只是暫時狀態。
505(HTTP 版本不受支持) | 服務器不支持請求中所用的 HTTP 協議版本。
2.7 this指向問題?
this指向的這個問題網上很多文章說了一大堆,說到了重點,但是有點啰嗦。自認為只要一句話就行了。
this永遠指向的是最后調用它的對象,也就是看它執行的時候是誰調用的
但是要注意的一點是:
this在嚴格模式下,默認的指向不是window而是undefined;
在真正開發中經常會使用var that = this, 來保存第一次執行環境下的this。(在ES6中箭頭函數的this指向問題,箭頭函數里的this是定義時所在的作用域,而不是運行時所在的作用域)
2.8 說一下作用域鏈是什么?
最近好幾天要不在老家要不在學校,沒有時間去更新, 今天有點時間,下面就談談作用域鏈。(網上的案例很多,我拿過來分析,并且將閉包放到這里來說明)
在JavaScript中,變量的作用域有全局作用域和局部作用域兩種。
☆ 作用域鏈
在JavaScript中,函數也是對象,實際上,JavaScript里一切都是對象。函數對象和其它對象一樣,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標準第三版定義,該內部屬性包含了函數被創建的作用域中對象的集合,這個集合被稱為函數的作用域鏈,它決定了哪些數據能被函數訪問。
1. 當一個函數創建后,它的作用域鏈會被創建此函數的作用域中可訪問的數據對象填充。例如定義下面這樣一個函數:
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
在函數add創建時,它的作用域鏈中會填入一個全局對象,該全局對象包含了所有全局變量(不要問我哪里產生了[[scope]],因為是自己生成的。),如下圖所示(注意:圖片只例舉了全部變量中的一部分):
2. 當函數執行時,作用域鏈會發生變化的。
比如執行下列代碼:
var total = add(5, 10);
執行此函數時會創建一個稱為“運行期上下文(execution context)”的內部對象,運行期上下文定義了函數執行時的環境。每個運行期上下文都有自己的作用域鏈,用于標識符解析,當運行期上下文被創建時,而它的作用域鏈初始化為當前運行函數的[[Scope]]所包含的對象。
這些值按照它們出現在函數中的順序被復制到運行期上下文的作用域鏈中。它們共同組成了一個新的對象,叫“活動對象(activation object)”,該對象包含了函數的所有局部變量、命名參數、參數集合以及this,然后此對象會被推入作用域鏈的前端,當運行期上下文被銷毀,活動對象也隨之銷毀。新的作用域鏈如下圖所示:
這里說明一下scope對象中的0的指向變成了活動對象,1的指向變成了全局對象。
3. 使用with和catch可以改變執行時期的作用域鏈
如果使用如下代碼:
function initUI(){
with(document){
var bd=body,
links=getElementsByTagName("a"),
i=0,
len=links.length;
while(i < len){
update(links[i++]);
}
getElementById("btnInit").onclick=function(){
doSomething();
};
}
}
這里使用width語句來避免多次書寫document,看上去更高效,實際上產生了性能問題。
當代碼運行到with語句時,運行期上下文的作用域鏈臨時被改變了。一個新的可變對象被創建,它包含了參數指定的對象的所有屬性。這個對象將被推入作用域鏈的頭部,這意味著函數的所有局部變量現在處于第二個作用域鏈對象中,因此訪問代價更高了。如下圖所示:
☆ 談一下閉包(結合作用域鏈)
閉包其實就是一個函數在運行期間創建了另外一個函數。如下代碼:
function outside() {
var a = 1;
function inside() {
console.log(a);
}
}
來說一下形成閉包的過程。當執行outside函數時, js引擎會創建outside函數的上下文的作用域鏈這個作用鏈包含了outside的執行時的活動對象和全局對象。當定義inside的時候,inside函數上的作用域鏈包含了兩大塊:outside函數的活動對象、全局對象。(當inside執行時,會在作用域鏈上增加一個新的對象,也就是它自身的活動對象。)
2.9 怎么阻止冒泡事件?
既然說到冒泡事件,那么就清晰去解析一下冒泡事件和捕獲事件。(IE8及其IE8以下版本是不支持捕獲事件的。)DOM的發展經歷了DOM0、DOM2、DOM3三個版本。目前各個瀏覽器基本上支持DOM2的標準,所以咱們從DOM2的事件流來說起。
一般來說,事件流分為三個階段: 捕獲階段、目標階段.
、冒泡階段。
1. 捕獲階段
事件的第一個階段就是捕獲階段。事件從文檔的根節點也就是window對象開始流向目標對象節點。(W3C規范是從Document出發,但是瀏覽器廠商都是從Window對象)途中經過各個層次的DOM節點,并在各個節點上觸發捕獲事件,直到到達事件的目標節點。捕獲階段的主要任務就是建立傳播途徑,在冒泡階段,事件通過這個路徑進行回溯到文檔根節點。捕獲事件就是在捕獲階段觸發的事件,并執行相應的處理函數。
2.目標階段
事件對象到達其事件目標。 這個階段被我們稱為目標階段。一旦事件對象到達事件目標,該階段的事件監聽器就要對它進行處理。如果一個事件對象類型被標志為不能冒泡。那么對應的事件對象在到達此階段時就會終止傳播。
3.冒泡階段
事件對象以一個與捕獲階段相反的方向從事件目標傳播經過其祖先節點傳播到window。這個階段被稱之為冒泡階段。在此階段注冊的事件監聽器會對相應的冒泡事件進行處理。
4. 實例驗證事件流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css" media="screen">
.box1 {
width: 800px;
height: 400px;
background: red;
margin: 0 auto;
}
.box2 {
width: 500px;
height: 300px;
background: green;
margin: auto;
}
.box3 {
width: 300px;
height: 100px;
background: blue;
margin: auto;
}
</style>
<script type="text/javascript">
window.onload = function() {
var box1 = document.getElementsByClassName("box1")[0];
var box2 = document.getElementsByClassName("box1")[0];
var box3 = document.getElementsByClassName("box1")[0];
window.addEventListener('click', function() {
console.log("捕獲階段:window執行捕獲事件");
}, true);
document.body.addEventListener('click', function() {
console.log("捕獲階段:body執行捕獲事件");
}, true);
window.addEventListener('click', function() {
console.log("冒泡階段:window執行冒泡事件");
}, false);
document.body.addEventListener('click', function() {
console.log("冒泡階段:body執行冒泡事件");
}, false);
box1.addEventListener('click', function() {
/* */
console.log("捕獲階段:box1執行捕獲事件");
}, true);
box2.addEventListener('click', function() {
/* */
console.log("捕獲階段:box2執行捕獲事件");
}, true);
box3.addEventListener('click', function() {
/* */
console.log("捕獲階段:box3執行捕獲事件");
}, true);
box3.addEventListener('click', function() {
/* */
console.log("冒泡階段:box3執行冒泡事件");
}, true);
box2.addEventListener('click', function() {
/* */
console.log("冒泡階段:box2執行冒泡事件");
}, true);
box1.addEventListener('click', function() {
/* */
console.log("冒泡階段:box1執行冒泡事件");
}, true);
}
</script>
</head>
<body>
<div class="box1">
<div class="box2">
<div class="box3">
</div>
</div>
</div>
</body>
</html>
執行結果如下:
執行結果
這里說明一下當執行到達目標階段時,可能出現冒泡事件比捕獲事件先執行,這種情況是因為冒泡事件比捕獲事件先注冊。