Skip to content

常见问题

运算符以及顺序问题

题 1

var x, y=0; x=10; y=x++; 运行以上程序后,y的值为()

分析 y = x++

  • 这个表达式中,x++后置递增运算符
  • y = x++ 的含义是:
    • 先将 x 当前的值(即 10)赋给 y,然后再对 x 进行递增。
  • 所以,y 将会被赋值为 10,然后 x 的值会递增到 11

所以 y 的值为 10

题 2

var a = 1 + (1>2)?1:2; a = ?

运算符优先级:

  • 加法运算符 + 的优先级高于三元运算符 ? :
  • 因此,1 + (1 > 2) 会先执行,然后结果会参与三元运算符的判断。

分析步骤:

  1. (1 > 2) 计算结果是 false,在 JavaScript 中,false 转换为 0
  2. 1 + (1 > 2) 就等同于 1 + 0,结果为 1
  3. 所以,代码实际上变成了:var a = 1 ? 1 : 2;
  4. 在 JavaScript 中,1 是一个真值(truthy),因此三元运算符会选择 1

题 3

var a=3;var b=4; a=a^b; b=b^a; b=?

在这段代码中,使用了按位异或运算符 (^) :相同为 0,相异为 1。

结果为 3。

typeof null : object

在 JavaScript 中,typeof null 的结果是 "object"。这是一个历史遗留的 bug,而不是逻辑上的正确结果。

为了正确检查一个值是否为 null,应该直接使用严格相等( === )操作符:

js
let value = null;
if (value === null) {
    console.log("Value is null");
}

这样可以准确地判断一个值是否为 null

Date 对象

题 1

new Date(2020,12,1).getMonth()

在 JavaScript 中,new Date(2020, 12, 1) 这段代码创建了一个日期对象,但月份参数的范围是从 0(表示一月)到 11(表示十二月)。因此,当你传入 12 作为月份参数时,JavaScript 会将日期推进到下一年。

具体来说,new Date(2020, 12, 1) 实际上表示的是 2021 年 1 月 1 日,因为 12 超出了月份范围。然后调用 .getMonth() 方法会返回月份的索引值。

所以返回值为 0 。

理解 HTML,CSS,JS

可以将 HTML、CSS 和 JavaScript 比喻成建造一座房子的过程:

  1. HTML 是房子的结构:
    想象一下,HTML 就像是房子的框架或骨架。它定义了房子的基本结构,比如墙壁、屋顶、门窗的位置。HTML 标签就是房子的砖块、梁柱,决定了房子是如何被搭建起来的。

  2. CSS 是房子的装修:
    如果 HTML 是房子的框架,那么 CSS 就是房子的外观和内饰。CSS 决定了房子的颜色、材质、装饰风格等。它让房子看起来更美观、舒适。比如,CSS 可以控制墙壁的颜色、窗帘的样式,以及家具的摆放。

  3. JavaScript 是房子的功能:
    JavaScript 就像是房子里的电器设备和智能家居系统。它让房子变得有生命,可以与人互动。比如,JavaScript 可以控制灯光的开关、空调的温度调节,甚至让窗帘自动开关。这使得房子不仅仅是一个静态的结构,而是一个可以响应住户需求的动态空间。

这三者在网页开发中的作用:

  • HTML 提供了网页的基本结构。
  • CSS 使得网页的外观更美观。
  • JavaScript 则赋予网页交互和动态功能。

浏览器渲染网页的过程涉及多个步骤,每个步骤对最终网页的呈现都有重要影响。理解这些步骤可以帮助你优化网页性能,特别是在避免不必要的重绘和重排方面。

