前端面试题

Fenger Lv3

Webpack(静态资源打包工具)使用方法:

需要电脑中安装了Node.js,使用npm安装webpack。

安装后,配置webpack.config.js配置文件,enter部分填入入口文件,output的path填入输出文件路径,filename填入文件名。保存后,在配置文件目录输入webpack就可以打包了。

Localstorage使用方法

为了解决cookie的4kb限制,H5新增的客户端数据存储技术,数据会一直保留直到手动删除。

需要浏览器支持LocalStorage,通过window.localstorage来判断。

使用localStorage.setItem(key,value);来存储数据

使用localStorage.getItem(key);来读取数据

使用localStorage.removeItem(key)来删除数据,

localStorage.clear();

跨域问题

Ø 使用CORS标准,在服务器相应头上设置Access-Control-Allow-Origin等额外的HTTP头允许特定外部域访问资源。

Ø 域名归一,使用反向代理工具将请求映射为不跨域的域名请求。

实现一个输入123456789返回123,456,789的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function formatNumber(num) {
let numStr = num.toString();
let result = '';
let count = 0;

for (let i = numStr.length - 1; i >= 0; i--) {
count++;
result = numStr[i] + result;
if (count % 3 === 0 && i !== 0) {
result = ',' + result;
}
}

return result;
}

console.log(formatNumber(1234567890)); // 输出: "123,456,789"

React常用的几个钩子(React Hooks)

React Hooks中常用的几个钩子函数包括:

  1. useState(): 状态钩子,让函数组件具有维持状态的能力
1
const [count, setCount] = useState(0);
  1. useEffect(): 副作用钩子,用于处理组件的副作用,如数据获取、订阅或手动更改React组件中的DOM
1
2
3
useEffect(() => {
// 执行副作用操作
}, [dependency]);
  1. useContext(): 共享状态钩子,用于让函数组件可以订阅 React context 的变化
1
const theme = useContext(ThemeContext);
  1. useReducer(): action钩子,用于处理更复杂的状态逻辑
1
const [state, dispatch] = useReducer(reducer, initialState);

TypeScript 泛型

允许在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型。泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出。

react组件通信

  1. 父组件向子组件通信:可以通过 props 方式传递数据。
  2. 子组件向父组件通信:可以通过回调函数方式传递数据。
  3. 兄弟组件之间的通信:可以通过共享父组件的状态。
  4. 跨组件层级的通信:可以通过 Context 或者全局变量来实现。
  5. 任意组件之间的通信:可以通过 Redux或者观察者模式来实现。

ReactRouter的跳转方式

  1. Link组件
1
2
3
import { Link } from 'react-router-dom';

<Link to="/your-path">Go to your-path</Link>
  1. 编程式导航
1
history.push('/your-path');

或者函数组件中

1
2
3
4
import { useHistory } from 'react-router-dom';

const history = useHistory();
history.push('/your-path');

Promise 的常用方法

Promise 是一种用于处理异步操作的解决方案,它在 JavaScript 中被广泛使用。

Promise 的常用方法:

  1. **Promise.resolve**:这是一个静态方法,用于创建一个成功状态的 Promise 对象。你可以直接在 .then 的成功回调中获取 resolve 的值。例如:

    1
    2
    3
    4
    const p = Promise.resolve("成功");
    p.then((res) => {
    console.log("打印:", res); // 打印:成功
    });
  2. **Promise.reject**:类似于 Promise.resolve,但是它创建的是拒绝状态的 Promise 对象。你可以在 .then 的失败回调中获取 reject 的值,或者使用 .catch 捕获异常。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const p = Promise.reject("失败");
    p.then(
    (res) => {
    console.log("打印:", res); // 不执行
    },
    (rej) => {
    console.log("打印:", rej); // 打印:失败
    }
    );
  3. **Promise.then**:这个实例方法常用于接收请求接口返回的数据。它有两个参数(函数):一个用于处理 Promise 解决时的回调函数,另一个是可选的用于处理 Promise 拒绝时的回调函数。同时,.then 的返回值也是一个 Promise 对象。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const p = new Promise((resolve, reject) => {
    resolve("成功");
    });

    const result = p.then(
    (res) => {
    console.log("打印:", res); // 打印:成功
    },
    (rej) => {
    console.log("不执行");
    }
    );

    result.then((res) => {
    console.log("第二次打印:", res); // 第二次打印: undefined
    });

Git的常用命令

  1. git config: 用于获取和设置配置变量,控制Git的外观和操作。例如:
    • 设置用户名:git config --global user.name "Your Name"
    • 设置邮箱:git config --global user.email "you@example.com"
  2. git init: 创建一个空的Git仓库或重新初始化现有仓库。例如:
    • 初始化新仓库:git init my-repo
  3. git clone: 将存储库克隆到新目录中。例如:
    • 克隆远程仓库:git clone https://github.com/user/repo.git
  4. git add: 将文件内容添加到索引(暂存区)。例如:
    • 添加单个文件:git add myfile.txt
  5. git commit: 将更改记录提交到存储库。例如:
    • 提交并添加描述:git commit -m "Add new feature"
  6. git diff: 显示提交和工作树之间的更改。例如:
    • 查看工作目录中的变化:git diff
    • 查看暂存区和版本库之间的差异:git diff --staged
  7. git status: 显示工作目录和暂存区的状态。
  8. git log: 显示提交日志信息。例如:
    • 查看完整提交历史:git log
    • 列出文件的版本历史记录:git log --follow myfile.txt
  9. git branch: 列出当前存储库中的所有本地分支。例如:
    • 创建新分支:git branch new-feature
  10. git checkout: 从一个分支切换到另一个分支。例如:
    • 切换到已有分支:git checkout existing-branch
    • 创建并切换到新分支:git checkout -b new-branch
  11. git merge: 将两个或多个开发历史合并在一起。例如:
    • 合并分支:git merge feature-branch
  12. git remote: 管理跟踪的存储库。例如:
    • 添加远程仓库:git remote add origin https://github.com/user/repo.git
  13. git push: 将本地分支的更新推送到远程主机。例如:
    • 推送到默认远程仓库:git push
    • 推送到指定远程仓库和分支:git push origin my-branch
  14. git pull: 从另一个存储库或本地分支获取并集成更改。例如:
    • 从远程仓库拉取:git pull origin master
  15. git stash: 临时存储已修改的跟踪文件。例如:
    • 存储更改:git stash save
    • 恢复最近隐藏的文件:git stash pop

