【秋招】字节前端面经合集

【秋招】字节前端面经合集
最新回答
明晨紫月

2022-08-31 01:45:38

一面问题解答
  • 自我介绍:简要阐述教育背景、与前端相关的项目经历、掌握的前端技能(如 HTML、CSS、JavaScript 框架等)以及个人优势和职业规划。
  • 项目亮点

    独特功能:介绍项目中具有创新性或独特性的功能,例如实现了复杂的交互效果、高效的数据处理等。

    性能优化:说明在项目中采取的性能优化措施,如代码压缩、图片优化、缓存策略等,以及这些优化带来的效果提升。

    用户体验改进:讲述如何通过设计和技术手段提升用户体验,如响应式设计、友好的界面交互等。

  • 实习期间学到了什么

    技术技能:列举在实习中学习到的新技术、新框架或新工具,如 Vue、React 的深入使用,Webpack 的配置等。

    项目流程:了解并参与了实际项目的开发流程,包括需求分析、设计、开发、测试和上线等环节,学会了如何与团队成员协作。

    问题解决能力:分享在实习中遇到的技术难题及解决方法,锻炼了自己独立思考和解决问题的能力。

  • 数据类型怎么判断是 Null:在 JavaScript 中,可以使用严格相等运算符 === 来判断一个值是否为 null,例如 if (value === null) {...}。因为 null 是一个特殊的值,表示空对象引用,只有与自身严格相等时才返回 true。
  • 怎么判断数组

    使用 Array.isArray() 方法:这是最直接和可靠的方法,例如 Array.isArray([1, 2, 3]) 返回 true,Array.isArray("abc") 返回 false。

    通过构造函数判断:obj.constructor === Array,但这种方法在对象构造函数被修改时不准确。

  • 浏览器为什么是单线程

    避免复杂性:如果浏览器是多线程的,不同线程对 DOM 的操作可能会导致竞态条件和渲染不一致的问题,增加开发的复杂性。

    简化编程模型:单线程模型使得 JavaScript 编程更加简单,开发者不需要考虑线程同步和锁的问题。

  • Web Worker:Web Worker 是 HTML5 提供的一种在后台运行脚本的机制,它允许 JavaScript 代码在独立于主线程的线程中运行,从而不会阻塞主线程的 UI 渲染和交互。通过 new Worker('worker.js') 创建 Web Worker,主线程和 Worker 之间通过 postMessage 和 onmessage 进行通信。
  • 布局有哪些

    普通文档流布局:元素按照 HTML 中的顺序依次排列,包括块级元素和行内元素的布局规则。

    浮动布局:通过 float 属性使元素脱离文档流,向左或向右浮动,常用于实现图文环绕效果。

    定位布局:使用 position 属性(static、relative、absolute、fixed、sticky)来精确控制元素的位置。

    Flex 布局:通过设置父元素为 display: flex,可以方便地实现子元素在水平或垂直方向上的灵活排列和对齐。

    Grid 布局:将页面划分为网格,通过定义行和列来精确控制元素的布局,适用于复杂的页面布局。

  • Vue2 和 3 区别

    响应式原理:Vue2 使用 Object.defineProperty 实现响应式,存在一些局限性,如无法监听数组索引变化和新增属性;Vue3 使用 Proxy 实现响应式,解决了这些问题,性能更好。

    组合式 API:Vue3 引入了组合式 API(Composition API),使得代码组织更加灵活,便于复用逻辑;Vue2 主要使用选项式 API(Options API)。

    性能优化:Vue3 对虚拟 DOM 进行了优化,采用了静态提升和补丁标志等技术,减少了不必要的渲染,提高了性能。

    Fragment:Vue3 支持 Fragment,即一个组件可以有多个根节点;Vue2 一个组件只能有一个根节点。

  • Diff 算法:Diff 算法是虚拟 DOM 对比的算法,用于找出新旧虚拟 DOM 之间的差异,然后只更新变化的部分,提高渲染性能。Vue 和 React 都实现了自己的 Diff 算法,它们都采用了同层比较的策略,通过对比节点的类型、属性和子节点等来确定是否需要更新。
  • Vue 的生命周期

    创建阶段:beforeCreate(实例初始化后,数据观测和事件配置之前)、created(实例创建完成,数据观测和事件配置已完成,但尚未挂载到 DOM)、beforeMount(在挂载开始之前被调用,相关的 render 函数首次被调用)、mounted(实例已挂载到 DOM 上)。

    更新阶段:beforeUpdate(数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前)、updated(由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后调用)。

    销毁阶段:beforeDestroy(实例销毁之前调用,此时实例仍然完全可用)、destroyed(Vue 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁)。

  • 浏览器关闭后,Vue 的生命周期:浏览器关闭后,Vue 实例会被销毁,触发 beforeDestroy 和 destroyed 生命周期钩子函数。此时,与该实例相关的所有数据、事件监听器和子实例都会被清除。
  • 解决跨域

    CORS(跨域资源共享):服务器设置响应头 Access-Control-Allow-Origin,允许特定域或所有域访问资源。

    JSONP:利用 <script> 标签不受同源策略限制的特点,通过动态创建 <script> 标签来获取数据,但只支持 GET 请求。

    代理服务器:在开发环境中,可以通过配置代理服务器(如 Webpack 的 devServer.proxy)将跨域请求转发到目标服务器;在生产环境中,可以使用 Nginx 等反向代理服务器实现跨域。

  • 手写:输出什么单行超出省略,多行超出省略