浏览器渲染原理

  1. 解析 HTML 和生成 DOM 树:
    浏览器从上到下解析 HTML 文件,并将每个元素转换成 DOM 树的节点。DOM 树(Document Object Model Tree)表示了网页的结构。
  2. 解析 CSS 和生成 CSSOM 树:
    同时,浏览器也会解析 CSS 文件,并生成 CSSOM 树(CSS Object Model Tree),表示所有样式规则和它们如何应用于 DOM 树中的各个元素。
  3. 生成渲染树(Render Tree):
    浏览器将 DOM 树和 CSSOM 树结合,生成渲染树。渲染树包含了每个可见元素的可视化信息,比如样式、大小和位置等。注意,渲染树不会包含如 <head>display: none 之类的不可见元素。
  4. 布局(Layout):
    布局阶段也叫做“排版”或“回流(Reflow)”,它确定了渲染树中每个节点的确切位置和尺寸。这个过程是基于视口的大小和元素之间的关系来进行的。
  5. 绘制(Painting):
    在这个阶段,浏览器根据渲染树将元素的视觉信息转换为实际的像素。这包括了文本、颜色、边框、阴影等所有样式的应用。
  6. 合成(Compositing):
    对于复杂的页面,浏览器可能会将页面分割成多个图层(Layers)。这些图层会独立渲染,最后再合成到一起,显示在屏幕上。

重绘(Repaint)和重排(Reflow)

重绘(Repaint):
当页面中的某些元素的外观(如颜色、边框、阴影等)发生变化时,但它们的布局和位置没有改变,这时浏览器只需要重新绘制这些元素的像素,而不需要重新计算布局。这种操作称为重绘。重绘的成本相对较低,因为不涉及页面布局的重新计算。

重排(Reflow):
当页面中的某些元素的尺寸、位置、结构等发生变化时,浏览器需要重新计算这些元素和它们周围元素的位置和尺寸。这种操作称为重排,也叫回流。重排的成本较高,因为它可能会影响整个页面的布局,导致更多的元素需要重新布局和绘制。

避免不必要的重绘和重排的技巧

  1. 尽量减少对 DOM 的频繁操作:
    频繁的 DOM 操作会导致大量的重排和重绘。可以通过将多次 DOM 操作合并在一起或者使用文档片段(Document Fragment)来减少影响。
  2. 避免逐个更改样式:
    如果需要更改一个元素的多个样式属性,最好一次性更改,比如通过修改 class 或者使用 cssText,而不是一个个属性地修改,这样可以减少重排的次数。
  3. 使用 CSS 的 will-change 提示:
    will-change 属性可以提前告诉浏览器哪些元素可能会发生变化,浏览器会为这些元素优化渲染过程,减少重排和重绘的开销。
  4. 脱离文档流的元素:
    使用 position: absoluteposition: fixed 让元素脱离文档流,这样它们的变化不会影响其他元素的布局,减少重排的范围。
  5. 使用 transformopacity
    使用 CSS 的 transformopacity 属性来执行动画或变换,它们通常只会触发重绘,而不会引起重排,从而减少性能开销。

HTTPS(Hypertext Transfer Protocol Secure)是 HTTP 的安全版本,利用 SSL/TLS 协议为数据传输提供加密和身份验证。HTTPS 的原理涉及以下几个方面:

HTTPS

原理

  1. 加密通信: - 混合加密,在通信建立前使用非对称加密,在通信建立后使用对称加密。
    • 在 HTTPS 中,HTTP 协议与 SSL/TLS(Secure Sockets Layer/Transport Layer Security)协议结合使用,保证数据在客户端和服务器之间的传输是加密的,防止被窃听或篡改。
    • 这通过对称加密实现,在握手过程中,客户端和服务器协商出一个共享密钥,用于加密通信内容。
  2. 身份验证: - 数字证书
    • HTTPS 使用数字证书来验证服务器的身份。客户端可以通过证书确保自己连接到的确实是目标服务器,而不是中间人攻击者伪装的服务器。
  3. 数据完整性: - 摘要算法(数字签名)
    • SSL/TLS 使用 消息验证码(MAC) 来确保数据的完整性。如果数据在传输过程中被篡改,接收方会发现数据的 MAC 不匹配,从而识别出问题。

