最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
复制代码
new Image运用的比较少,可以单独自己处理自己的错误。
但通用的fetch怎么办呢,fetch返回Promise,但Promise的错误不能被捕获,怎么办呢?
Promise错误
- 普通Promise错误
try/catch不能捕获Promise中的错误
// try/catch 不能处理 JSON.parse 的错误,因为它在 Promise 中
try {
new Promise((resolve,reject) => {
JSON.parse(‘’)
resolve();
})
} catch(err) {
console.error(‘in try catch’, err)
}
// 需要使用catch方法
new Promise((resolve,reject) => {
JSON.parse(‘’)
resolve();
}).catch(err => {
console.log(‘in catch fn’, err)
})
复制代码
- async错误
try/catch不能捕获async包裹的错误
const getJSON = async () => {
throw new Error(‘inner error’)
}
// 通过try/catch处理
const makeRequest = async () => {
try {
// 捕获不到
JSON.parse(getJSON());
} catch (err) {
console.log(‘outer’, err);
}
};
try {
// try/catch不到
makeRequest()
} catch(err) {
console.error(‘in try catch’, err)
}
try {
// 需要await,才能捕获到
await makeRequest()
} catch(err) {
console.error(‘in try catch’, err)
}
复制代码
- import chunk错误
import其实返回的也是一个promise,因此使用如下两种方式捕获错误
// Promise catch方法
import(/* webpackChunkName: “incentive” */‘./index’).then(module => {
module.default()
}).catch((err) => {
console.error(‘in catch fn’, err)
})
// await 方法,try catch
try {
const module = await import(/* webpackChunkName: “incentive” */‘./index’);
module.default()
} catch(err) {
console.error(‘in try catch’, err)
}
复制代码
小结:全局捕获Promise中的错误
以上三种其实归结为Promise类型错误,可以通过unhandledrejection捕获
// 全局统一处理Promise
window.addEventListener(“unhandledrejection”, function(e){
console.log(‘捕获到异常:’, e);
});
fetch(‘https://tuia.cn/test’)
复制代码
为了防止有漏掉的 Promise 异常,可通过unhandledrejection用来全局监听Uncaught Promise Error。
Vue错误
由于Vue会捕获所有Vue单文件组件或者Vue.extend继承的代码,所以在Vue里面出现的错误,并不会直接被window.onerror捕获,而是会抛给Vue.config.errorHandler。
/**
- 全局捕获Vue错误,直接扔出给onerror处理
*/
Vue.config.errorHandler = function (err) {
setTimeout(() => {
throw err
})
}
复制代码
React错误
react 通过componentDidCatch,声明一个错误边界的组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return
Something went wrong.
;}
return this.props.children;
}
}
class App extends React.Component {
render() {
return (
)
}
}
复制代码
但error boundaries并不会捕捉以下错误:React事件处理,异步代码,error boundaries自己抛出的错误。
跨域问题
一般情况,如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。
如果当前投放页面和云端JS所在不同域名,如果云端JS出现错误,window.onerror会出现Script Error。通过以下两种方法能给予解决。
- 后端配置Access-Control-Allow-Origin、前端script加crossorigin。
const script = document.createElement(‘script’);
script.crossOrigin = ‘anonymous’;
script.src = ‘http://yun.tuia.cn/test.js’;
document.body.appendChild(script);
复制代码
- 如果不能修改服务端的请求头,可以考虑通过使用 try/catch 绕过,将错误抛出。
<!doctype html>
复制代码
会发现如果不加try catch,console.log就会打印script error。加上try catch就能捕获到。
我们捋一下场景,一般调用远端js,有下列三种常见情况。
-
调用远端JS的方法出错
-
远端JS内部的事件出问题
-
要么在setTimeout等回调内出错
调用方法场景
可以通过封装一个函数,能装饰原方法,使得其能被try/catch。
<!doctype html>
复制代码
大家可以尝试去掉wrapErrors感受下。
事件场景
可以劫持原生方法。
<!doctype html>
复制代码
大家可以尝试去掉封装EventTarget.prototype.addEventListener的那段代码,感受下。
上报接口
为什么不能直接用GET/POST/HEAD请求接口进行上报?
这个比较容易想到原因。一般而言,打点域名都不是当前域名,所以所有的接口请求都会构成跨域。
为什么不能用请求其他的文件资源(js/css/ttf)的方式进行上报?
创建资源节点后只有将对象注入到浏览器DOM树后,浏览器才会实际发送资源请求。而且载入js/css资源还会阻塞页面渲染,影响用户体验。
构造图片打点不仅不用插入DOM,只要在js中new出Image对象就能发起请求,而且还没有阻塞问题,在没有js的浏览器环境中也能通过img标签正常打点。
使用new Image进行接口上报。最后一个问题,同样都是图片,上报时选用了1x1的透明GIF,而不是其他的PNG/JEPG/BMP文件。
首先,1x1像素是最小的合法图片。而且,因为是通过图片打点,所以图片最好是透明的,这样一来不会影响页面本身展示效果,二者表示图片透明只要使用一个二进制位标记图片是透明色即可,不用存储色彩空间数据,可以节约体积。因为需要透明色,所以可以直接排除JEPG。
同样的响应,GIF可以比BMP节约41%的流量,比PNG节约35%的流量。GIF才是最佳选择。
-
可以进行跨域
-
不会携带cookie
-
不需要等待服务器返回数据
非阻塞加载
尽量避免SDK的js资源加载影响。
通过先把window.onerror的错误记录进行缓存,然后异步进行SDK的加载,再在SDK里面处理错误上报。
这是一个测试页面(new)
复制代码
采集聚合端(日志服务器)
这个环节,输入是借口接收到的错误记录,输出是有效的数据入库。核心功能需要对数据进行清洗,顺带解决了过多的服务压力。另一个核心功能是对数据进行入库。
总体流程可以看为错误标识 -> 错误过滤 -> 错误接收 -> 错误存储。
错误标识(SDK配合)
聚合之前,我们需要有不同维度标识错误的能力,可以理解为定位单个错误条目,单个错误事件的能力。
单个错误条目
通过date和随机值生成一条对应的错误条目id。
const errorKey = ${+new Date()}@${randomString(8)}
function randomString(len) {
len = len || 32;
let chars = ‘ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678’;
let maxPos = chars.length;
let pwd = ‘’;
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
复制代码
单个错误事件
首先需要有定位同个错误事件(不同用户,发生相同错误类型、错误信息)的能力。
通过message、colno与lineno进行相加计算阿斯克码值,可以生成错误的errorKey。
const eventKey = compressString(String(e.message), String(e.colno) + String(e.lineno))
function compressString(str, key) {
let chars = ‘ABCDEFGHJKMNPQRSTWXYZ’;
if (!str || !key) {
return ‘null’;
}
let n = 0,
m = 0;
for (let i = 0; i < str.length; i++) {
n += str[i].charCodeAt();
}
for (let j = 0; j < key.length; j++) {
m += key[j].charCodeAt();
}
let num = n + ‘’ + key[key.length - 1].charCodeAt() + m + str[str.length - 1].charCodeAt();
if(num) {
num = num + chars[num[num.length - 1]];
}
return num;
}
复制代码
如下图,一个错误事件(事件列表),下属每条即为实际的错误条目。
错误过滤(SDK配合)
域名过滤
过滤本页面script error,可能被webview插入其他js。
我们只关心自己的远端JS问题,因此做了根据本公司域名进行过滤。
// 伪代码
if(!e.filename || !e.filename.match(/^(http|https)😕/yun./)) return true
复制代码
重复上报
怎么避免重复的数据上报?根据errorKey来进行缓存,重复的错误避免上报的次数超过阈值。
// 伪代码
const localStorage = window.localStorage;
const TIMES = 6; // 缓存条数
export function setItem(key, repeat) {
if(!key) {
key = ‘unknow’;
}
if (has(key)) {
const value = getItem(key);
// 核心代码,超过条数,跳出
if (value >= repeat) {
return true;
}
storeStorage[key] = {
value: value + 1,
time: Date.now()
}
} else {
storeStorage[key] = {
value: 1,
time: Date.now()
}
}
return false;
}
复制代码
错误接收
在处理接收接口的时候,注意流量的控制,这也是后端开发需要投入最多精力的地方,处理高并发的流量。
错误记录
接收端使用Koa,简单的实现了接收及打印到磁盘。
// 伪代码
module.exports = async ctx => {
const { query } = ctx.request;
// 对于字段进行简单check
check([ ‘mobile’, ‘network’, ‘ip’, ‘system’, ‘ua’, …], query);
ctx.type = ‘application/json’;
ctx.body = { code: ‘1’, msg: ‘数据上报成功’ };
// 进行日志记录到磁盘的代码,根据自己的日志库选择
};
复制代码
削峰机制
比如每秒设置2000的阈值,然后根据请求量减少上限,定时重置上限。
// 伪代码
// 1000ms
const TICK = 1000;
// 1秒上限为2000
const MAX_LIMIT = 2000;
// 每台服务器请求上限值
let maxLimit = MAX_LIMIT;
/**
- 启动重置函数
*/
const task = () => {
setTimeout(() => {
maxLimit = MAX_LIMIT;
task();
}, TICK);
};
task();
const check = () => {
if (maxLimit <= 0) {
throw new Error(‘超过上报次数’);
}
maxLimit–;
// 执行业务代码。。。
};
复制代码
采样处理
超过阈值,还可以进行采样收集。
// 只采集 20%
if(Math.random() < 0.2) {
collect(data) // 记录错误信息
}
复制代码
错误存储
对于打印在了磁盘的日志,我们怎么样才能对于其进行聚合呢,这里得考虑使用存储方案。
一般选择了存储方案后,设置好配置,存储方案就可以通过磁盘定时周期性的获取数据。因此我们需要选择一款存储方案。
对于存储方案,我们对比了日常常见方案,阿里云日志服务 - Log Service(SLS)、ELK(Elastic、Logstash、Kibana)、Hadoop/Hive(将数据存储在 Hadoop,利用 Hive 进行查询) 类方案的对比。
从以下方面进行了对比,最终选择了Log Service,主要考虑为无需搭建,成本低,查询功能满足。
| 功能项 | ELK 类系统 | Hadoop + Hive | 日志服务 |
| — | — | — | — |
| 日志延时 | 1~60 秒 | 几分钟~数小时 | 实时 |
| 查询延时 | 小于 1 秒 | 分钟级 | 小于 1 秒 |
| 查询能力 | 好 | 好 | 好 |
| 扩展性 | 提前预备机器 | 提前预备机器 | 秒级 10 倍扩容 |
| 成本 | 较高 | 较低 | 很低 |
日志延时:日志产生后,多久可查询。 查询延时:单位时间扫描数据量。 查询能力:关键词查询、条件组合查询、模糊查询、数值比较、上下文查询。 扩展性:快速应对百倍流量上涨。 成本:每 GB 费用。
具体API使用,可查看日志服务。
可视分析端(可视化平台)
这个环节,输入是借口接收到的错误记录,输出是有效的数据入库。核心功能需要对数据进行清洗,顺带解决了过多的服务压力。另一个核心功能是对数据进行入库。
主功能
这部分主要是产品功能的合理设计,做到小而美,具体的怎么聚合,参考阿里云SLS就可以。
-
首页图表,可选1天、4小时、1小时等等,聚合错误数,根据1天切分24份来聚合。
-
首页列表,聚合选中时间内的数据,展示错误文件、错误key、事件数、错误类型、时间、错误信息。
-
错误详情,事件列表、基本信息、设备信息、设备占比图表(见上面事件列表的图)。
排行榜
刚开始做了待处理错误列表、我的错误列表、已解决列表,错误与人没有绑定关系,过于依赖人为主动,需要每个人主动到平台上处理,效果不佳。
后面通过错误作者排行榜,通过钉钉日报来提醒对应人员处理。紧急错误,通过实时告警来责任到人,后面告警会说。
具体原理:
-
webpack打包通过git命令把作者和作者邮箱、时间打包在头部。
-
在可视化服务中,去请求对应的报错url匹配到对应作者,返回给展示端。
SourceMap
利用webpack的hidden-source-map构建。与 source-map 相比少了末尾的注释,但 output 目录下的 index.js.map 没有少。线上环境避免source-map泄露。
webpackJsonp([1],[
总结
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
具体API使用,可查看日志服务。
可视分析端(可视化平台)
这个环节,输入是借口接收到的错误记录,输出是有效的数据入库。核心功能需要对数据进行清洗,顺带解决了过多的服务压力。另一个核心功能是对数据进行入库。
主功能
这部分主要是产品功能的合理设计,做到小而美,具体的怎么聚合,参考阿里云SLS就可以。
-
首页图表,可选1天、4小时、1小时等等,聚合错误数,根据1天切分24份来聚合。
-
首页列表,聚合选中时间内的数据,展示错误文件、错误key、事件数、错误类型、时间、错误信息。
-
错误详情,事件列表、基本信息、设备信息、设备占比图表(见上面事件列表的图)。
排行榜
刚开始做了待处理错误列表、我的错误列表、已解决列表,错误与人没有绑定关系,过于依赖人为主动,需要每个人主动到平台上处理,效果不佳。
后面通过错误作者排行榜,通过钉钉日报来提醒对应人员处理。紧急错误,通过实时告警来责任到人,后面告警会说。
具体原理:
-
webpack打包通过git命令把作者和作者邮箱、时间打包在头部。
-
在可视化服务中,去请求对应的报错url匹配到对应作者,返回给展示端。
SourceMap
利用webpack的hidden-source-map构建。与 source-map 相比少了末尾的注释,但 output 目录下的 index.js.map 没有少。线上环境避免source-map泄露。
webpackJsonp([1],[
总结
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
[外链图片转存中…(img-MvygrKUE-1715527607424)]