本轮要点: 本次面试主要考察校招生的基础内容
本轮共 21 道题。答案默认折叠,便于先自行作答。
1. 先简单自我介绍
题目要点
自我认知。
参考答案
您好,我叫[你的名字],毕业于[学校名称]的[专业名称]专业。在大学期间,我就对计算机科学产生了浓厚的兴趣,尤其是前端开发领域。在学习和实践中,我积累了扎实的编程基础,熟练掌握了HTML、CSS、JavaScript等前端开发技术,并在多个项目中担任核心开发角色。我热爱前端开发工作,喜欢通过代码创造用户友好的界面和交互体验。在业余时间,我会不断学习新技术,提升自己的专业能力。我希望能够在一个富有创新和挑战的环境中工作,与优秀的团队成员共同成长。
2. 什么时候学前端?
题目要点
学习经历。
参考答案
我是在大学二年级的时候开始接触前端开发的。当时,我选修了一门网页设计与开发的课程,这让我对前端开发产生了浓厚的兴趣。从那时起,我便开始系统地学习HTML、CSS和JavaScript等前端技术,并通过大量的实践项目不断提升自己的技能。毕业后,我继续深入学习前端框架和工具,如Vue、React等,并在实际工作中不断积累经验。
3. 怎么学前端?
题目要点
学习方法。
参考答案
学习前端开发需要系统的学习和不断的实践。首先,我从基础的HTML、CSS和JavaScript开始,通过阅读书籍、在线教程和官方文档,打下了坚实的基础。然后,我深入学习了前端框架,如Vue和React,通过实践项目来理解框架的核心概念和使用方法。同时,我积极参与开源项目和在线社区,与其他开发者交流经验,解决遇到的问题。此外,我还关注前端技术的最新发展趋势,通过参加技术会议、观看在线课程等方式不断更新自己的知识体系。
4. 你的未来规划?
题目要点
职业规划。
参考答案
我的短期目标是在前端开发领域持续深耕,提升自己的技术能力和项目经验。我希望能够在当前的岗位上承担更多的责任,参与复杂项目的开发,并不断优化现有系统的性能和用户体验。长期来看,我希望能够成为前端技术专家,带领团队进行技术创新和项目研发。同时,我也希望能够不断提升自己的架构设计能力和团队管理能力,为公司的技术发展做出更大的贡献。
5. 你觉得你什么地方比较强?哪里比较薄弱?
题目要点
自我认知。
参考答案
我认为我的强项在于对前端技术的深入理解和实践经验。我能够快速学习新技术,并将其应用到实际项目中。同时,我具备良好的问题解决能力和团队协作精神,能够与团队成员高效合作,共同完成项目目标。然而,我也意识到自己在某些方面还有提升的空间。例如,在性能优化和大型项目的架构设计方面,我还需要进一步积累经验。此外,我还需要加强对后端技术的了解,以更好地进行前后端协同开发。
6. 一个前端架构师要有什么长处?
题目要点
职业素养。
参考答案
一个优秀的前端架构师需要具备多方面的长处。首先,需要有深厚的技术功底,对前端技术栈有全面而深入的理解,包括HTML、CSS、JavaScript、前端框架、构建工具等。其次,需要具备良好的系统设计和架构能力,能够设计出高性能、可扩展、易维护的前端系统。此外,前端架构师还需要有较强的项目管理能力,能够领导团队进行项目的规划、开发和交付。良好的沟通能力和团队协作精神也是必不可少的,能够与产品、后端团队等进行有效的沟通和协作。最后,前端架构师需要有敏锐的技术洞察力,能够关注行业动态,引入新的技术和方法,推动团队的技术进步。
7. 微前端是什么?
题库原题:什么是微前端?
题目要点
微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
参考答案
微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
各个前端应用还可以独立运行、独立开发、独立部署。
微前端不是单纯的前端框架或者工具,而是一套架构体系,
8. SPA和MPA的区别?
题库原题:SPA和MPA的区别?
题目要点
SPA 通过前端路由在单一 HTML 中完成页面切换,交互流畅但首屏和 SEO 成本较高;MPA 每次导航请求新页面,SEO 友好且隔离性强,但交互体验有限;两者本质差异在于前后端渲染与路由职责的划分;现代项目常通过 SSR、SSG 等方式在两种模式之间折中。
参考答案
SPA 和 MPA 的区别,表面看是“页面跳转方式不同”,本质上是前端与服务器在渲染职责、路由控制以及资源加载策略上的分工差异。理解这一点,才能在具体业务中做出合理选择。
SPA(Single Page Application)只有一个 HTML 入口,首屏加载时会拉取完整的 JavaScript 应用骨架,之后的页面切换通过前端路由完成。URL 的变化并不会触发浏览器重新请求文档,而是由前端框架根据路由状态切换视图并复用已有资源。这种模式的优势在于页面切换流畅、状态可以在客户端长期保留,复杂交互体验更接近原生应用,非常适合后台系统、强交互工具型产品。但代价也同样明显:首屏资源压力大,SEO 先天不足,对 JS 执行和异常处理的依赖极高,一旦主 bundle 出现问题,整个应用都无法正常工作。
MPA(Multi Page Application)则以“页面”为核心单元。每一次导航都会向服务器请求一个新的 HTML 文档,路由和渲染主要由服务端控制。页面之间天然隔离,资源和状态会随页面刷新而重置。这种模式的优势在于首屏直观、SEO 友好、容错性强,适合内容型站点、营销页和对搜索引擎依赖较高的业务场景。但在交互复杂度上,MPA 往往需要付出更多成本,例如重复加载公共资源、跨页面状态共享困难,整体体验也更接近传统 Web。
从工程角度看,SPA 和 MPA 的差异还体现在构建和部署方式上。SPA 更强调前端工程化和运行时能力,构建目标是一个可长期运行的客户端应用;MPA 更强调页面级拆分和服务端模板渲染,前端更多是增强层而非控制中心。在现代实践中,两者并非对立关系,常见的折中方案包括 SSR、SSG 以及“多入口 SPA”,试图在交互体验和首屏性能、SEO 之间取得平衡。
因此,选择 SPA 还是 MPA,并不存在绝对优劣,关键在于业务目标:是以复杂交互和状态管理为核心,还是以内容分发和搜索可见性为核心。
9. CDN的作用是什么?
题库原题:为什么推荐将静态资源放到cdn上?
题目要点
静态资源是指在不同请求中访问到的数据都相同的文件,如图片、视频、HTML、CSS、JS等。动态资源则是指在不同请求中访问到的数据不相同的文件,如网站中的ASP、JSP、PHP等文件,API接口,数据库交互请求等。
CDN(内容分发网络)是一种分布式网络,由分布在不同区域的边缘节点服务器群组成。CDN通过缓存静态内容,使得用户可以直接从最近的CDN节点获取内容,从而加速访问速度并减轻源站的压力。CDN不适用于动态内容的加速,因为动态内容需要服务器实时生成。
CDN的作用包括加速网站访问、实现跨运营商和地域的全网覆盖、保障网站安全、异地备援、节约成本投入以及让网站运营者更专注于业务本身。
CDN的工作原理是用户通过浏览器访问网站时,DNS服务器将域名解析成CDN节点的IP地址,CDN根据用户的IP地址和请求内容选择最近的缓存服务器,如果缓存服务器上有请求内容则直接响应,否则向上级服务器请求内容。
没有CDN时,用户通过浏览器访问网站,本地DNS服务器解析域名,浏览器向服务器请求内容,服务器响应请求并将内容传送给浏览器。
参考答案
静态资源是什么
静态资源
静态资源是指在不同请求中访问到的数据都相同的静态文件。例如:图片、视频、网站中的文件(html、css、js)、软件安装包、apk文件、压缩包文件等。
动态资源
动态资源是指在不同请求中访问到的数据不相同的动态内容。例如:网站中的文件(asp、jsp、php、perl、cgi)、API接口、数据库交互请求等。
CDN是什么
内容分发网络,Content Delivery Network或Content Ddistribute Network,简称CDN,是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。
CDN加速的本质是缓存加速。将服务器上存储的静态内容缓存在CDN节点上,当访问这些静态内容时,无需访问服务器源站,就近访问CDN节点即可获取相同内容,从而达到加速的效果,同时减轻服务器源站的压力。
CDN应用广泛,解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度和成功率。
由于访问动态内容时,每次都需要访问服务器,由服务器动态生成实时的数据并返回。因此CDN的缓存加速不适用于加速动态内容,CDN无法缓存实时变化的动态内容。对于动态内容请求,CDN节点只能转发回服务器源站,没有加速效果。
CDN的作用
1. 加速网站的访问
2. 为了实现跨运营商、跨地域的全网覆盖
互联不互通、区域ISP地域局限、出口带宽受限制等种种因素都造成了网站的区域性无法访问。CDN加速可以覆盖全球的线路,通过和运营商合作,部署IDC资源,在全国骨干节点商,合理部署CDN边缘分发存储节点,充分利用带宽资源,平衡源站流量。
3. 为了保障你的网站安全
CDN的负载均衡和分布式存储技术,可以加强网站的可靠性,相当无无形中给你的网站添加了一把保护伞,应对绝大部分的互联网攻击事件。防攻击系统也能避免网站遭到恶意攻击。
4. 为了异地备援
当某个服务器发生意外故障时,系统将会调用其他临近的健康服务器节点进行服务,进而提供接近100%的可靠性,这就让你的网站可以做到永不宕机。
5. 为了节约成本投入
使用CDN加速可以实现网站的全国铺设,你根据不用考虑购买服务器与后续的托管运维,服务器之间镜像同步,也不用为了管理维护技术人员而烦恼,节省了人力、精力和财力。
6. 为了让你更专注业务本身
CDN加速厂商一般都会提供一站式服务,业务不仅限于CDN,还有配套的云存储、大数据服务、视频云服务等,而且一般会提供7x24运维监控支持,保证网络随时畅通,你可以放心使用。并且将更多的精力投入到发展自身的核心业务之上。
CDN工作原理