浏览器缓存相关

将已经请求的资源存储在浏览器中,下次请求相同资源时直接使用保存在本地的资源

节约带宽、降低服务器压力、加速页面打开速度

Eslint的配置

ESLint 是一个插件化的 JavaScript 代码检查工具,用于识别并报告 ECMAScript/JavaScript 代码中的问题,以确保代码风格一致并避免错误。

使用 JavaScript、JSON 或 YAML 文件指定整个目录及其所有子目录的配置信息。可以是 **.eslintrc.***文件,也可以是 package.json文件中的 eslintConfig 字段,ESLint 都会自动寻找并读取这两处的配置,或者还可以用命令行]指定配置文件。

Import和require的区别

  1. require是运行时加载,这意味着它可以在代码的任何位置调用,并且可以根据运行时的需要动态加载模块

    import是编译时加载,这意味着它只能在模块的顶部调用,并且必须在编译时就确定要加载哪些模块

  2. require加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成

    import不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成

  3. require输出的是一个值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值

    import模块输出的是值的引用,如果文件引用的模块值改变,import引入的模块值会改变

学习前端的途径有哪些?

各个前端技术的社区官网,菜鸟教程,哔哩哔哩公开课

http位于五层网络模型的哪一层?五层模型包含哪些?各层主要负责什么?

HTTP协议位于五层网络模型的应用层

五层网络模型包括以下几层:

  1. 应用层:为操作系统或网络应用程序提供访问网络服务的接口
  2. 传输层:负责向两个主机中进程之间的通信提供服务
  3. 网络层:负责对子网间的数据包进行路由选择
  4. 数据链路层:负责在通信的实体间建立数据链路连接
  5. 物理层:负责在物理线路上传输原始的二进制数据

各层的主要职责如下:

  • 应用层:为应用程序或用户请求提供各种请求服务,例如HTTP协议
  • 传输层:建立主机端到端的链接,为会话层和网络层提供端到端可靠的和透明的数据传输服务,确保数据能完整的传输到网络层
  • 网络层:通过路由选择算法,为报文或通信子网选择最适当的路径
  • 数据链路层:接收来自物理层的位流形式的数据,封装成帧,传送到网络层;将网络层的数据帧,拆装为位流形式的数据转发到物理层;负责建立和管理节点间的链路
  • 物理层:在物理线路上传输原始的二进制数据

讲一下tcp的三次握手,如何保证传输的可靠性、拥塞控制和如何处理超时重传?

TCP的三次握手

  1. SYN:客户端发送一个SYN包(同步序列编号)到服务器,选择一个初始序列号X。
  2. SYN+ACK:服务器接收到SYN包后,如果同意连接,则向客户端发送一个SYN+ACK包。该包中的确认号为X+1,同时选择一个初始序列号Y。
  3. ACK:客户端收到SYN+ACK包后,向服务器发送一个ACK包,确认号为Y+1。此时,TCP连接建立。

TCP如何保证传输的可靠性

  1. 建立连接:通过三次握手建立连接,保证连接实体真实存在。
  2. 序号机制:保证数据是按序、完整到达。
  3. 合理分片:TCP会按最大传输单元 (MTU)合理分片,接收方会缓存未按序到达的数据,重新排序后交给应用层。
  4. 数据校验:TCP报文头有校验和,用于校验报文是否损坏。
  5. 超时重传:如果发送一直收不到应答,可能是发送数据丢失,也可能是应答丢失,发送方再等待一段时间之后都会进行重传。
  6. 流量控制:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。
  7. 拥塞控制:网络层拥塞造成的拥塞,包括慢启动,拥塞避免,快速重传三种机制。

TCP的拥塞控制

  1. 慢开始:初始阶段,拥塞窗口大小从1开始,每经过一个往返时间RTT,拥塞窗口大小加倍,直到达到阈值。
  2. 拥塞避免:当拥塞窗口大小达到阈值后,拥塞窗口按线性规律缓慢增长。
  3. 快速重传:当发送方连续收到三个重复确认时,就会立即重传未被确认的报文段。
  4. 快恢复:当接收到重复确认时,减小拥塞窗口,然后再慢慢增大。

TCP如何处理超时重传

  1. 超时重传:发送方在发送数据时设置一个定时器,当超过指定时间后如果还没有收到接收方的ACK响应,就会重发数据包。
  2. 快速重传:当发送方连续收到三个重复确认时,就会在超时定时器之前重传未被确认的报文段。
  3. SACK重传:接收方会将已经收到数据包序列号范围发送给发送方,这样发送方通过SACK信息就能找到丢失的数据包重传此数据包。
  4. D-SACK:让发送方知道有哪些数据包被重复接收了。

tcp和udp的区别?

