前端面试题手册
什么是 web 语义化,有什么好处
什么是 Web 语义化Web 语义化是指通过 HTML 标记表示页面包含的信息,包含了 HTML 标签的语义化和 css 命名的语义化。HTML 标签的语义化通过使用包含语义的标签(如 h1-h6)恰当地表示文档结构 。CSS 命名的语义化 为 html 标签添加有意义的 class,id 补充未表达的语义,如通过添加符合规则的 class 描述信息。 Web 语义化的好处需要语义化的主要理由有以下几点:去掉样式后页面呈现清晰的结构盲人使用读屏器更好地阅读搜索引擎更好地理解页面,有利于收录便团队项目的可持续运作及维护
阅读 38·2024年6月24日 16:43
JavaScript 执行过程分为哪些阶段?
JavaScript 的执行过程大致可以分为以下几个阶段:1. 解析(Parsing)在这一阶段,JavaScript 引擎会读取源代码,并将其解析成抽象语法树(AST)。抽象语法树是一种深层次的、结构化的代码表达方式,能够以树形结构表现代码中的每个语句、表达式等元素。解析过程中,如果遇到语法错误,会抛出错误,停止进一步执行。2. 编译(Compilation)JavaScript 引擎(如V8)通常会将解析后的代码进行即时编译(JIT)。编译器会先生成字节码,这是一种低级的、比源代码更接近机器语言的代码。随后根据程序的执行情况,编译器可能会把热点代码(经常执行的代码)编译成优化的机器码,提高执行效率。3. 执行(Execution)编译后得到的字节码或机器码被送到 JavaScript 引擎的执行环境中执行。在执行过程中,会进入下面的子阶段:创建执行上下文(Execution Context):首先会创建全局执行上下文,随后每当调用一个函数时,就会为该函数创建一个新的执行上下文。执行上下文包括变量对象、作用域链和 this 引用等信息。变量提升(Hoisting):在执行代码前,函数声明和变量(声明)会被提升到它们各自的执行上下文的顶部。变量会初始化为 undefined,而函数则会完整地提升。执行代码(Running Code):按照执行上下文中的代码逐行执行,进行变量赋值、函数调用等。垃圾回收(Garbage Collection):在执行过程中,引擎会进行内存管理,自动释放那些不再被需要的内存空间。4. 优化(Optimization)在代码执行的过程中,某些代码可能会被执行多次,引擎会尝试对这些频繁执行的代码进行优化。例如,在V8引擎中,有一个称为“TurboFan”的优化编译器,它可以根据代码执行的特点对代码进行优化,提高性能。如果优化假设失败了(即出现了“去优化” deoptimization),引擎还可以将代码回退到一个较少优化的版本。5. 回收(Deoptimization & Garbage Collection)对于那些不再需要的数据和优化,JavaScript 引擎会进行去优化和垃圾回收,以保证内存的高效使用。例子:假设我们有这样一个简单的 JavaScript 函数:function sum(a, b) { return a + b;}let result = sum(5, 3);首先,该函数会被解析成 AST。然后,它可能会被编译成字节码,当我们调用 sum(5, 3) 时,会创建一个新的执行上下文,包含 a 和 b 的参数以及任何局部变量。在这个上下文中,a 和 b 被赋予了值 5 和 3,函数执行,并返回结果 8。这个过程中可能还包括了对 sum 函数的优化,如果函数被频繁调用。最后,当执行上下文离开作用域,如果没有其他引用指向其中的数据,垃圾回收器最终会清理掉这些对象。
阅读 22·2024年6月24日 16:43
jsonp 为什么不支持 post 方法?
JSONP(JSON with Padding)是一种利用<script>标签不受同源策略限制的特性来实现跨源请求的技术。因为<script>标签的初衷是加载静态的JavaScript文件,所以<script>标签仅支持GET方法来请求资源,它并不支持POST方法。这就是为什么JSONP不支持POST方法的根本原因。当使用JSONP进行通信时,您会将请求参数包含在URL中,并通过动态创建<script>标签的方式将其发出。服务器接收到GET请求后,将数据包裹在一个函数调用中,并将其作为响应返回。客户端定义好回调函数后,这段包裹着JSON数据的JavaScript被执行,回调函数便会被调用并处理返回的数据。例如,假设您的页面需要从http://example.com获取一些用户数据,您可能会发送如下的JSONP请求:<script type="text/javascript"> // 定义回调函数 function handleResponse(data) { console.log('Received data:', data); }</script><script type="text/javascript" src="http://example.com/data?callback=handleResponse"></script>服务器端需要接收到callback参数后,把数据包装在该函数调用中:// 服务器端响应handleResponse({ "user": "Alice", "age": 25 });如上所述,JSONP请求的本质是一个GET请求,它是通过<script>标签的src属性来发起的。因此,它不能使用POST方法,后者通常用于传输大量数据或者发送需要安全传输的数据。如果您需要进行跨域的POST请求,可以考虑使用更现代的技术,如CORS(跨源资源共享),它允许在各种HTTP方法中使用跨源请求,同时提供了更好的安全性。
阅读 20·2024年6月24日 16:43
Ajax 如何处理请求跨域问题?CORS 如何设置?
Ajax 处理请求跨域问题Ajax(Asynchronous JavaScript and XML)本身是不支持跨源请求的,这是因为浏览器出于安全考虑实施的同源策略(Same-Origin Policy)。同源策略限制了来自不同源的文档或脚本对当前文档读取或设置某些属性的能力。不过,有几种方法可以绕过这个限制来实现跨源请求:JSONP(JSON with Padding):这是一种老的技巧,它利用 <script>标签不受同源策略限制的特点来进行跨域请求。服务器返回的响应拼接一个函数调用作为JavaScript代码。这种方法的缺点是它只能用于GET请求,并且存在一定的安全隐患。CORS(Cross-Origin Resource Sharing):这是现在推荐的方法,它允许服务器显式地指定哪些源可以访问该服务器上的资源。CORS是一个 W3C 标准,它通过在服务器上设置特定的HTTP头来工作。代理服务器:使用一个服务器充当中间人的角色,接受来自客户端的跨源请求,然后转发请求到目标服务器。代理服务器接收到响应后,再将数据返回给客户端。这种方法需要额外的服务器端配置。WebSockets: WebSockets提供了一个全双工的通信渠道,可以在浏览器和服务器之间建立持久连接。虽然WebSocket协议与HTTP不同,但它可以绕过同源策略,从而实现跨域通信。CORS 设置当你控制服务器时,可以通过设置HTTP响应头来启用CORS。这些头部主要包括:Access-Control-Allow-Origin: 指定了哪些网站可以参与跨域资源共享。例如,设置为 *代表允许所有域进行跨域请求,但出于安全考虑通常会指定明确的域名。Access-Control-Allow-Methods: 指定了允许的HTTP请求方法,如 GET, POST, PUT, DELETE, OPTIONS等。Access-Control-Allow-Headers: 在实际请求中允许自定义的HTTP头部字段列表。Access-Control-Allow-Credentials: 表示是否允许发送Cookie。如果服务器端设置了这个值为 true,那么前端请求的时候也必须将 withCredentials属性设置为 true。Access-Control-Expose-Headers: 允许客户端访问的服务器白名单头部字段。Access-Control-Max-Age: 表明在多少秒内,不需要再发送预检验请求(对于某一资源的预检请求结果的有效期)。下面是一个简单的例子,展示了如何在Node.js的Express框架中设置CORS响应头:const express = require('express');const app = express();app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'https://example.com'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); res.header('Access-Control-Allow-Credentials', true); if (req.method === 'OPTIONS') { res.header('Access-Control-Max-Age', '86400'); return res.status(200).send(); } next();});// 你的其他路由和逻辑app.listen(3000, () => { console.log('Server running on port 3000');});
阅读 26·2024年6月24日 16:43
base64 为什么能提升性能?
Base64编码通常不是直接用来提升性能的,而是用来确保二进制数据可以通过仅支持文本格式的传输层安全地传输。Base64将二进制数据编码为ASCII字符串,这使得它可以在不支持二进制数据的系统(如电子邮件)中传输。虽然Base64编码的数据比原始二进制数据大约增加了33%,但在某些情况下,它可以间接地提升性能:减少HTTP请求:在Web开发中,Base64编码通常用于将小的图片或其他文件直接嵌入到HTML或CSS中。这样做的好处是可以减少浏览器发起的HTTP请求的数量,因为所有的资源都包含在了主文档中。少了额外的请求,网页加载时间就会缩短,间接提高了用户体验和性能。举个例子,如果一个网页中有多个小图标,通常的做法可能是每个图标一个HTTP请求来获取图像文件。如果将这些图标的图片用Base64编码后嵌入到CSS中,就可以将多个HTTP请求合并为一个请求,从而减少了服务器的请求负载和网络延迟,提高了页面的加载速度。数据URI方案:Base64编码可以使用数据URI方案在Web页面中直接嵌入图像数据,这样可以避免服务器配置对小文件的较慢响应时间。服务器对小文件的处理往往不如大文件高效,因为涉及到磁盘I/O等开销。通过避免这些小文件请求,可以在服务器端节省资源,从而提升性能。安全和兼容性:有些系统不支持二进制数据的传输,或者在传输过程中可能会因为某些字符(如NUL byte)的存在而出现问题。在这种情况下,Base64编码提供了一种可靠的方法来处理和传输数据,避免了潜在的数据损坏和传输错误,从而确保系统的顺畅运行和性能。总之,Base64编码本身增加了数据量,理论上会降低传输效率,但通过减少HTTP请求的数量、充分利用缓存、避免小文件请求开销以及提高数据的安全性和兼容性,它可以在特定场景下间接提升系统的整体性能。
阅读 67·2024年6月24日 16:43
如何去除 url 中的#号?
如果我们要在JavaScript中去除URL中的 # 号以及后面的部分,我们可以使用 window.location 对象,具体是 window.location.href 属性,再结合 String 对象的 split 方法。请看以下例子: // 假设当前URL为: https://www.example.com/page.html#section1// 使用 JavaScript 获取当前URL并去除 # 及之后的部分function removeHashFromUrl() { var currentUrl = window.location.href; var urlWithoutHash = currentUrl.split('#')[0]; window.location.href = urlWithoutHash; // 如果需要导航到去除hash的URL return urlWithoutHash; // 如果只是需要获取新的URL而不导航}var newUrl = removeHashFromUrl();console.log(newUrl); // 输出:https://www.example.com/page.html如果我们是在后端处理URL字符串,比如在Node.js环境或者其他不涉及浏览器的上下文中,我们可以简单地使用字符串处理方法。这里是用Node.js中的JavaScript例子:// 假设有一个URL字符串var url = "https://www.example.com/page.html#section1";// 去除URL中的 # 及之后的部分function removeHashFromUrl(url) { return url.split('#')[0];}var newUrl = removeHashFromUrl(url);console.log(newUrl); // 输出:https://www.example.com/page.html在Python中处理URL也很简单,我们可以使用内置的 urlparse库,这样可以更加优雅地处理复杂的URL。这是一个Python例子:from urllib.parse import urlparse# 假设有一个URL字符串url = "https://www.example.com/page.html#section1"# 去除URL中的 # 及之后的部分parsed_url = urlparse(url)new_url = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.pathprint(new_url) # 输出:https://www.example.com/page.html以上提供了去除URL中 # 号的几种方法,具体使用哪种取决于具体的应用场景以及开发环境。在前端JavaScript开发中,我们通常可能会涉及到浏览器的 window.location 对象,而在服务器端或者其他一些脚本处理中,则可能会使用字符串处理函数或者URL解析库。
阅读 34·2024年6月24日 16:43
Promise 和 async/await 和 Callback 有什么区别?
Promise、async/await 和 Callback 都是在 JavaScript 中处理异步操作的机制。每种技术都有其特点和适用场景。CallbackCallback 是一种较老的异步编程技术,它是将一个函数作为参数传递给另一个函数,并在那个函数执行完毕后调用。它最常见的用途是在进行文件操作或者请求网络资源时。优点:简单易懂,易于实现。缺点:容易导致 "回调地狱"(Callback Hell),即多个嵌套的回调函数使代码难以阅读和维护。错误处理不方便,需要在每个回调中处理错误。例子:fs.readFile('example.txt', 'utf8', function(err, data) { if (err) { return console.error(err); } console.log(data);});PromisePromise 是异步编程的一种解决方案,比传统的解决方案 —— 回调函数和事件 —— 更合理和更强大。它表示一个尚未完成但预期将来会完成的操作的结果。优点:提供了更好的错误处理机制,通过 .then() 和 .catch() 方法链。避免了回调地狱,代码更加清晰和易于维护。支持并行执行异步操作。缺点:代码仍然有些冗长。可能不够直观,特别是对于新手。例子:const promise = new Promise((resolve, reject) => { fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { reject(err); } else { resolve(data); } });});promise.then(data => { console.log(data);}).catch(err => { console.error(err);});async/awaitasync/await 是建立在 Promise 上的语法糖,它允许我们以更同步的方式写异步代码。优点:代码更加简洁、直观。更容易理解,特别是对于习惯了同步代码的开发者。方便的错误处理,可以用传统的 try/catch 块。缺点:可能会导致性能问题,因为 await 会暂停函数的执行,直到 Promise 解决。在某些情况下不够灵活,例如并行处理多个异步任务。例子:async function readFileAsync() { try { const data = await fs.promises.readFile('example.txt', 'utf8'); console.log(data); } catch (err) { console.error(err); }}readFileAsync();总结来说,Callback 是最基本的异步处理形式,Promise 提供了更强大的控制能力和错误处理机制,而 async/await 是在 Promise 基础上提高代码可读性和减少样板代码的语法糖。
阅读 43·2024年6月24日 16:43
JavaScript 中什么是暂时性死区?
在JavaScript中,暂时性死区(Temporal Dead Zone,简称TDZ)是一个术语,用来描述在代码块中使用let或const声明变量后,和这些变量实际可以被访问之前的区域。在这个区域内访问这些变量会导致一个ReferenceError。暂时性死区的存在是因为let和const声明的变量不会像使用var声明的变量那样在代码执行前进行提升。变量提升(hoisting)是一个过程,其中变量和函数声明被移动到它们所在作用域的顶部。但对于let和const声明的变量,它们会被绑定到它们所在的块级作用域中,并且不会在声明之前存在于该作用域中。这里有一个例子来说明暂时性死区:console.log(a); // 这会抛出 ReferenceError,因为变量a还处于TDZ中let a = 3;console.log(a); // 这时不会有错误,因为变量a已经声明且初始化完毕在上面的代码中,在声明变量a之前就尝试打印它,这会导致一个ReferenceError,因为在那个点上变量a还在TDZ中。只有在let a = 3;这行代码执行后,变量a才会离开TDZ,从那时起它就可以被安全地访问了。TDZ的设计主要是为了捕获编程中的错误,如在变量声明之前就使用它们的情况,这有助于开发者发现潜在的问题,并使代码更加可靠。
阅读 23·2024年6月24日 16:43
== 和 === 的区别是什么?什么情况下用 == 相等?
在JavaScript中,== 和 === 都用于比较运算符,但它们在比较值时使用不同的方式。=== 称为严格等于或恒等运算符,它比较两个值的类型和值是否完全相同。如果比较的两边数据类型不同,则直接返回false,不会进行类型转换。只有当数据类型及值都相同时,=== 才返回true。例子:3 === 3 // true,因为类型和值都相同3 === '3' // false,因为一个是数字类型,另一个是字符串类型== 称为宽松等于或等于运算符,它在比较时会进行类型转换,如果两个值类型不同,它会尝试将它们转换为相同类型,然后再进行值的比较。例子:3 == 3 // true,类型和值都相同3 == '3' // true,尽管类型不同(一个是数字,一个是字符串), // 但'3'会在比较之前转换为数字3,然后进行比较通常在编码中建议使用===来避免由于类型转换导致的意外结果,这也是代码质量工具和最佳实践的推荐。然而,有些情况下,如果你确切知道类型转换的机制,并且想利用这个特性来简化代码,可以使用==。比如:// 这里我们知道x的值可能是数值0或者"0",且两者我们视为等同的情况function checkZero(x) { return x == 0;}checkZero(0); // truecheckZero("0"); // true,因为"0"会被转换为数字0然后比较在上述代码中,使用==可以接受字符串'0'和数字0,并认为他们是等价的。如果使用===,就需要写更多的代码来处理类型检查和转换。不过,除非有非常清晰的理由,一般还是推荐使用===,因为它能让代码的行为更加可预测和清晰。
阅读 42·2024年6月24日 16:43
JavaScript 异步解决方案的发展历程以及优缺点
JavaScript的异步编程解决方案从早期的回调函数(Callbacks)开始发展,随后引入了Promises,再到现在的Async/Await。以下是每种解决方案的发展历程以及它们的优缺点: 回调函数(Callbacks)发展历程:最初,JavaScript中的异步编程依靠的是回调函数。这是一种将函数作为参数传递给另一个函数,并在后者完成异步操作后调用的技术。优点:简单直观:对于简单的异步操作,回调提供了一种直接的解决方案。广泛支持:所有的JavaScript环境都支持回调。缺点:回调地狱(Callback Hell):在复杂的应用中,回调可能嵌套得非常深,导致代码难以读懂和维护。难以处理错误:错误处理需要在每个回调中单独处理,不能很好地进行错误传递。例子:fs.readFile('example.txt', function(err, data) { if (err) { console.error('Error reading file'); return; } console.log(`File content: ${data}`);});Promises发展历程:为了解决回调地狱的问题,Promises被提出并在ES6中成为标准。Promise是一个代表着异步操作最终完成或失败的对象。优点:链式调用:能够通过 .then()和 .catch()进行链式调用,使得异步流程更加直观。错误处理:通过 .catch()方法可以集中处理错误。更好的控制:提供了更丰富的控制异步操作的方法,如 Promise.all()。缺点:代码可能还是有些冗长,特别是在多个异步操作依赖同一条件时。仍需要一定的学习曲线,特别是对于初学者。例子:new Promise((resolve, reject) => { fs.readFile('example.txt', (err, data) => { if (err) { reject('Error reading file'); } else { resolve(data); } });}).then(data => console.log(`File content: ${data}`)).catch(error => console.error(error));Async/Await发展历程:Async/Await是建立在Promises之上的,被引入在ES2017中。它们让异步代码看起来和同步代码更为相似。优点:代码可读性强:看起来就像是阻塞式的同步代码,虽然它是非阻塞的。易于理解和维护:使用 try/catch可以用同步代码的方式处理错误。简化了错误处理和条件语句。缺点:需要理解Promises,并且Babel转译对老JavaScript环境的支持可能有限。需要注意不要在非异步函数中使用 await,否则会导致编译错误。例子:async function readFileAsync() { try { const data = await fs.promises.readFile('example.txt'); console.log(`File content: ${data}`); } catch (error) { console.error('Error reading file'); }}readFileAsync();每一种异步解决方案都有其用武之地,随着JavaScript的发展,我们可以根据具体场景和个人偏好选择合适的方式来编写异步代码。
阅读 12·2024年6月24日 16:43