/* 单行超出省略 */.single-line-ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}/* 多行超出省略 */.multi-line-ellipsis { display: -webkit-box; -webkit-line-clamp: 3; /* 显示行数 */ -webkit-box-orient: vertical; overflow: hidden;}
  • 手写:深度优先遍历
function dfs(node, callback) { if (!node) return; callback(node); if (node.children && node.children.length > 0) { for (let i = 0; i < node.children.length; i++) { dfs(node.children[i], callback); } }}// 使用示例const tree = { value: 'A', children: [ { value: 'B', children: [ { value: 'D', children: [] }, { value: 'E', children: [] } ] }, { value: 'C', children: [ { value: 'F', children: [] } ] } ]};dfs(tree, (node) => console.log(node.value));二面问题解答
  • 自我介绍:同一面自我介绍内容,可适当补充在一面后的学习和项目进展。
  • 项目难点,拷打二十分钟

    技术难点:详细描述项目中遇到的技术难题,如复杂的交互逻辑、性能瓶颈、兼容性问题等,并说明解决方法和思路。

    团队协作难点:讲述在项目中与团队成员沟通协作时遇到的问题,如需求变更、任务分配不合理等,以及如何解决这些问题,保证项目顺利进行。

  • JS defer 和 async 区别

    defer:脚本会在文档解析完成后,DOMContentLoaded 事件触发之前按照它们在文档中的顺序执行。适用于需要依赖 DOM 且对执行顺序有要求的脚本。

    async:脚本会在下载完成后立即执行,不保证执行顺序,可能会在文档解析过程中执行。适用于独立运行的脚本,如统计脚本。

  • IOS 七层模型:IOS 七层模型通常指 OSI 七层模型在 iOS 网络开发中的应用,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。但在 iOS 实际开发中,主要关注的是传输层(TCP/UDP)和应用层(HTTP/HTTPS 等)。
  • HTML 缓存

    强缓存:通过设置 Expires 或 Cache-Control 响应头,浏览器在缓存有效期内直接使用本地缓存,不发送请求到服务器。

    协商缓存:通过设置 Last-Modified/If-Modified-Since 或 ETag/If-None-Match 响应头和请求头,浏览器先发送请求到服务器,服务器根据资源的更新情况决定是否使用缓存。

  • Cookie 有什么缺点

    安全性问题:Cookie 数据存储在客户端,容易被窃取和篡改,可能存在 XSS 和 CSRF 攻击风险。

    性能问题:每次请求都会携带 Cookie 数据,增加了网络传输量,影响页面加载速度。

    存储容量有限:不同浏览器对 Cookie 的存储容量和数量有限制。

  • TCP 和 UDP 的区别

    连接方式:TCP 是面向连接的协议,在通信前需要建立连接(三次握手),通信结束后需要释放连接(四次挥手);UDP 是无连接的协议,不需要建立连接,直接发送数据。

    可靠性:TCP 提供可靠的传输服务,通过确认机制、重传机制和流量控制等保证数据的可靠传输;UDP 不保证数据的可靠传输,可能会丢失、重复或乱序。

    传输效率:TCP 由于需要进行连接建立和释放、确认和重传等操作,传输效率相对较低;UDP 传输效率高,适用于对实时性要求较高的应用。

    应用场景:TCP 适用于对数据可靠性要求较高的应用,如文件传输、电子邮件等;UDP 适用于对实时性要求较高的应用,如视频直播、在线游戏等。

  • UDP 既然这么不可靠,那么他的应用场景有哪些

    视频直播:对实时性要求高,允许一定的数据丢失和延迟,UDP 可以快速传输视频数据。

    在线游戏:需要实时交互,UDP 的低延迟特性可以保证游戏的流畅性。

    DNS 查询:DNS 查询通常只需要发送一个简单的请求和接收一个响应,UDP 的无连接特性可以提高查询效率。

  • HTTPS 比 HTTP 好在哪里

    安全性:HTTPS 通过 SSL/TLS 协议对数据进行加密传输,防止数据在传输过程中被窃取和篡改,保护用户隐私和数据安全。

    信任机制:HTTPS 使用数字证书来验证服务器的身份,确保用户访问的是合法的网站,防止中间人攻击。

    SEO 优化:搜索引擎更倾向于收录 HTTPS 网站,提高网站的排名和曝光率。

  • Promise 用来干什么,解决了什么问题:Promise 是一种用于处理异步操作的对象,它代表一个异步操作的最终完成(或失败)及其结果值。解决了回调地狱(Callback Hell)问题,使异步代码的编写更加清晰、易读和易维护。通过链式调用 .then() 和 .catch() 方法,可以方便地处理异步操作的成功和失败情况。
  • 宏任务和微任务有哪些

    宏任务:包括 script(整体代码)、setTimeout、setInterval、setImmediate(Node.js)、I/O 操作、UI 渲染等。

    微任务:包括 Promise.then、Promise.catch、Promise.finally、MutationObserver 等。

  • 手写:事件循环