TCP(传输控制协议)和UDP(用户数据报协议)是两种主要的传输层协议,它们在许多方面有所不同:

  1. 连接:TCP是面向连接的协议,传输数据前需要先建立连接。而UDP是无连接的,可以直接传输数据。
  2. 服务对象:TCP提供一对一的服务,即一条连接只有两个端点。而UDP支持一对一、一对多、多对多的交互通信。
  3. 可靠性:TCP提供可靠的数据传输,数据可以无差错、不丢失、不重复、并且按序到达。而UDP尽最大努力交付,但不保证可靠交付。
  4. 拥塞控制和流量控制:TCP有拥塞控制和流量控制机制,保证数据传输的安全性。而UDP没有这些机制,即使网络非常拥堵了,也不会影响UDP的发送速率。
  5. 首部开销:TCP的首部长度较长,会有一定的开销。而UDP的首部只有8个字节,开销较小。
  6. 传输方式:TCP是流式传输,没有边界,但保证顺序和可靠。而UDP是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
  7. 分片:如果TCP的数据大小大于MSS大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装TCP数据包。而如果UDP的数据大小大于MTU大小,则会在IP层进行分片,目标主机收到后,在IP层组装完数据,接着再传给传输层。

总的来说,TCP和UDP各有其优势和应用场景。TCP因其可靠性,常用于文件传输、HTTP/HTTPS等场景。而UDP因其高效和实时性,常用于DNS、SNMP、视频音频通信、广播通信等场景。

http如果基于udp可行吗?如何实现?

讲一下dns协议?如何进行做到域名和IP地址的映射?基于传输层的什么协议实现?

DNS(Domain Name System,域名系统)是一种将域名转换为IP地址的系统。当我们使用域名访问网站时,DNS协议就会发挥作用,将域名转换为对应的IP地址。这样,计算机就可以通过IP地址进行通信。DNS协议是应用层协议,运行在UDP之上,使用53端口。

关于域名和IP地址的映射,这是通过DNS解析来实现的。IP是互联网上设备的电子方位,是计算机进行识别通信的标识符,但是IP地址由一串纯数字组成,不便于人们的记忆和输入,在这种背景下又发明了域名,和IP相比,域名由数字和字母组成,具有一定的规律性,比如 baidu.com、12306.cn 这些都是域名。但计算机并不能直接识别域名,所以需要一种机制将域名与IP进行映射,这就是域名解析,而提供域名解析服务的是一套被称为DNS(域名系统)的复杂系统。

DNS协议主要基于UDP运输层协议。一次UDP名字服务器交换可以短到两个包:一个查询包、一个响应包。一次TCP交换则至少包含9个包:三次握手初始化TCP会话、一个查询包、一个响应包以及四次分手的包交换。因此,DNS查询通常选择基于UDP协议,以减少网络通信的开销。

https解决的是什么问题?ssl进行加密在报文中有体现吗、是哪一部分?服务端加密使用的公钥还是私钥?数字对称加密和非对称加密有什么区别?

HTTPS解决的问题主要是HTTP存在的安全问题。HTTP的问题包括明文传输,不知道服务器是否是我们要请求的服务器(中间人攻击),客户端和服务器拿到的数据可能被黑客获取修改。HTTPS通过数字证书来解决这些问题,数字证书主要用于确认请求的服务器是安全的。

SSL协议是一种安全协议,它改变了通信方式,由以前的http—–>tcp,改为http——>SSL—–>tcp。SSL协议中的数据加密过程包括数字证书、对称加密、非对称加密和SSL握手过程等概念。在SSL协议中,数据的加密过程是在记录层(Record Layer)和传输层中体现的。

在服务端加密的过程中,公钥用于加密数据,而私钥用于解密数据。当客户端连接到使用TLS加密的服务器时,服务器会与客户端共享其公钥,以使用包含公钥和服务器身份的数字证书建立加密连接。客户端使用此公钥加密并向服务器发送密钥。服务器使用其私人密钥解密客户端发送的密钥。

对于数字对称加密和非对称加密的区别,对称加密要求发送方和接收方在安全通信之前商定一个密钥。优势在于加密效率高,因为加密和解密使用相同的算法和密钥。然而,对称加密的安全性较低,因为如果密文被拦截且密钥被破解,信息就很容易被破译。非对称加密,与对称加密不同,它使用一对密钥:公钥和私钥。公钥用于加密数据,而私钥用于解密数据。私钥只能由一方安全保管,不能外泄,而公钥可以发给任何请求它的人。非对称加密提供了更高的安全性,因为公钥和私钥是相互独立的,使用其中一个密钥加密的数据只能由另一个密钥解密。

前端模块化有哪些方案?commonjs和esm的区别?如何进行转换?

前端模块化是指将一个大型的前端应用程序拆分成多个小模块,每个模块都有自己的功能与职责,可以独立开发、测试和部署。目前,前端模块化方案主要有四种:AMDCommonJSES6模块UMD

CommonJS和**ESM (ES6模块)**都是JavaScript模块标准,但是它们有一些区别,主要包括以下几个方面:

语法差异:CommonJS使用require语法引入模块,而ESM使用import语法引入模块。

  • 加载方式:CommonJS使用同步加载方式,即遇到require就执行代码,并等待结果返回后再继续执行;而ESM使用异步加载方式,它是通过Promise的方式异步加载模块,遇到import不会阻止程序继续执行。

至于如何将CommonJS转化为ESM,可以参考以下步骤:

  1. package.json中设置typemodule
  2. tsconfig.json中设置modulenodenext
  3. tsconfig.json中设置moduleResolutionnodenext
  4. 全局安装ts2esm,运行npm i -g ts2esm
  5. 在项目的目录中执行ts2esm命令。

antd的弹出层组件被其他组件遮挡住,如何解决?getcontainer是如何实现?可以通过样式解决这个问题为什么还要提供这个方法呢?说一下xss,csp

antd的弹出层组件被其他组件遮挡住的问题,可以通过以下几种方式解决:

  1. 调整定位方式:Ant Popover组件默认使用绝对定位(position: absolute),可以尝试修改为固定定位(position: fixed),以确保它始终可见且不会被其他元素遮挡。
  2. 调整层级关系:确认Ant Popover组件的z-index属性值较高,以确保它处于其他元素之上。
  3. 对弹框的modal设置动态的key,即 :key=Math.random(),保证modal每次都是重新创建出来的,而不是只创建一次。

