面试题手册
谈一谈 HTTP 是如何数据传输
HTTP (HyperText Transfer Protocol) 是用于分布式、协作式、超媒体信息系统的应用层协议。它是互联网上数据通信的基础。其数据传输流程大致如下:建立连接:客户端到服务器的连接:当用户通过浏览器或应用程序请求一个网页时,浏览器首先需要与服务器建立连接。在HTTP/1.1协议中,每次请求通常都会创建一个新的TCP连接,而在HTTP/2中,多个请求可以在同一个连接上复用。发送请求:组建HTTP请求:客户端(如浏览器)会创建一个HTTP请求消息,这个消息包括请求行(包括请求方法如GET或POST,请求的资源路径,以及HTTP版本),请求头部(包括各种元数据如Accept, User-Agent, Host等),以及请求正文(通常在POST请求中携带数据)。发送请求到服务器:客户端将这个请求通过TCP连接发送给服务器。服务器处理请求:服务器解析请求:服务器接收请求消息,并解析请求行和头部,确定请求的资源和操作。处理请求:服务器根据请求类型,调用相关的服务或脚本,如数据库查询、文件读取等,来处理这个请求。发送响应:组建HTTP响应:服务器处理完请求后,会创建一个HTTP响应消息,包括状态行(HTTP版本,状态码,状态文本),响应头部(包括内容类型、内容长度、缓存控制等元数据)和响应正文(请求的资源内容)。发送响应到客户端:服务器通过TCP连接将响应消息发送回客户端。客户端接收响应:解析响应:客户端收到响应后,会解析状态码以了解请求是否成功,以及如何处理返回的数据。显示内容:如果是网页请求,浏览器会解析响应正文中的HTML、CSS和JavaScript内容,并在屏幕上渲染显示网页。关闭连接:断开连接:在HTTP/1.0中,通常每个请求/响应之后连接即关闭。而在HTTP/1.1中,引入了持久连接(keep-alive),允许在一个连接上发送、接收多个请求/响应。不过,最终这个连接也会在一定时间后或按照客户端或服务器的需求被关闭。例子:假设您在浏览器的地址栏输入了一个URL http://example.com 并按下了回车键:建立连接:浏览器通过DNS解析获得 example.com的IP地址,然后向这个地址的80端口发起TCP连接(HTTP默认端口)。发送请求:浏览器构建一个GET请求消息,请求行为 GET / HTTP/1.1,请求头部包含了浏览器类型、接受的内容类型等。服务器处理请求:example.com的服务器接收请求,解析路径 /,找到首页的内容。发送响应:服务器构建响应消息,状态行可能为 HTTP/1.1 200 OK,响应头部包含内容类型为 text/html,然后是响应正文,即HTML内容。客户端接收响应:浏览器接收到响应,解析HTML内容,并在屏幕上渲染出来。关闭连接:如果没有更多的请求,或服务器/客户端决定关闭连接,TCP连接将被关闭。
阅读 67·2024年6月24日 16:43
Promise 是如何实现链式调用的?
Promise 实现链式调用主要依赖于其返回一个新的 Promise 对象的特性。在 JavaScript 中,Promise 是一个处理异步操作的对象,可以在原调用位置以同步方式处理异步操作结果。下面是 Promise 的链式调用的基本实现:Promise 构造函数接收一个执行函数,执行函数接收两个参数:resolve 和 reject,分别用于异步操作成功与失败的情况。调用 Promise 对象的 .then 方法提供链式调用。.then 方法接收两个参数(都是可选的):onFulfilled 和 onRejected,分别在 Promise 成功或失败时调用。.then 方法也返回一个 Promise 对象,以便进行链式调用。如果 onFulfilled 或 onRejected 返回一个值 x,运行 Promise 解决过程:[Promise Resolution Procedure]。如果 onFulfilled 或 onRejected 抛出一个异常 e,Promise.then 的返回的 Promise 对象会被 reject 掉。如果 onFulfilled 不是函数且 promise1(前一个 promise) 成功执行,promise2(下一个 promise)成功处理 promise1 的 final state。如果 onRejected 不是函数且 promise1 失败,promise2 会拒绝 promise1 的原因。以下是一个示例:new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // 第一步:创建一个 Promise 并执行一个异步操作}).then(function(result) { // 第二步:注册一个 onFulfilled 回调 console.log(result); // 打印:1 return result + 2;}).then(function(result) { // 第三步:链式调用 console.log(result); // 打印:3 return result + 2;}).then(function(result) { console.log(result); // 打印:5 return result + 2;});在这个例子中,每个 .then 调用后都返回一个新的 Promise 对象,这个新的 Promise 对象会立即执行,并在执行完毕后调用下一个 .then 注册的回调。通过这种方式,我们可以以同步的方式处理异步的结果,而这就是 Promise 链式调用的本质。
什么是混合应用 hybrid app?
混合应用(Hybrid App)是一种移动应用程序,它结合了原生应用以及网页应用的特点。它们通常是通过Web技术(如HTML5, CSS和JavaScript)来开发,并通过一个原生容器在移动设备上运行。下面是混合应用的几个关键特点:跨平台兼容性:混合应用的一个主要优势是能够使用一套代码基础适用于不同的操作系统,例如iOS和Android。这意味着开发者可以编写一次代码,然后通过桥接技术使其在不同的移动平台上运行。开发成本和时间:与分别为各个平台开发原生应用相比,混合应用可以显著降低开发成本和时间,因为它们共享一套代码库。易于更新:由于混合应用的内容可以像网页一样从服务器端获取,因此它们可以更频繁地更新而无需经过应用商店的审核。性能问题:混合应用通常比原生应用性能稍逊,因为它们需要通过Web视图(如WebView)来运行,这可能会导致比直接在原生平台上开发的应用更慢的性能。设备特性访问:虽然混合应用可以通过插件访问设备的原生特性,如摄像头、GPS等,但通常访问这些特性的效率比原生应用要低。举一个例子,假设我们开发了一款健身应用,该应用需要能够在iOS和Android设备上运行。通过选择混合应用开发模式,我们可以使用例如Cordova或Ionic这样的框架来开发应用,这样我们就能够编写一次代码,并将其部署到不同平台的应用商店。这样做省去了为每个平台单独开发应用的需要,节约了资源和时间。总之,混合应用是一种平衡方案,它结合了原生应用的高性能和设备特性访问能力以及Web应用的跨平台兼容性和开发效率。根据项目的需求和资源,混合应用可以是一个非常有吸引力的选择。
阅读 26·2024年6月24日 16:43
页面意外崩溃,这时候 JS 线程都已经崩溃了,如何传递通知呢?
在Web应用中,当JavaScript线程崩溃导致页面无法正常工作时,确实需要一种机制来通知用户或者开发者。在这种情况下,由于主JavaScript线程已经崩溃,传统的错误捕获如 try-catch或者 window.onerror事件监听可能都不起作用。但是,我们仍然可以采用以下几种策略:1. 使用Web WorkersWeb Workers运行在与主JavaScript线程分离的后台线程中。即使主线程崩溃,Web Workers可能仍然保持运行状态。因此,可以在页面加载时,启动一个监控Worker,用来检测主线程的心跳。如果主线程心跳停止(例如,可以通过设置定时消息来实现心跳),Worker可以尝试通知服务器或者更新UI来告诉用户发生了错误。2. 利用 window.onunload和 window.onbeforeunload事件可以在 window.onunload或 window.onbeforeunload事件中进行错误上报。如果浏览器支持 navigator.sendBeacon方法,即使在页面卸载的情况下也可以向服务器发送数据。虽然这两个事件不是为错误处理设计的,但它们可以用于在页面关闭时发送一些信息,可能包括崩溃通知。3. 使用Service WorkersService Workers作为一种在浏览器后台运行的脚本,可以用来拦截和缓存网络请求,推送通知等。如果设置了Service Worker,并且页面发生崩溃的情况下,它仍然能够接收到fetch请求或者推送事件,从而可以实现一定程度上的错误处理或状态报告。4. 外部心跳系统通过外部系统定时检测Web应用的状态,例如可以通过服务器定时发送请求到客户端,检测页面的响应。如果在预定时间内没有收到响应,或者收到错误响应,服务器则可以记录这类事件并采取相应的措施。5. 自动化监控和错误上报工具使用像Sentry、LogRocket这样的第三方错误监控服务,它们可以帮助在JavaScript出现未捕获异常时自动上报错误。虽然在某些崩溃情况下这些工具也可能失效,但它们仍然是一种有效的自动监控手段。6. 客户端存储在客户端使用如localStorage或sessionStorage,可以在检测到问题时写入状态标志。然后在页面重载或重新打开时检查这些标志,如果发现有异常状态,可以采取相应的通知措施。7. 使用全局异常处理器在可能的情况下,可以注册全局异常处理器 window.addEventListener('error', function() {...}),尽管在某些崩溃情况下可能不会被调用,但它提供了一个捕获异常并尝试通知的机会。示例举一个使用Web Workers进行心跳检测的简单示例:假设我们在主线程中有一个定期运行的函数,模拟心跳:function sendHeartbeat() { if (worker) { worker.postMessage('heartbeat'); }}// 定期发送心跳到WorkersetInterval(sendHeartbeat, 5000);
阅读 39·2024年6月24日 16:43
什么是 MVVM 模式?是为了解决什么问题?
MVVM 模式介绍MVVM 是 Model-View-ViewModel 的缩写,是一种设计模式,专门用于简化用户界面的事件驱动编程。它将用户界面(UI)的表示和业务逻辑分离开来,以达到更好的关注点分离(Separation of Concerns),从而使得开发和维护变得更加容易。MVVM 的组成部分Model(模型):代表的是数据和业务逻辑层。这是应用程序的核心,包含了数据的状态以及对数据的处理方法。View(视图):是用户界面层,显示数据并捕获用户行为。视图的任务是向用户展示信息,并接收用户的输入。ViewModel(视图模型):是视图的抽象,它负责处理视图的逻辑。它会监听模型的变化并更新视图,反之亦然,它也会处理视图的用户输入并可能影响模型。MVVM 解决的问题UI与业务逻辑分离:MVVM 通过引入 ViewModel,实现了界面逻辑与业务逻辑的分离。开发人员可以专注于业务逻辑,而设计师可以专注于界面设计,两者可以独立进行。双向数据绑定:ViewModel 通常实现了双向数据绑定,即当数据发生变化时,UI自动更新;用户界面变化(如用户输入),数据也会同步更新。这极大地简化了状态同步的复杂性。更易于测试:由于 ViewModel 不依赖于视图层的具体实现,因此可以在不涉及用户界面的情况下进行测试。提高代码的可维护性:将视图逻辑(如状态的显示和转换)移动到 ViewModel 可以减少视图代码的复杂性,使其变得更加整洁和可维护。提高可复用性:ViewModel 可以从视图中抽象出来,因此可以在不同的视图中复用。实例应用假设我们的应用中有一个用户表单界面,用户需要输入他们的信息。在不使用 MVVM 的情况下,视图代码可能会变得非常复杂,因为它需要处理数据的加载、显示、编辑、验证和保存等逻辑。在 MVVM 模式下,这些逻辑将会从视图中分离出来:Model:包含用户信息的数据结构。它可能还包含与数据存储和业务规则相关的逻辑。View:显示一个表单,用户可以在其中输入他们的信息。它不包含逻辑,只是简单的显示和收集用户输入。ViewModel:处理表单的显示逻辑,例如当用户点击保存时验证输入并更新模型。通过这种方式,视图不需要知道数据是如何被处理和验证的,而 ViewModel 中的逻辑可以被独立测试,不需要考虑用户界面的具体实现。
JavaScript 如何使用 setTimeout 模拟实现 setInterval?
使用 setTimeout 模拟实现 setInterval 的基本思路是:在 setTimeout 的回调函数中再次调用 setTimeout,这样可以不断地延迟执行相同的操作,形成类似 setInterval 的效果。不过,值得注意的是,使用这种方法可以更精确地控制下一次执行的时间,因为你可以在当前任务结束后再设置下一次执行,这样就不会受到之前任务执行时间的影响。下面是一个模拟 setInterval 的示例函数 simulateSetInterval:function simulateSetInterval(callback, interval) { // 用来清除定时器的函数,在simulateSetInterval返回的对象上调用clear方法即可停止。 let timer = { clear: function() { clearTimeout(this.timeoutId); } }; // 这是一个递归函数,用于模拟重复间隔执行 const repeat = function() { callback(); timer.timeoutId = setTimeout(repeat, interval); }; // 开始执行 timer.timeoutId = setTimeout(repeat, interval); // 返回一个控制对象,可以用来停止间隔执行 return timer;}// 使用 simulateSetInterval 的例子let counter = 0;const exampleTimer = simulateSetInterval(() => { console.log('Hello World!'); counter++; if (counter >= 5) { exampleTimer.clear(); console.log('Timer stopped after 5 iterations.'); }}, 1000);在这个例子中,simulateSetInterval 函数接受一个回调函数 callback 和一个间隔时间 interval。函数内部定义了一个递归的 repeat 函数,它首先执行传入的 callback,然后使用 setTimeout 来延迟下一次执行 repeat 函数本身,从而达到周期执行的效果。返回的 timer 对象包含一个 clear 方法,可以用来清除定时器,停止进一步的执行。这种模拟实现方式的好处是,它更加灵活,可以根据任务的实际执行时间动态调整间隔,而 setInterval 在一些情况下可能会导致任务之间的间隔不准确,尤其是在某些任务执行时间较长时。使用 simulateSetInterval,下一次任务的开始时刻总是在当前任务完成后按照设定的间隔进行计时。
阅读 37·2024年6月24日 16:43
如何删除一个cookie值?
删除一个cookie的值可以通过多种方式实现,具体取决于您使用的编程语言和环境。以下是一些通用的方法和例子:在JavaScript中删除一个Cookie:要在客户端JavaScript中删除一个cookie,您可以将cookie的过期时间设置为过去的一个时间点。这样做会告诉浏览器此cookie已经过期,于是浏览器会删除它。function deleteCookie(name) { document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';}使用这个函数,您只需要传入您希望删除的cookie的名称即可。在HTTP响应中删除一个Cookie:如果您是在服务器端工作,比如使用Node.js的Express框架,您可以通过设置响应头来告诉浏览器删除一个cookie。res.clearCookie('cookieName');这行代码会设置一个响应头来清除名称为 cookieName的cookie。在PHP中删除一个Cookie:在PHP中,您可以通过设置一个负的过期时间来删除一个cookie。setcookie("cookieName", "", time() - 3600);上面的代码将 cookieName的过期时间设置为一个小时前,这会导致它被删除。在Python的Flask框架中删除一个Cookie:如果您在使用Flask框架,可以利用响应对象来删除cookie。from flask import make_response@app.route('/delete-cookie')def delete_cookie(): response = make_response('Cookie has been deleted') response.set_cookie('cookieName', '', expires=0) return response这段代码创建了一个响应对象,并使用 set_cookie方法将 cookieName的值设置为空字符串,并且将过期时间设置为0,这样浏览器会删除这个cookie。总结:通常删除cookie的方法是将其过期时间设置为过去的某个时间点,这样浏览器就会认为cookie已经过期,自动将其删除。不同的编程语言和框架有各自的函数或方法来实现这一点。重要的是要确保您发送正确的HTTP头信息,以便浏览器知道要删除的cookie。
React 项目中有哪些内存泄露的场景?
在React项目中,内存泄露主要是指应用程序持续占用不再需要使用的内存。这会导致应用性能下降,甚至崩溃。以下是一些典型的内存泄露场景:1. 组件卸载后的挂载状态更新例子:class MyComponent extends React.Component { state = { data: null }; componentDidMount() { someAsyncCall().then(data => { if (!this.isUnmounted) { this.setState({ data }); } }); } componentWillUnmount() { this.isUnmounted = true; }}在上面的例子中,someAsyncCall是一个异步操作,如果在异步操作完成前组件被卸载,而异步操作的回调仍试图执行this.setState,这可能会导致内存泄露。通过设置一个标志isUnmounted并在componentWillUnmount中标记组件已卸载,可以避免这种情况。2. 没有清理的定时器和数据订阅例子:class MyComponent extends React.Component { componentDidMount() { this.intervalId = setInterval(this.fetchData, 1000); } componentWillUnmount() { clearInterval(this.intervalId); } fetchData = () => { // Fetch data from API }}如果在组件卸载后,没有清理定时器,定时器依然会运行,这会阻止JavaScript引擎回收相关内存,导致内存泄露。在componentWillUnmount中清除定时器可以解决这个问题。3. 外部库的事件监听器未正确移除例子:class MyComponent extends React.Component { componentDidMount() { ExternalLibrary.on('data', this.handleData); } componentWillUnmount() { ExternalLibrary.off('data', this.handleData); } handleData = (data) => { // Handle the event }}如果忘记移除事件监听器,那么即使组件卸载了,事件监听器仍然存在,导致内存泄露。应该在componentWillUnmount中移除监听器。4. 闭包引用导致的内存泄露例子:function setup() { let largeData = new Array(1000).fill(new Array(1000).fill('data')); document.getElementById('button').addEventListener('click', function handleClick() { console.log('Button clicked'); });}在上面的例子中,即使按钮点击事件的处理函数handleClick并不需要使用largeData,但由于闭包的作用,largeData会被保留在内存中,除非handleClick被移除或者largeData变量被显式设置为null。5. 长时间运行的WebSocket连接如果组件与一个WebSocket服务建立了连接,并且在组件卸载时没有关闭连接,那么这个连接可能会持续存在并保持对组件的引用,从而导致内存泄露。防止内存泄露的一般建议:当组件卸载时,确保取消所有订阅和异步操作。使用useEffect的清理函数来处理功能性组件的副作用和相关的清理操作。对于外部事件监听器和定时器,确保在组件卸载时移除或清理它们。考虑使用WeakMap或WeakSet来缓存大对象,它们不会阻止垃圾回收。避免在组件状态中保存大量的数据,特别是如果这些数据在组件的生命周期内不再需要。使用工具(如Chrome开发者工具的Memory选项卡)定期对应用的内存使用进行分析和调试。
浏览器为什么要有同源限制?
同源政策(Same-origin policy)是一种重要的安全措施,它被设计为Web浏览器中的一个关键安全特性。它的目的是为了保护用户的在线安全,防止恶意网站访问或操纵另一个网站的敏感数据。同源限制确保了一个源(origin)中加载的文档或脚本只能与同一源的资源进行交互,而不能与其他源的资源进行交互。这里的源是指协议,端口和主机名的组合。以下是同源政策的必要性以及它如何提高网络安全的几个方面:防止跨站点请求伪造(CSRF):跨站点请求伪造是一种网络攻击,攻击者通过伪装成受信任用户的请求来利用用户已经认证的身份。如果没有同源限制,攻击者可以轻易地从自己的网站发起请求到另一个网站,并潜在地进行未经授权的操作。例如,用户登录到他们的银行网站,并在另一个标签页中访问了一个恶意网站。如果没有同源政策,恶意网站可以发起请求到银行网站,并可能执行一些不良操作,例如转账,而这些操作是在用户的权限下进行的。保护用户隐私:同源政策防止了一个网站的脚本访问另一个网站的数据,这样可以保护用户个人信息不被未授权的第三方获取。例如,如果你访问了一个电子邮件提供商的网页,同源政策将防止其他网站的脚本读取你的邮件内容。阻止DOM操作:如果没有同源限制,恶意脚本可以操纵其他网站的DOM(文档对象模型),从而可能篡改页面内容、捕获敏感信息或者在不知情的情况下在用户界面上执行操作。限制网络攻击范围:通过限制脚本只能与同一源的资源交互,同源政策有助于减少网络攻击面,因为攻击者不能简单地通过自己控制的网站操纵其他网站的资源。隔离潜在恶意文件:当网站加载第三方插件或广告时,同源政策防止这些第三方资源访问宿主网站的数据,因此即使这些资源被恶意利用,它们也不能直接访问宿主网站的敏感信息。控制Web API和资源的访问:一些现代Web API,如Web Storage、IndexedDB等,受同源政策的保护,只有在同源环境下才能被访问。这样可以确保只有在合适的安全上下文中才能访问和操作用户数据。促进安全的跨源通信:同源政策实施了严格的限制,但同时,Web也提供了如CORS(跨源资源共享)这样的机制来安全地允许跨源访问,其中需要明确服务器端的许可和合适的客户端处理。
什么是对称加密和非对称加密?
对称密钥加密是最简单的一种加密方式,它的加解密用的都是相同的密钥,这样带来的好处就是加解密效率很快,但是并不安全,如果有人拿到了这把密钥那谁都可以进行解密了。非对称密钥会有两把密钥,一把是私钥,只有自己才有;一把是公钥,可以发布给任何人。并且加密的内容只有相匹配的密钥才能解。这样带来的一个好处就是能保证传输的内容是安全的,因为例如如果是公钥加密的数据,就算是第三方截取了这个数据但是没有对应的私钥也破解不了。不过它也有缺点,一是公钥因为是公开的,谁都可以过去,如果内容是通过私钥加密的话,那拥有对应公钥的黑客就可以用这个公钥来进行解密得到里面的信息;二来公钥里并没有包含服务器的信息,也就是并不能确保服务器身份的合法性;并且非对称加密的时候要消耗一定的时间,减低了数据的传输效率。
阅读 29·2024年6月24日 16:43