本轮概述: 比较简单,主要考察一些前端基础知识。
本轮共 21 道题。答案默认折叠,便于先自行作答。
1. 请你简单介绍一下自己,你研究生期间学了哪些课程?
题目要点
计算机专业研究生,系统学习了相关理论课程,对前端技术有浓厚兴趣并有实践经验。
参考答案
本科学习计算机科学与技术专业,研究生阶段继续深入学习相关领域知识。研究生期间的核心课程包括高级数据结构与算法分析、分布式系统、软件架构设计、人工智能原理、计算机图形学、高级数据库系统等。同时选修了前端开发相关的课程,如Web技术与应用、用户界面设计等。在学习过程中,对前端技术产生了浓厚兴趣,通过自学掌握了React、Vue等主流框架,并在多个课程项目中实践应用。研究方向偏向Web技术和用户体验,毕业论文也是围绕前端性能优化展开的研究。
2. 在之前参与的项目里,你主要担任什么职责,都负责了哪些模块的开发呀?
题目要点
主要通过课程项目、实验室项目和实习积累经验,担任前端开发角色,涉及多种常见功能模块。
参考答案
主要参与过学校的课程项目、实验室项目以及一段实习经历。在课程项目中,通常担任前端开发的主要负责人,负责整体页面架构设计和核心功能实现。具体开发过在线学习平台的用户界面、数据可视化展示模块、以及实时聊天功能。在实验室项目中,参与开发了一个科研数据管理系统,主要负责数据录入界面、图表展示组件和用户权限管理前端部分。实习期间,在导师指导下参与了公司内部管理系统的开发,负责表单处理、文件上传和基础的增删改查功能。虽然项目规模不算很大,但通过这些实践对前端开发的完整流程有了较好的理解。
3. HTML5 相较于之前的版本有哪些新特性?
题目要点
语义化标签、原生多媒体支持、Canvas绘图、增强表单、Web存储、丰富的API等新特性。
参考答案
HTML5相比之前版本引入了很多重要特性。语义化标签是最显著的改进,新增了header、nav、main、article、section、aside、footer等标签,让页面结构更清晰,也有利于SEO优化。多媒体支持方面,audio和video标签可以直接嵌入音视频内容,不再依赖Flash等插件。Canvas元素提供了强大的2D绘图能力,可以用JavaScript动态绘制图形和动画。表单功能得到增强,新增了email、url、date、range等输入类型,以及内置的表单验证功能。Web存储API包括localStorage和sessionStorage,提供了比cookie更好的本地存储方案。还有地理位置API、拖拽API、Web Workers等,为Web应用提供了更丰富的功能支持。
4. 浏览器缓存这块,你是怎么理解的?
题目要点
性能优化机制,通过HTTP头部控制,能减少网络请求但需要合理配置避免更新问题。
参考答案
浏览器缓存是Web性能优化的重要机制,通过在本地存储已请求的资源来减少网络请求,提升页面加载速度。在学习过程中了解到,缓存主要通过HTTP协议的相关头部来控制。当浏览器首次请求资源时,服务器会返回资源内容以及缓存相关的响应头,浏览器根据这些头部信息决定是否缓存资源以及缓存多长时间。再次请求相同资源时,浏览器会先检查本地缓存,如果缓存有效就直接使用,否则向服务器发起请求。这个机制在减少服务器负载、节省带宽、提升用户体验方面都有重要作用。在实际项目中也遇到过缓存导致的问题,比如更新代码后用户看不到最新内容。
5. 浏览器缓存主要有哪些类型?
题目要点
HTTP缓存(强缓存、协商缓存)、内存缓存、磁盘缓存、Service Worker缓存等多层次缓存机制。
参考答案
浏览器缓存可以分为几个层次。HTTP缓存是最主要的类型,包括强缓存和协商缓存两种机制。强缓存通过Cache-Control和Expires头部设置过期时间,在有效期内直接使用本地缓存。协商缓存则通过ETag和Last-Modified与服务器确认资源是否有更新。除了HTTP缓存,还有内存缓存和磁盘缓存的区别,内存缓存速度快但容量小,磁盘缓存容量大但速度相对较慢。Service Worker缓存是比较新的技术,可以通过编程方式控制缓存策略,支持离线应用。另外还有DNS缓存、CDN缓存等,虽然不在浏览器内部,但也是整个缓存体系的组成部分。
6. 你有没有遇到过因为浏览器缓存带来的问题?比如,用户没有获取到最新的资源这种情况,你是怎么解决的?
题目要点
通过版本号、时间戳、文件hash等方式解决缓存更新问题,构建工具可以自动化处理。
参考答案
在做课程项目和实习期间确实遇到过这类问题。最典型的情况是修改了CSS或JavaScript文件后,浏览器仍然加载旧版本的文件,导致页面显示异常或功能不正常。刚开始遇到这种问题时比较困惑,后来学习了解到是缓存机制导致的。解决方法主要有几种:最简单的是在开发阶段使用浏览器的强制刷新功能,或者在开发者工具中禁用缓存。对于生产环境,学会了在文件名后添加版本号或时间戳参数来强制更新,比如style.css?v=1.0.1。后来了解到webpack等构建工具可以自动为文件生成hash值,这样文件内容改变时文件名也会改变,从而绕过缓存。这个问题让我深刻理解了缓存机制的重要性和复杂性。
7. 说说 sessionStorage 和 localStorage 的区别
题目要点
主要区别在生命周期和作用域,sessionStorage会话级,localStorage持久化且可跨页面共享。
参考答案
这两个都是HTML5提供的Web存储API,在项目中都有使用过。
最主要的区别是生命周期不同:sessionStorage只在当前浏览器标签页的会话期间有效,关闭标签页后数据就会被清除;而localStorage的数据会持久保存在用户设备上,除非主动删除或者达到存储限制。
作用域方面也不同,sessionStorage的数据只能在当前标签页内访问,不同标签页之间是隔离的;localStorage则可以在同一域名下的所有页面间共享数据。
在实际使用中,sessionStorage适合存储临时的会话相关数据,比如表单的临时保存;localStorage更适合存储用户偏好设置、购物车内容等需要跨会话保持的信息。
8. 除了它们的存储大小区别外,还有哪些不同?
题目要点
事件机制、安全特性、使用场景和数据共享能力等方面存在差异。
参考答案
除了生命周期差异,还有一些其他区别。数据共享机制上,localStorage支持storage事件,当其他页面修改localStorage数据时,当前页面可以监听到这个变化,这在多标签页应用中很有用;而sessionStorage不支持这种跨页面的事件通知。安全性方面,sessionStorage由于其会话特性,数据泄露的风险相对较小。在浏览器兼容性上,两者的支持程度基本一致,都是HTML5的特性。使用场景也有明显区别:sessionStorage常用于单页面应用的状态管理、表单数据的临时保存等;localStorage则用于用户设置、主题偏好、离线数据缓存等需要持久化的场景。在移动端开发中,还需要考虑存储空间限制和清理策略。
9. 对 Promise 了解多少?
题目要点
异步处理机制,三种状态,支持链式调用,有丰富的静态方法,与async/await配合使用。
参考答案
Promise是JavaScript处理异步操作的重要机制,在学习现代前端开发时深入学习过。Promise解决了传统回调函数嵌套造成的"回调地狱"问题,让异步代码更易读和维护。Promise对象有三种状态:pending(等待中)、fulfilled(已完成)、rejected(已拒绝),状态一旦改变就不可逆。通过then方法可以链式处理异步结果,catch方法处理错误,finally方法无论成功失败都会执行。Promise还提供了一些静态方法,如Promise.all可以并行处理多个异步操作,Promise.race返回最先完成的结果。在实际项目中经常使用Promise处理API请求、文件读取等异步操作。配合async/await语法,可以让异步代码看起来像同步代码一样简洁。
10. 实现一个 Promise.all
题目要点
核心是并行执行、全部成功才成功、保持结果顺序、任一失败即整体失败的逻辑。
参考答案
function myPromiseAll<T>(promises: (Promise<T> | T)[]): Promise<T[]> {
return new Promise((resolve, reject) => {
// 处理空数组的情况
if (promises.length === 0) {
resolve([])
return
}
const results: T[] = new Array(promises.length)
let completedCount = 0
promises.forEach((promise, index) => {
// 将非Promise值也包装成Promise
Promise.resolve(promise)
.then((value) => {
results[index] = value
completedCount++
// 当所有Promise都完成时,resolve结果数组
if (completedCount === promises.length) {
resolve(results)
}
})
.catch((error) => {
// 任何一个Promise失败,整个Promise.all就失败
reject(error)
})
})
})
}
// 测试用例
function testPromiseAll() {
const promise1 = Promise.resolve(1)
const promise2 = new Promise<number>((resolve) => {
setTimeout(() => resolve(2), 1000)
})
const promise3 = Promise.resolve(3)
console.log("开始测试 Promise.all...")
myPromiseAll([promise1, promise2, promise3])
.then((results) => {
console.log("所有Promise都成功:", results) // 应该输出 [1, 2, 3]
})
.catch((error) => {
console.error("有Promise失败:", error)
})
// 测试失败情况
const failingPromise = Promise.reject("出错了")
myPromiseAll([promise1, failingPromise, promise3])
.then((results) => {
console.log("不应该执行到这里")
})
.catch((error) => {
console.log("捕获到错误:", error) // 应该输出 "出错了"
})
}
// 执行测试
testPromiseAll()
export { myPromiseAll }
实现Promise.all的关键是要理解它的行为特点:所有Promise都成功时才成功,任何一个失败就立即失败,并且要保持结果的顺序。使用计数器来跟踪完成状态,用数组按索引存储结果确保顺序正确。
11. 在 Promise 链中,若 Promise.all 的某个子 Promise 抛出错误,是否会中断后续链式操作?
题目要点
Promise.all失败会触发catch,但不一定中断链式调用,取决于错误处理方式和返回值。
参考答案
Promise.all中任何一个子Promise失败时,Promise.all会立即变为rejected状态,这会触发链式调用中的catch处理器。但是否中断后续链式操作取决于catch处理器的处理方式。如果catch处理器正常处理了错误(比如返回一个值或者resolved的Promise),那么链式调用会继续执行后面的then操作;如果catch处理器重新抛出错误或者返回rejected的Promise,那么错误会继续向下传播。这种机制允许在链式调用中进行错误恢复,比如在catch中提供默认值或者重试逻辑。在实际项目中,这种特性很有用,可以让程序在部分操作失败时仍然能够继续执行。
12. 讲一下盒模型,外边距坍塌这种情况你会怎么解决
题目要点
盒模型包含四个区域,外边距坍塌可通过BFC、边框、新布局方式等方法解决。
参考答案
CSS盒模型定义了元素的空间结构,包括内容区域、内边距、边框和外边距四个部分。有两种盒模型:标准盒模型中width和height只包含内容区域;IE盒模型(border-box)中width和height包含内容、内边距和边框。外边距坍塌是指相邻元素的垂直外边距会合并成较大的那个值,而不是相加。在学习过程中了解到几种解决方法:可以给父元素添加border或padding来阻止坍塌;使用overflow: hidden创建BFC(块级格式化上下文);使用flexbox或grid布局,这些新的布局方式不会产生外边距坍塌;或者将元素设置为inline-block。在实际项目中,有时候会利用外边距坍塌的特性来实现更自然的间距效果。
13. 常见的跨域方式都有哪些?
题目要点
CORS、JSONP、代理服务器、postMessage、document.domain等,CORS和代理是主流方案。
参考答案
CORS是现在最主流的方式,通过服务器设置Access-Control-Allow-Origin等响应头来允许跨域请求。
JSONP是比较传统的方法,利用script标签不受同源策略限制的特点,但只支持GET请求。
代理服务器是开发中常用的方案,通过同域的服务器转发请求来避免跨域问题,webpack-dev-server就提供了这种代理功能。 postMessage API可以实现不同窗口间的安全通信,在iframe嵌入场景中很有用。
document.domain方法适用于主域相同但子域不同的情况。
在实习期间主要使用CORS和开发代理两种方式,CORS需要后端配合,开发代理在前端就能解决。
14. CORS(跨源资源共享) 是怎么实现跨域请求的呢?
题目要点
通过HTTP头部控制,分简单请求和预检请求,服务器配置相应头部实现跨域授权。
参考答案
CORS通过HTTP头部机制来实现跨域资源共享。浏览器会将跨域请求分为简单请求和复杂请求两类。简单请求(如GET、POST且满足特定条件)会直接发送,服务器通过Access-Control-Allow-Origin头部指定允许访问的域名,浏览器检查这个头部来决定是否允许访问响应数据。复杂请求(如PUT、DELETE或包含自定义头部)会先发送OPTIONS预检请求,服务器返回Access-Control-Allow-Methods、Access-Control-Allow-Headers等头部,告诉浏览器允许的HTTP方法和头部。只有预检通过后,浏览器才会发送实际请求。如果需要携带cookie等凭证信息,还需要设置Access-Control-Allow-Credentials。
15. 小程序开发和 H5 开发,有哪些区别?
题目要点
运行环境、架构模式、开发语法、API能力、性能表现、发布方式等方面存在显著差异。
参考答案
运行环境不同是最根本的区别,小程序运行在特定的宿主环境中(如微信、支付宝),有自己的运行时和API;H5运行在标准浏览器环境中。
架构上,小程序采用双线程模型,逻辑层和渲染层分离,通过JSBridge通信;H5是单线程执行。开发语言方面,小程序使用类似Vue的模板语法和组件化开发,但有自己的语法规范;H5可以使用任何前端框架。
API能力上,小程序提供了丰富的原生功能接口,如支付、定位、摄像头等,这些在H5中需要通过浏览器API或第三方SDK实现。
性能表现上,小程序由于预加载和原生组件的优势,通常有更好的启动速度。发布方式也不同,小程序需要通过平台审核,H5可以直接部署。
16. 遇到过哪些兼容性问题,怎么解决的?
题目要点
通过转译工具、polyfill、autoprefixer、fallback方案等技术手段解决兼容性问题。
参考答案
CSS方面,flexbox在一些老版本浏览器中支持不完整,通过使用autoprefixer自动添加厂商前缀解决,或者为老浏览器提供float布局的fallback方案。
JavaScript方面,ES6的一些新特性如箭头函数、Promise等在老版本浏览器中不支持,通过Babel转译和polyfill解决。
移动端适配中遇到过iOS Safari的100vh问题,通过CSS自定义属性和JavaScript动态计算视口高度解决。
还有不同浏览器对事件处理的差异,通过使用标准化的事件处理方法和事件委托来解决。
虽然现在大部分用户使用的都是现代浏览器,但在项目中还是会考虑基本的兼容性处理,使用工具链来自动化处理这些问题。
17. v-if 和 v-show 有什么区别?
题目要点
v-if控制DOM创建销毁,v-show控制CSS显示隐藏,在性能开销和使用场景上有区别。
参考答案
v-if是条件渲染,当条件为false时,对应的DOM元素根本不会被创建,条件变为true时才会创建并插入到DOM中。
v-show则是通过控制CSS的display属性来显示或隐藏元素,DOM元素始终存在。
这导致了性能上的差异:v-if有更高的切换开销,因为涉及DOM的创建和销毁,但如果初始条件为false,则没有渲染开销;v-show有更高的初始渲染开销,但切换时开销很小,只是修改CSS属性。
功能上,v-if支持与template标签和v-else配合使用,v-show不支持。在组件生命周期方面,v-if控制的组件在条件变化时会触发完整的生命周期,v-show不会。
18. 你一般会根据什么情况来选择使用 v-if 或者 v-show?
题目要点
基于切换频率、组件复杂度、性能要求和状态保持需求来选择合适的指令。
参考答案
如果条件很少改变,比如基于用户权限的显示控制,会选择v-if,因为可以避免不必要的DOM创建和组件初始化开销。如果需要频繁切换显示状态,比如标签页切换、模态框的显示隐藏,会选择v-show,避免重复的DOM操作开销。对于包含复杂子组件或大量数据的元素,如果不经常显示,使用v-if可以节省初始化成本。在移动端项目中,考虑到性能限制,对于复杂组件更倾向于使用v-if。还要考虑用户体验,如果希望保持组件状态(比如表单输入内容),v-show更合适;如果希望每次显示都是全新状态,v-if更合适。在实际开发中会根据具体需求权衡选择。
19. JavaScript 实现继承有哪些方法?实现一个
题目要点
多种继承实现方式,组合继承和ES6 class是主流,需要理解原型链机制和super关键字。
参考答案
// 1. 原型链继承
function prototypeInheritance() {
function Animal(name: string) {
this.name = name
this.colors = ["red", "blue"]
}
Animal.prototype.sayName = function () {
console.log("My name is " + this.name)
}
function Dog(name: string, breed: string) {
this.breed = breed
}
// 设置原型链
Dog.prototype = new Animal("")
Dog.prototype.constructor = Dog
return { Animal, Dog }
}
// 2. 构造函数继承
function constructorInheritance() {
function Animal(name: string) {
this.name = name
this.colors = ["red", "blue"]
}
Animal.prototype.sayName = function () {
console.log("My name is " + this.name)
}
function Dog(name: string, breed: string) {
// 调用父构造函数
Animal.call(this, name)
this.breed = breed
}
return { Animal, Dog }
}
// 3. 组合继承(最常用的方式)
function combinationInheritance() {
function Animal(name: string) {
this.name = name
this.colors = ["red", "blue"]
}
Animal.prototype.sayName = function () {
console.log("My name is " + this.name)
}
Animal.prototype.eat = function () {
console.log(this.name + " is eating")
}
function Dog(name: string, breed: string) {
// 继承实例属性
Animal.call(this, name)
this.breed = breed
}
// 继承原型方法
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
// 添加子类特有方法
Dog.prototype.bark = function () {
console.log(this.name + " is barking")
}
return { Animal, Dog }
}
// 4. ES6 Class继承(现代推荐方式)
class ModernAnimal {
name: string
colors: string[]
constructor(name: string) {
this.name = name
this.colors = ["red", "blue"]
}
sayName() {
console.log(`My name is ${this.name}`)
}
eat() {
console.log(`${this.name} is eating`)
}
}
class ModernDog extends ModernAnimal {
breed: string
constructor(name: string, breed: string) {
super(name) // 调用父类构造函数
this.breed = breed
}
bark() {
console.log(`${this.name} is barking`)
}
// 重写父类方法
eat() {
console.log(`${this.name} the ${this.breed} is eating dog food`)
}
}
// 演示和测试
function demonstrateInheritance() {
console.log("=== 组合继承演示 ===")
const { Animal, Dog } = combinationInheritance()
const animal = new Animal("Generic Animal")
const dog = new Dog("Buddy", "Golden Retriever")
console.log("Animal name:", animal.name)
animal.sayName()
animal.eat()
console.log("Dog name:", dog.name)
console.log("Dog breed:", dog.breed)
dog.sayName() // 继承的方法
dog.eat() // 继承的方法
dog.bark() // 自己的方法
// 验证继承关系
console.log("dog instanceof Dog:", dog instanceof Dog)
console.log("dog instanceof Animal:", dog instanceof Animal)
console.log("\n=== ES6 Class继承演示 ===")
const modernAnimal = new ModernAnimal("Modern Animal")
const modernDog = new ModernDog("Max", "Labrador")
modernAnimal.sayName()
modernAnimal.eat()
modernDog.sayName() // 继承的方法
modernDog.eat() // 重写的方法
modernDog.bark() // 自己的方法
console.log("modernDog instanceof ModernDog:", modernDog instanceof ModernDog)
console.log("modernDog instanceof ModernAnimal:", modernDog instanceof ModernAnimal)
}
// 执行演示
demonstrateInheritance()
export { combinationInheritance, ModernAnimal, ModernDog }
20. 你有什么想问我的吗?
题目要点
关注工作内容、团队培养、技术环境、协作方式和发展前景等关键信息。
参考答案
希望了解几个方面的信息。首先想了解这个岗位的具体工作内容,主要会参与哪些类型的项目,使用什么技术栈?
其次是团队情况,前端团队的规模如何,新人会有怎样的培养计划和成长路径?
技术方面,公司对新技术的接受度如何,是否鼓励学习和尝试新的前端技术?
工作环境方面,团队的协作方式是怎样的,与后端、设计、产品等团队如何配合?
最后想了解一下公司的发展前景,以及对校招生的期望是什么?
作为应届生,希望能在一个有良好学习氛围和成长空间的环境中开始职业生涯,不断提升技术能力和解决问题的能力。
21. 你怎么看待 AI 与前端的未来呢?
题目要点
AI是强有力的辅助工具而非替代者,需要学会与AI协作,同时培养高层次的技术能力和思维。
参考答案
作为即将进入前端行业的新人,对AI与前端的结合充满期待和思考。目前已经看到AI在代码生成、设计稿转代码、自动化测试等方面的应用,这些工具确实能提高开发效率。比如GitHub Copilot可以帮助编写代码,一些AI工具可以根据设计稿生成基础的HTML和CSS代码。
认为AI不会完全替代前端开发者,而是会成为强有力的辅助工具。AI擅长处理重复性、规律性的工作,但在创意设计、复杂业务逻辑、用户体验优化等方面,还是需要人的思考和判断。未来的前端开发者可能需要更多地关注架构设计、用户体验、性能优化等高层次的工作。
作为新人,计划在掌握扎实基础技能的同时,也要学会与AI工具协作,提高工作效率。同时要培养批判性思维,能够评估和优化AI生成的代码。相信AI的发展会让前端开发变得更加高效和有趣,也会为行业带来新的机遇和挑战。