getContainer是antd中提供的一个属性,它的作用是让用户可以自定义渲染的容器。getContainer方法会在组件挂载时调用,返回一个容器节点,组件会通过createPortal渲染到这个节点下。这样做的好处是,当用户滚动屏幕时,弹出层会跟随滚动。如果只通过样式来解决,可能无法处理这种情况。

关于XSS(Cross-Site Scripting),它是一种网络安全漏洞,允许攻击者将恶意脚本注入到受信任的网站中。当恶意代码在受害者的浏览器中执行时,攻击者可以完全破坏他们与应用程序的交互。XSS攻击通常分为三种类型:反射型XSS、存储型XSS和基于DOM的XSS。

CSP(Content Security Policy)是一种网络安全标准,它可以帮助防止XSS攻击。CSP通过定义哪些内容是浏览器可以加载和执行的,从而限制攻击者的能力。但在这里,CSP可能也指的是英国的公务员养老金计划,或者是英国的物理治疗师协会,具体含义需要根据上下文来判断。

0.1+0.2 是否等于0.3?为什么?为什么会存在精度丢失?如何表示754标准的浮点数?怎么解决精度丢失问题?

在JavaScript中,0.1 + 0.2 的结果并不等于 0.3,而是接近 0.3 的一个值 0.30000000000000004。这是由于计算机内部表示数字的方式导致的。

计算机内部使用二进制来表示和存储所有的数据,包括数字。然而,有些十进制小数在二进制中不能精确表示。例如,十进制的 0.10.2 在二进制中都是无限循环小数,所以在计算机中存储时只能存储到一定的精度,这就导致了精度丢失。

IEEE 754标准定义了计算机如何存储浮点数。一个浮点数由三部分组成:符号位(表示正负),指数(表示10的幂次)和尾数(表示精确的数值)。这种表示方法允许计算机表示非常大或非常小的数。

解决精度丢失的问题有多种方法,其中一种常见的方法是将小数转换为整数进行计算。例如,要精确计算 0.1 + 0.2,可以先将 0.10.2 分别乘以 10 得到 12,然后相加得到 3,最后再除以 10 得到 0.3

1
var result = (0.1 * 10 + 0.2 * 10) / 10;  // result 现在是 0.3

这样就可以避免因为二进制表示导致的精度丢失问题。但是需要注意的是,这种方法只适用于已知小数位数的情况。如果小数位数未知,可能需要使用其他方法,例如使用专门处理高精度计算的库。

[!IMPORTANT]

IEEE 754标准定义了计算机如何存储和操作浮点数。这个标准规定了四种表示浮点数值的方式:

  1. 单精度(32位)
  2. 双精度(64位)
  3. 延伸单精度(43位以上,很少使用)
  4. 延伸双精度(79位以上,通常以80位实现)

每种浮点数格式由三部分组成:

  • 符号位(1位):用于表示数的正负。
  • 阶码(Exponent):用于表示2的幂次。阶码使用移码(bias)表示,即阶码 = 真值 + 偏置值。在IEEE 754标准中,移码的偏置值是 2^{(n-1)}-12(n−1)−1,其中n是阶码的位数。例如,对于单精度浮点数(32位),阶码有8位,所以偏置值是 2^{(8-1)}-1 = 1272(8−1)−1=127。
  • 尾数(Mantissa):表示精确的数值。尾数采用原码表示,且尾数码隐含了最高位1,在计算时我们需要加上最高位1,即 1.M1.M。

这种表示方法允许计算机表示非常大或非常小的数。然而,由于尾数和阶码的位数有限,所以浮点数只能表示实数的一组有限子集,这就可能导致精度丢失。

对于单精度浮点数(32位),符号位占1位,阶码占8位,尾数占23位。对于双精度浮点数(64位),符号位占1位,阶码占11位,尾数占52位。

此外,IEEE 754标准还定义了特殊的浮点数,如正负无穷(+/- Infinity)和非数值(NaN,Not a Number)。这些特殊的浮点数用于表示某些特殊的计算结果,如0除以0的结果是NaN,任何数除以0的结果是正无穷或负无穷。

你印象最深刻的一个问题、如何解决的(与技术相关的)?从问题是什么、问题发生的背景、如何拆解这个问题、最终如何解决的、获得的收益是怎样的。

你读了内存管理和进程管理所收获的知识?

内存管理和进程管理是操作系统的两个核心组成部分,它们对于构建高效、稳定的计算机系统至关重要。以下是关于这两个主题的一些关键知识点:

进程管理

  • 进程是操作系统的一个管理对象,它的内部结构包含了很多东西,不仅有我们自己编写的代码流程,还有当前跟 CPU、内存等硬件交互的状态信息。
  • 进程的创建和销毁需要耗费系统资源,因此进程管理需要有效地分配和回收资源,以确保系统的稳定和高效运行。
  • 在实际应用中,多任务处理和并发执行是常见的需求,而进程管理能够实现这些需求。

内存管理

  • 操作系统内存管理包括物理内存管理和虚拟内存管理。
  • 物理内存管理包括程序装入等概念、交换技术、连续分配管理方式和非连续分配管理方式(分页、分段、段页式)。
  • 虚拟内存管理包括虚拟内存概念、请求分页管理方式、页面置换算法、页面分配策略、工作集和抖动。

深入理解这些概念有助于更好地掌握计算机系统的工作原理和性能优化方法。在实践中,掌握这些概念有助于提高编程技能,编写出更加高效和可靠的程序。

解除死锁的方式有哪些?产生死锁的条件有哪些?采用怎样的方式可以破除这些条件?在什么样子的规则下产生不了死锁?