// 模拟宏任务队列和微任务队列const macroTaskQueue = [];const microTaskQueue = [];function eventLoop() { // 执行宏任务队列中的任务 while (macroTaskQueue.length > 0) { const macroTask = macroTaskQueue.shift(); macroTask(); // 每次宏任务执行完后,检查并执行微任务队列中的任务 while (microTaskQueue.length > 0) { const microTask = microTaskQueue.shift(); microTask(); } }}// 添加宏任务function addMacroTask(task) { macroTaskQueue.push(task);}// 添加微任务function addMicroTask(task) { microTaskQueue.push(task);}// 示例addMacroTask(() => { console.log('MacroTask 1'); addMicroTask(() => console.log('MicroTask 1'));});addMacroTask(() => console.log('MacroTask 2'));eventLoop();
  • 手写:promise.all
function promiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let completedCount = 0; if (promises.length === 0) { resolve(results); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then((value) => { results[index] = value; completedCount++; if (completedCount === promises.length) { resolve(results); } }) .catch((error) => { reject(error); }); }); });}// 使用示例const promise1 = Promise.resolve(1);const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 100));const promise3 = Promise.reject('Error');promiseAll([promise1, promise2, promise3]) .then((results) => console.log(results)) .catch((error) => console.log(error));
  • 手写:输出两个日期之间的所有日期
function getDatesBetween(startDateStr, endDateStr) { const startDate = new Date(startDateStr); const endDate = new Date(endDateStr); const dates = []; while (startDate <= endDate) { dates.push(new Date(startDate).toISOString().split('T')[0]); startDate.setDate(startDate.getDate() + 1); } return dates;}// 使用示例const start = '2023-01-01';const end = '2023-01-05';console.log(getDatesBetween(start, end));三面问题解答
  • 前面的面试体验怎么样:客观评价前面面试的流程、面试官的专业程度和问题难度等,表达积极的感受和对公司的期待。
  • 实习之后的提升

    技术能力:列举在实习中学习到的新技术、新框架或新工具,以及在实际项目中的应用经验。

    问题解决能力:分享在实习中遇到的技术难题及解决方法,锻炼了自己独立思考和解决问题的能力。

    团队协作能力:讲述在实习中与团队成员沟通协作的经验,学会了如何有效地表达自己的想法和倾听他人的意见。

  • Websocket 断开后怎么重连:可以在 Websocket 的 onclose 事件回调函数中实现重连逻辑,使用 setTimeout 设置一定的延迟后重新创建 Websocket 连接。