- 当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。
- CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户。
- 用户向CDN的全局负载均衡设备发起内容URL访问请求。
- CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。
- 区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址。
- 全局负载均衡设备把服务器的IP地址返回给用户。
- 用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。
DNS服务器根据用户IP地址,将域名解析成相应节点的缓存服务器IP地址,实现用户就近访问。使用CDN服务的网站,只需将其域名解析权交给CDN的GSLB设备,将需要分发的内容注入CDN,就可以实现内容加速了。
当没有CDN时
今天我们看到的网站系统基本上都是基于B/S架构的。B/S架构,即Browser-Server(浏览器 服务器)架构。
用户通过浏览器等方式访问网站的过程:
- 用户在自己的浏览器中输入要访问的网站域名。
- 浏览器向本地DNS服务器请求对该域名的解析。
- 本地DNS服务器中如果缓存有这个域名的解析结果,则直接响应用户的解析请求。
- 本地DNS服务器中如果没有关于这个域名的解析结果的缓存,则以递归方式向整个DNS系统请求解析,获得应答后将结果反馈给浏览器。
- 浏览器得到域名解析结果,就是该域名相应的服务设备的IP地址。
- 浏览器向服务器请求内容。
- 服务器将用户请求内容传送给浏览器。
10. Webpack的多线程编译怎么实现?
题目要点
在Webpack中实现多线程编译可以通过使用thread-loader这个插件来实现。thread-loader允许将部分耗时的_loader_操作(如Babel编译、CSS处理等)放到单独的线程中执行,从而提高编译速度。以下是实现步骤:
参考答案
在Webpack中实现多线程编译可以通过使用thread-loader这个插件来实现。thread-loader允许将部分耗时的_loader_操作(如Babel编译、CSS处理等)放到单独的线程中执行,从而提高编译速度。以下是实现步骤:
- 安装
thread-loader:
npm install thread-loader --save-dev
- 修改Webpack配置文件:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers: require('os').cpus().length - 1 // 设置线程数,减1是为了避免系统过载
}
},
'babel-loader'
]
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'thread-loader',
options: {
workers: require('os').cpus().length - 1
}
},
'css-loader'
]
}
]
}
};
- 注意事项:
- 线程通信开销:虽然多线程可以提高编译速度,但线程间的通信和数据序列化/反序列化也会带来一定的开销。因此,对于小型项目,多线程编译可能不会带来明显的性能提升。
- 资源限制:根据系统的CPU核心数合理设置线程数,避免系统资源耗尽导致编译失败。
- Loader适用性:有些loader可能不适合多线程执行,如需要共享状态或有副作用的loader。应确保所使用的loader支持多线程环境。
使用thread-loader可以显著缩短大型项目的编译时间,提高开发效率。
11. 你做的项目除了基础语法,还学到了什么?
题目要点
除了掌握基础的前端语法知识外,在项目实践中我学到了很多宝贵的经验:
参考答案
除了掌握基础的前端语法知识外,在项目实践中我学到了很多宝贵的经验:
- 组件化开发思想:通过使用Vue等框架,我学会了如何将复杂的用户界面拆分成多个独立的组件,每个组件负责特定的功能,从而提高了代码的可维护性和复用性。
- 状态管理:在大型项目中,我学会了使用Vuex等状态管理工具来集中管理应用的状态,使得数据的流动更加清晰可控,便于调试和维护。
- 路由管理:我深入理解了前端路由的概念和实现方式,通过Vue Router等工具实现了单页面应用的页面切换和导航功能。
- 性能优化:我学会了如何通过代码分割、懒加载、图片优化等技术手段来提高应用的性能,确保用户能够获得流畅的体验。
- 跨浏览器兼容性:在实际项目中,我积累了处理不同浏览器兼容性问题的经验,学会了使用Babel、Autoprefixer等工具来确保代码能够在各种浏览器中正常运行。
- 团队协作:在项目团队中,我学会了如何与设计师、后端开发人员等其他角色进行有效的沟通和协作,共同推动项目的进展。
最重要的是,我学会了如何将理论知识应用到实际场景中,并在实践中不断总结和提高自己的技术水平。
12. ES6的变量提升是什么?
题库原题:什么是变量提升
题目要点
函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。
参考答案
函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。
在创建执行上下文的过程中会做三件事:创建变量对象,创建作用域链,确定 this 指向,其中创建变量对象的过程中,首先会为 arguments 创建一个属性,值为 arguments,然后会扫码 function 函数声明,创建一个同名属性,值为函数的引用,接着会扫码 var 变量声明,创建一个同名属性,值为 undefined,这就是变量提升。
13. JS普通函数和箭头函数的区别?
题目要点
什么是箭头函数?
参考答案
什么是箭头函数?
ES6中允许使用箭头=>来定义箭头函数,具体语法,我们来看一个简单的例子:
// 箭头函数
let fun = (name) => {
// 函数体
return `Hello ${name} !`;
};
// 等同于
let fun = function (name) {
// 函数体
return `Hello ${name} !`;
};
可以看出,定义箭头函在数语法上要比普通函数简洁得多。箭头函数省去了function关键字,采用箭头=>来定义函数。函数的参数放在=>前面的括号中,函数体跟在=>后的花括号中。
箭头函数与普通函数的区别
1、语法更加简洁、清晰
从上面的基本语法示例中可以看出,箭头函数的定义要比普通函数定义简洁、清晰得多,很快捷。
2、箭头函数不会创建自己的this(重要!!深入理解!!)
我们先来看看MDN上对箭头函数this的解释。
箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this。
箭头函数没有自己的this,它会捕获自己在定义时(注意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变。
3、箭头函数继承而来的this指向永远不变(重要!!深入理解!!)
上面的例子,就完全可以说明箭头函数继承而来的this指向永远不变。对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。
4、.call()/.apply()/.bind()无法改变箭头函数中this的指向
.call()/.apply()/.bind()方法可以用来动态修改函数执行时this的指向,但由于箭头函数的this定义时就已经确定且永远不会改变。所以使用这些方法永远也改变不了箭头函数this的指向,虽然这么做代码不会报错。
5、箭头函数不能作为构造函数使用
我们先了解一下构造函数的new都做了些什么?简单来说,分为四步:
① JS内部首先会先生成一个对象; ② 再把函数中的this指向该对象; ③ 然后执行构造函数中的语句; ④ 最终返回该对象实例。
但是!!因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!
6、箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。
7、箭头函数没有原型prototype
let sayHi = () => {
console.log('Hello World !')
};
console.log(sayHi.prototype); // undefined
8、箭头函数不能用作Generator函数,不能使用yeild关键字
14. 闭包是什么?有什么应用场景?
题库原题:说说你对闭包的理解,以及闭包使用场景
题目要点
什么是闭包
简单理解就是函数中嵌套函数。我们都知道局部变量我们是无法访问的,但是通过闭包可以做到。
// 正常访问
var lan = 'zh';
function hello(){
var name = '前端未来';
}
console.log(name)//很明显'undefined'
// 换成闭包
function hello(){
var name = '前端未来';
function demo(){
console.log(name)//打印:前端未来
}
}
闭包的应用场景
1. 数据封装和隐私
闭包可以用来封装数据和功能,创建具有私有变量和公共接口的模块。
应用
- 创建具有私有状态的模块或对象。
2. 函数工厂
闭包用于创建返回函数的函数,这些返回的函数可以维持状态。
应用
- 生成具有特定配置或状态的函数。
3. 柯里化(Currying)
闭包允许将多参数的函数转换成一系列单参数的函数。
应用
- 简化函数调用,逐步应用参数。
4. 延迟计算
闭包可以用于延迟计算,只在必要时才执行计算。
应用
- 实现性能优化,如懒加载。
5. 迭代器和生成器
闭包在迭代器和生成器中用于维护状态。
应用
- 实现可重复使用的迭代器。
6. 异步编程
闭包在异步回调中保持状态,避免在多层嵌套回调中使用额外的参数。
应用
- 管理异步操作的状态和结果。
7. 事件处理器
闭包可以捕获事件处理器需要的局部变量。
应用
- 为事件绑定具有特定状态的处理器。
8. 缓存和记忆
闭包可以用来实现缓存逻辑,存储和复用计算结果。
应用
- 减少重复计算,提高性能。
注意事项
- 闭包可能会导致内存使用增加,因为它们会保持对外部变量的引用。
- 理解闭包的作用域链对于避免意外的行为和内存泄漏很重要。
- 闭包提供了强大的功能,但应谨慎使用,以保持代码的清晰和可维护性。
参考答案
一、是什么
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁
下面给出一个简单的例子
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
displayName() 没有自己的局部变量。然而,由于闭包的特性,它可以访问到外部函数的变量
二、使用场景
任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的
下面举个例子:
在页面上添加一些可以调整字号的按钮
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
柯里化函数
柯里化的目的在于避免频繁调用具有相同参数函数的同时,又能够轻松的重用
// 假设我们有一个求长方形面积的函数
function getArea(width, height) {
return width * height
}
// 如果我们碰到的长方形的宽老是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)
const area3 = getArea(10, 40)
// 我们可以使用闭包柯里化这个计算面积的函数
function getArea(width) {
return height => {
return width * height
}
}
const getTenWidthArea = getArea(10)
// 之后碰到宽度为10的长方形就可以这样计算面积
const area1 = getTenWidthArea(20)
// 而且如果遇到宽度偶尔变化也可以轻松复用
const getTwentyWidthArea = getArea(20)
使用闭包模拟私有方法
在JavaScript中,没有支持声明私有变量,但我们可以使用闭包来模拟私有方法
下面举个例子:
var Counter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = Counter();
var Counter2 = Counter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
上述通过使用闭包来定义公共函数,并令其可以访问私有函数和变量,这种方式也叫模块方式
两个计数器 Counter1 和 Counter2 是维护它们各自的独立性的,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境,不会影响另一个闭包中的变量
其他
例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期
三、注意事项
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响
例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。
原因在于每个对象的创建,方法都会被重新赋值
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
15. null和undefined的区别?
题目要点
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
参考答案
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
typeof null; // "object" (not "null" for legacy reasons)
typeof undefined; // "undefined"
null === undefined; // false
null == undefined; // true
null === null; // true
null == null; // true
!null; // true
Number.isNaN(1 + null); // false
Number.isNaN(1 + undefined); // true
16. 标准盒模型和IE盒模型的区别?
题库原题:说说你对盒子模型的理解
题目要点
当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)
参考答案
一、是什么
当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)
一个盒子由四个部分组成:content、padding、border、margin
\r\n\r\ncontent,即实际内容,显示文本和图像
boreder,即边框,围绕元素内容的内边距的一条或多条线,由粗细、样式、颜色三部分组成padding,即内边距,清除内容周围的区域,内边距是透明的,取值不能为负,受盒子的background属性影响margin,即外边距,在元素外创建额外的空白,空白通常指不能放其他元素的区域
上述是一个从二维的角度观察盒子,下面再看看看三维图:

