189 lines
6.5 KiB
JavaScript
189 lines
6.5 KiB
JavaScript
|
|
/**
|
|||
|
|
* H5 polyfill: 在 UniApp 框架初始化完成前,为 uni API 提供浏览器降级实现。
|
|||
|
|
*
|
|||
|
|
* 核心策略:
|
|||
|
|
* - 用 Object.defineProperty 拦截 window.uni 的赋值,防止 UniApp 运行时
|
|||
|
|
* 用存根对象覆盖我们的实现。
|
|||
|
|
* - 当 UniApp 将 stub 对象赋给 window.uni 时,把 stub 合并到我们的代理
|
|||
|
|
* 对象中,同时保留我们对"未实现"API 的真实降级实现。
|
|||
|
|
* - 之后 UniApp 注册真正的 H5 实现(如 uni.request = xhrImpl),属性直接
|
|||
|
|
* 写到代理对象上,我们的 polyfill 就会被替换,互不干扰。
|
|||
|
|
*/
|
|||
|
|
;(function () {
|
|||
|
|
if (typeof window === 'undefined') return;
|
|||
|
|
|
|||
|
|
/* -------- 各 API 的降级实现 -------- */
|
|||
|
|
var polyfills = {};
|
|||
|
|
|
|||
|
|
// uni.request → XHR
|
|||
|
|
polyfills.request = function (opts) {
|
|||
|
|
opts = opts || {};
|
|||
|
|
var xhr = new XMLHttpRequest();
|
|||
|
|
var method = (opts.method || 'GET').toUpperCase();
|
|||
|
|
var url = opts.url || '';
|
|||
|
|
|
|||
|
|
// GET/HEAD 把 data 追加到 query string
|
|||
|
|
if (opts.data && (method === 'GET' || method === 'HEAD')) {
|
|||
|
|
var qs = Object.keys(opts.data).map(function (k) {
|
|||
|
|
return encodeURIComponent(k) + '=' + encodeURIComponent(opts.data[k]);
|
|||
|
|
}).join('&');
|
|||
|
|
if (qs) url += (url.indexOf('?') >= 0 ? '&' : '?') + qs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
xhr.open(method, url, true);
|
|||
|
|
} catch (e) {
|
|||
|
|
opts.fail && opts.fail({ errMsg: 'request:fail ' + e.message });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置请求头
|
|||
|
|
var header = opts.header || opts.headers || {};
|
|||
|
|
Object.keys(header).forEach(function (k) {
|
|||
|
|
try { xhr.setRequestHeader(k, header[k]); } catch (e) {}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
xhr.onreadystatechange = function () {
|
|||
|
|
if (xhr.readyState !== 4) return;
|
|||
|
|
var statusCode = xhr.status;
|
|||
|
|
var data;
|
|||
|
|
try { data = JSON.parse(xhr.responseText); } catch (e) { data = xhr.responseText; }
|
|||
|
|
|
|||
|
|
// 解析响应头
|
|||
|
|
var resHeader = {};
|
|||
|
|
try {
|
|||
|
|
(xhr.getAllResponseHeaders() || '').split('\r\n').forEach(function (line) {
|
|||
|
|
var idx = line.indexOf(':');
|
|||
|
|
if (idx > 0) resHeader[line.slice(0, idx).trim().toLowerCase()] = line.slice(idx + 1).trim();
|
|||
|
|
});
|
|||
|
|
} catch (e) {}
|
|||
|
|
|
|||
|
|
var res = { statusCode: statusCode, data: data, header: resHeader };
|
|||
|
|
if (statusCode >= 200 && statusCode < 400) {
|
|||
|
|
opts.success && opts.success(res);
|
|||
|
|
} else {
|
|||
|
|
opts.fail && opts.fail({ errMsg: 'request:fail statusCode ' + statusCode });
|
|||
|
|
}
|
|||
|
|
opts.complete && opts.complete(res);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
xhr.onerror = function () {
|
|||
|
|
var err = { errMsg: 'request:fail network error' };
|
|||
|
|
opts.fail && opts.fail(err);
|
|||
|
|
opts.complete && opts.complete(err);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
xhr.ontimeout = function () {
|
|||
|
|
var err = { errMsg: 'request:fail timeout' };
|
|||
|
|
opts.fail && opts.fail(err);
|
|||
|
|
opts.complete && opts.complete(err);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var body = null;
|
|||
|
|
if (opts.data && method !== 'GET' && method !== 'HEAD') {
|
|||
|
|
body = typeof opts.data === 'string' ? opts.data : JSON.stringify(opts.data);
|
|||
|
|
}
|
|||
|
|
try { xhr.send(body); } catch (e) {
|
|||
|
|
opts.fail && opts.fail({ errMsg: 'request:fail ' + e.message });
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// getStorageSync → localStorage
|
|||
|
|
polyfills.getStorageSync = function (key) {
|
|||
|
|
try {
|
|||
|
|
var v = localStorage.getItem(key);
|
|||
|
|
if (v === null) return undefined;
|
|||
|
|
try { return JSON.parse(v); } catch (e) { return v; }
|
|||
|
|
} catch (e) { return undefined; }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
polyfills.setStorageSync = function (key, data) {
|
|||
|
|
try {
|
|||
|
|
localStorage.setItem(key, typeof data === 'object' ? JSON.stringify(data) : String(data));
|
|||
|
|
} catch (e) {}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
polyfills.removeStorageSync = function (key) {
|
|||
|
|
try { localStorage.removeItem(key); } catch (e) {}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// getWindowInfo → 浏览器 window
|
|||
|
|
polyfills.getWindowInfo = function () {
|
|||
|
|
return {
|
|||
|
|
windowWidth: window.innerWidth || 375,
|
|||
|
|
windowHeight: window.innerHeight || 667,
|
|||
|
|
screenWidth: window.screen ? window.screen.width : 375,
|
|||
|
|
screenHeight: window.screen ? window.screen.height : 667,
|
|||
|
|
statusBarHeight: 0,
|
|||
|
|
safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 0 }
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// getEnterOptionsSync → 解析当前 URL query
|
|||
|
|
polyfills.getEnterOptionsSync = function () {
|
|||
|
|
var query = {};
|
|||
|
|
try {
|
|||
|
|
var search = location.search.slice(1);
|
|||
|
|
if (search) {
|
|||
|
|
search.split('&').forEach(function (pair) {
|
|||
|
|
var kv = pair.split('=');
|
|||
|
|
if (kv[0]) query[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (e) {}
|
|||
|
|
return { query: query, path: location.pathname };
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* -------- 代理对象:稳定的 window.uni -------- */
|
|||
|
|
// 创建持久代理对象,把 polyfill 方法写入
|
|||
|
|
var _proxy = {};
|
|||
|
|
Object.keys(polyfills).forEach(function (k) { _proxy[k] = polyfills[k]; });
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 把一个 uni 对象(可能含存根)合并到 _proxy。
|
|||
|
|
* 原则:如果 polyfill 中有对应 key,则仅当对方的实现不是存根时才覆盖;
|
|||
|
|
* 其余 key 直接合并,以保留 UniApp 运行时的其他功能(路由、组件等)。
|
|||
|
|
*/
|
|||
|
|
function mergeIntoProxy(srcObj) {
|
|||
|
|
if (!srcObj || typeof srcObj !== 'object') return;
|
|||
|
|
Object.keys(srcObj).forEach(function (k) {
|
|||
|
|
var val = srcObj[k];
|
|||
|
|
if (polyfills[k]) {
|
|||
|
|
// 对于我们有 polyfill 的 key:只有对方是真正的实现时才覆盖
|
|||
|
|
// 简单判断:UniApp 存根函数不含 prototype.constructor 以外的属性且长度固定
|
|||
|
|
// 最安全的方式:暂时保留我们的实现,等真实实现直接赋值给 uni.xxx 时再替换
|
|||
|
|
// 这里什么都不做——让 UniApp 运行时后续直接赋值来替换
|
|||
|
|
} else {
|
|||
|
|
_proxy[k] = val;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// 把 prototype 链上的方法也复制(如 $emit/$on 等)
|
|||
|
|
try {
|
|||
|
|
var proto = Object.getPrototypeOf(srcObj);
|
|||
|
|
if (proto && proto !== Object.prototype) {
|
|||
|
|
Object.getOwnPropertyNames(proto).forEach(function (k) {
|
|||
|
|
if (k !== 'constructor' && !_proxy[k]) {
|
|||
|
|
try { _proxy[k] = proto[k].bind(srcObj); } catch (e) {}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (e) {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 拦截 window.uni 赋值 */
|
|||
|
|
try {
|
|||
|
|
Object.defineProperty(window, 'uni', {
|
|||
|
|
get: function () { return _proxy; },
|
|||
|
|
set: function (newVal) {
|
|||
|
|
// UniApp 运行时把存根对象赋给 window.uni 时在这里被拦截
|
|||
|
|
mergeIntoProxy(newVal);
|
|||
|
|
},
|
|||
|
|
configurable: true,
|
|||
|
|
enumerable: true
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
// 极少数情况下 defineProperty 失败,退化为直接赋值
|
|||
|
|
window.uni = _proxy;
|
|||
|
|
}
|
|||
|
|
})();
|