面试题大杂烩(二)🤡

805 阅读8分钟

前言

不多BB,直接开卷

面试题:

算法:大数相加,你会如何写函数

在前端面试中,如果面试官让你写一个大数相加,可能你会想到用big int去加,但是一般他是想考察你将大数转成string再每位相加。

代码:

/**
 * 大数相加
 * @param {string} num1 - 第一个大数(字符串形式)
 * @param {string} num2 - 第二个大数(字符串形式)
 * @returns {string} - 两个大数的和(字符串形式)
 */
function addLargeNumbers(num1, num2) {
    let result = ''; // 存储结果
    let carry = 0;   // 进位
    let i = num1.length - 1; // num1 的索引
    let j = num2.length - 1; // num2 的索引

    // 从后向前逐位相加
    while (i >= 0 || j >= 0 || carry > 0) {
        const digit1 = i >= 0 ? parseInt(num1[i]) : 0; // 获取 num1 当前位数字
        const digit2 = j >= 0 ? parseInt(num2[j]) : 0; // 获取 num2 当前位数字

        // 计算当前位的和
        const sum = digit1 + digit2 + carry;
        carry = Math.floor(sum / 10); // 计算进位
        result = (sum % 10) + result; // 当前位的结果

        i--; // 移动到 num1 的前一位
        j--; // 移动到 num2 的前一位
    }

    return result; // 返回最终结果
}

// 使用示例
const num1 = "123456789012345678901234567890";
const num2 = "987654321098765432109876543210";
const sum = addLargeNumbers(num1, num2);
console.log(sum); // 输出: "1111111110111111111011111111100"

易错点:

  • 首先:我们要得到,两端字符串的长度,可能会出现一长一短的情况,所以要记得在短的那边补零。
  • 其次:在做加法的时候,要记得用变量保存进位。
  • 最后:正确拼接答案,注意要将新计算出的数放在前面 result = (sum % 10) + result;

三次握手 四次挥手

考你对网络的理解

三次握手(TCP连接建立)​

  1. ​第一次握手​
  • 客户端发送 SYN 报文(序列号 x),进入 SYN_SENT 状态。
  • 意义:告知服务端“我要建立连接”。
  1. ​第二次握手​
  • 服务端回复 SYN-ACK 报文(确认号 x+1,序列号 y),进入 SYN_RECEIVED 状态。
  • 意义:服务端确认收到客户端的请求,并同意建立连接。
  1. ​第三次握手​
  • 客户端发送 ACK 报文(确认号 y+1),进入 ESTABLISHED 状态。
  • 服务端收到后也进入 ESTABLISHED 状态。
  • 意义:双方确认彼此的收发能力正常。

​为什么是三次?​

  • 如果只有两次握手,客户端可能发送过期的 SYN 报文(如因网络延迟),导致服务端误认为这是一个新连接,浪费资源。
  • 三次握手确保服务端的 SYN-ACK 报文必须被客户端响应,排除历史连接的干扰。

四次挥手(TCP连接断开)​

  1. ​第一次挥手​
  • 客户端发送 FIN 报文,进入 FIN_WAIT_1 状态。
  • 意义:告知服务端“我不再发送数据”。
  1. ​第二次挥手​
  • 服务端回复 ACK 报文,进入 CLOSE_WAIT 状态。
  • 客户端收到后进入 FIN_WAIT_2 状态。
  • 意义:服务端确认收到关闭请求,但可能仍有数据要发送。
  1. ​第三次挥手​
  • 服务端发送 FIN 报文,进入 LAST_ACK 状态。
  • 意义:服务端通知客户端“我也不会再发送数据”。
  1. ​第四次挥手​
  • 客户端回复 ACK 报文,进入 TIME_WAIT 状态,等待 2MSL(最大报文段生存时间)后关闭。
  • 服务端收到 ACK 后立即关闭。
  • 意义:客户端确保服务端收到最后的 ACK,防止数据丢失。

​为什么是四次?​

TCP 是全双工协议,双方均可独立收发数据。关闭连接时,主动关闭方(如客户端)发送 FIN 后,被动方(如服务端)可能仍有数据要发送,需单独发送自己的 FIN

​TIME_WAIT 的作用​

  • 确保最后一个 ACK 抵达服务端,避免因网络延迟导致服务端重传 FIN
  • 等待 2MSL 可让网络中残留的旧数据包消失,防止新连接收到历史数据。

https 更安全的http

如果使用http有可能有以下问题:

  • ​明文传输​​:HTTP 数据以明文形式传输,易被中间人窃听(如公共 Wi-Fi 下的密码泄露)。
  • ​无身份验证​​:无法验证服务器真实性,可能遭遇钓鱼网站(如伪造银行网站)。
  • ​数据篡改​​:攻击者可篡改传输内容(如修改交易金额),且客户端无法察觉。

