百度网盘一面值得look:我有点难受🤧🤧
前言:
投了那么多,兄弟们,终于也是面大厂了,我直接准备狂看面经,也是面完了,总体感觉不难,但是一直穷追猛打,问到不会为止,还得练。
顺便吆喝一句,技术大厂跳板机会,前后端测试,待遇还可以,感兴趣的兄弟可以试试。
面试题:
上来就是自我介绍
项目介绍:
当时没睡醒,介绍的有点模糊,感觉很乱,之后需要总结出一段话来,以后直接套用。总之就是提到了,使用了路由懒加载,然后就开始了穷追猛打。
Vue路由的两种模式,以及实现的原理
两种模式无非就是hash 模式和 history 模式
hash模式
定义:
1.hash
模式是一种把前端路由的路径用井号 #
拼接在真实 url
后面的模式。
2.URL 可以分为两部分,#
前面的部分是向服务器请求的资源路径,#
后面的部分是浏览器内部管理的,不会发送给服务器。
3.当井号 #
后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange
事件。
优点
- 不需要后端服务器配置支持,因为服务器不会收到 hash 部分的请求。只要服务器能够提供入口 HTML 文件,前端路由就可以正常工作。这对于一些简单的单页应用(SPA)或者开发环境来说非常方便,可以快速搭建和运行应用。
- 兼容性很好。
缺点
- URL 中带有
#
号,这在视觉上不够美观,可能会影响用户体验。例如,看到http://www.example.com/#/about
这样的地址,用户可能会觉得不太直观。 - 对搜索引擎优化(SEO)可能不太友好。虽然现在主流的搜索引擎已经能够处理带有 hash 的 URL,但早期的搜索引擎爬虫可能不会对 hash 部分进行索引,导致网站内容的收录效果不佳。而且,即使对于现在的搜索引擎,带有 hash 的 URL 可能在某些特殊的搜索算法或排名因素中处于不利地位。
history 模式
工作原理
- history 模式主要依赖 HTML5 的 History API,它提供了
pushState()
和replaceState()
等方法来操作浏览器的历史记录栈,从而实现 URL 的改变而无需重新加载页面。在 Vue Router 中,当使用 history 模式时,它会利用这些 API 来改变 URL,并且会监听popstate
事件(当浏览器的前进、后退按钮触发时)来更新路由。 - 例如,初始页面加载时,URL 是
http://www.example - history.com/
,当跳转到/home
路由时,Vue Router 会调用pushState()
方法将 URL 改为http://www.example - history.com/home
,然后加载对应的组件。如果用户点击浏览器的后退按钮,会触发popstate
事件,Vue Router 再根据事件来切换回之前的路由。
优点:
- URL 更加美观和直观,没有
#
号。例如,http://www.example.com/about
这样的 URL 更符合用户对网站地址的期望,也更有利于 SEO,因为搜索引擎可以更好地索引这些干净的 URL。
存在的问题:
- 使用
history
模式时,在对当前的页面进行刷新时,此时浏览器会重新发起请求。如果nginx
没有匹配得到当前的url
,就会出现404
的页面。 - 而对于
hash
模式来说, 它虽然看着是改变了url
,但不会被包括在http
请求中。所以,它算是被用来指导浏览器的动作,并不影响服务器端。因此,改变hash
并没有真正地改变url
,所以页面路径还是之前的路径,nginx
也就不会拦截。 - 因此,在使用
history
模式时,需要通过服务端来允许地址可访问,如果没有设置,就很容易导致出现404
的局面。
使用css实现一个三角形
这题属于是看到过就会,没刷过确实没有头绪
方法一:利用边框实现
原理: 当你为一个元素设置了四个边框(上、下、左、右),并且该元素的宽高都设为0,这时,浏览器会按照你指定的颜色和宽度来绘制这四个边框。由于没有实际的内容区域,这些边框将会在元素中心点处相遇并相互重叠,从而形成对角线效果。通过设置其中三个边框为透明(transparent
),仅保留一个方向的边框为非透明色,就可以利用这种对角线效果创建出一个三角形。
.triangle-up { width: 0; height: 0; border-left: 50px solid transparent; /* 左边框 */ border-right: 50px solid transparent; /* 右边框 */ border-bottom: 100px solid #007bff; /* 底边框(显示的颜色) */ }
方法二:利用渐变色
原理
width: 100px; height: 100px;
:这设置了容器的宽度和高度。background-image: linear-gradient(45deg, orangered 50%, rgba(255, 255, 255, 0) 50%);
:这里定义了一个线性渐变背景,从橙红色平滑过渡到完全透明,渐变方向是45度。orangered 50%
和rgba(255, 255, 255, 0) 50%
表示在50%的位置颜色发生突变,而不是平滑过渡,因为两者都是在50%位置指定的颜色。background-size: 100px 100px;
:此属性值使得背景图像(在这个情况下是渐变)的大小恰好等于容器的大小,即100px x 100px。
.container{ width: 100px; height: 100px; background-image: linear-gradient(45deg, orangered 50%, rgba(255, 255, 255, 0) 50%); background-size: 100px 100px; }
回答到这里已经到了我的极限,让我们再学一种吧!
方法三:clip-path
虽然现代浏览器对clip-path
的支持已经相当广泛,但在一些较老版本的浏览器中可能仍存在兼容性问题。确保你的浏览器是否支持该属性。
原理:clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
:它定义了一个裁剪路径(即一个三角形),只显示该路径内的内容,其余部分则被裁剪掉。具体来说:
- 第一个点
(0% 0%)
:位于元素的左上角。 - 第二个点
(100% 50%)
:位于元素的右侧中间位置。 - 第三个点
(0% 100%)
:位于元素的左下角。 - 最后三点相连。
width: 100px; height: 100px; background-color: black; /* 背景颜色 */ clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
垂直水平居中方法有哪些?
我直接回答最常用的三种
方法一:flex布局
.parent { display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ }
方法二:Grid 方法:
.parent { display: grid; place-items: center; /* 同时实现水平和垂直居中 */ }
方法三: 绝对定位与 transform 方法
.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
这时候面试官就问:你的top,left,相对于谁进行偏移呢?
答:当然是基于更高级别,且position属性为非static,进行偏移。
又问?那translate中的(-50%,-50%)呢?我直接昏了,忘记了答:我当时说的是基于自己原来的位置进行偏移。
相对于元素自身的宽度和高度进行的偏移,目的是为了精确地将元素的中心点与其父容器的中心点对齐。
还问? 知道如果margin为负值是怎么样吗?
答:会反方向移动,也可以实现垂直水平居中。
.parent { position: relative; width: 300px; height: 300px; background-color: lightgray; } .child { position: absolute; top: 50%; left: 50%; width: 100px; height: 100px; background-color: lightblue; margin-top: -50px; /* 负的顶部外边距,等于高度的一半 */ margin-left: -50px; /* 负的左侧外边距,等于宽度的一半 */ }
原理:top: 50%;
和 left: 50%;
将 .child
的左上角放置在其父容器的中心。然后,通过 margin-top: -50px;
和 margin-left: -50px;
(即宽度和高度的一半),将 .child
向上和向左移动自身尺寸的一半,从而实现完全居中。
方法四:margin:auto
如果元素具有固定的宽度和高度,可以通过设置 margin: auto
来实现水平和垂直居中。实现的并不是里面的内容居中,而是盒子相对于外层盒子,垂直水平居中。
.parent { position: relative; width: 300px; /* 示例宽度 */ height: 300px; /* 示例高度 */ } .child { width: 100px; /* 固定宽度 */ height: 100px; /* 固定高度 */ position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; }
浅拷贝,深拷贝的区别?浅、深拷贝的方法有哪些?如何手动实现深拷贝?
概念:
- 浅拷贝:仅复制对象的“表层”属性,如果属性是引用类型(如对象、数组),新对象和原对象会共享同一块内存地址。
- 深拷贝:递归复制对象的所有层级属性,新对象和原对象完全独立,互不影响。
方法:
浅拷贝方法
- Object.assign()
- 展开运算符 ...
- 数组方法:slice()、concat()
深拷贝方法
- JSON 序列化(简单场景)局限性:
- 无法复制函数、
undefined
、Symbol
。 - 循环引用会报错。
- 递归实现(手动深拷贝)
- structuredClone()(现代浏览器支持)
- 第三方库(如 Lodash)
Cookie vs Local Storage vs Session Storage区别,用过吗?
生命周期
- localStorage: 数据没有过期时间,除非用户手动清除浏览器缓存或通过JavaScript代码删除,否则数据会一直存在。
- sessionStorage: 数据的生命周期与页面会话相同,即从打开页面到关闭页面这段时间,包括页面刷新和恢复(不包括浏览器关闭后重新打开的情况)。
- Cookies: 可以设置一个过期时间(Expires/Max-Age属性),如果未设置,则默认为会话Cookie,在浏览器关闭时自动删除。
存储大小
- localStorage 和 sessionStorage: 大多数浏览器提供至少5MB的存储空间,但具体大小可能因浏览器而异。
- Cookies: 通常每个域名最多只能存储4KB的数据。
Cookies 的缺点及安全设置
缺点:
- 影响性能:由于cookie在每次请求时都会被发送到服务器,这会增加额外的数据传输量,尤其是对于包含大量数据的cookie来说,会影响网页加载速度和整体性能。
- 安全性问题:如果没有设置安全标志(Secure flag),cookie可以通过非HTTPS连接被窃取;如果没有HttpOnly标志,JavaScript可以访问cookie,这使得XSS攻击更加容易获取用户的敏感信息。
安全设置:
- Secure 标志:确保cookie仅在通过HTTPS协议发送的请求中被传送,避免通过不安全的HTTP连接泄露。
- HttpOnly 标志:当设置一个cookie时,如果加上了HttpOnly属性,那么这个cookie将不能通过JavaScript的Document.cookie API访问,从而有助于防止跨站脚本(XSS)攻击。
其他Cookie属性
- Secure:只通过HTTPS 发送。
- HttpOnly:阻止JavaScript访问cookie,增强安全性。
- Expires/MAX-age:设置过期时间。
- Domain:指定哪些域名可以访问该cookie,支持跨域访问。
当 面试官又问localStorage 满了会发生什么?
当浏览器的 localStorage
存储空间已满,再尝试往里面存储新的数据时,将会抛出一个 QuotaExceededError
异常。因此,在实际应用中,开发者应该处理这种异常情况,比如提示用户清理存储或者优化数据存储策略。
使用场景
- localStorage: 适合长期保存用户偏好设置、应用状态等。
- sessionStorage: 适用于临时保存信息,如在一个标签页内进行的操作,关闭标签页后无需保留的信息。
- Cookies: 主要用于保持用户的登录状态、个性化设置以及跟踪用户行为等需要在客户端和服务器之间交换的信息。
然后就是一些输出题了
第一个:
var a = 1; function fn1() { function fn2() { console.log(a); } function fn3() { var a = 4; fn2(); } var a = 2; return fn3; } var fn = fn1(); fn(); // 输出?
解释:
- 全局作用域:var a = 1,全局变量 a 初始化为 1。
- 执行 fn1():内部定义 fn2() 和 fn3()。fn1 中声明 var a = 2,覆盖全局 a,此时 fn1 的作用域中 a = 2。返回 fn3 函数。
- 调用 fn()(即 fn3):进入 fn3 的执行上下文,变量 a 提升(未赋值时为 undefined)。执行 var a = 4,将 a 赋值为 4。 调用 fn2(),此时:
fn2
的作用域链为fn1
的上下文,未包含fn3
。- 查找
a
时,先在fn1
的变量对象中找到a = 2
,故输出 2。
重点: 函数的作用域链基于定义时的上下文(闭包特性),而非调用时的上下文。
变种:第二个
当时面试官一直催催,心乱了,也就错了😭
var a = 1; function fn1() { function fn3() { function fn2() { console.log(a); } var a; fn2(); a = 4; } var a = 2; return fn3; } var fn = fn1(); fn(); // 输出多少?
解释:
- 全局作用域:变量a被初始化为1。
- 执行fn1(): 在fn1的作用域内,声明var a = 2,此时a的值为2。定义并返回fn3函数。此时fn3的作用域链包含fn1的作用域。
- 调用fn3()(通过fn变量):在fn3的作用域内,声明var a(初始化为undefined,由于变量提升)。调用fn2()时: fn2的作用域链为:fn2 → fn3 → fn1 → 全局。
- 所以在
fn2
内部访问a
时,首先查找fn2
自身作用域(无a
),接着查找fn3
作用域。此时fn3
的a
已被声明但尚未赋值(赋值a = 4
在fn2()
之后执行),因此值为undefined
。 - 执行
a = 4
(此时fn3
的a
变为4
,但已不影响fn2
的输出)。
关键点:
JavaScript的作用域链在函数定义时确定,但变量赋值发生在代码执行时。fn2
访问的是fn3
作用域中的a
,而该变量在调用fn2
时尚未被赋值,因此输出undefined
。
算法:斐波那契数列
我写了两种方法,然后他让我对递归方法进行优化:
var fib = function(n) { var a=[]; a[1]=1,a[2]=1; if(n==1||n==2) return 1; if(n==0) return 0; for(let i=3;i<=n;i++){ a[i]=a[i-1]+a[i-2]; } return a[n]; }; var fib = function(n) { // 处理 n 为 0 或 1 的特殊情况,直接返回 n if (n === 0) { return n; } if(n==1||n==2){ return 1; } let prev=1,curr=1; for(let i=3;i<=n;i++){ let next=prev+curr; prev=curr; curr=next; } return curr; };
优化:
var fib = (function() { const dp = [0, 1, 1]; // 闭包内私有变量 return function(n) { if (n <= 2) return 1; if (dp[n] !== undefined) return dp[n]; dp[n] = fib(n-1) + fib(n-2); return dp[n]; }; })();
总结:
总体该表达的都表达了,虽然还有点可惜,还得努力,一些细节上没有把握到位,期待下次的大厂面试。
——转载自:Danta
#牛客创作赏金赛#