封裝跨瀏覽器 AJAX 元件時,最隱蔽的坑在於 onreadystatechange 回呼函式的 this 指向差異:標準瀏覽器指向 XMLHttpRequest 實例,IE 6/7 卻指向 window 全域物件。本文詳細重現問題、診斷過程與解決方案。
var Ajax = {
xhr: null,
callback: null,
connect: function(params) {
this.xhr = this.createXHR();
this.callback = params.callback;
this.xhr.onreadystatechange = this.response; // 直接指派方法
this.xhr.open(params.method, params.url, true);
this.xhr.send(params.data);
},
response: function() {
// 標準瀏覽器 this === this.xhr
// IE 6/7 this === window
if (this.readyState === 4) { ... }
}
};
在 IE 下,this.readyState 讀取 window.readyState(undefined),條件判斷失效,回呼永不執行,且無任何錯誤訊息。
response 內插入 alert(this instanceof Object):IE 回傳 false,證明 this 非物件。alert(this === window):IE 回傳 true,確認指向全域物件。IE 將 onreadystatechange 視為全域事件處理器,以 window 為執行上下文呼叫;標準瀏覽器依 DOM Level 2 Events 規範,以事件目標(XHR 物件)為 this。
connect: function(params) {
var xhr = this.createXHR();
var self = this;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) self.callback(xhr.responseText);
else self.callback(null, xhr.status);
}
};
...
}
Function.prototype.bind (ES5,需 polyfill)xhr.onreadystatechange = this.response.bind(this); // this 鎖定為 Ajax 物件 // 但 response 內仍需用 Ajax.xhr 而非 this.xhr
Ajax.xhr 而非 this(最簡單)response: function() {
if (Ajax.xhr.readyState === 4) { ... }
}
放棄手寫 XHR 封裝,改用 fetch + Promise 或成熟庫:
// fetch 範例
fetch(url, { method: 'POST', body: data })
.then(r => r.json())
.then(data => callback(data))
.catch(err => callback(null, err));
this 指向問題。whatwg-fetch 與 promise-polyfill。