解除死锁的方式主要有以下几种:

  1. 撤消所有的死锁进程:这是最极端的方法,可以立即解除死锁,但可能会导致大量的工作丢失。
  2. 连续撤消死锁进程:直至不再存在死锁。这种方法需要选择一个代价最小的进程进行撤消,代价可能包括优先级、运行代价、进程的重要性和价值等。
  3. 连续剥夺资源:直到不再存在死锁。这种方法需要从其他进程中剥夺足够数量的资源给死锁进程,以解除死锁状态。
  4. 进程回退:让进程回退到某个状态(回退到没有获取某种资源的状态),从而释放已经获得的资源,这样其他进程就可以获得因回退而被释放的资源并解除死锁状态。

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

破除死锁的方式主要是破坏产生死锁的四个必要条件:

  1. 破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁。但是许多资源是独占性资源,如可写文件、键盘等只能互斥的占有;所以这种做法在许多场合是不适用的。
  2. 破坏占有和等待条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行。实现简单,但是严重的减低了资源利用率。
  3. 破坏不剥夺条件:剥夺调度能够防止死锁,但是只适用于内存和处理器资源。方法一:占有资源的进程若要申请新资源,必须主动释放已占有资源,若需要此资源,应该向系统重新申请。
  4. 破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。采用层次分配策略,将系统中所有的资源排列到不同层次中。

多线程多进程开发的时候,如何去加一把锁?指令上加一把锁,在机器层面应该如何实现?

描述一下时间片轮转算法、LRU算法、优先级调度算法?如何实现时间片轮转的?如何保证进程可中断切换?实现中断的几个核心步骤?

时间片轮转算法

  • 这是一种基于时间片的调度算法,主要用于实现多任务操作系统中进程的调度。
  • 系统将所有就绪进程按照先来先服务(即FIFO)规则排成一个队列,将CPU分配给队首进程,且规定每个进程最多允许运行一个时间片。
  • 若时间片使用完进程还没有结束,则被加入就绪FIFO队列队尾,并把CPU交给下一个进程。

LRU算法

  • LRU(Least Recently Used)最近最少使用的,就是剔除旧的很少使用的。
  • 新数据插入到链表头部;当缓存命中(即缓存数据被访问),数据要移到表头;当链表满的时候,将链表尾部的数据丢弃。

优先级调度算法

  • 给每个进程赋予一个优先级,每次需要进程切换时,找一个优先级最高的进程进行调度。
  • 如果赋予长进程一个高优先级,则该进程就不会再“饥饿”。

如何实现时间片轮转

  • 初始化:将所有进程按照到达时间排序,并将它们放入就绪队列中。
  • 设置时间片:设定一个固定的时间片大小,例如 10 毫秒。
  • 调度进程:选择就绪队列中的第一个进程,将其调度到 CPU 中执行,并开始计时时间片。

如何保证进程可中断切换

  • CPU 寄存器里原来用户态的指令位置,需要先保存起来。
  • 接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。
  • 最后才是跳转到内核态运行内核任务。

实现中断的几个核心步骤

  • 关中断:进入不可响应中断请求的中断,由硬件自动完成。
  • 保存断点:把当前的程序计数器PC中的内容保存起来,用于中断处理结束后能继续执行主程序。
  • 识别中断源:有多个中断源同时请求时,只能响应最高优先级的,因此需进一步判断中断源。
  • 保存现场和屏蔽字:进入中断服务程序后,要先保存现场。
  • 设置新的屏蔽字:用于改变中断优先级和控制中断的产生。
  • 开中断:执行中断程序时,打开中断可实现更高优先级的中断响应,实现中断嵌套。
  • 执行中断服务程序:执行中断服务程序中的内容。
  • 再次关中断:使得恢复现场和屏蔽字时不会被中断打断。

时间片轮转算法在react中的体现?react如何实现可中断,这个算法本身是如何设计的以及其核心步骤?

React中的时间片轮转体现在其并发模式Fiber架构上。React通过将任务分割成小的时间片然后分批次处理任务以提高应用程序性能。这种技术被称为时间切片

React的并发模式允许渲染过程在执行和暂停之间切换状态。这种模式被称为可中断渲染,它使得React在渲染过程中可以暂停、终止和恢复工作。

时间片轮转算法的设计和核心步骤如下:

  1. 队列排列:将所有就绪进程按照先来先服务(FIFO)的原则排成一个队列。
  2. 时间片分配:将CPU分配给队首进程,并规定每个进程最多允许运行一个时间片。
  3. 时间片用尽:如果进程在时间片内没有完成,调度程序会中断该进程,将其放回就绪队列的末尾。
  4. 下一个进程:然后,调度程序将处理机分配给就绪队列中的新的队首进程,让它执行一个时间片。

这种算法的核心思想是将相等长度的时间片按照不变的顺序依次分配给每个就绪进程,而不考虑进程的优先级。这样,每个进程都有机会获得CPU时间,实现了公平的资源分配。在React中,这种算法的应用使得所有的任务都有机会被执行,从而提高了应用程序的性能。

描述一下react源码的渲染过程、在这个过程中的哪个阶段能够中断?

React的渲染过程可以分为两个主要阶段:Reconciliation阶段和Commit阶段。

  1. Reconciliation阶段:这个阶段也被称为“渲染阶段”,在这个阶段,React会遍历所有的组件,确定哪些组件需要更新。这个阶段可以被中断,这是因为如果有更高优先级的更新出现(例如用户交互),React需要能够中断正在进行的渲染,以便尽快处理这个高优先级的更新。
  2. Commit阶段:在这个阶段,React会将Reconciliation阶段产生的变化应用到DOM上。这个阶段不能被中断,因为一旦开始更新DOM,就必须将其完成,以确保UI的一致性。

