2023-03-07面试题
1.为什么需要清除浮动,写出清除浮动的方式
浮动元素在文档流中脱离了正常的位置,因此会导致父元素高度塌陷,影响布局的正确性和美观性。所以通常需要清除浮动。
以下是常见的清除浮动的方式:
父元素设置 overflow: auto 或 overflow: hidden 属性:
1 | .clearfix { |
使用空标签清除浮动:
1 | <div class="parent"> |
1 | .clear { |
使用伪元素清除浮动:
1 | .clearfix::after { |
在父元素上添加一个伪元素,设置 clear: both 属性,这样就可以清除浮动。
需要注意的是,以上清除浮动的方式可能会对其他布局产生影响,例如 overflow: hidden 可能会导致内容被裁剪,空标签和伪元素可能会产生多余的 DOM 元素。因此需要根据实际情况选择合适的方式。
2.对原型、原型链的理解
在 JavaScript 中,每个对象都有一个指向另一个对象的引用,这个被引用的对象就是该对象的原型(prototype)。对象通过原型继承属性和方法,这样可以实现代码的复用和减少代码量。
具体来说,原型是一个普通的对象,它拥有一些属性和方法。在创建一个新的对象时,JavaScript 会自动将该对象的原型指向构造函数的原型,形成原型链。
当访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 会沿着原型链一级一级地查找,直到找到该属性或方法为止。如果一直查找到最顶层的 Object.prototype 对象,仍然没有找到该属性或方法,那么返回 undefined。
对原型链的理解,可以理解为一个对象与其他对象之间形成的链式关系。每个对象的原型都指向它的父对象的原型,最终形成一个链条。在访问对象的属性或方法时,如果该对象自身没有该属性或方法,就会一级一级地沿着这个链条向上查找,直到找到该属性或方法或者查找到最顶层的 Object.prototype 对象。
总的来说,原型和原型链是 JavaScript 中的一个核心概念,它们是实现继承和代码复用的重要手段,对于深入理解 JavaScript 的面向对象编程非常重要。
3. for…in 和 for…of 的区别
for…in 和 for…of 都是 JavaScript 中用来遍历对象和数组的循环语句,但它们的遍历方式有所不同。
for…in 循环是用来遍历对象的属性名的,它会遍历对象及其原型链上的所有可枚举属性,包括字符串类型的属性名和 Symbol 类型的属性名,但不包括数组的索引值。
for…of 循环则是用来遍历可迭代对象的元素的,包括数组、字符串、Map、Set、Generator 等,它遍历的是对象的属性值,而不是属性名。对于普通对象来说,它并不是可迭代对象,因此不能使用 for…of 进行遍历。
具体来说,for…in 循环的语法结构如下:
1 | for (variable in object) { |
其中,variable 是一个变量名,用来代表每个属性名的变量;object 是要遍历的对象。
而 for…of 循环的语法结构如下:
1 | for (variable of iterable) { |
其中,variable 是一个变量名,用来代表每个元素的变量;iterable 是要遍历的可迭代对象。
总的来说,for…in 循环主要用来遍历对象属性名,for…of 循环主要用来遍历数组、字符串等可迭代对象的元素。在使用这两种循环语句时,需要根据具体的需求来选择合适的遍历方式。
4.如何实现深拷贝?
递归实现
递归是实现深拷贝最常用的方法。具体实现方法是,遍历需要拷贝的对象的每一个属性,如果属性是一个对象,则递归调用深拷贝方法,直到拷贝完所有的属性。
1 | function deepClone(obj) { |
JSON.parse()和 JSON.stringify()实现
这种方法的原理是,将对象先转为 JSON 字符串,再将 JSON 字符串转为新的对象。这种方法的缺点是,如果对象中有函数或 undefined 等属性,转换后就会丢失这些信息。
1 | function deepClone(obj) { |
Object.assign()实现
这种方法的原理是,使用 Object.assign() 方法将原对象的属性复制到一个新的对象中,达到深拷贝的效果。需要注意的是,Object.assign() 方法只能拷贝对象的可枚举属性,无法拷贝不可枚举属性和原型链上的属性。
1 | function deepClone(obj) { |
需要根据具体的情况选择合适的实现方法来实现深拷贝。在使用递归方法实现深拷贝时,需要注意循环引用的问题,否则可能会导致死循环。
5.数组的遍历方法有哪些
for 循环
使用 for 循环可以遍历数组的每一个元素。
1 | const arr = [1, 2, 3]; |
forEach 方法
forEach 方法是数组原型上的一个方法,可以遍历数组的每一个元素并执行指定的操作,无法中途终止循环。
1 | const arr = [1, 2, 3]; |
map 方法
map 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并对其进行操作,返回一个新的数组。可以用来对数组进行转换或映射。
1 | const arr = [1, 2, 3]; |
filter 方法
filter 方法是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定条件过滤出符合条件的元素,返回一个新的数组。
1 | const arr = [1, 2, 3]; |
reduce 方法
reduce 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定规则累加元素,返回一个累加结果。
1 | const arr = [1, 2, 3]; |
for…in 循环
for…in 循环可以遍历数组的索引和属性名,但不推荐使用,因为可能会遍历到数组的原型链上的属性。
1 | const arr = [1, 2, 3]; |
这些遍历数组的方法各有特点,需要根据具体情况选择合适的方法来遍历数组。
6.对 this 对象的理解
在 JavaScript 中,this 是一个关键字,用来表示当前对象的引用。
this 的指向会根据函数的调用方式和上下文的不同而发生变化。当一个函数被调用时,this 的指向会动态地绑定到不同的对象上。
具体来说,this 的指向有以下几种情况:
默认绑定
如果函数独立调用,即没有被绑定到任何对象上,那么 this 的指向就是全局对象 window(在浏览器中),或者是 global 对象(在 Node.js 环境中)。
1 | function test() { |
隐式绑定
如果函数作为对象的方法被调用,那么 this 的指向就是该对象。
1 | const obj = { |
显式绑定
如果使用 call 或 apply 方法调用函数,或者使用 bind 方法创建一个新的函数时,可以显式地指定 this 的值。
1 | function test() { |
new 绑定
如果使用 new 关键字调用构造函数,this 的指向就是新创建的对象。
1 | function Person(name) { |
需要注意的是,箭头函数中的 this 的指向是在定义函数时确定的,而不是在运行时确定的。箭头函数中的 this 指向是外层作用域的 this,无法通过 call、apply、bind 方法来改变其指向。
7.call()和 apply()的区别
call() 和 apply() 是 JavaScript 中的函数方法,它们都可以用来显式地设置函数的执行上下文(即函数中的 this 指向)。
它们的主要区别在于传参的方式不同:
- call() 方法的参数是一个一个列举出来的,比如 function.call(thisArg, arg1, arg2, arg3, …),它们会依次传入函数中。
- apply() 方法的第二个参数是一个数组,比如 function.apply(thisArg, [arg1, arg2, arg3, …]),数组中的元素会作为参数传入函数中。
1 | function greeting(name, age) { |
1 | function greeting(name, age) { |
除了传参方式不同,call() 和 apply() 的作用是相同的,都是用来明确设置函数的执行上下文。在一些特定的场景下,它们可以被用来实现一些非常有用的功能,比如实现继承、修改 this 指向等。
8.双向数据绑定的原理
双向数据绑定是指当数据模型中的数据发生改变时,视图会自动更新;当用户操作视图时,数据模型也会自动更新。在 Vue 中,双向数据绑定是通过数据劫持和发布订阅模式来实现的。
具体来说,Vue 中双向数据绑定的原理如下:
- Vue 在初始化时会对数据对象进行递归遍历,使用 Object.defineProperty() 方法将数据对象的属性转换为“响应式对象”,并在内部为每个“响应式对象”创建一个依赖收集器 Dep,用于收集和管理依赖于该“响应式对象”的视图组件。
- 当视图组件访问到“响应式对象”的某个属性时,会触发 getter 函数,这个函数会将当前视图组件作为依赖,添加到该属性对应的依赖收集器 Dep 中。
- 当“响应式对象”中的属性发生变化时,会触发 setter 函数,这个函数会将新值赋给该属性,并通知该属性对应的依赖收集器 Dep 中的所有视图组件进行更新操作。
- 在 Vue 中,实现双向数据绑定还需要结合指令 v-model,v-model 会监听表单元素的 input 事件,并将表单元素的值赋给数据模型中指定的属性,这个过程也是通过数据劫持实现的。
- 当数据模型中指定的属性发生变化时,依赖于该属性的所有视图组件都会进行更新操作,并将新的属性值赋给表单元素,从而实现双向数据绑定。
总的来说,双向数据绑定的原理就是通过数据劫持和依赖收集来实现的,当数据模型中的数据发生变化时,会通知依赖于该数据的所有视图组件进行更新操作,而视图组件中的用户操作也会通过数据劫持同步到数据模型中,从而实现数据的双向绑定。
9.对虚拟 DOM 的理解?
虚拟 DOM(Virtual DOM)是一个程序概念,用于描述一个虚拟的、内存中的 DOM 对象,它是由 JavaScript 对象构成的,它将真实的 DOM 树映射到虚拟的 DOM 树上,并通过比较前后两个虚拟 DOM 树的差异,最终只更新真实 DOM 树中需要变化的部分,从而提高性能和效率。
在 Vue 和 React 等现代 Web 框架中,当应用的状态发生变化时,会自动更新虚拟 DOM,然后将新的虚拟 DOM 和旧的虚拟 DOM 进行对比,找出需要更新的部分,并只更新这些部分,从而减少了对实际 DOM 的操作,提高了应用的性能和效率。
虚拟 DOM 的优点包括:
- 提高性能:虚拟 DOM 可以减少对实际 DOM 的操作,从而提高性能。
- 简化开发:通过使用虚拟 DOM,可以更方便地组织和管理应用的状态,使得应用开发更加简单和高效。
- 支持跨平台:虚拟 DOM 可以运行在不同的平台和环境中,从而使得应用可以更容易地适应不同的场景和需求。
虚拟 DOM 的缺点包括:
- 存在一定的学习成本:需要开发者熟悉虚拟 DOM 的相关概念和使用方法。
- 代码体积较大:使用虚拟 DOM 需要引入额外的代码库,从而增加了应用的代码体积。
- 可能会引起一些问题:虚拟 DOM 可能会引起一些问题,比如可能会导致性能问题,或者可能会引起一些不必要的复杂度。
10.如何解决跨越问题
跨域问题指的是在浏览器上,当请求的资源与当前页面所在的域名、端口、协议不一致时,就会发生跨域。由于浏览器的同源策略,跨域请求会被浏览器拦截,导致请求失败。
解决跨域问题的方法有多种,下面列举几种常用的方法:
- JSONP(JSON with Padding):JSONP 是一种通过动态创建\标签的方式实现跨域的技术。通过指定回调函数的名称,将要请求的数据作为参数传递给回调函数,服务器返回一段 JS 代码,浏览器执行这段 JS 代码,从而实现跨域。
- CORS(Cross-Origin Resource Sharing):CORS 是一种由 W3C 标准化的跨域解决方案,它需要服务器设置 Access-Control-Allow-Origin 等响应头来允许跨域请求。在浏览器端,如果发现请求的资源与当前页面所在的域名、端口、协议不一致,会自动发起一次预检请求 OPTIONS,询问服务器是否允许该请求。
- 代理:在同域下通过代理服务器来实现跨域请求。即在服务器端设置代理服务器,在代理服务器上进行跨域请求,并将请求结果返回给前端。
- WebSocket:通过 WebSocket 协议实现跨域通信。WebSocket 是一种全双工通信协议,与 HTTP 协议不同的是,它是建立在 TCP 连接上的协议,可以实现客户端和服务器之间的长连接。
- postMessage:通过 HTML5 提供的 postMessage 方法实现跨域通信。该方法可以实现跨窗口、跨域通信,可以在不同的窗口、甚至不同的域之间传递数据。
11.常见的状态码
常见的状态码包括:
- 200 OK:请求成功。
- 201 Created:已成功创建资源。
- 204 No Content:请求已成功处理,但响应报文不含实体的主体部分。
- 301 Moved Permanently:永久性重定向。
- 302 Found:临时性重定向。
- 304 Not Modified:服务端资源未改变,可使用客户端缓存。
- 400 Bad Request:请求报文存在语法错误。
- 401 Unauthorized:未经授权,需要身份认证。
- 403 Forbidden:服务器拒绝该请求。
- 404 Not Found:请求的资源不存在。
- 500 Internal Server Error:服务器内部错误,无法完成请求。
- 503 Service Unavailable:服务器当前无法处理请求,一段时间后可能恢复正常。
每种状态码代表了不同的含义,客户端可根据不同的状态码进行不同的处理。
12.对节流与防抖的理解
节流和防抖都是为了解决频繁触发某些事件而产生的性能问题。
防抖:在事件触发 n 秒后再执行回调,如果在这 n 秒内又触发了该事件,则重新计时,直到 n 秒后执行回调。常用于输入框输入验证、搜索框查询等场景。
节流:每隔一段时间执行一次事件回调,即在固定的时间间隔内只执行一次。常用于页面滚动、窗口大小改变等频繁触发的事件。
比如在监听页面滚动时,若每次滚动都触发事件回调函数,会造成性能问题,而使用节流技术可以在滚动过程中一定时间内只执行一次事件回调,有效地降低了频繁触发事件的性能问题。
在实现防抖和节流时,可以通过设置定时器来实现。在防抖中,每次触发事件时都先清除之前的定时器,再重新设置定时器;而在节流中,每次触发事件时若定时器已存在,则不再重新设置,直到定时器回调后再清除定时器。
以下是节流和防抖的代码实现:
节流(throttle)的实现:
1 | function throttle(fn, delay) { |
防抖(debounce)的实现:
1 | function debounce(fn, delay) { |
其中,fn 是要执行的函数,delay 是时间间隔,单位是毫秒。节流和防抖都可以使用在事件频繁触发的场景中,比如滚动、输入框输入等。节流是限制事件的执行频率,而防抖是限制事件的触发频率,具体要选择哪一种方式,要根据具体情况而定。
13.Vue 项目优化思路
Vue 项目优化是一个比较复杂的话题,以下是一些常见的优化思路:
- 减少 HTTP 请求:合并 CSS 和 JS 文件,使用雪碧图等方式减少图片请求等。
- 路由懒加载:将页面的不同部分拆分成不同的代码块,只有在需要时才加载,以减少初始加载时间。
- 使用 CDN 加速:将静态资源存放在 CDN 上,加速资源访问。
- 使用异步组件:将组件按需加载,减少首屏加载时间。
- 使用 Keep-alive 缓存组件:使用缓存机制,避免重复渲染和请求数据。
- 使用 Element 等 UI 框架:使用现成的 UI 框架,避免自己编写大量 CSS 代码。
- 图片压缩:使用压缩工具对图片进行压缩,减小图片大小。
- 代码压缩:使用工具对 JS 和 CSS 代码进行压缩,减小文件大小。
- 懒加载图片:将图片延迟加载,当用户需要查看图片时再进行加载,减小初始加载时间。
- 使用 Webpack 优化:使用 Webpack 的优化功能,如使用 webpack-bundle-analyzer 来分析包大小、使用 tree shaking 减少无用代码等。
以上是一些常见的 Vue 项目优化思路,具体优化方式要根据具体情况而定。
14.性能优化的方式有哪些
以下是一些常见的性能优化方式:
- 减少 HTTP 请求:将多个文件合并为一个文件,压缩文件,使用 CDN 等方式来减少请求次数,从而提高页面加载速度。
- 图片优化:使用图片压缩工具,减少图片文件大小,使用图片懒加载,按需加载图片,优化图片格式等方式来提高页面加载速度。
- 减少 DOM 操作:DOM 操作是非常消耗性能的操作,应该尽量避免。可以使用批量操作 DOM 的方式,避免频繁操作 DOM。
- 使用缓存:使用浏览器缓存、服务器缓存等方式,避免重复请求数据,从而提高页面加载速度。
- 代码优化:避免使用全局变量,减少闭包嵌套,使用事件委托,避免不必要的计算等方式,提高 JavaScript 的执行效率。
- 使用 Webpack 等构建工具进行优化:使用 Webpack 可以对代码进行压缩、混淆、按需加载等处理,从而提高页面的加载速度。
- 服务器优化:对服务器进行优化,包括优化服务器硬件、增加带宽、使用缓存、使用负载均衡等方式,提高页面响应速度。
- 数据库优化:对数据库进行优化,包括优化数据库表结构、使用索引、避免频繁连接数据库等方式,提高数据读取速度。
以上是一些常见的性能优化方式,具体要根据实际情况进行选择和实现。
15.谈一下你对前端开发工程师这个职位的理解
作为一个前端开发工程师,其主要职责是负责网站或应用的前端设计和开发,实现用户界面的交互和效果,提升用户体验。具体的职责包括但不限于:
- 实现网站或应用的前端设计和开发,编写 HTML、CSS 和 JavaScript 等前端代码;
- 与 UI 设计师和产品经理协作,制定网站或应用的交互和效果方案;
- 与后端开发人员协作,完成前后端的数据交互和接口对接;
- 负责网站或应用的性能优化和调试工作,保证用户体验;
- 熟悉前端开发的最新技术和工具,保持学习和更新。
除此之外,前端开发工程师还需要具备良好的沟通能力和团队协作能力,能够与其他团队成员有效地沟通和协作,推动项目的进展和完成。同时,前端开发工程师还需要具备较强的问题解决能力和创新意识,能够独立思考和解决问题,提出创新性的解决方案,为产品提供更好的用户体验。