下面来段代码:
<style>
.box {
width: 200px;
height: 100px;
padding: 20px;
}
</style>
<div class=\"box\">
盒子模型
</div>
当我们在浏览器查看元素时,却发现元素的大小变成了240px
这是因为,在CSS中,盒子模型可以分成:
- W3C 标准盒子模型
- IE 怪异盒子模型
默认情况下,盒子模型为W3C 标准盒子模型
二、标准盒子模型
标准盒子模型,是浏览器默认的盒子模型
下面看看标准盒子模型的模型图:

从上图可以看到:
- 盒子总宽度 = width + padding + border + margin;
- 盒子总高度 = height + padding + border + margin
也就是,width/height 只是内容高度,不包含 padding 和 border 值
所以上面问题中,设置width为200px,但由于存在padding,但实际上盒子的宽度有240px
三、IE 怪异盒子模型
同样看看IE 怪异盒子模型的模型图:

从上图可以看到:
- 盒子总宽度 = width + margin;
- 盒子总高度 = height + margin;
也就是,width/height 包含了 padding 和 border 值
Box-sizing
CSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度
语法:
box-sizing: content-box|border-box|inherit;
- content-box 默认值,元素的 width/height 不包含padding,border,与标准盒子模型表现一致
- border-box 元素的 width/height 包含 padding,border,与怪异盒子模型表现一致
- inherit 指定 box-sizing 属性的值,应该从父元素继承
回到上面的例子里,设置盒子为 border-box 模型
<style>
.box {
width: 200px;
height: 100px;
padding: 20px;
box-sizing: border-box;
}
</style>
<div class=\"box\">
盒子模型
</div>
这时候,就可以发现盒子的所占据的宽度为200px
17. px、rem、em的区别?
题目要点
传统的项目开发中,我们只会用到px、%、em这几个单位,它可以适用于大部分的项目开发,且拥有比较良好的兼容性
参考答案
一、介绍
传统的项目开发中,我们只会用到px、%、em这几个单位,它可以适用于大部分的项目开发,且拥有比较良好的兼容性
从CSS3开始,浏览器对计量单位的支持又提升到了另外一个境界,新增了rem、vh、vw、vm等一些新的计量单位
利用这些新的单位开发出比较良好的响应式页面,适应多种不同分辨率的终端,包括移动设备等
二、单位
在css单位中,可以分为长度单位、绝对单位,如下表所指示
| CSS单位 | |
|---|---|
| 相对长度单位 | em、ex、ch、rem、vw、vh、vmin、vmax、% |
| 绝对长度单位 | cm、mm、in、px、pt、pc |
这里我们主要讲述px、em、rem、vh、vw
px
px,表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中
有些人会把px认为是相对长度,原因在于在移动端中存在设备像素比,px实际显示的大小是不确定
这里之所以认为px为绝对单位,在于px的大小和元素的其他属性无关
em
em是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px)
为了简化 font-size 的换算,我们需要在css中的 body 选择器中声明font-size= 62.5%,这就使 em 值变为 16px*62.5% = 10px
这样 12px = 1.2em, 10px = 1em, 也就是说只需要将你的原来的 px 数值除以 10,然后换上 em 作为单位就行了
特点:
- em 的值并不是固定的
- em 会继承父级元素的字体大小
- em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸
- 任意浏览器的默认字体高都是 16px
举个例子
<div class="big">
我是14px=1.4em
<div class="small">我是12px=1.2em</div>
</div>
样式为
<style>
html {font-size: 10px; } /* 公式16px*62.5%=10px */
.big{font-size: 1.4em}
.small{font-size: 1.2em}
</style>
这时候.big元素的font-size为14px,而.small元素的font-size为12px
rem
rem,相对单位,相对的只是HTML根元素font-size的值
同理,如果想要简化font-size的转化,我们可以在根元素html中加入font-size: 62.5%
html {font-size: 62.5%; } /* 公式16px*62.5%=10px */
这样页面中1rem=10px、1.2rem=12px、1.4rem=14px、1.6rem=16px;使得视觉、使用、书写都得到了极大的帮助
特点:
- rem单位可谓集相对大小和绝对大小的优点于一身
- 和em不同的是rem总是相对于根元素,而不像em一样使用级联的方式来计算尺寸
vh、vw
vw ,就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh则为窗口的高度
这里的窗口分成几种情况:
- 在桌面端,指的是浏览器的可视区域
- 移动端指的就是布局视口
像vw、vh,比较容易混淆的一个单位是%,不过百分比宽泛的讲是相对于父元素:
对于普通定位元素就是我们理解的父元素
- 对于position: absolute;的元素是相对于已定位的父元素
- 对于position: fixed;的元素是相对于 ViewPort(可视窗口)
三、总结
px:绝对单位,页面按精确像素展示
em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算,整个页面内1em不是一个固定的值
rem:相对单位,可理解为root em, 相对根节点html的字体大小来计算
vh、vw:主要用于页面视口大小布局,在页面布局上更加方便简单
18. 实现水平垂直居中的方法?
题目要点
- Flexbox 和 Grid 是现代 CSS 布局的强大工具,适用于大多数情况。
- Absolute + Transform 是一个经典的解决方案,适用于任何情况。
- Line-Height 是用于简单文本居中的方法。
参考答案
要在未知高度和宽度的元素中实现水平和垂直居中,可以使用多种 CSS 技巧。以下是几种常见的方案以及它们的简单手写实现:
1. Flexbox
HTML:
<div class="container">
<div class="centered-element">Centered</div>
</div>
CSS:
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 使容器填满视口 */
}
.centered-element {
/* 元素的样式 */
}
2. Grid
HTML:
<div class="container">
<div class="centered-element">Centered</div>
</div>
CSS:
.container {
display: grid;
place-items: center; /* 同时水平和垂直居中 */
height: 100vh; /* 使容器填满视口 */
}
.centered-element {
/* 元素的样式 */
}
3. Absolute + Transform
HTML:
<div class="container">
<div class="centered-element">Centered</div>
</div>
CSS:
.container {
position: relative;
height: 100vh; /* 使容器填满视口 */
}
.centered-element {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 使元素居中 */
}
4. Line-Height (适用于单行文本)
HTML:
<div class="container">
<div class="centered-element">Centered</div>
</div>
CSS:
.container {
height: 100vh; /* 使容器填满视口 */
line-height: 100vh; /* 设置行高为容器高度 */
text-align: center; /* 水平居中 */
}
.centered-element {
display: inline-block;
vertical-align: middle; /* 垂直居中 */
line-height: normal; /* 恢复正常行高 */
}
19. 手写防抖或节流函数
题库原题:什么是防抖和节流,以及如何编码实现?
题目要点
本质上是优化高频率执行代码的一种手段
参考答案
一、是什么
本质上是优化高频率执行代码的一种手段
如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用throttle(节流)和debounce(防抖)的方式来减少调用频率
定义
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
一个经典的比喻:
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制
电梯第一个人进来后,15秒后准时运送一次,这是节流
电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖
代码实现
节流
完成节流可以使用时间戳与定时器的写法
使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
function throttled1(fn, delay = 500) {
let oldtime = Date.now()
return function (...args) {
let newtime = Date.now()
if (newtime - oldtime >= delay) {
fn.apply(null, args)
oldtime = Date.now()
}
}
}
使用定时器写法,delay毫秒后第一次执行,第二次事件停止触发后依然会再一次执行
function throttled2(fn, delay = 500) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay);
}
}
}
可以将时间戳写法的特性与定时器写法的特性相结合,实现一个更加精确的节流。实现如下
function throttled(fn, delay) {
let timer = null
let starttime = Date.now()
return function () {
let curTime = Date.now() // 当前时间
let remaining = delay - (curTime - starttime) // 从上一次到现在,还剩下多少多余时间
let context = this
let args = arguments
clearTimeout(timer)
if (remaining <= 0) {
fn.apply(context, args)
starttime = Date.now()
} else {
timer = setTimeout(fn, remaining);
}
}
}
防抖
简单版本的实现
function debounce(func, wait) {
let timeout;
return function () {
let context = this; // 保存this指向
let args = arguments; // 拿到event对象
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
防抖如果需要立即执行,可加入第三个参数用于判断,实现如下:
function debounce(func, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout); // timeout 不为null
if (immediate) {
let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) {
func.apply(context, args)
}
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
二、区别
相同点:
- 都可以通过使用
setTimeout实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用
clearTimeout和setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能 - 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次
如下图所示:

三、应用场景
防抖在连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小
resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
节流在间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
20. 实现一个深拷贝(考虑循环引用)
题库原题:实现深拷贝
题目要点
核心考查:实现一个深拷贝(考虑循环引用)的概念、原理与实际应用。
参考答案
const cloneDeep1 = (target, hash = new WeakMap()) => {
// 对于传入参数处理
if (typeof target !== 'object' || target === null) {
return target;
}
// 哈希表中存在直接返回
if (hash.has(target)) return hash.get(target);
const cloneTarget = Array.isArray(target) ? [] : {};
hash.set(target, cloneTarget);
// 针对Symbol属性
const symKeys = Object.getOwnPropertySymbols(target);
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof target[symKey] === 'object' && target[symKey] !== null) {
cloneTarget[symKey] = cloneDeep1(target[symKey]);
} else {
cloneTarget[symKey] = target[symKey];
}
})
}
for (const i in target) {
if (Object.prototype.hasOwnProperty.call(target, i)) {
cloneTarget[i] =
typeof target[i] === 'object' && target[i] !== null
? cloneDeep1(target[i], hash)
: target[i];
}
}
return cloneTarget;
}
21. 实现固定宽高比(如16:9)的容器
题目要点
固定宽高比的本质是用宽度推导高度;传统方案利用 padding 百分比规则实现,兼容性好但结构冗余;现代方案使用 aspect-ratio 原生支持,语义清晰、维护成本低;具体选择需结合浏览器兼容性和布局上下文。
参考答案
实现固定宽高比容器的核心,是让高度由宽度推导出来,而不是直接写死高度。围绕这一目标,随着 CSS 能力的演进,实践方式也发生了明显变化。
在较早阶段,最常见的做法是利用 百分比 padding 的计算规则。在 CSS 中,纵向的 padding-top / padding-bottom 百分比是相对于父元素宽度计算的,这一点恰好可以用来推导高度。一个 16:9 的容器,本质上就是高度等于宽度的 56.25%。因此可以通过一个“占位层”来撑开高度,再用绝对定位承载真实内容。这种方式兼容性极好,在旧项目或需要兼容老浏览器时仍然可靠,但结构上需要多一层包裹,语义和可读性都不算理想。
随着现代浏览器能力的提升,CSS 已经原生支持了 aspect-ratio 属性。通过直接声明宽高比,浏览器可以在布局阶段自动计算高度,无需额外 hack。对于一个响应式容器,只需要控制宽度,浏览器就能按比例推导出高度。这个方案在语义、可维护性和可读性上都明显优于传统 padding 技巧,非常适合新项目和组件库场景。需要注意的是,当容器内部存在绝对定位或内容溢出时,仍然需要配合 overflow 等属性进行约束。
在一些布局受限的场景下,例如容器高度受父级约束,也可以通过 Flex 或 Grid 间接实现比例控制。通过让容器在某个维度上参与布局,而另一个维度通过计算或约束推导出来,可以实现近似的固定比例效果。不过这种方式通常依赖上下文条件,更适合作为补充方案,而非通用解法。
在实际工程中,选择哪种方案取决于项目的浏览器支持范围和组件复用要求。老项目或跨端环境优先选择 padding 百分比方案,新项目和现代浏览器环境应优先使用 aspect-ratio,以减少结构复杂度和潜在 bug。