证书的防篡改、防调包机制

  1. 数字证书的结构:
    • 数字证书包含服务器的公钥、证书持有者的信息(如域名)、证书颁发机构(CA)的信息、证书的有效期等。
    • 证书由证书颁发机构(CA)使用其私钥签名,这意味着任何篡改都会导致签名无效,从而防止证书被伪造或篡改。
  2. 证书的防篡改机制:
    • 证书颁发机构(CA)使用其私钥对证书内容生成数字签名。当客户端收到服务器的证书时,会用 CA 的公钥验证该签名。如果签名验证失败,说明证书可能被篡改,客户端会拒绝建立安全连接。
    • 由于私钥是保密的,只有合法的 CA 才能生成有效的签名,任何篡改都会导致签名失效。
  3. 防调包机制(防止中间人攻击):
    • 当客户端发起 HTTPS 请求时,服务器会返回其数字证书,客户端验证证书的有效性和签名。若证书验证通过,客户端使用证书中的公钥加密一个随机生成的会话密钥(对称密钥),并发送给服务器。
    • 只有拥有相应私钥的服务器才能解密这个会话密钥,从而完成安全的对称加密通信。如果中间人试图在此过程中插入自己的证书,由于中间人的证书没有经过受信任的 CA 签名,客户端会检测到并警告用户连接不安全。
  4. 证书链验证:
    • 浏览器通常会验证证书的链条,即从服务器证书向上追溯到根证书。每个证书都由上一级证书签发,最终到达根证书。根证书被预置在操作系统或浏览器中,通常是可信的。通过这种链式验证,确保证书的合法性和安全性。

HTTPS 握手过程

  1. 客户端请求:
    • 客户端(如浏览器)请求与服务器建立 HTTPS 连接,并提供支持的加密算法列表。
  2. 服务器响应:
    • 服务器选择一个加密算法,并发送服务器的数字证书。
  3. 客户端验证:
    • 客户端使用 CA 的公钥验证服务器证书的真实性。如果证书有效,客户端生成一个随机的会话密钥,并用服务器的公钥加密这个密钥,然后发送给服务器。
  4. 加密通信:
    • 服务器用私钥解密会话密钥,之后的通信都使用这个对称密钥加密,从而保证数据传输的安全性。

虚拟 DOM

虚拟 DOM(Virtual DOM, VDOM) 是一种编程概念和技术,用于优化 UI 渲染性能。它通过在内存中构建一个虚拟表示(通常是以 JavaScript 对象形式)来跟踪实际 DOM 的状态,并在 UI 发生变化时有效地更新真实 DOM。

VDOM 的工作原理

  1. 创建 VDOM:
    当 UI 需要被渲染时,首先会在内存中生成一个虚拟 DOM 树。这个虚拟 DOM 树是使用 JavaScript 对象来描述实际 DOM 树的结构和属性。
  2. VDOM 更新:
    当状态或数据发生变化时,新的 VDOM 树会被生成。然后,框架会将这个新树与旧的 VDOM 树进行比较(通常使用“Diffing”算法),找出变化的部分。
  3. 更新真实 DOM:
    框架会基于 VDOM 的差异计算出最小的必要操作,将这些操作应用到实际 DOM 上,从而更新浏览器显示的内容。

VDOM 与原生 DOM 的对比

  1. 性能优化:
    • 原生 DOM: 原生 DOM 操作直接与浏览器的渲染引擎交互,每次操作都会触发 DOM 更新和重绘、重排。这种操作在大量元素或频繁操作时会非常耗时,导致性能问题。
    • 虚拟 DOM: VDOM 通过在内存中进行计算,将多次小的 DOM 变更合并为一次大的更新,再将这一更新批量应用到真实 DOM 上。这样就减少了浏览器的重排和重绘次数,从而提升了性能。
  2. 简化开发:
    • 原生 DOM: 直接操作 DOM 需要开发者手动跟踪每个元素的状态,维护复杂的 DOM 结构,容易出现错误,尤其是在大型应用中。
    • 虚拟 DOM: VDOM 让开发者可以专注于编写声明式的 UI 代码,而无需关注底层的 DOM 操作细节。框架(如 React)负责在状态改变时自动更新 UI,使代码更简洁、更易于维护。
  3. 跨平台能力:
    • 原生 DOM: 是浏览器特有的技术,直接操作依赖于浏览器的 API,难以移植到非浏览器环境中。
    • 虚拟 DOM: 是一种抽象的概念,不依赖具体的平台。因此,它可以用于跨平台应用开发,比如 React Native 中使用虚拟 DOM 来生成原生移动应用的组件。