在Reconciliation阶段,React使用了Fiber架构来实现渲染的可中断。每个React组件都对应一个Fiber节点,React通过管理这个Fiber节点树,来控制渲染的过程和优先级。具体来说,React会为每个更新分配一个优先级,然后按优先级顺序进行渲染。如果一个低优先级的更新正在进行中,而又出现了一个高优先级的更新,那么React可以中断低优先级的更新,先去执行高优先级的更新。

总的来说,React的渲染过程是高度优化的,它可以确保用户交互等高优先级的任务能够得到及时的处理,从而提供流畅的用户体验。

描述一下tcp协议的设计目标、具有怎样的特点、这些特点具体通过什么来实现的?

http协议具有什么样的特点?http1.0和1.1有什么样的变化?流水线形式有什么缺点?

HTTP协议的特点:

  • 简单快速:客户端向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

HTTP1.0和HTTP1.1的主要变化:

  • 连接方式:HTTP 1.0为短连接,即每次请求都需要与服务器建立一个TCP连接,请求结束后立即断开连接。HTTP 1.1则默认支持长连接,即在一个TCP连接中可以进行多个HTTP请求,从而减少了建立和关闭连接的消耗和延迟。
  • 状态响应码:HTTP/1.1中新加入了大量的状态码,例如100 (Continue) ——在请求大资源前的预热请求,206 (Partial Content) ——范围请求的标识码等。
  • 缓存处理:HTTP/1.1的缓存机制在HTTP/1.0的基础上,增加了更多细致的特性,例如请求头中的Cache-Control。
  • Host头处理:HTTP/1.1在请求头中加入了Host字段,使得一台服务器可以承载多个域名。

流水线形式的缺点:

  • 不能充分有效地利用作业时间:由于各个工序的作业工时不均等,可能出现工作等待现象。
  • 生产适应性较差:流水线生产的自身设置决定了流水线只能生产一种或一类产品,一旦市场需求变化了,它适应变化的能力则较弱。
  • 大量生产导致在制品库存占用量大:为了保证生产的经济性必须大量生产,这可能导致在制品库存占用量大。
  • 工人在流水线上工作比较单调、紧张、容易疲劳,不利于提高生产技术水平。
  • 对流水线进行调整和改组需要较大的投资和花费较多的时间

http2.0发送的内容要比1.1小很多,如何实现传输内容减小的?描述一下http的缓存策略?让你实现一个缓存方案,你会怎么设计?

HTTP/2.0相比于HTTP/1.1在传输内容上减小的主要原因有以下几点:

  1. 二进制分帧:HTTP/2.0通过二进制分帧来进行数据传输,将所有传输信息分割为更小的消息和帧,并对它们采用二进制格式的编码将其封装。
  2. 多路复用:HTTP/2.0允许同时通过单一的HTTP/2连接发起多重的请求-响应消息。每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级,最后再在另一端把它们重新组合起来。
  3. 头部压缩:HTTP/2.0使用HPACK算法对HTTP头部进行压缩,减少了头部传输的数据量,从而减少了网络延迟。

关于HTTP的缓存策略,主要有两种:

  1. 强制缓存:服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。主要通过ExpiresCache-Control实现。
  2. 对比缓存:浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。主要通过Last-Modified / If-Modified-SinceEtag / If-None-Match实现。

如果让我设计一个缓存方案,我会考虑以下几点:

  1. 确定缓存类型:根据需求确定是使用私有缓存还是共享缓存。
  2. 设置缓存策略:根据业务需求和数据变化频率,设置合适的强制缓存和对比缓存策略。
  3. 考虑版本控制:如果数据更新频繁,可以考虑使用版本号或者时间戳的方式来控制缓存。
  4. 异常处理:设计缓存更新失败或者缓存数据获取失败的处理机制,保证系统的健壮性。
  5. 缓存淘汰策略:当缓存满时,需要有合理的淘汰策略,如LRU(Least Recently Used)等。
  6. 监控和日志:实现缓存的监控和日志记录,以便于问题定位和性能优化。

以上就是我对于HTTP/2.0如何实现传输内容减小,HTTP的缓存策略以及如何设计一个缓存方案的理解和建议。希望对你有所帮助!

使用es6之前和之后两种方式继承(笔试)

在ES6之前,JavaScript使用原型链实现继承。以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Animal(name) {
this.name = name;
}

Animal.prototype.sayName = function() {
console.log('My name is ' + this.name);
};

function Dog(name) {
Animal.call(this, name);
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

var dog = new Dog('Spot');
dog.sayName(); // 输出: "My name is Spot"

在ES6中,我们可以使用classextends关键字来更简洁地实现继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
constructor(name) {
this.name = name;
}

sayName() {
console.log(`My name is ${this.name}`);
}
}

class Dog extends Animal {
constructor(name) {
super(name);
}
}

const dog = new Dog('Spot');
dog.sayName(); // 输出: "My name is Spot"

在这两个例子中,Dog类继承了Animal类,包括Animal的构造函数和sayName方法。在ES6的例子中,super关键字用于调用父类的构造函数。在ES5的例子中,我们使用Animal.call(this, name)达到同样的效果。在两种情况下,我们都可以创建一个Dog实例并调用sayName方法,该方法首先在Dog的原型链中查找,如果没有找到,就会继续在Animal的原型链中查找。这就是JavaScript的继承机制。希望这个解释对你有所帮助!

web安全方面需要注意哪些?

说一下position有哪些类型以及各个类型的特点?

