Ajax là một thành phần cốt lõi trong Zepto, cung cấp khả năng gửi yêu cầu không đồng bộ đến máy chủ thông qua XMLHttpRequest, đồng thời hỗ trợ cơ chế JSONP để xử lý các tình huống truy cập chéo miền (cross-origin).
Bài viết này dựa trên mã nguồn Zepto phiên bản 1.2.0.
Thứ tự kích hoạt sự kiện trong quá trình Ajax
Zepto định nghĩa một chuỗi sự kiện toàn cục nhằm theo dõi vòng đời của mỗi yêu cầu Ajax. Thứ tự thực thi chuẩn như sau:ajaxStart: Kích hoạt ngay trước khi tạo thể hiệnXMLHttpRequest.ajaxBeforeSend: Gọi ngay trước khi gửi yêu cầu — có thể hủy bỏ yêu cầu nếu trả vềfalse.ajaxSend: Kích hoạt ngay sau khi yêu cầu được gửi đi.ajaxSuccess/ajaxError: Tương ứng với kết quả thành công hoặc thất bại.ajaxComplete: Luôn được gọi khi yêu cầu kết thúc — bất kể trạng thái.ajaxStop: Kích hoạt khi không còn yêu cầu Ajax nào đang chạy.
Các tùy chọn cấu hình chính
Dưới đây là giải thích ngắn gọn các tham số thường dùng trong hàm$.ajax():
type: Phương thức HTTP (GET,POST, v.v.).url: Địa chỉ đích của yêu cầu.data: Dữ liệu gửi kèm (có thể là đối tượng, chuỗi hoặcFormData).processData: Nếutrue, tự động chuyển dữ liệu sang dạng query string.contentType: Giá trị tiêu đềContent-Type(mặc định:application/x-www-form-urlencoded).dataType: Loại dữ liệu mong đợi từ máy chủ (json,xml,script,html,text,jsonp).jsonp: Tên tham số chứa tên callback trong yêu cầu JSONP (mặc định:callback).jsonpCallback: Tên hàm callback phía client (nếu không truyền, Zepto sẽ sinh tên tự động).timeout: Thời gian chờ tối đa (đơn vị: mili-giây;0= vô hạn).headers: Đối tượng chứa các tiêu đề HTTP tùy chỉnh.async: Xác định yêu cầu có phải bất đồng bộ hay không (truemặc định).global: Cho phép/ẩn các sự kiện toàn cục (truemặc định).context: Đối tượngthisđược sử dụng khi gọi các callback (windowmặc định).traditional: Kiểm soát cách tuần tự hóa dữ liệu lồng sâu (false→p[nested]=v;true→p=object).xhrFields: Các thuộc tính cần gán trực tiếp vào thể hiệnXMLHttpRequest.cache: Kiểm soát cache cho yêu cầuGET(falsethêm timestamp để bypass cache).username/password: Thông tin xác thực HTTP cơ bản.dataFilter: Hàm xử lý dữ liệu phản hồi trước khi phân tích.xhr: Hàm tạo thể hiệnXMLHttpRequesttùy chỉnh.accepts: Danh sách MIME types ưu tiên gửi tới máy chủ.beforeSend: Hàm được gọi trước khi gửi yêu cầu — trả vềfalseđể hủy.success,error,complete: Các callback tương ứng với trạng thái yêu cầu.
Các hàm tiện ích nội bộ
triggerAndReturn(context, eventName, data)
Kích hoạt sự kiện và trả về false nếu hành vi mặc định bị ngăn chặn:
function triggerAndReturn(ctx, name, payload) {
const evt = $.Event(name);
$(ctx).trigger(evt, payload);
return !evt.isDefaultPrevented();
}
triggerGlobal(settings, context, name, data)
Gọi triggerAndReturn trên document hoặc ngữ cảnh đã chỉ định nếu settings.global === true:
function triggerGlobal(opts, ctx, name, payload) {
if (opts.global) {
return triggerAndReturn(ctx || document, name, payload);
}
}
ajaxStart(settings)
Tăng bộ đếm yêu cầu đang hoạt động và phát sự kiện ajaxStart nếu đây là yêu cầu đầu tiên:
function ajaxStart(opts) {
if (opts.global && $.active++ === 0) {
triggerGlobal(opts, null, 'ajaxStart');
}
}
ajaxStop(settings)
Giảm bộ đếm và phát ajaxStop khi không còn yêu cầu nào đang chạy:
function ajaxStop(opts) {
if (opts.global && !(--$.active)) {
triggerGlobal(opts, null, 'ajaxStop');
}
}
ajaxBeforeSend(xhr, settings)
Xử lý logic trước khi gửi yêu cầu — gọi beforeSend và sự kiện ajaxBeforeSend. Nếu bất kỳ bước nào trả về false, yêu cầu sẽ bị hủy:
function ajaxBeforeSend(xhrInst, opts) {
const ctx = opts.context;
const prevent = opts.beforeSend.call(ctx, xhrInst, opts) === false ||
triggerGlobal(opts, ctx, 'ajaxBeforeSend', [xhrInst, opts]) === false;
if (prevent) return false;
triggerGlobal(opts, ctx, 'ajaxSend', [xhrInst, opts]);
}
ajaxComplete(status, xhr, settings)
Gọi callback complete, sau đó phát sự kiện ajaxComplete và kiểm tra dừng toàn cục:
function ajaxComplete(status, xhrInst, opts) {
const ctx = opts.context;
opts.complete.call(ctx, xhrInst, status);
triggerGlobal(opts, ctx, 'ajaxComplete', [xhrInst, opts]);
ajaxStop(opts);
}
ajaxSuccess(data, xhr, settings, deferred)
Xử lý phản hồi thành công: gọi success, resolve deferred, phát sự kiện ajaxSuccess, rồi gọi ajaxComplete:
function ajaxSuccess(resp, xhrInst, opts, def) {
const ctx = opts.context;
opts.success.call(ctx, resp, 'success', xhrInst);
if (def) def.resolveWith(ctx, [resp, 'success', xhrInst]);
triggerGlobal(opts, ctx, 'ajaxSuccess', [xhrInst, opts, resp]);
ajaxComplete('success', xhrInst, opts);
}
ajaxError(error, type, xhr, settings, deferred)
Xử lý lỗi: gọi error, reject deferred, phát ajaxError, rồi gọi ajaxComplete:
function ajaxError(err, errType, xhrInst, opts, def) {
const ctx = opts.context;
opts.error.call(ctx, xhrInst, errType, err);
if (def) def.rejectWith(ctx, [xhrInst, errType, err]);
triggerGlobal(opts, ctx, 'ajaxError', [xhrInst, opts, err || errType]);
ajaxComplete(errType, xhrInst, opts);
}
mimeToDataType(mime)
Chuyển đổi giá trị Content-Type từ header thành kiểu dữ liệu ngắn gọn (json, xml, script, html, text):
const htmlType = 'text/html';
const jsonType = 'application/json';
const scriptRE = /^(?:text|application)\/javascript/i;
const xmlRE = /^(?:text|application)\/xml/i;
function mimeToDataType(mimeStr) {
if (!mimeStr) return 'text';
const base = mimeStr.split(';', 2)[0];
return base === htmlType ? 'html' :
base === jsonType ? 'json' :
scriptRE.test(base) ? 'script' :
xmlRE.test(base) ? 'xml' : 'text';
}
appendQuery(url, query)
Nối chuỗi truy vấn vào URL, đảm bảo ký tự phân tách đúng (? thay vì && hay ?&):
function appendQuery(urlStr, queryStr) {
if (!queryStr) return urlStr;
return (urlStr + '&' + queryStr).replace(/[&?]{1,2}/, '?');
}
serialize(params, obj, traditional, keyPrefix)
Hàm đệ quy tuần tự hóa dữ liệu lồng sâu — hỗ trợ cả định dạng truyền thống và chuẩn mới:
function serialize(params, obj, isTraditional, prefix) {
const isArray = $.isArray(obj);
const isPlainObj = $.isPlainObject(obj);
$.each(obj, (k, v) => {
let key = k;
const type = $.type(v);
if (prefix) {
key = isTraditional ? prefix : `${prefix}[${isPlainObj || type === 'object' || type === 'array' ? k : ''}]`;
}
if (!prefix && isArray) {
params.add(v.name, v.value);
} else if (type === 'array' || (!isTraditional && type === 'object')) {
serialize(params, v, isTraditional, key);
} else {
params.add(key, v);
}
});
}
serializeData(options)
Chuẩn bị dữ liệu gửi đi: tuần tự hóa data nếu cần, và nối vào URL nếu là yêu cầu GET hoặc jsonp:
function serializeData(opts) {
if (opts.processData && opts.data && $.type(opts.data) !== 'string') {
opts.data = $.param(opts.data, opts.traditional);
}
if (opts.data && (!opts.type || /get|jsonp/i.test(opts.type))) {
opts.url = appendQuery(opts.url, opts.data);
opts.data = undefined;
}
}
Giao diện công khai
$.active
Số lượng yêu cầu Ajax hiện đang chạy — khởi tạo bằng 0.
$.ajaxSettings
Cấu hình mặc định toàn cục cho mọi yêu cầu Ajax:
$.ajaxSettings = {
type: 'GET',
beforeSend: $.noop,
success: $.noop,
error: $.noop,
complete: $.noop,
context: null,
global: true,
xhr: () => new XMLHttpRequest(),
accepts: {
script: 'text/javascript, application/javascript',
json: 'application/json',
xml: 'application/xml, text/xml',
html: 'text/html',
text: 'text/plain'
},
crossDomain: false,
timeout: 0,
processData: true,
cache: true,
dataFilter: $.noop
};
$.param(obj, traditional)
Biến đổi đối tượng thành chuỗi truy vấn — sử dụng serialize và mã hóa URI:
$.param = function(obj, isTraditional) {
const parts = [];
parts.add = function(k, v) {
if ($.isFunction(v)) v = v();
if (v == null) v = '';
this.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
};
serialize(parts, obj, isTraditional);
return parts.join('&').replace(/%20/g, '+');
};
$.ajaxJSONP(options, deferred)
Cơ chế JSONP: chèn thẻ <script>, thiết lập callback toàn cục, xử lý timeout và lỗi:
let jsonpCounter = Date.now();
$.ajaxJSONP = function(opts, def) {
if (!('type' in opts)) return $.ajax(opts);
const cbNameRaw = opts.jsonpCallback;
const cbName = typeof cbNameRaw === 'function'
? cbNameRaw()
: cbNameRaw || `Zepto${jsonpCounter++}`;
const script = document.createElement('script');
const originalCb = window[cbName];
let response;
let abortTimer;
const xhr = { abort: () => {
clearTimeout(abortTimer);
$(script).remove();
window[cbName] = originalCb;
}
};
if (def) def.promise(xhr);
$(script).on('load error', function(e, errType) {
clearTimeout(abortTimer);
$(script).off().remove();
if (e.type === 'error' || !response) {
ajaxError(null, errType || 'error', xhr, opts, def);
} else {
ajaxSuccess(response[0], xhr, opts, def);
}
window[cbName] = originalCb;
if (response && $.isFunction(originalCb)) {
originalCb(response[0]);
}
});
if (ajaxBeforeSend(xhr, opts) === false) {
xhr.abort();
return xhr;
}
window[cbName] = function() {
response = arguments;
};
script.src = opts.url.replace(/\?(.+)=\?/, `?$1=${cbName}`);
document.head.appendChild(script);
if (opts.timeout > 0) {
abortTimer = setTimeout(() => xhr.abort(), opts.timeout);
}
return xhr;
};
$.ajax(options)
Hàm trung tâm xử lý tất cả yêu cầu Ajax — bao gồm: hợp nhất cấu hình, kiểm tra miền, tuần tự hóa dữ liệu, thiết lập header, mở kết nối và xử lý phản hồi:
$.ajax = function(opts) {
const settings = $.extend({}, $.ajaxSettings, opts);
const def = $.Deferred && $.Deferred();
const xhr = settings.xhr();
ajaxStart(settings);
// Phát hiện cross-domain
const origin = document.createElement('a');
origin.href = location.href;
if (!settings.crossDomain) {
const target = document.createElement('a');
target.href = settings.url;
settings.crossDomain = `${origin.protocol}//${origin.host}` !== `${target.protocol}//${target.host}`;
}
// Xử lý URL và dữ liệu
if (!settings.url) settings.url = location.href;
if (settings.url.includes('#')) settings.url = settings.url.split('#')[0];
serializeData(settings);
// Bypass cache cho script/jsonp
const isJsonp = /(?:\?.+=\?)/.test(settings.url) || settings.dataType === 'jsonp';
if (isJsonp) settings.dataType = 'jsonp';
if (settings.cache === false || (isJsonp && settings.cache !== true)) {
settings.url = appendQuery(settings.url, '_=' + Date.now());
}
// Chuyển hướng sang JSONP nếu cần
if (settings.dataType === 'jsonp') {
if (!isJsonp) {
const param = settings.jsonp === false ? '' : (settings.jsonp || 'callback') + '=?';
settings.url = appendQuery(settings.url, param);
}
return $.ajaxJSONP(settings, def);
}
// Thiết lập header
const headers = {};
const setHeader = (name, val) => headers[name.toLowerCase()] = [name, val];
setHeader('Accept', settings.accepts[settings.dataType] || '*/*');
if (!settings.crossDomain) {
setHeader('X-Requested-With', 'XMLHttpRequest');
}
if (settings.contentType || (settings.data && /post|put|delete/i.test(settings.type))) {
setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded');
}
if (settings.headers) {
$.each(settings.headers, (k, v) => setHeader(k, v));
}
xhr.setRequestHeader = setHeader;
// Gọi beforeSend
if (ajaxBeforeSend(xhr, settings) === false) {
xhr.abort();
ajaxError(null, 'abort', xhr, settings, def);
return xhr;
}
// Mở kết nối
const async = settings.async !== false;
xhr.open(settings.type, settings.url, async, settings.username, settings.password);
// Áp dụng xhrFields và headers
if (settings.xhrFields) {
$.each(settings.xhrFields, (k, v) => xhr[k] = v);
}
$.each(headers, (k, v) => xhr.setRequestHeader.apply(xhr, v));
// Xử lý phản hồi
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
xhr.onreadystatechange = $.noop;
clearTimeout(abortTimer);
let result;
const isSuccess = (xhr.status >= 200 && xhr.status < 300) ||
xhr.status === 304 ||
(xhr.status === 0 && location.protocol === 'file:');
if (isSuccess) {
const mimeType = settings.mimeType || xhr.getResponseHeader('content-type');
const dataType = settings.dataType || mimeToDataType(mimeType);
if (xhr.responseType === 'arraybuffer' || xhr.responseType === 'blob') {
result = xhr.response;
} else {
result = xhr.responseText;
try {
result = ajaxDataFilter(result, dataType, settings);
if (dataType === 'script') (1, eval)(result);
else if (dataType === 'xml') result = xhr.responseXML;
else if (dataType === 'json') result = /^\s*$/.test(result) ? null : $.parseJSON(result);
} catch (e) {
return ajaxError(e, 'parsererror', xhr, settings, def);
}
}
ajaxSuccess(result, xhr, settings, def);
} else {
ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, def);
}
};
// Thiết lập timeout
if (settings.timeout > 0) {
abortTimer = setTimeout(() => {
xhr.onreadystatechange = $.noop;
xhr.abort();
ajaxError(null, 'timeout', xhr, settings, def);
}, settings.timeout);
}
// Gửi yêu cầu
xhr.send(settings.data || null);
return xhr;
};
Các phương thức tiện ích
$.get(url, data, success, dataType)— gọi$.ajaxvớitype: 'GET'.$.post(url, data, success, dataType)— gọi$.ajaxvớitype: 'POST'.$.getJSON(url, data, success)— gọi$.ajaxvớidataType: 'json'.$.fn.load(url, data, success)— tải nội dung HTML và chèn vào phần tử hiện tại, hỗ trợ selector con.