为什么要用 VDOM

  1. 性能提升:
    VDOM 通过减少直接 DOM 操作的次数和范围,提高了应用的渲染性能。尤其在复杂 UI 场景下,VDOM 的性能优势更为明显。
  2. 开发体验:
    VDOM 支持声明式编程,使 UI 的开发和管理变得更加简单和直观。开发者可以用更少的代码、更高层次的抽象来管理复杂的 UI 状态。
  3. 一致性和可维护性:
    VDOM 的更新逻辑是由框架内部自动处理的,减少了手动操作 DOM 可能带来的 bug 和不一致性,使代码更加可靠和可维护。

异步问题

Promise:解决的问题

在 JavaScript 中,异步操作(如网络请求、文件读写、计时器等)可能导致“回调地狱”(Callback Hell)问题。回调地狱是指多层嵌套的回调函数,导致代码难以阅读和维护。

Promise 是 JavaScript 提供的一种更优雅的方式来处理异步操作。它是一个对象,代表一个尚未完成但承诺未来会完成的操作,并最终返回一个值(成功的结果或失败的原因)。

Promise 解决了以下问题:

  • 更清晰的异步流程控制:
    使用链式调用(then)可以避免深层嵌套的回调函数,使代码更容易理解。
  • 错误处理:
    Promise 提供了统一的错误处理机制,通过 .catch() 捕获异步操作中的错误,避免了在每个回调函数中手动处理错误。
  • 并行执行异步操作:
    Promise.all() 等方法允许并行执行多个异步操作,并在所有操作完成后执行下一步操作。

示例:

javascript
// 回调地狱的示例
doSomething(function(result) {
    doSomethingElse(result, function(newResult) {
        doThirdThing(newResult, function(finalResult) {
            console.log('Final result: ' + finalResult);
        }, failureCallback);
    }, failureCallback);
}, failureCallback);

// 使用 Promise
doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => console.log('Final result: ' + finalResult))
  .catch(failureCallback);

async/await:进一步优化异步代码

虽然 Promise 改善了异步代码的可读性,但仍然依赖于链式调用,代码仍然有些复杂。为了解决这个问题,JavaScript 引入了 async/await,它是基于 Promise 的语法糖,使得异步代码看起来像是同步代码。

  • 更简洁、更易读:
    async/await 让异步代码写起来像是同步代码,没有复杂的 .then 链式调用,使得代码结构更清晰。
  • 更好的错误处理:
    async/await 允许使用 try/catch 来捕获错误,这种方式比 Promise 的 .catch() 更直观,更符合传统的同步错误处理习惯。

示例:

javascript
// 使用 Promise
function getData() {
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
      console.log(data);
    })
    .catch(error => {
      console.error('Error:', error);
    });
}

// 使用 async/await
async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

没有这些之前如何处理异步操作

在 Promise 和 async/await 出现之前,JavaScript 的异步操作主要依赖于 回调函数。回调函数是异步操作完成后执行的函数,但这种方式容易导致以下问题:

  • 回调地狱(Callback Hell):
    由于回调函数可能嵌套多层,导致代码结构混乱、难以维护。
  • 错误处理复杂:
    需要在每个回调中手动处理错误,并传递给下一个回调,容易遗漏错误处理。

示例:

javascript
function doSomething(callback) {
  setTimeout(() => {
    // 异步操作
    callback(null, 'result');
  }, 1000);
}

