1: 请求失败会弹出一个 toast , 如何保证批量请求失败, 只弹出一个 toast
方法一:使用全局标志位
javascript
let isToastShown = false; // 全局标志位
function makeRequests() {
const requests = [
fetchPost(),
fetchComments()
]; // 多个请求
Promise.all(requests)
.then(() => {
// 所有请求成功的处理逻辑
})
.catch(errors => {
if (!isToastShown) {
// 检查标志位
notify(errors[0]); // 弹出 toast
isToastShown = true; // 设置标志位为 true
}
});
}
function fetchJSON(url, input) {
return fetch(url, input)
.then(res => {
if (res.ok) {
return res.json();
}
const err = new HttpError(res);
if (!isToastShown) { // 检查标志位
notify(err); // 弹出 toast
isToastShown = true; // 设置标志位为 true
}
throw err; // 确保错误被抛出,否则后续逻辑可能无法捕获
});
}
// 示例错误类
class HttpError extends Error {
constructor(response) {
super(`${response.status} ${response.statusText}`);
this.name = "HttpError";
this.response = response;
}
}
// 重置标志位(在新的请求开始前)
makeRequests().finally(() => {
isToastShown = false; // 确保在下一次请求时标志位被重置
});
方法二:使用防抖函数
ini
// 防抖函数
function debounce(fn, delay = 1000) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const notifyDebounced = debounce(message => {
notify(message);
}, 3000); // 3 秒内只执行一次
// 示例调用
fetchJSON(url, input)
.catch(err => {
notifyDebounced(err); // 使用防抖后的通知函数
});
2: js 如何判空? 「空」包含了:空数组、空对象、空字符串、0、undefined、null、空 map、空 set , 都属于为空的数据
javascript
function isEmpty(value) {
// 1. 基础空值判断
if (value === null || value === undefined) return true; // :ml-citation{ref="1,3" data="citationList"}
// 2. 字符串、数字判断
if (typeof value === 'string' && value.trim() === '') return true; // :ml-citation{ref="3,5" data="citationList"}
if (typeof value === 'number' && value === 0) return true; // :ml-citation{ref="4,5" data="citationList"}
// 3. 数组判断
if (Array.isArray(value) && value.length === 0) return true; // :ml-citation{ref="2,3" data="citationList"}
// 4. 对象判断
if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) return true; // :ml-citation{ref="2,3" data="citationList"}
// 5. Map/Set判断
if ((value instanceof Map || value instanceof Set) && value.size === 0) return true;
return false;
}
-
**
typeof null
的陷阱**
typeof null
返回"object"
,需优先排除null
干扰。 -
**
0
的特殊性**若需区分
0
和"空值",需单独处理逻辑。 -
不可枚举属性的影响
Object.keys()
忽略不可枚举属性,若需包含所有属性,改用Object.getOwnPropertyNames()
。 -
ES6+ 数据结构的兼容性
Map/Set 的
size
属性需在支持 ES6 的环境中生效四、使用示例
scss
isEmpty(null); // true
isEmpty(undefined); // true
isEmpty(" "); // true
isEmpty(0); // true
isEmpty([]); // true
isEmpty({}); // true
isEmpty(new Map()); // true
isEmpty(new Set()); // true
3:判断一个对象是否为空,包含了其原型链上是否有自定义数据或者方法。 该如何判定?
一、核心判断逻辑
-
遍历对象自身属性
使用
Object.getOwnPropertyNames()
和Object.getOwnPropertySymbols()
获取对象自身的所有属性(含不可枚举和 Symbol 类型)
ini
const ownProps = Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj));
检查属性有效性
对每个属性检查其描述符(Descriptor),若存在非空值或函数,则对象非空
javascript
for (const prop of ownProps) {
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
// 数据属性有值,或属性是函数,则非空
if (descriptor.value !== null && descriptor.value !== undefined) return false;
if (typeof descriptor.value === 'function') return false;
}
递归检查原型链
沿原型链向上遍历,重复上述步骤,直到 Object.prototype
(排除内置原型的干扰)
javascript
let proto = Object.getPrototypeOf(obj);
while (proto && proto !== Object.prototype) {
const protoProps = Object.getOwnPropertyNames(proto)
.concat(Object.getOwnPropertySymbols(proto));
if (protoProps.length > 0) return false;
proto = Object.getPrototypeOf(proto);
}
二、完整函数实现
javascript
function isObjectTrulyEmpty(obj) {
// 基础类型直接返回 true
if (typeof obj !== 'object' || obj === null) return true;
// 1. 检查自身属性
const ownProps = Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj));
for (const prop of ownProps) {
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (descriptor.value !== null && descriptor.value !== undefined) return false;
if (typeof descriptor.value === 'function') return false;
}
// 2. 检查原型链(排除内置原型)
let proto = Object.getPrototypeOf(obj);
while (proto && proto !== Object.prototype) {
const protoProps = Object.getOwnPropertyNames(proto)
.concat(Object.getOwnPropertySymbols(proto));
if (protoProps.length > 0) return false;
proto = Object.getPrototypeOf(proto);
}
return true;
}
三、使用示例
javascript
// 空对象(自身和原型链无自定义属性)
console.log(isObjectTrulyEmpty({})); // true
// 原型链有自定义方法
const objWithProto = Object.create({ method: () => {} });
console.log(isObjectTrulyEmpty(objWithProto)); // false
// 自身有不可枚举属性
const objWithHiddenProp = {};
Object.defineProperty(objWithHiddenProp, 'hidden', { value: 123 });
console.log(isObjectTrulyEmpty(objWithHiddenProp)); // false
4:浏览器有同源策略, 但是为何 cdn 请求资源的时候不会有跨域限制
一、同源策略的适用范围差异
-
限制目标不同
同源策略的核心是防止恶意脚本窃取用户数据,主要限制以下行为:
- 通过
XMLHttpRequest
或Fetch API
发起的跨域请求(需 CORS 支持)12。 - 跨域 DOM 操作(如通过
iframe
访问其他源的页面内容)1。 - 跨域 Cookie 或 LocalStorage 操作1。
静态资源(如图片、CSS、JS 文件) 的加载默认不受同源策略限制,浏览器允许跨域加载这些资源。
资源敏感性差异
CDN 分发的静态资源(如公开的 JS 库、图片)通常不包含敏感数据,浏览器认为此类资源对用户安全威胁较低,因此默认允许跨域加载
- 通过
二、CDN 请求的特殊处理机制
-
CORS 配置支持
CDN 服务商通常会在服务器端配置
Access-Control-Allow-Origin
响应头,允许指定域或所有域(*
)访问资源,从而绕过同源策略限制。例如:
makefile
Access-Control-Allow-Origin: *
资源加载方式
HTML 标签(如 <script>
、<img>
、<link>
)的 src
属性加载资源时,浏览器仅执行"读取"操作,不涉及跨域数据写入或敏感操作,因此不会触发同源策略拦截
三、CDN 的设计目标与实现
- 分发效率优先
CDN 的核心目标是加速资源加载,若强制同源策略会阻碍跨域分发能力。因此 CDN 默认允许跨域请求,并通过 CORS 配置保障合法性46。 - 缓存与协议优化
CDN 通过全局缓存节点分发资源,若跨域请求被限制,会降低缓存命中率和用户体验。浏览器对静态资源的宽松策略与 CDN 的优化目标一致
5:日志监控问题:可有办法将请求的调用源码地址包括代码行数也上报上去?
捕获调用堆栈
在发起请求时,通过 new Error().stack
获取当前调用堆栈信息,其中包含调用链中的文件路径和行号
arduino
const stackTrace = new Error().stack;
解析堆栈信息
使用正则表达式从堆栈字符串中提取调用者文件路径 和行号,例如:
ini
const match = stackTrace.match(/at (.*):(\d+):(\d+)/);
const [filePath, line, column] = match.slice(1);
上报附加数据
将解析后的源码路径、行号、列号作为额外参数附加到请求日志中:
sql
fetch(url, {
headers: {
'X-Source-Path': filePath,
'X-Source-Line': line,
'X-Source-Column': column
}
});
二、关键实现步骤
-
封装统一请求方法
拦截所有请求方法(如
fetch
、XMLHttpRequest
),注入堆栈捕获逻辑:
javascript
const originalFetch = window.fetch;
window.fetch = function(...args) {
const stack = new Error().stack;
// 解析并添加堆栈信息到请求头
return originalFetch.apply(this, args);
};
-
兼容压缩代码场景
- 开发环境:直接使用未压缩代码的堆栈信息1。
- 生产环境 :结合 Source Map 文件 ,将压缩后的行号还原为源码位置(需借助
source-map
库)27。
-
性能优化
- 按需采集:通过配置开关控制是否启用堆栈上报。
- 抽样上报:对高频请求按比例抽样采集(如 10%)。
三、示例代码
ini
// 拦截并增强 fetch 请求
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
// 捕获堆栈
const stack = new Error().stack;
const match = stack.match(/at (.*?):(\d+):(\d+)/);
// 构造新请求头
const newHeaders = new Headers(options?.headers);
if (match) {
newHeaders.set('X-Source-File', match:ml-citation{ref="1" data="citationList"});
newHeaders.set('X-Source-Line', match:ml-citation{ref="6" data="citationList"});
}
// 发起请求
return originalFetch(url, { ...options, headers: newHeaders });
};