文章目录
📈「作者简介」:不知名十八线技术博主
📚「推荐主页」:阿珊和她的猫
🕐「简历必备」前后端实战项目(推荐:⭐️⭐️⭐️⭐️⭐️)
前端面试可能会出现一些具有挑战性的问题,以评估面试者的深度和技能水平。以下是一些可能的 “十大刁钻问题” 的示例:
1. 闭包
请解释什么是闭包并提供一个实际的例子。
闭包是指内部函数可以访问外部函数作用域中变量或函数的特性。
在 JavaScript 中,当一个内部函数被定义在外部函数内部,并且引用了外部函数的变量时,就创建了一个闭包。
具体来说,闭包由两部分组成:
- 内部函数以及它所在的词法环境(包含了内部函数定义时的作用域链)。
内部函数可以访问外部函数的变量,即使外部函数已经执行完毕,该变量的引用仍然保留在内部函数的词法环境中。
以下是一个具体的闭包示例:
function outerFunction() {
var outerVariable = 'I am from outer';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
var closure = outerFunction();
closure(); // 输出: "I am from outer"
在上面的例子中,outerFunction
内部定义了一个 innerFunction
,并从内部函数中访问了 outerVariable
,尽管 outerFunction
已经执行完毕。当我们调用 outerFunction
并将其返回值赋给 closure
变量时,closure
实际上获得了对 innerFunction
的引用,而 innerFunction
依然可以访问 outerVariable
,因此在调用 closure
时,它能正确地打印出 “I am from outer”。
这就是闭包的概念:内部函数 innerFunction
通过保留对外部函数 outerFunction
的词法环境的引用,使得外部函数的变量在内部函数中仍然可访问。闭包在 JavaScript 中具有广泛的应用,可以用于实现封装、模块化和私有成员等功能。
2. this 指针
请解释 JavaScript 中的 this 关键字,并指出它在不同情况下的值。
JavaScript 中的关键字 this
是一个特殊的对象引用,它指向当前执行代码的上下文对象。具体 this
的值取决于函数的调用方式和上下文环境。
在 JavaScript 中,this
的值在以下情况下可能会有不同的取值:
1. 全局环境
在全局作用域中,函数没有被绑定到任何对象上时,this
指向全局对象(在浏览器环境中是 window
,在 Node.js 中是 global
)。
2. 函数调用
当函数作为独立函数调用时,this
指向全局对象。(在严格模式下,它将是 undefined
)
function myFunction() {
console.log(this);
}
myFunction(); // 浏览器环境下输出: Window,Node.js 环境下输出: global
3. 方法调用
当函数作为对象的方法调用时,this
指向调用该方法的对象。
var obj = {
name: 'ChatAI',
greet: function() {
console.log('Hello, ' + this.name);
}
};
obj.greet(); // 输出: "Hello, ChatAI"
4. 构造函数
当函数作为构造函数使用时,this
指向正在创建的实例对象。
function Person(name) {
this.name = name;
}
var person = new Person('Alice');
console.log(person.name); // 输出: "Alice"
5. call
和 apply
方法
通过 call
或 apply
方法,我们可以显式地设置函数的上下文对象,并将 this
的值绑定到指定对象。
function sayHello() {
console.log('Hello, ' + this.name);
}
var person1 = { name: 'Alice' };
var person2 = { name: 'Bob' };
sayHello.call(person1); // 输出: "Hello, Alice"
sayHello.apply(person2); // 输出: "Hello, Bob"
需要根据代码上下文和函数调用方式来确定 this
的值。确切理解 this
关键字的规则是理解 JavaScript 中面向对象编程和函数调用的重要一步。
3. 事件循环
请解释事件循环的工作原理,并说明微任务与宏任务之间的区别。
事件循环是 JavaScript 中处理异步执行的机制,它负责协调和调度不同类型的任务以实现非阻塞的并发执行。它的工作原理可以简单描述如下:
-
执行同步代码:首先,JavaScript 引擎会执行当前代码块中的同步代码,这是按照顺序逐行执行的。
-
处理任务队列:当遇到异步操作(如定时器、网络请求、事件监听等)时,不会立即执行相应的回调函数,而是将这些任务放入任务队列中等待执行。
-
事件循环轮次:在每个事件循环轮次中,JavaScript 引擎会从任务队列中取出一个任务并执行它。这个过程是循环进行的,直到任务队列为空。
事件循环中的任务可以分为两种类型:微任务和宏任务。
-
微任务(microtask)
:微任务是具有高优先级的任务,在每个事件循环轮次中都会被优先执行。常见的微任务包括Promise
的then
和catch
回调、MutationObserver
的回调以及queueMicrotask
函数等。微任务会在当前任务结束后立即执行,因此它们通常用于执行需要尽早处理的任务。 -
宏任务(macrotask)
:宏任务是具有较低优先级的任务,包括setTimeout
、setInterval
、I/O
操作、UI 渲染等。宏任务的执行需要等待当前任务执行完毕,并且会在下一个事件循环轮次中执行。
在每一个事件循环轮次中,首先会执行所有微任务,直到微任务队列为空,然后才会继续执行宏任务。这保证了微任务的及时执行,并且允许用户界面的更新在下一个渲染帧之前完成。
下面是一个简单的示例来展示事件循环的工作原理:
console.log('Synchronous 1');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('Synchronous 2');
执行上述代码,会按照以下顺序输出:
Synchronous 1
Synchronous 2
Promise
Timeout
可以看到,同步代码会立即执行,而微任务(Promise
)会在同步代码执行完毕后被添加并执行,宏任务(setTimeout
)会在下一个事件循环轮次中执行。
理解事件循环和微任务与宏任务之间的区别对于编写高效的异步 JavaScript 代码非常重要。它帮助我们更好地处理异步操作,确保顺序和优先级的正确性。
4. 跨域资源共享 (CORS)
请解释什么是 CORS,以及如何解决跨域请求。
跨域资源共享(Cross-Origin Resource Sharing,CORS)是一种机制,用于在浏览器中通过跨域请求获取跨源资源(如不同域名、端口或协议的资源)
。
浏览器的同源策略限制了在脚本中从不同来源加载或传递数据的能力,而CORS通过在服务器端设置特定的HTTP响应头来解决这个问题。
CORS的工作原理如下:
-
浏览器发起跨域请求:当浏览器中的脚本尝试通过AJAX请求或其他方式发送跨域请求时,浏览器会发送一个预检请求(
OPTIONS
请求)给目标服务器,以确定是否允许该跨域请求。 -
服务器响应预检请求:目标服务器收到预检请求后,会根据请求中的
Origin
头和其他指定的信息进行验证。 -
响应处理:如果服务器允许该跨域请求,它会在响应的HTTP头中设置一些特定的CORS头(例如
Access-Control-Allow-Origin
),来指示浏览器当前域名是被允许访问该资源的。 -
授权访问:一旦浏览器收到服务器的响应,且CORS头中允许当前域访问资源,浏览器就会处理响应并将资源传递给脚本进行处理。
解决跨域请求还可以使用其他方法,例如代理服务器、JSONP或WebSocket
。但CORS是一种更通用且规范化的解决方案,因为它能与现代浏览器兼容并提供更安全和灵活的控制。
要在服务器上启用CORS,需要在响应中设置合适的HTTP头。以下是一些常用的CORS头:
-
Access-Control-Allow-Origin
:指定允许访问该资源的域名。可以设置为*
表示允许所有域名访问,但这很少用于涉及用户凭据的请求。 -
Access-Control-Allow-Methods
:指定允许的HTTP方法(例如GET, POST, PUT, DELETE)。 -
Access-Control-Allow-Headers
:指定允许的自定义头,用于在请求中发送附加信息。 -
Access-Control-Allow-Credentials
:指定是否允许请求发送凭据(如Cookie、HTTP认证)。
以下是一个使用Node.js和Express设置CORS头的示例:
const express = require('express');
const app = express();
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', 'http://example.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
});
// 处理请求的路由和逻辑...
在上述示例中,Access-Control-Allow-Origin
设置为http://example.com
表示只允许该域名访问资源,Access-Control-Allow-Methods
设置了允许的HTTP方法,Access-Control-Allow-Headers
设置了允许的自定义头,Access-Control-Allow-Credentials
设置为true
表示允许发送凭据。
通过设置CORS头,服务器可以明确告知浏览器哪些域被允许访问资源,以及允许的请求方法和头。这样浏览器就可以在遵守安全限制的同时,与不同域上的资源进行交互。
5. 事件委托
请解释什么是事件委托,并提供一个使用事件委托的示例。
事件委托(Event delegation)是一种在 JavaScript 中处理事件的技术,它利用事件冒泡的特性,将事件处理程序绑定到它们共同的父元素上,而不是每个子元素上。
通过事件委托,我们可以减少事件处理程序的数量,提高性能,并且在动态添加或移除元素时,无需重新绑定事件处理程序。
下面是一个使用事件委托的示例:
假设我们有一个带有多个按钮的列表,我们想要监听每个按钮的点击事件。使用事件委托,我们可以将事件处理程序绑定到整个列表的父元素,并根据目标元素来判断按钮是否被点击。
HTML:
<ul id="buttonList">
<li><button>按钮 1</button></li>
<li><button>按钮 2</button></li>
<li><button>按钮 3</button></li>
</ul>
JavaScript:
var buttonList = document.getElementById('buttonList');
buttonList.addEventListener('click', function(event) {
if (event.target.nodeName === 'BUTTON') {
console.log('按钮被点击:', event.target.textContent);
}
});
在上述示例中,我们将点击事件处理程序绑定到 buttonList
父元素上。每次点击按钮时,由于事件冒泡,事件会在父元素上触发。通过检查 event.target
的节点类型,我们可以判断出正在被点击的元素是否为按钮。如果是按钮,则输出按钮的文本内容。
使用事件委托的好处是,我们只需绑定一个事件处理程序即可,而不是为每个按钮都绑定一个,这在处理大量元素时会显著减少事件处理程序的数量。此外,如果动态地添加或移除按钮,事件委托可以自动适应这些变化,无需重新绑定事件处理程序。
事件委托在处理大型页面、列表或动态内容时非常有用,能够提供更高的性能和更简洁的代码结构。
6. 渲染性能优化
请提供一些用于优化网页渲染性能的技术和策略。
优化网页渲染性能是提升用户体验和减少页面加载时间的关键。
下面是一些用于优化网页渲染性能的常见技术和策略:
-
减少HTTP请求数量:减少页面所需的资源数量可以改善加载时间。通过合并和压缩CSS和JavaScript文件、使用CSS Sprites来组合图像、使用Base64编码嵌入小图像等方式来减少HTTP请求。
-
延迟加载和按需加载资源:只在需要时加载资源,特别是图片、视频和
JavaScript
库。可以使用延迟加载、异步加载或懒加载技术,以提高初始页面加载速度。 -
压缩和优化资源:使用压缩工具(例如
Gzip
)对HTML、CSS、JavaScript
和图像等资源进行压缩,以减少文件大小。可以去除不必要的空格、注释和代码,并使用图像压缩进行优化。 -
减少重绘和回流:重绘(
Repaint
)和回流(Reflow
)是渲染页面时的昂贵操作。优化CSS,避免频繁的样式更改,使用transform
和opacity
等属性来实现动画效果,可以减少重绘和回流的次数。 -
使用缓存:合理设置缓存策略,使浏览器可以缓存静态资源,从而减少服务器请求和页面加载时间。确保正确设置HTTP响应头,例如使用适当的缓存控制和版本控制。
-
使用CDN加速:使用内容分发网络(CDN)来加速静态内容的传输,将资源分布到全球不同的服务器节点,使用户可以从最近的服务器获取资源,减少延迟和传输时间。
-
图像优化:对图像进行适当的压缩和优化,选择适合场景的图像格式(如使用WebP替代JPEG,使用SVG替代图标等),根据需要加载不同尺寸和分辨率的图像。
-
移除不必要的插件和脚本:审查并删除页面中无用、过时或不必要的插件和脚本,减少额外的负载和性能开销。
-
异步加载和延迟执行脚本:将脚本标记为异步或延迟加载,以允许页面渲染不受阻塞。异步脚本会在后台下载并在下载完成后立即执行,而延迟脚本会在页面加载完成后执行。
-
使用现代的CSS和JS技术:使用CSS的新特性(如
Flexbox
和Grid
布局)和JavaScript
的现代API(如Intersection Observer
、Web Workers
)来提高性能和响应性。
这些是一些常见的优化网页渲染性能的技术和策略。根据特定的项目和需求,可以选择适合的优化方法来改善页面的加载速度和用户体验。
7. 盒模型
请解释标准盒模型和 IE 盒模型的区别,并说明如何在 CSS 中选择盒模型。
盒模型是CSS中用于描述元素尺寸和边框的概念。标准盒模型和IE盒模型是两种不同的计算元素尺寸的方式。
标准盒模型(W3C盒模型) Width (width)
在标准盒模型中,元素的实际尺寸由内容区域的宽度(width)和高度(height)决定,不包括内边距(padding)和边框(border)。内容的尺寸不会受到内边距和边框的影响。
+----------------------------------+
| Content | Padding | Border |
| |
| Width (width) |
| |
| |
| |
| |
+----------------------------------+
IE盒模型 Width (width + padding + border)
在IE盒模型中,元素的实际尺寸包括内容区域、内边距和边框,就是将内容区域的尺寸加上内边距和边框的尺寸。
+------------------------------------------------+
| Content + Padding + Border |
| |
| Width (width + padding + border) |
| |
| |
| |
| |
+------------------------------------------------+
在CSS中选择盒模型:
CSS的box-sizing
属性用于选择使用哪种盒模型。它有两个可选值:
-
content-box
(默认值):使用标准盒模型,元素的尺寸只包括内容区域。 -
border-box
:使用IE盒模型,元素的尺寸包括内容、内边距和边框。
例如,我们可以通过以下方式将元素的盒模型设置为border-box
:
.element {
box-sizing: border-box;
}
通过设置box-sizing
属性,我们可以根据具体需求选择不同的盒模型,确保元素在布局和样式计算时符合预期。
8. CSS 选择器的优先级
请解释 CSS 选择器的优先级计算规则,并给出一个示例。
CSS选择器的优先级计算规则用于确定当多个规则应用到同一个元素时,哪个规则具有更高的优先级。通过优先级计算规则,可以确定应用到元素的最终样式。
下面是CSS选择器优先级计算规则的总结:
-
内联样式优先级最高:直接在元素的
style
属性中定义的样式具有最高优先级,它将覆盖其他任何选择器定义的样式。 -
选择器的特殊性:选择器的特殊性值用来确定优先级。特殊性值高的选择器具有更高的优先级。
- ID选择器的特殊性值为100,例如:
#myElement
。 - 类选择器、属性选择器和伪类选择器的特殊性值都为10,例如:
.myClass
、[type="text"]
、:hover
。 - 元素选择器和伪元素选择器的特殊性值都为1,例如:
div
、::before
。
特殊性值的计算方式是将各个选择器类型的特殊性值相加,并根据选择器写法从左到右比较。例如,
.myClass div
的特殊性值为11(类选择器10 + 元素选择器1),而div.myClass
的特殊性值为101(元素选择器1 + 类选择器10)。 - ID选择器的特殊性值为100,例如:
-
继承样式:继承的样式具有较低的优先级,它会被直接定义在元素上的样式所覆盖。
-
重要性标志:使用
!important
标记的样式具有最高优先级,它会覆盖其他所有非重要样式。尽量避免滥用!important
,以免导致样式难以管理和维护。
下面是一个示例,演示CSS选择器优先级计算规则的应用:
<style>
/* 优先级:内联样式 > ID选择器 > 类选择器 */
#myElement {
color: red; /* ID选择器优先级高 */
}
.myClass {
color: blue;
}
</style>
<div id="myElement" class="myClass" style="color: green;">Hello, World!</div>
在上述示例中,div
元素应用了多个样式规则:
- 内联样式
color: green;
定义了字体颜色为绿色。 - ID选择器
#myElement
定义了字体颜色为红色。由于特殊性值较高,红色样式将覆盖其他样式。 - 类选择器
.myClass
定义了字体颜色为蓝色。由于特殊性值较低,蓝色样式会被红色样式覆盖。
所以,最终div
元素的字体颜色将是红色。这个示例展示了不同选择器之间的优先级比较。
9. 重绘与回流
请解释重绘和回流的概念,并提供一些优化网页性能的建议。
重绘(Repaint)和回流(Reflow)是与网页渲染相关的两个概念。
重绘指的是当元素的可见样式发生变化,但不影响其布局的时候,浏览器会将元素的可见部分重新绘制一遍,使其呈现新样式。
回流指的是当元素的布局属性发生变化,需要重新计算元素的几何尺寸和位置,对整个文档结构进行重新布局的过程。
由于重绘和回流都会导致浏览器重新计算元素样式和布局,因此它们都具有一定的性能开销。频繁的重绘和回流操作可能会导致页面性能下降,造成页面的卡顿和加载延迟。
以下是一些优化网页性能的建议:
-
使用CSS3动画和过渡:避免使用JavaScript操作样式来实现动画效果。使用CSS3的
transform
和opacity
等属性来创建流畅的动画和过渡效果,因为它们会触发合成线程,减少回流和重绘。 -
批量修改样式:当需要对多个元素进行样式修改时,尽量对它们进行批量处理,而不是分别修改。因为对样式的单次修改可能会引起多次重绘和回流。
-
使用文档片段:当需要对DOM进行大量操作时(如插入、移动或删除多个元素),建议使用文档片段(Document Fragment)进行批量操作,然后再一次性插入到文档中,以减少回流次数。
-
避免频繁查询布局信息:当需要获取元素的位置或尺寸时,尽量避免频繁使用诸如
offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
等属性,因为它们会引起回流。可以通过一次查询并保存结果,避免多次重复计算。 -
使用transform替代top/left
:如果需要对元素进行定位,尽量使用transform
的translate
函数替代top
和left
属性。因为使用transform
不会引起回流,所以能够更高效地实现元素的平移和定位效果。 -
避免table布局:表格布局(使用
<table>
元素和相关的HTML表格属性)在进行布局计算时比较复杂,容易引起回流。尽量避免使用table布局,可以使用CSS的布局技术(如Flexbox和Grid)来代替。 -
使用虚拟化列表:当需要展示大量数据时(如列表或表格),使用虚拟化列表技术,只渲染可见区域的部分,而不是将所有数据都渲染到页面上,以提高性能和响应性。
通过避免不必要的重绘和回流,可以大幅度提升网页性能,改善用户体验。优化这些方面需要综合考虑,根据具体情况选择合适的优化策略。
10. CSS 动画与 JavaScript 动画
请解释 CSS 动画和 JavaScript 动画之间的区别,并讨论在哪种情况下使用它们。
CSS动画和JavaScript动画都是用于实现网页中的动态效果,但它们有一些区别。
CSS动画:
- CSS动画是在CSS中使用
@keyframes
规则定义动画的关键帧和过渡效果,通过添加样式类或伪类来触发动画。 - CSS动画使用浏览器的动画引擎进行渲染,比较高效而且能够在大多数现代浏览器上平滑运行。
- CSS动画可以通过简单的样式声明来实现复杂的动画效果,如渐变、旋转、缩放等,而无需编写复杂的JavaScript代码。
- CSS动画适用于简单的动画效果和过渡效果,特别适合对样式属性的过渡和变化。
JavaScript动画:
- JavaScript动画是通过编写JavaScript代码来控制元素的属性值,从而实现动画效果。
- JavaScript动画通常使用
setInterval
或requestAnimationFrame
等定时器函数来不断更新元素的属性值,实现动画的平滑过渡。 - JavaScript动画可以实现更多复杂和交互式的动画效果,例如根据用户输入、处理动画逻辑等。
- 与CSS动画相比,JavaScript动画的灵活性更高,可以处理更复杂的场景,比如实现粒子效果、物理模拟等。
在选择CSS动画和JavaScript动画时,可以根据以下几点考虑:
- 简单动画效果:对于简单的过渡效果和基本的样式动画,使用CSS动画会更简洁高效。
- 复杂动画逻辑:如果动画效果涉及到复杂的计算、交互逻辑或需要根据用户输入进行动态调整,使用JavaScript动画更灵活。
- 性能要求:对于大量元素或需要高性能的动画效果(如游戏或粒子系统),JavaScript动画通常能提供更好的控制和性能。
- 浏览器兼容性:CSS动画在现代浏览器上有很好的支持,而且由于使用浏览器的原生动画引擎,因此可以更好地利用硬件加速。但在较旧的浏览器上可能会有兼容性问题,而JavaScript动画可以提供更广泛的浏览器支持。
综上所述,CSS动画适用于简单的样式动画和过渡效果,能够通过简单的样式声明来实现;而JavaScript动画则适用于复杂的动画逻辑和交互式效果,提供更高的灵活性和控制性
。根据具体需求和场景选择适合的动画方式是很重要的。
这些问题涉及到前端的核心概念和技术,需要面试者具备扎实的知识和实际经验,以便进行深入的回答和解决方案的提供。请注意,实际的面试问题可能会因公司、职位和面试官的个人偏好而有所不同。