position属性主要有以下几种类型,以及各自的特点:

  1. position: static;(静态定位):
    • 是元素的默认值,不受topbottomleftright属性影响,元素出现在正常的文档流中。
  2. position: relative;(相对定位):
    • 不影响元素本身特性(无论区块元素还是内联元素会保留其原本特性)。
    • 不会使元素脱离文档流(元素原本位置会被保留,即改变位置也不会占用新位置)。
    • 没有定位偏移量时对元素无影响(相对于自身原本位置进行偏移)。
    • 提升层级(用z-index样式的值可以改变一个定位元素的层级关系,从而改变元素的覆盖关系,值越大越在上面,z-index只能在position属性值为relativeabsolutefixed的元素上有效)。
  3. position: absolute;(绝对定位):
    • 使元素完全脱离文档流(在文档流中不再占位)。
    • 使内联元素在设置宽高的时候支持宽高(改变内联元素的特性)。
    • 使区块元素在未设置宽度时由内容撑开宽度(改变区块元素的特性)。
    • 相对于最近一个有定位的父元素偏移(若其父元素没有定位则逐层上找,直到document——页面文档对象)。
  4. position: fixed;(固定定位):
    • 生成固定定位的元素,相对于浏览器窗口进行定位。
  5. position: sticky;(粘性定位):
    • 粘性定位,该定位基于用户滚动的位置。
    • 它的行为就像position:relative;而当页面滚动超出目标区域时,它的表现就像position:fixed;,它会固定在目标位置。
    • 注意: Internet Explorer, Edge 15 及更早 IE 版本不支持 sticky 定位。 Safari 需要使用 -webkit- prefix
  6. position: inherit;
    • 规定应该从父元素继承 position 属性的值。
  7. position: initial;
    • 设置该属性为默认值,详情查看 CSS initial 关键字。
    • initial 关键字用于设置 CSS 属性为它的默认值。 initial 关键字可用于任何 HTML 元素上的任何 CSS 属性。

为什么读取usestate可以按顺序读取,在react源码内部是如何实现?

关于数据管理用过什么样的库?为什么能够做到让组件重新渲染?

代码题

调用接口获取数据然后展示出来

实现一个判断全等的函数获取数组里的某个元素

防抖节流的题

两数的求合乌写个下拉框模拟组件库中的 select 可以输入井更新

css样式题,写个动画从左移到右

1
2
3
4
5
6
7
8
9
10
11
12
@keyframes moveRight {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100px);
}
}

.myElement {
animation: moveRight 2s linear infinite;
}

实现一个控制前端并发类

实现一个判断全等的函数

1
2
3
4
5
let a = 10;
let b = '10';

console.log(a == b); // 输出:true
console.log(a === b); // 输出:false

实现 antd 的 Modal

使用 css html 画一个水平垂直的圆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.circle {
width: 100px;
height: 100px;
background-color: #000;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="circle"></div>
</body>
</html>

实现一个promise.race()

1
2
3
4
5
6
7
8
9
10
Promise.myRace = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
Promise.resolve(promise).then(
value => resolve(value),
reason => reject(reason)
);
});
});
};

实现一个倒计时组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<title>倒计时组件</title>
</head>
<body>
<p id="countdown">10</p>

<script>
var countdown = document.getElementById('countdown');
var timeLeft = 10;

var timerId = setInterval(function() {
timeLeft--;
countdown.textContent = timeLeft;

if (timeLeft <= 0) {
clearInterval(timerId);
}
}, 1000);
</script>
</body>
</html>

输入:[2999,2999,2999,2888,2888,2666,2499,2399,2299,1999,1999,1999]

记录用户在一个页面的停留时间,写一个计时组件,要求刷新页面还能正常显示计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
<head>
<title>倒计时组件</title>
</head>
<body>
<div id="timer"></div>
<script>
// 初始化计时器
let timer;

// 检查localStorage中是否有已经开始的计时器
if (localStorage.getItem('start_time')) {
// 如果有,计算已经过去的时间
let start_time = new Date(localStorage.getItem('start_time'));
let elapsed_time = Math.floor((new Date() - start_time) / 1000);

// 开始计时器
startTimer(elapsed_time);
} else {
// 如果没有,开始新的计时器
startTimer(0);
}

// 计时器函数
function startTimer(seconds) {
// 设置开始时间
localStorage.setItem('start_time', new Date());

// 设置初始时间
timer = seconds;

// 每秒更新计时器
setInterval(function() {
timer++;
let hours = Math.floor(timer / 3600);
let minutes = Math.floor((timer % 3600) / 60);
let seconds = timer % 60;

// 更新页面上的计时器
document.getElementById('timer').textContent = `${hours}:${minutes}:${seconds}`;
}, 1000);
}
</script>
</body>
</html>

数组扁平化

数组扁平化是一种常见的编程任务,它将一个嵌套多层的数组转换为只有一层的数组。这里有一个使用 JavaScript 实现的例子:

1
2
3
4
5
6
7
8
9
function flattenArray(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flattenArray(toFlatten) : toFlatten);
}, []);
}

// 使用示例
var arr = [1, [2, [3, 4], 5]];
console.log(flattenArray(arr)); // 输出: [1, 2, 3, 4, 5]

这个 flattenArray 函数使用了 reduceconcat 方法,递归地将所有层级的子数组扁平化。如果遇到一个元素是数组,就继续递归扁平化,否则直接加入到结果数组中。最后返回的结果就是一个扁平化的数组。希望这个例子对你有所帮助!

高阶组件实现按钮权限

在React中,我们可以使用高阶组件(Higher-Order Component,HOC)来实现按钮权限控制。高阶组件是一种模式,它接受一个组件作为参数,并返回一个新的组件,这个新的组件具有额外的功能或行为。

以下是一个简单的例子,展示了如何使用高阶组件来实现按钮权限控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import PropTypes from 'prop-types';

// 检查当前用户是否有功能编码对应的权限
function checkAuth(functionName) {
// 这里是权限检查的逻辑,例如从sessionStorage中获取权限列表并进行判断
// 这部分需要根据实际的权限管理系统进行实现
// ...
}