doSomething(function(error, result) {
  if (error) {
    console.error('Error:', error);
  } else {
    console.log('Result:', result);
  }
});

宏任务与微任务

宏任务(Macro Tasks)

宏任务(也称为任务或事件任务)是一个较大的执行单元,通常包括以下类型的任务:

  • setTimeout:定时器任务。
  • setInterval:定期执行的任务。
  • setImmediate(Node.js):在当前事件循环周期结束时执行的任务。
  • I/O 操作(如文件读写、网络请求等)。
  • UI 渲染:如页面重排和重绘。

宏任务队列中的任务是一次执行一个的,每当执行完成后,事件循环会继续从宏任务队列中取出下一个任务执行。

微任务(Micro Tasks)

微任务是一个较小的执行单元,通常包括以下类型的任务:

  • Promisethencatch 回调:在 Promise 解决后立即执行的回调。
  • MutationObserver:观察 DOM 变动的回调。
  • process.nextTick(Node.js):在当前阶段执行的任务。

微任务队列中的任务会在当前宏任务完成后,立即执行所有微任务,然后才会继续处理下一个宏任务。

任务队列的执行顺序

  1. 宏任务队列:事件循环从宏任务队列中取出一个任务执行。宏任务包括代码执行、定时器、I/O 等。
  2. 微任务队列:在当前宏任务执行完成后,事件循环会立即处理所有的微任务。微任务包括 Promise 的回调和 MutationObserver 的回调。
  3. 渲染和更新:在执行完所有微任务后,浏览器可以进行 UI 更新和重绘操作。
  4. 继续下一个宏任务:事件循环再次从宏任务队列中取出下一个任务执行,重复上述过程。

状态码

HTTP 状态码用于表示服务器对客户端请求的处理结果。它们分为五类,每类都有不同的含义。以下是一些常见的 HTTP 状态码及其说明:

1xx 信息性状态码

这些状态码表示请求已被接受,继续处理。

  • 100 Continue:表示服务器已收到请求的初步部分,客户端应继续发送请求的其余部分。
  • 101 Switching Protocols:表示服务器已接受客户端的请求,并将协议切换到客户端请求的协议。

2xx 成功状态码

这些状态码表示请求已成功处理。

  • 200 OK:请求成功,通常与 GETPOST 请求一起使用。
  • 201 Created:请求成功并且服务器创建了一个新的资源,通常与 POST 请求一起使用。
  • 204 No Content:请求成功,但没有返回任何内容,通常用于 DELETE 请求。

3xx 重定向状态码

这些状态码表示请求需要进一步操作才能完成。

  • 301 Moved Permanently:请求的资源已被永久移动到新位置,响应包含 Location 头部指向新位置。
  • 302 Found:请求的资源临时移动到新位置,响应包含 Location 头部指向新位置。
  • 304 Not Modified:资源未被修改,客户端可以使用缓存的版本。

4xx 客户端错误状态码

这些状态码表示客户端请求存在问题。

  • 400 Bad Request:服务器无法理解请求,通常由于语法错误。
  • 401 Unauthorized:请求未经授权,需要进行身份验证。
  • 403 Forbidden:服务器拒绝请求,客户端无权访问请求的资源。
  • 404 Not Found:请求的资源未找到。
  • 405 Method Not Allowed:请求的方法被禁止,通常表示客户端请求使用了不被允许的 HTTP 方法。

5xx 服务器错误状态码

这些状态码表示服务器在处理请求时发生错误。

  • 500 Internal Server Error:服务器遇到意外情况,无法完成请求。
  • 501 Not Implemented:服务器不支持请求的方法或功能。
  • 502 Bad Gateway:服务器作为网关或代理时,从上游服务器收到无效响应。
  • 503 Service Unavailable:服务器当前无法处理请求,通常由于过载或维护。
  • 504 Gateway Timeout:服务器作为网关或代理时,未能在规定时间内从上游服务器收到响应。