走还是停留?

职场转折:面试与抉择

         转眼间,八月已经剩下最后一天,时间过的很快,今年一大半已经过去了,离过年还剩下五个月多点的时间了,我在目前这家公司也已经呆了两年零两个月了。

        七月初的时候我还在外地出差,等我出差回来时,发现组里的同事都好奇怪,都在准备复习面试,了解了一圈才知道我们老大已经找好下家准备跑路了,我当时咯噔的一下真的让我触不及防,蒙圈的我被迫也踏上了复习的道路,在复习的过程中家庭琐事要处理所以前期的进度一直很慢,过了一周第一位同事提出了离职,已经找好了下家,他是最早知道老大动静的人,接下来就是陆陆续续有同事找好了,我还算比较淡定的,上周才面试了一家,结果让我惊讶, 竟然被录用了。

      现在才是纠结的时候了, 走吧新公司刚进去事情不少,而我国庆又要准备婚礼事情比较多,留吧做的事情不喜欢,就像是做选择题的时候两个答案非常相近,但是只有一个是对的。

      也许人生就是如此,在前进的道路上会有各种十字路口等待着我们去选择,不管如何我必须做出一个选择并且为自己的选择负责到底。后续更新。。。

<think>我们被要求记录用户在页面上的停留时间。根据提供的引用,有几种常见的方法:1.在页面加载时记录开始时间,在页面卸载时(离开时)记录结束时间,计算停留时间。2.使用路由钩子(在单页面应用中)来记录路由切换时的停留时间。3.使用定时器每秒记录一次,然后在离开时计算总时间。我们参考了四个引用:[^1]:在进入页面时记录开始时间到localStorage,在离开页面时(useEffect的清理函数中)计算停留时间并发送。[^2]:使用Vue的路由钩子(beforeEach)来记录从上一个页面离开的时间,并在跳转后立即计算上一个页面的停留时间。[^3]:使用定时器每秒增加一个计数器,并在onbeforeunload事件中发送停留时间(以秒计)。[^4]:使用定时器检查停留时间是否超过设定值,但这里不是用于记录总停留时间,而是用于超时判断。根据需求,我们需要记录整个页面的停留时间(从进入页面到离开页面)。我们主要采用以下两种方式:方式一:适用于传统页面(多页应用)或单页应用中的具体页面方式二:适用于单页应用(SPA),利用路由钩子下面分别介绍:###方法一:使用页面生命周期事件(适用于多页应用或单页应用的具体页面)在页面加载时记录开始时间,在页面卸载时记录结束时间。步骤:1.在页面加载时(或组件挂载时)记录开始时间。2.在页面卸载时(或组件卸载时)计算停留时间并发送。示例代码(使用ReactHooks):```javascriptimport{useEffect}from'react';constPage=()=>{useEffect(()=>{conststartTime=newDate().valueOf();//记录开始时间//也可以存到localStorage,但这里我们用变量存储,避免localStorage的读写开销return()=>{constendTime=newDate().valueOf();conststayTime=endTime-startTime;//停留时间(毫秒)//发送数据到后端sendStayTime(stayTime);};},[]);//...页面内容};```但是,如果页面刷新或关闭,上面的方法可能无法可靠发送数据(因为卸载时发送的请求可能被取消)。因此,可以使用`navigator.sendBeacon`方法。改进的卸载事件处理:```javascriptreturn()=>{constendTime=newDate().valueOf();conststayTime=endTime-startTime;//使用sendBeacon发送数据,确保在页面卸载时也能发送constdata=newBlob([JSON.stringify({stayTime})],{type:'application/json'});navigator.sendBeacon('/api/record-stay-time',data);};```注意:`sendBeacon`是异步的,并且不阻塞页面卸载。###方法二:使用路由钩子(适用于单页应用SPA)在单页应用中,页面切换不会触发页面卸载,而是通过路由变化。因此,我们可以利用路由钩子来记录每个页面的停留时间。以VueRouter为例:在路由全局前置守卫中,我们可以记录离开上一个页面的时间,并发送上一个页面的停留时间。步骤:1.在路由跳转前(beforeEach)记录当前时间作为上一个页面的结束时间,并计算停留时间(结束时间-开始时间)。2.同时,为下一个页面记录开始时间(当前时间)。示例代码:```javascriptletstartTime=Date.now();router.beforeEach((to,from,next)=>{//记录当前时间作为结束时间constendTime=Date.now();//计算上一个页面(from)的停留时间conststayTime=endTime-startTime;//如果from存在(不是首次进入),则发送停留时间if(from.name){//发送数据,可以包含from.name(路由名称)和stayTimesendStayTime(from.name,stayTime);}//重置开始时间为当前时间,作为下一个页面的开始时间startTime=Date.now();next();});```注意:首次进入页面时,from对象可能为空,因此需要判断。###方法三:使用定时器(用于需要实时记录的场景)如果我们需要实时记录(比如每秒记录一次),可以使用定时器,然后在离开时发送总时间。示例代码(在React中):```javascriptimport{useEffect,useRef}from'react';constPage=()=>{conststartTimeRef=useRef(Date.now());consttimerRef=useRef(null);constsecondsRef=useRef(0);useEffect(()=>{//每秒更新一次停留秒数timerRef.current=setInterval(()=>{secondsRef.current+=1;},1000);return()=>{clearInterval(timerRef.current);//计算总停留时间(毫秒)也可以用秒数乘以1000,但这里我们直接用了秒数累加conststayTimeSeconds=secondsRef.current;//发送stayTimeSecondssendStayTime(stayTimeSeconds*1000);//转换为毫秒,或者直接按秒传给后端};},[]);//...页面内容};```但是,定时器方法可能不够精确(因为定时器可能延迟),且每秒执行一次,如果页面打开很久,会执行很多次。所以通常推荐使用时间差的方式。###总结根据应用类型选择:-如果是单页应用(SPA),推荐使用路由钩子(方法二)。-如果是多页应用(MPA)或者单页应用中的某个独立页面,可以使用页面生命周期事件(方法一),并结合`sendBeacon`确保数据发送。另外,如果需要记录用户关闭页面或刷新页面的情况,可以使用`window`的`beforeunload`或`unload`事件,但注意在单页应用中路由切换不会触发这些事件。因此,在单页应用中,路由切换用路由钩子,而关闭/刷新页面则可以用`beforeunload`事件来补充记录当前页面的停留时间。下面给出一个综合方案(单页应用):1.在进入页面时记录开始时间(在路由钩子中记录下一个页面的开始时间,同时在组件内也记录开始时间)。2.在路由切换时,通过路由钩子记录上一个页面的停留时间并发送。3.在页面关闭/刷新时,通过`beforeunload`事件发送当前页面的停留时间。但是注意,路由切换和`beforeunload`事件可能会重复发送(比如在刷新时,路由钩子会记录离开当前页面,同时`beforeunload`也会触发)。因此需要设计避免重复发送。为了避免重复,我们可以:-在路由钩子中发送数据后,标记该页面的数据已发送,这样在`beforeunload`中就不再发送。然而,由于刷新时路由钩子会先触发(记录离开当前页面),然后`beforeunload`也会触发,所以我们可以考虑在路由钩子中发送后,清除该页面的开始时间,这样`beforeunload`中就不会再发送。具体实现较复杂,这里不展开。对于大多数场景,我们只需要记录用户在该页面的停留时间,而不关心是路由切换还是关闭页面,因此使用路由钩子即可覆盖路由切换,再配合`beforeunload`来覆盖关闭页面。###代码示例(Vue单页应用综合方案)在main.js或路由配置文件中:```javascriptletstartTime=Date.now();router.beforeEach((to,from,next)=>{constendTime=Date.now();if(from.name){conststayTime=endTime-startTime;sendStayTime(from.name,stayTime);//发送from页面的停留时间}startTime=endTime;//重置开始时间为进入下一个页面的时间next();});//监听页面刷新或关闭window.addEventListener('beforeunload',()=>{constendTime=Date.now();if(router.currentRoute.value.name){//当前路由名称conststayTime=endTime-startTime;//使用sendBeacon发送constdata=JSON.stringify({routeName:router.currentRoute.value.name,stayTime});navigator.sendBeacon('/api/record-stay-time',data);}});```注意:在刷新或关闭时,路由钩子中的`beforeEach`也会触发,所以当前页面(即即将被卸载的页面)的停留时间会在路由钩子中发送(作为from页面),同时也会在`beforeunload`中发送。这样就重复了。为了解决重复,我们可以:1.在路由钩子中发送后,设置一个标记,表示该页面的停留时间已经发送,这样在`beforeunload`中就不再发送。2.或者在`beforeunload`中只处理刷新和关闭,而不处理路由切换。但路由钩子无法区分是切换还是关闭。实际上,在关闭或刷新时,路由钩子中的`from`页面就是当前页面,然后`beforeunload`也会触发,所以会发送两次同一个页面的停留时间(一次作为from页面,一次作为当前页面)。因此我们需要避免这种情况。改进:在路由钩子中,如果发现是刷新或关闭,则不在路由钩子中发送from页面的停留时间?但是路由钩子无法知道是跳转还是关闭。另一种思路:我们不在路由钩子中发送from页面的停留时间,而是每个页面自己记录开始时间,然后在离开时(无论是路由切换还是关闭)发送。但这样就需要每个页面组件都写一遍。因此,为了简化,我们可以在全局只使用一种方式:即路由钩子记录路由切换的页面停留,而`beforeunload`只记录最后一个页面的停留(因为关闭时最后一个页面不会被路由钩子记录)。但这样最后一个页面在关闭时会被记录两次(一次是作为from页面在路由钩子中?不,关闭时不会触发路由跳转,所以不会触发路由钩子中的from页面发送)。实际上,在关闭页面时,不会触发路由切换,所以路由钩子不会执行。那么为什么我们会在`beforeunload`中发送最后一个页面的停留时间?因为关闭页面时,当前页面就是最后一个页面,且没有发生路由跳转。但是,刷新页面时,会触发路由跳转吗?在单页应用中,刷新页面不会改变路由,但会重新加载整个应用。此时,路由钩子会如何触发?在刷新页面时:1.当前页面(假设为A)首先会触发卸载(包括组件的卸载),然后整个应用重新加载。2.重新加载后,会进入当前路由(A),触发路由钩子:这时from是空(因为是首次进入),to是A。所以不会发送A页面的停留时间(因为from为空)。3.在刷新时,会触发`beforeunload`事件,我们在其中发送A页面的停留时间(从进入A页面到刷新)。因此,刷新时,我们通过`beforeunload`发送了A页面的停留时间。而在正常的路由切换(比如A切换到B):1.路由钩子触发:from为A,to为B,发送A页面的停留时间。2.不会触发`beforeunload`事件。所以,这样就不会重复。因此,综合方案:-路由钩子负责记录非当前页面的停留时间(即跳转的页面)。-`beforeunload`负责记录刷新或关闭时的当前页面停留时间。代码调整:在路由钩子中,我们只记录路由切换时的页面停留,而`beforeunload`记录当前页面的停留(在关闭或刷新时触发)。注意:在路由钩子中,我们重置了`startTime`为进入下一个页面的时间,所以`beforeunload`中使用的`startTime`是当前页面的开始时间(即进入当前页面的时间)。但是,在刷新时,路由钩子也会执行(因为刷新后重新进入当前页面,会触发路由钩子,此时from为空,to是当前页面)。这时,我们在路由钩子中重置了`startTime`(因为执行了`startTime=endTime;`),所以当前页面的开始时间被更新为刷新后的时间。这样,在刷新时发送的停留时间是从刷新后到刷新前?不对,刷新前的页面已经卸载了。实际上,刷新页面时,整个页面重新加载,所以之前的全局变量`startTime`已经丢失。因此,我们需要将`startTime`存储在sessionStorage中,以便刷新后还能获取到。所以,我们需要:1.在每次记录开始时间的时候,将开始时间保存到sessionStorage。2.在路由钩子中更新开始时间时,同时更新sessionStorage。3.在`beforeunload`事件中,从sessionStorage读取开始时间。修改后的代码:```javascript//初始化,从sessionStorage中获取开始时间,如果没有则使用当前时间letstartTime=sessionStorage.getItem('startTime')?parseInt(sessionStorage.getItem('startTime')):Date.now();router.beforeEach((to,from,next)=>{constendTime=Date.now();if(from.name){conststayTime=endTime-startTime;sendStayTime(from.name,stayTime);}//更新开始时间为当前时间(进入下一个页面的时间)startTime=endTime;sessionStorage.setItem('startTime',startTime.toString());next();});window.addEventListener('beforeunload',()=>{constendTime=Date.now();constcurrentRouteName=router.currentRoute.value.name;if(currentRouteName){//从sessionStorage读取开始时间(因为全局变量可能在刷新时丢失)conststartTime=parseInt(sessionStorage.getItem('startTime'));conststayTime=endTime-startTime;constdata=JSON.stringify({routeName:currentRouteName,stayTime});navigator.sendBeacon('/api/record-stay-time',data);}//清除sessionStorage中的startTime,避免下次进入页面时误用sessionStorage.removeItem('startTime');});```注意:在单页应用中,如果用户直接关闭页面,那么`beforeunload`事件会触发,发送当前页面的停留时间。而如果用户刷新页面,则`beforeunload`事件触发,发送当前页面的停留时间,然后页面重新加载,重新加载后,路由钩子会执行(from为空),然后设置新的开始时间(刷新后的时间)。这样,我们就能准确记录每个页面的停留时间。以上是针对单页应用的方案。对于多页应用,由于页面跳转会导致整个页面重新加载,因此每个页面都需要独立记录。可以使用方法一,在每个页面中独立实现。###多页应用方案在每个页面的`<script>`中:```javascript//记录开始时间conststartTime=Date.now();//页面卸载时发送停留时间window.addEventListener('beforeunload',()=>{constendTime=Date.now();conststayTime=endTime-startTime;constdata=JSON.stringify({stayTime,page:window.location.pathname});navigator.sendBeacon('/api/record-stay-time',data);});```或者使用localStorage/sessionStorage来传递开始时间(如果需要在多个页面间共享数据,但多页应用通常不需要)。以上是几种实现方案,可以根据具体技术栈和需求选择。§§相关问题§§1.如何确保在页面关闭时可靠地发送停留时间数据?2.在单页应用中,如何处理页面刷新时的停留时间记录?3.使用前端路由钩子记录停留时间有什么优缺点?</think>以下是前端记录页面停留时间的完整解决方案,结合了多种场景的最佳实践: ### 一、核心实现方案 #### 方案1:生命周期钩子法(推荐) ```javascript // 记录开始时间 let startTime = Date.now(); // 页面卸载时计算停留时间 window.addEventListener('beforeunload', () => { const endTime = Date.now(); const stayTime = Math.floor((endTime - startTime) / 1000); // 转换为秒 navigator.sendBeacon('/api/record', JSON.stringify({ stayTime, page: location.pathname })); }); ``` **优点**:精准记录完整停留时长 **适用**:多页应用(MPA)或单页应用(SPA)的页面级记录 #### 方案2:路由拦截法(SPA专用) ```javascript // Vue Router示例 let startTime = Date.now(); router.beforeEach((to, from) => { const endTime = Date.now(); const stayTime = Math.floor((endTime - startTime) / 1000); // 发送上一个页面的停留时间 navigator.sendBeacon('/api/record', JSON.stringify({ from: from.path, stayTime })); // 重置为新页面的开始时间 startTime = Date.now(); }); ``` **优点**:精准记录SPA中每个路由的停留时间 **注意**:需配合`beforeunload`处理最后页面关闭的情况[^2] #### 方案3:定时上报法(实时监控) ```javascript let seconds = 0; const timer = setInterval(() => { seconds++; // 每30秒上报一次(可按需调整) if(seconds % 30 === 0) { fetch('/api/heartbeat', { method: 'POST' }); } }, 1000); // 页面卸载时上报最终时间 window.addEventListener('beforeunload', () => { clearInterval(timer); navigator.sendBeacon('/api/record', JSON.stringify({ finalStayTime: seconds })); }); ``` **优点**:可实时监控用户活跃度 **适用**:需要检测用户是否在线的场景[^3] ### 二、关键优化措施 1. **数据可靠性保障** ```javascript // 使用sendBeacon确保关闭页面时数据发送 navigator.sendBeacon(url, data); // 兼容方案 if (!navigator.sendBeacon) { const xhr = new XMLHttpRequest(); xhr.open('POST', url, false); // 同步请求 xhr.send(data); } ``` 2. **数据持久化存储** ```javascript // 页面加载时读取已有时间 const startTime = localStorage.getItem('pageStart') || Date.now(); localStorage.setItem('pageStart', startTime); // 卸载时综合计算 window.addEventListener('beforeunload', () => { const totalTime = calculateTotalTime(); localStorage.removeItem('pageStart'); }); ``` 3. **用户行为过滤** ```javascript let activeStart = Date.now(); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { // 记录非活跃时间段 } else { // 重新激活时重置计时 activeStart = Date.now(); } }); ``` ### 三、不同框架实现示例 #### React Hooks方案 ```jsx import { useEffect } from 'react'; export default function usePageTiming() { useEffect(() => { const startTime = Date.now(); return () => { const stayTime = Math.floor((Date.now() - startTime)/1000); navigator.sendBeacon('/api/timing', JSON.stringify({ stayTime })); }; }, []); } // 在组件中调用 function HomePage() { usePageTiming(); return <div>...</div>; } ``` #### Vue Composition API ```javascript import { onMounted, onUnmounted } from 'vue'; export default function usePageTiming() { let startTime; onMounted(() => { startTime = Date.now(); }); onUnmounted(() => { const stayTime = Math.floor((Date.now() - startTime)/1000); navigator.sendBeacon('/api/timing', JSON.stringify({ stayTime })); }); } ``` ### 四、注意事项 1. **精度平衡**:浏览器可能限制`beforeunload`中异步操作时间(通常300ms) 2. **数据去重**:SPA中路由跳转和页面关闭可能重复触发 3. **隐私合规**:需在隐私政策中说明数据收集行为 4. **异常处理**: ```javascript window.addEventListener('unhandledrejection', handleError); window.addEventListener('error', handleError); ``` > **最佳实践**:对核心页面使用方案1+方案2组合,关键用户流程添加方案3的心跳检测[^1][^2][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值