let socket;function connectWebSocket() { socket = new WebSocket('ws://example.com'); socket.onopen = () => { console.log('WebSocket connected'); }; socket.onclose = () => { console.log('WebSocket disconnected'); setTimeout(() => { console.log('Reconnecting...'); connectWebSocket(); }, 5000); }; socket.onerror = (error) => { console.error('WebSocket error:', error); };}connectWebSocket();
  • Websocket 是否有跨域:Websocket 也存在跨域问题,但与 HTTP 跨域的解决方案不同。Websocket 跨域需要在服务器端进行配置,设置响应头 Access-Control-Allow-Origin 来允许特定域或所有域连接。
  • 大文件的断点续传怎么实现,数据传输一半断掉了怎么处理

    前端:使用 Blob.slice() 方法将大文件分割成多个小块,通过 Websocket 或 HTTP 请求逐个上传小块。记录已上传的小块索引,在断点续传时从断点处继续上传。

    后端:接收前端上传的文件小块,存储在服务器上,并记录已接收的小块信息。当所有小块上传完成后,将小块合并成完整的文件。如果数据传输一半断掉了,前端可以根据已上传的小块索引重新上传未上传的小块。

  • 前端怎么进行分片处理:使用 Blob.slice() 方法将大文件分割成多个小块,例如:
function sliceFile(file, chunkSize) { const chunks = []; let start = 0; while (start < file.size) { const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); chunks.push(chunk); start = end; } return chunks;}// 使用示例const fileInput = document.getElementById('fileInput');fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; const chunkSize = 1024 * 1024; // 1MB const chunks = sliceFile(file, chunkSize); console.log(chunks);});
  • 按需加载怎么实现原理

    动态导入:使用 ES6 的动态导入语法 import(),在需要加载模块时才进行导入,实现按需加载。

    路由懒加载:在 Vue 或 React 等框架中,使用路由懒加载技术,将不同路由对应的组件在访问该路由时才进行加载,减少初始加载时间。

  • ES6 和 CommonJS 模块化区别

    语法:ES6 使用 import 和 export 语法进行模块导入和导出;CommonJS 使用 require 和 module.exports 语法。

    加载机制:ES6 模块是静态加载的,在编译时就能确定模块的依赖关系;CommonJS 模块是动态加载的,在运行时才能确定模块的依赖关系。

    输出值:ES6 模块输出的是值的引用,模块内部的变化会影响引用的值;CommonJS 模块输出的是值的拷贝,模块内部的变化不会影响拷贝的值。

  • 路由 hash 和 history 区别

    URL 格式:hash 模式的 URL 中带有 # 符号,例如

    http://example.com/#/home
    ;history 模式的 URL 与普通 URL 相同,例如
    http://example.com/home

    工作原理:hash 模式通过监听 hashchange 事件来实现路由变化;history 模式通过 history.pushState() 和 history.replaceState() 方法来操作浏览器历史记录,并监听 popstate 事件来实现路由变化。

    兼容性:hash 模式兼容性更好,可以在所有浏览器中使用;history 模式需要服务器进行配置,否则在刷新页面时可能会出现 404 错误。

  • 怎么监听路由的变化

    Vue Router:可以通过 watch 监听 $route 对象的变化,或者在组件中使用 beforeRouteUpdate 导航守卫。

// 使用 watch 监听export default { watch: { '$route'(to, from) { console.log('Route changed from', from.path, 'to', to.path); } }};// 使用 beforeRouteUpdate 导航守卫export default { beforeRouteUpdate(to, from, next) { console.log('Route changed from', from.path, 'to', to.path); next(); }};- React Router:可以使用 `useLocation` 钩子获取当前路由信息,并通过 `useEffect` 监听路由变化。import { useLocation, useEffect } from 'react-router-dom';function MyComponent() { const location = useLocation(); useEffect(() => { console.log('Route changed:', location.pathname); }, [location]); return <div>My Component</div>;}
  • 服务端怎么配置 history:以 Nginx 为例,配置如下:
server { listen 80; server_name example.com; location / { root /path/to/your/project; try_files $uri $uri/ /index.html; }}
  • addEventListener 第三个值了解吗:addEventListener 的第三个参数是一个布尔值或一个选项对象。如果是布尔值,true 表示在捕获阶段触发事件,false 表示在冒泡阶段触发事件;如果是选项对象,可以设置 capture(是否在捕获阶段触发)、once(是否只触发一次)、passive(是否不调用 preventDefault() 方法)等属性。
  • 用 image 和 background-size 做背景,调整大小时怎么修改样式
/* 使用 image 标签作为背景(不推荐,通常使用 div 包裹 image) */.image-background { position: relative; width: 500px; height: 300px;}.image-background img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; /* 控制图片的填充方式 */}/* 使用 background-size 做背景 */.bg-background { width: 500px; height: 300px; background-image: url('your-image.jpg'); background-size: cover; /* 控制背景图片的填充方式,可选值有 cover、contain、100% 100% 等 */ background-position: center; /* 控制背景图片的位置 */ background-repeat: no-repeat; /* 防止背景图片重复 */}
  • object-fit:object-fit 属性用于控制替换元素(如 <img>、<video>)内容的填充方式,可选值有:

    fill:拉伸填充整个容器,可能会改变元素的宽高比。

    contain:保持元素的宽高比,将元素完整地显示在容器内,可能会有留白。

    cover:保持元素的宽高比,覆盖整个容器,可能会裁剪元素的部分内容。

    none:保持元素的原始尺寸,不进行缩放。

    scale-down:比较 none 和 contain 的效果,选择较小的尺寸。

  • ES5 实现继承的方式

    原型链继承:通过将子类的原型指向父类的实例来实现继承。

function Parent() { this.name = 'Parent';}Parent.prototype.sayHello = function() { console.log('Hello, ' + this.name);};function Child() { this.name = 'Child';}Child.prototype = new Parent();const child = new Child();child.sayHello();- 构造函数继承:在子类构造函数中调用父类构造函数,实现属性继承。function Parent() { this.name = 'Parent';}Parent.prototype.sayHello = function() { console.log('Hello, ' + this.name);};function Child() { Parent.call(this); this.name = 'Child';}const child = new Child();child.sayHello();- 组合继承:结合原型链继承和构造函数继承的优点,既实现了属性继承,又实现了方法继承。function Parent() { this.name = 'Parent';}Parent.prototype.sayHello = function() { console.log('Hello, ' + this.name);};function Child() { Parent.call(this); this.name = 'Child';}Child.prototype = new Parent();Child.prototype.constructor = Child;const child = new Child();child.sayHello();
  • ES5 实现一个类,里面的 id 递增
function MyClass() { this.id = (function() { let id = 0; return function() { return ++id; }; })();}const obj1 = new MyClass();console.log(obj1.id()); // 1const obj2 = new MyClass();console.log(obj2.id()); // 1// 上述方式每个实例的 id 独立递增,若要全局递增可修改为:let globalId = 0;function MyClassGlobal() { this.id = function() { return ++globalId; };}const obj3 = new MyClassGlobal();console.log(obj3.id()); // 1const obj4 = new MyClassGlobal();console.log(obj4.id()); // 2
  • 寻找公共父节点
function findCommonParent(node1, node2) { const parents1 = []; const parents2 = []; while (node1) { parents1.unshift(node1); node1 = node1.parentNode; } while (node2) { parents2.unshift(node2); node2 = node2.parentNode; } let commonParent = null; for (let i = 0; i < Math.min(parents1.length, parents2.length); i++) { if (parents1[i] === parents2[i]) { commonParent = parents1[i]; } else { break; } } return commonParent;}// 使用示例const node1 = document.getElementById('node1');const node2 = document.getElementById('node2');console.log(findCommonParent(node1, node2));
  • 前端安全性问题怎么预防 XSS 攻击

    输入过滤:对用户输入的内容进行过滤,过滤掉