// 权限组件封装
class AuthWrapper extends React.Component {
render() {
return checkAuth(this.props.functionName) && this.props.children;
}
}

AuthWrapper.propTypes = {
functionName: PropTypes.string,
};

// 使用方式:
// <AuthWrapper functionName='权限按钮唯一标识'>
// <Button>添加</Button>
// </AuthWrapper>

在这个例子中,AuthWrapper是一个高阶组件,它接受一个functionName属性,这个属性是权限按钮的唯一标识。然后在render方法中,我们调用checkAuth函数来检查当前用户是否有这个权限,如果有权限,就渲染子组件(即按钮),否则不渲染。

这只是一个基本的例子,实际的实现可能会更复杂,例如你可能需要处理多个权限,或者在没有权限时显示一个禁用的按钮等等。但是基本的思路是一样的,就是使用高阶组件来封装权限检查的逻辑,然后在需要进行权限控制的地方使用这个高阶组件。这样可以大大简化代码,提高复用性,也更符合React的组件化思想。

事件循环机制例题,写出执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('1');

setTimeout(function() {
console.log('2');
}, 0);

Promise.resolve().then(function() {
console.log('3');
}).then(function() {
console.log('4');
});

console.log('5');

这段代码的执行顺序是:

  1. 首先,同步代码会立即执行,所以 console.log('1')console.log('5') 会首先被打印出来。
  2. 然后,事件循环会查看任务队列,看是否有任何待处理的任务。在这个例子中,setTimeout 函数已经被放入了宏任务队列,而 Promise.resolve().then() 则被放入了微任务队列。
  3. 由于事件循环优先处理微任务队列,所以 console.log('3')console.log('4') 会接下来被打印出来。
  4. 最后,当微任务队列被清空后,事件循环会开始处理宏任务队列。因此,console.log('2') 会被打印出来。

所以,这段代码的输出顺序是:15342

对象拍平

1
2
3
4
sum(1,2,3). sumOf(); // 6
sum(2 , 3)(2).sumOf(); // 7
sum(1)(2)(3)(4).sumOf(); //10
sum(2)(4 , 1)(2).sumOf(); //9

共用 model 怎么做

设计一个对象 cache,他支持下列两个基本操作:(十分钟)- set(id.object), 根据 id 设置对象:- get(id):根据 id得到一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Cache {
constructor() {
this.store = new Map();
}

set(id, object) {
this.store.set(id, object);
}

get(id) {
return this.store.get(id);
}
}

// 使用示例
let cache = new Cache();
cache.set('123', {name: 'Copilot'});
console.log(cache.get('123')); // 输出:{ name: 'Copilot' }

mobx

electron 主窗口副窗口通信

在 Electron 中,主窗口和副窗口之间的通信可以通过 Electron 的 IPC (Inter-Process Communication) 机制来实现。这种机制允许主进程和渲染进程相互发送消息和数据。

以下是一个基本的示例,展示了如何在 Electron 中使用 ipcMainipcRenderer 进行通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在主进程中
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.reply('asynchronous-reply', 'pong')
})

ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})

// 在渲染器进程 (网页) 中
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')

在这个例子中,渲染进程(网页)通过 ipcRenderer.send 发送一个异步消息。然后,主进程监听这个消息,并通过 event.reply 回复消息。

如果你想在两个窗口之间进行通信,你可以使用主进程作为中介。例如,窗口 A 的渲染进程可以发送一个消息到主进程,然后主进程再将这个消息转发给窗口 B 的渲染进程。这样,你就可以实现多窗口之间的通信了。

另外,你也可以使用 WebContents 对象在窗口之间直接发送消息。这种方法不需要通过主进程转发,可以直接在窗口 A 和窗口 B 之间进行通信。

electron 存储

1
2
3
4
5
6
7
8
9
10
11
12
const Store = require('electron-store');
const store = new Store();

store.set('unicorn', '🦄');
console.log(store.get('unicorn')); //=> '🦄'

// Use dot-notation to access nested properties
store.set('foo.bar', true);
console.log(store.get('foo')); //=> {bar: true}

store.delete('unicorn');
console.log(store.get('unicorn')); //=> undefined

手写防抖节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 防抖
function debounce(fn, delay) {
let timer = null;
return function() {
let context = this;
let args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}

// 节流
function throttle(fn, delay) {
let last = 0;
return function() {
let now = Date.now();
if (now - last > delay) {
last = now;
fn.apply(this, arguments);
}
};
}

找出二叉树最长路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function TreeNode(val, left, right) {
this.val = (val===undefined ? 0 : val)
this.left = (left===undefined ? null : left)
this.right = (right===undefined ? null : right)
}

var diameterOfBinaryTree = function(root) {
let diameter = 0;

const longestPath = (node) => {
if (node === null) {
return 0;
}
// 递归找到左右子树的最长路径
let leftPath = longestPath(node.left);
let rightPath = longestPath(node.right);

// 更新直径,如果左路径加右路径更大的话
diameter = Math.max(diameter, leftPath + rightPath);

// 返回节点的最长路径
return Math.max(leftPath, rightPath) + 1;
};

longestPath(root);

return diameter;
};

node.js文件异步写入问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('fs');
const params = ["config.json", '{"data": "123456123456"}', "utf-8", function(err, data) {
if (err) {
this.fail && this.fail(err);
return;
}
this.success && this.success(data);
}.bind({
success: function(data) {
console.log("success")
}
})];
fs.writeFile(...params);
params[1] = '{"data": "123123"}';
fs.writeFile(...params);

  • 标题: 前端面试题
  • 作者: Fenger
  • 创建于 : 2024-04-08 18:51:00
  • 更新于 : 2024-04-09 18:42:05
  • 链接: https://www.fenger.net.cn/2024/04/08/Front-end interview questions/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
此页目录
前端面试题