基础知识点:

  • ​对称加密​​:使用 AES 等算法加密实际数据,速度快,加密和解密使用同一个密钥。

    • 加密和解密使用同一密钥,必须通过安全通道预先共享密钥。
    • 无法验证通信双方身份,可能被伪造身份的攻击者欺骗(如中间人冒充服务器)。
  • ​非对称加密​​:在 TLS 握手阶段,通过 RSA/ECDHE 算法交换对称密钥,解决密钥传输的安全问题。

    1. ​生成密钥对​​:服务端生成公钥(Public Key)和私钥(Private Key)。
    2. ​分发公钥​​:服务端将公钥发送给客户端(可能被中间人篡改)。
    3. ​加密数据​​:客户端用服务端公钥加密数据,发送给服务端。
    4. ​解密数据​​:服务端用私钥解密数据。
    • 非对称加密算法(如 RSA)计算复杂度高,加密/解密速度远低于对称加密。
  • ​混合加密机制​​:结合两者优点,既保证效率,又确保密钥交换安全。 -问题: 攻击者截获公钥并替换为自己的公钥,客户端误用攻击者公钥加密数据,攻击者用私钥解密后窃取信息。

  • ​身份认证​​: 服务端证书中的公钥用于密钥交换,浏览器通过 CA 签名验证服务器身份,防止中间人攻击

总结HTTPS = TCP 握手 + TLS 握手(非对称加密交换密钥 + 证书验证) + 对称加密传输数据​​。
通过非对称加密解决密钥分发和身份认证问题,再通过对称加密高效传输数据,兼顾安全与性能。

关于promsie的手写题

如何用promise,每隔1秒输出1 2 3 呢?

当时想的是在promsie里面使用定时器

const x = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('1', new Date());
        resolve();
    }, 1000);
});

x.then(() => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('2', new Date());
            resolve();
        }, 1000);
    });
}).then(() => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('3', new Date());
            resolve();
        }, 1000);
    });
}).then(() => {
    console.log("所有步骤完成");
});
1 2025-05-08T12:40:21.351Z
2 2025-05-08T12:40:22.364Z
3 2025-05-08T12:40:23.377Z
所有步骤完成

让我们看看吸引面试官的写法:

const a = [1, 2, 3];
a.reduce((p, x) => {
  return p.then(() => {
    return new Promise(r => {
      setTimeout(() => r(console.log(x, new Date())), 1000);
    });
  });
}, Promise.resolve());
初始化

a = [1, 2, 3]:定义了一个包含三个数字的数组。

Promise.resolve():作为 reduce 方法的第二个参数传入,它是一个已经 resolved 状态的 Promise,作为累积器(累加器)的初始值。

第一次迭代 (x = 1)
  • 累积器 p 初始为 Promise.resolve()

  • 调用 p.then(...),因为 p 已经处于 resolved 状态,.then(...) 注册的回调函数会立即进入事件队列等待执行。

  • .then(...) 回调中,创建一个新的 Promise:

    return new Promise(r => {
      setTimeout(() => r(console.log(x, new Date())), 1000);
    });
    

    这个新的 Promise 包含一个 setTimeout,在 1000ms(1秒)后触发,并且在其回调中打印当前元素 x 和当前时间戳。然后调用 r() 来 resolve 这个 Promise。

关键点解释
定时器与事件循环
  • 当前 JavaScript 引擎继续处理同步任务,而定时器开始计时。
  • 1秒后,setTimeout 的回调被放入任务队列等待执行。
  • 一旦当前调用栈为空(即所有同步任务完成),JavaScript 引擎开始从任务队列中取出并执行回调函数。
  • 回调函数首先执行 console.log(x, new Date()),这将输出 1 和当时的时间。
  • 然后调用 r(),这意味着这个新的 Promise 被标记为 resolved,允许任何后续 .then(...) 回调被执行。
后续迭代 (x = 2 和 x = 3)

对于数组中的每个后续元素(x = 2x = 3),上述过程重复:

  1. 累积器 p 变成上一步返回的新 Promise。
  2. 每次 .then(...) 回调都会创建一个新的 Promise,该 Promise 会在 1秒后通过 setTimeout 触发,并在触发时打印当前元素和时间戳,随后 resolve 自身。
  3. 这种方式确保了每次输出之间有固定的延迟(1秒),并且按照数组顺序依次输出。

使用Promise实现红绿灯交替重复亮

当时的想法如果按第一题那样写,再加个while(true)不就好了 错误写法:

function x() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('red', new Date());
            resolve();
        }, 1000);
    });
}
while(true){
x().then(() => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('green', new Date());
            resolve();
        }, 2000);
    });
}).then(() => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('yellow', new Date());
            resolve();
        }, 3000);
    });
}).then(() => {
    console.log("所有步骤完成");
});
}

解释: 因为 while 循环不会等待 Promise 链式调用完成就会立即开始下一次循环,这会导致大量 Promise 实例同时创建和执行,可能使内存占用过高。可以使用 async/await 来确保每次循环都在前一次循环的 Promise 链式调用完成后再开始。

所以下面的版本出来了:

function sleep(time){
    return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve()

    }, time)
})
}

async function traficLight() {
    while (true) {
        console.log('red', new Date())
        await sleep(3000)
        console.log('yellow',   new Date())
        await sleep(1000)
        console.log('green',  new Date())
        await sleep(2000)
    }
}

结语:

面试就是总结,面试再总结,不断优化的过程啊!