/** * polyfills.js — IE11 及老浏览器兼容垫片(ES5,离线内置,无外部依赖) * 提供: Promise / fetch / EventSource / padStart / startsWith / endsWith / includes * 注: 仅在浏览器缺少对应能力时才生效,现代浏览器加载本文件无任何副作用。 */ /* ---------------- String / Array 补齐 ---------------- */ if (!String.prototype.padStart) { String.prototype.padStart = function (targetLength, padString) { targetLength = targetLength >> 0; if (this.length >= targetLength) return String(this); padString = padString === undefined ? " " : String(padString); var fill = ""; while (fill.length < targetLength - this.length) fill += padString; return fill.slice(0, targetLength - this.length) + String(this); }; } if (!String.prototype.startsWith) { String.prototype.startsWith = function (search, pos) { pos = pos || 0; return this.substr(pos, search.length) === String(search); }; } if (!String.prototype.endsWith) { String.prototype.endsWith = function (search, endPos) { if (endPos === undefined || endPos > this.length) endPos = this.length; return this.substring(endPos - search.length, endPos) === String(search); }; } if (!String.prototype.includes) { String.prototype.includes = function (search, start) { if (start === undefined) start = 0; return this.indexOf(String(search), start) !== -1; }; } if (!Array.prototype.includes) { Array.prototype.includes = function (search) { for (var i = 0; i < this.length; i++) { if (this[i] === search) return true; } return false; }; } if (!Object.assign) { Object.assign = function (target) { if (target === null || target === undefined) { throw new TypeError("Cannot convert undefined or null to object"); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var src = arguments[i]; if (src !== null && src !== undefined) { for (var k in src) { if (Object.prototype.hasOwnProperty.call(src, k)) to[k] = src[k]; } } } return to; }; } /* ---------------- Promise 补齐 (含 finally) ---------------- */ if (typeof Promise !== "function") { function PromisePolyfill(executor) { var self = this; this._state = "pending"; this._value = undefined; this._callbacks = []; function resolve(value) { settle(self, "fulfilled", value); } function reject(reason) { settle(self, "rejected", reason); } function settle(p, state, value) { if (p._state !== "pending") return; p._state = state; p._value = value; setTimeout(function () { flush(p); }, 0); } function flush(p) { var cbs = p._callbacks; p._callbacks = []; for (var i = 0; i < cbs.length; i++) handle(p, cbs[i]); } try { executor(resolve, reject); } catch (e) { reject(e); } } function handle(p, cb) { if (p._state === "pending") { p._callbacks.push(cb); return; } var state = p._state, value = p._value; var thenFn = state === "fulfilled" ? cb.onFulfilled : cb.onRejected; if (typeof thenFn !== "function") { (state === "fulfilled" ? cb.resolve : cb.reject)(value); return; } var called = false; try { var result = thenFn(value); if ( result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function" ) { result.then(cb.resolve, cb.reject); } else { cb.resolve(result); } } catch (e) { if (!called) cb.reject(e); called = true; } } function resolvePromise(p, x, resolve, reject) { var called = false; try { var then = x && x.then; if ( x !== null && (typeof x === "object" || typeof x === "function") && typeof then === "function" ) { then.call( x, function (y) { if (called) return; called = true; resolvePromise(p, y, resolve, reject); }, function (r) { if (called) return; called = true; reject(r); } ); } else { resolve(x); } } catch (e) { if (!called) reject(e); } } PromisePolyfill.prototype.then = function (onFulfilled, onRejected) { var self = this; return new PromisePolyfill(function (resolve, reject) { handle(self, { onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject, }); }); }; PromisePolyfill.prototype.catch = function (onRejected) { return this.then(null, onRejected); }; PromisePolyfill.prototype.finally = function (fn) { var C = this.constructor; function wrap() { return C.resolve(fn && fn()).then(function () { return; }); } return this.then(wrap, wrap); }; PromisePolyfill.resolve = function (value) { if (value instanceof PromisePolyfill) return value; return new PromisePolyfill(function (resolve) { resolve(value); }); }; PromisePolyfill.reject = function (reason) { return new PromisePolyfill(function (resolve, reject) { reject(reason); }); }; PromisePolyfill.all = function (list) { return new PromisePolyfill(function (resolve, reject) { var results = [], remaining = list.length; if (!remaining) return resolve(results); list.forEach(function (item, i) { PromisePolyfill.resolve(item).then(function (v) { results[i] = v; if (--remaining === 0) resolve(results); }, reject); }); }); }; window.Promise = PromisePolyfill; } else if (typeof Promise.prototype.finally !== "function") { Promise.prototype.finally = function (fn) { var C = this.constructor; function wrap() { return C.resolve(fn && fn()).then(function () {}); } return this.then(wrap, wrap); }; } /* ---------------- fetch 补齐 (XHR 实现) ---------------- */ if (typeof window.fetch !== "function") { function FetchResponse(xhr) { this.ok = xhr.status >= 200 && xhr.status < 300; this.status = xhr.status; this.statusText = xhr.statusText; this.url = xhr.responseURL || ""; this._raw = xhr; var raw = xhr; this.headers = { get: function (name) { var v = raw.getResponseHeader(name); return v === null ? null : String(v); }, }; } FetchResponse.prototype.text = function () { return Promise.resolve(this._raw.responseText); }; FetchResponse.prototype.json = function () { var raw = this._raw; return Promise.resolve().then(function () { return JSON.parse(raw.responseText); }); }; FetchResponse.prototype.blob = function () { var raw = this._raw; return Promise.resolve().then(function () { var type = raw.getResponseHeader("Content-Type") || "application/octet-stream"; return new Blob([raw.responseText], { type: String(type) }); }); }; window.fetch = function (input, init) { init = init || {}; var url = typeof input === "string" ? input : String(input.url || input); return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(init.method || "GET", url, true); if (init.headers) { var h = init.headers; if (typeof h.forEach === "function") { h.forEach(function (v, k) { xhr.setRequestHeader(k, v); }); } else { for (var k in h) { if (Object.prototype.hasOwnProperty.call(h, k)) { xhr.setRequestHeader(k, h[k]); } } } } xhr.timeout = init.timeout || 0; xhr.onload = function () { resolve(new FetchResponse(xhr)); }; xhr.onerror = function () { reject(new TypeError("Network request failed")); }; xhr.ontimeout = function () { reject(new TypeError("Request timed out")); }; xhr.send(init.body === undefined ? null : init.body); }); }; } /* ---------------- EventSource (SSE) 补齐 (XHR 流式实现) ---------------- */ if (typeof window.EventSource !== "function") { var ES_CONNECTING = 0, ES_OPEN = 1, ES_CLOSED = 2; function EventSourcePolyfill(url) { var self = this; this.url = url; this.readyState = ES_CONNECTING; this.onopen = null; this.onmessage = null; this.onerror = null; this._xhr = null; this._processed = 0; this._buf = ""; this._closed = false; this._connect(); } EventSourcePolyfill.prototype._fire = function (type, data) { var handler = this["on" + type]; if (typeof handler !== "function") return; var evt; try { evt = new MessageEvent(type, { data: data }); } catch (e) { evt = document.createEvent("MessageEvent"); evt.initMessageEvent(type, false, false, data, "", "", window, []); } handler.call(this, evt); }; EventSourcePolyfill.prototype._connect = function () { var self = this; var xhr = new XMLHttpRequest(); this._xhr = xhr; this._processed = 0; this._buf = ""; xhr.open("GET", this.url, true); xhr.setRequestHeader("Accept", "text/event-stream"); xhr.setRequestHeader("Cache-Control", "no-cache"); if (xhr.overrideMimeType) xhr.overrideMimeType("text/plain; charset=x-user-defined"); var opened = false; function parseChunk(text) { // 以空行分隔事件块 var blocks = self._buf.split("\n\n"); self._buf = blocks.pop(); blocks.forEach(function (block) { var dataLines = []; block.split("\n").forEach(function (line) { if (line.indexOf("data:") === 0) { dataLines.push(line.charAt(5) === " " ? line.slice(6) : line.slice(5)); } // event:/id:/retry: 当前业务未使用, 忽略 }); if (dataLines.length) { self._fire("message", dataLines.join("\n")); } }); } xhr.onprogress = function () { if (!opened) { opened = true; self.readyState = ES_OPEN; self._fire("open", undefined); } var full = xhr.responseText; if (full.length > self._processed) { self._buf += full.slice(self._processed); self._processed = full.length; parseChunk(); } }; xhr.onload = xhr.onprogress; xhr.onerror = function () { if (self._closed) return; self.readyState = ES_CONNECTING; self._fire("error", undefined); // 简单重连策略: 关闭后由上层重新创建; 此处自动重连 if (self._retryTimer) clearTimeout(self._retryTimer); self._retryTimer = setTimeout(function () { if (!self._closed && self.readyState !== ES_CLOSED) self._connect(); }, 3000); }; try { xhr.send(null); } catch (e) { setTimeout(function () { self._fire("error", undefined); }, 0); } }; EventSourcePolyfill.prototype.close = function () { this._closed = true; this.readyState = ES_CLOSED; if (this._xhr) { try { this._xhr.abort(); } catch (e) {} } if (this._retryTimer) { clearTimeout(this._retryTimer); this._retryTimer = null; } }; window.EventSource = EventSourcePolyfill; } /* ---------------- 文件下载兜底 (IE11 不支持 a[download]) ---------------- */ /** * 统一文件下载入口: 现代浏览器走 a[download], IE10/11 走 msSaveBlob * @param {Blob} blob * @param {string} filename */ function downloadBlobFile(blob, filename) { if (window.navigator && window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveOrOpenBlob(blob, filename); return; } var url = URL.createObjectURL(blob); var a = document.createElement("a"); a.href = url; a.download = filename || "download"; document.body.appendChild(a); a.click(); setTimeout(function () { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); }