一、JS基础
1、写出以下代码的输出顺序并解释原因
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => {
console.log('Promise 1');
setTimeout(() => console.log('Nested Timeout'), 0);
});
Promise.resolve().then(() => console.log('Promise 2'));
console.log('End');
输出顺序为:
Start End Promise1 Promise2 Nested Timeout Timeout
原因解释(基于 JavaScript 的事件循环机制):
- 同步代码执行(调用栈):
-
console.log('Start')立即执行 → 输出 "Start" -setTimeout回调被放入 Web APIs 环境(计时器开始计时,但延迟为 0) - 第一个Promise.resolve().then()将回调放入 微任务队列(Microtask Queue) - 第二个Promise.resolve().then()也将回调放入 微任务队列 -console.log('End')立即执行 → 输出 "End" - 微任务队列执行(优先级高于宏任务):
- 调用栈清空后,事件循环先处理所有微任务:
- 第一个 Promise 回调执行 → 输出 "Promise 1",内部的
setTimeout被放入 Web APIs - 第二个 Promise 回调执行 → 输出 "Promise 2" - 宏任务队列执行:
- 微任务队列清空后,事件循环处理宏任务:
- 第一个
setTimeout回调(Web APIs 中的 "Timeout")被推入任务队列 → 输出 "Timeout" - 第一个 Promise 中嵌套的setTimeout回调(Web APIs 中的 "Nested Timeout")也被推入任务队列 → 输出 "Nested Timeout"
2、找出二叉树中所有根节点到叶子节点的路径
可以使用深度搜索优先的方式
class Node {
constructor(val, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function findPaths(root) {
const paths = [];
function dfs(node, currentPath) {
if (!node) return;
// 将当前节点的值添加到路径中
currentPath += node.val.toString();
// 如果是叶子节点,将路径添加到结果中
if (!node.left && !node.right) {
paths.push(currentPath);
return;
}
// 递归处理左右子节点
currentPath += '->';
dfs(node.left, currentPath);
dfs(node.right, currentPath);
}
dfs(root, '');
return paths;
}
// 示例用法
const root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.right = new Node(5);
console.log(findPaths(root)); // 输出: ["1->2->5", "1->3"]
3、实现防抖函数
/**
* 防抖函数
* @param {Function} func 需要防抖的函数
* @param {number} delay 延迟时间(毫秒)
* @returns {Function} 返回防抖处理后的函数
*/
function debounce(func, delay) {
let timer = null;
return function(...args) {
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 设置新的定时器
timer = setTimeout(() => {
func.apply(this, args); timer = null;
}, delay);
};
}
二、前端框架设计
1、实现组件 withAuth:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const withAuth = (WrappedComponent, requiredRoles = []) => {
return (props) => {
const currentUser = { roles: ['user'], // 实际应用中从状态管理获取
isAuthenticated: true, // 是否已登录
};
const hasPermission = requiredRoles.some(role =>
currentUser.roles.includes(role)
);
// 如果未登录,重定向到登录页
if (!currentUser.isAuthenticated) {
return <Redirect to="/login" />;
}
// 如果有权限,渲染组件;否则显示未授权页面
return hasPermission ? (
<WrappedComponent {...props} />
) :
(
<Unauthorized />
);
};
};
// 使用示例
const ProtectedRoute = ({ component: Component, roles, ...rest }) => (
<Route
{...rest}
render={props => (
<withAuth(Component, roles) {...props} />
)}
/>
);
// 在路由配置中使用
<ProtectedRoute path="/admin" component={AdminDashboard} roles={['admin']} />
2、解释Render Props模式并给出使用示例
Render Props 是一种 React 中共享组件逻辑的技术模式,它通过组件将渲染逻辑作为函数属性(prop)传递给子组件来实现代码复用
什么是 Render Props?
Render Props 是一个函数属性,该函数定义了组件应该如何渲染其内容。组件接收一个返回 React 元素的函数作为 prop,并在适当的时候调用这个函数来决定渲染什么内容。
核心思想
- 使用一个 prop(通常命名为
render或children,但也可以是其他名称) - 这个 prop 是一个函数,接收数据作为参数
- 组件内部在适当的时候调用这个函数来决定渲染内容
示例:数据获取组件
import React, { Component } from 'react';
// 数据获取组件
class DataProvider extends Component {
state = {
data: null,
loading: true,
error: null
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
try {
// 模拟 API 调用
const response = await fetch('https://api.example.com/data');
const data = await response.json();
this.setState({ data, loading: false });
}
catch (error) {
this.setState({ error, loading: false });
}
};
render() {
const { data, loading, error } = this.state;
return this.props.render(
{ data, loading, error, retry: this.fetchData }
);
}
}
// 使用示例
const UserList = () => {
return (
<DataProvider>
{({ data, loading, error, retry }) => {
if (loading) return <div>Loading...</div>;
if (error)
return
<div>Error: {error.message} <button onClick={retry}>Retry</button>
</div>;
return (
<div>
<h1>User List</h1>
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li> )
)}
</ul>
</div>
);
}}
</DataProvider>
);
};
3、手写伪代码实现Vue3的 reactive() 核心逻辑
// 存储响应式对象和其对应的依赖跟踪器
const targetMap = new WeakMap();
// 获取对象上所有键的依赖集合
function getDependMap(target, createIfNotFound = true) {
let depsMap = targetMap.get(target);
if (!depsMap && createIfNotFound) {
targetMap.set(target, (depsMap = new Map()));
}
return depsMap;
}
// 获取特定属性键的依赖集合
function getDependSet(target, key, createIfNotFound = true) {
const depsMap = getDependMap(target, createIfNotFound);
let deps = depsMap.get(key);
if (!deps && createIfNotFound) {
depsMap.set(key, (deps = new Set()));
}
return deps;
}
// 跟踪当前正在运行的
effect let activeEffect = null;
// 依赖收集函数
function track(target, key) {
if (activeEffect) {
const deps = getDependSet(target, key);
deps.add(activeEffect);
}
}
// 触发更新函数
function trigger(target, key) {
const deps = getDependSet(target, key, false);
if (deps) {
deps.forEach(effect => {
if (effect.scheduler) {
effect.scheduler();
} else {
effect();
}
});
}
}
// 创建响应式对象的
handler const reactiveHandler = {
get(target, key, receiver) {
// 收集依赖
track(target, key);
const result = Reflect.get(target, key, receiver);
// 如果是对象,递归调用
reactive if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 只有当值实际改变时才触发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
trigger(target, key);
}
return result;
}
};
// 响应式函数主入口
function reactive(target) {
// 只处理对象类型
if (!isObject(target)) {
return target;
}
// 如果已经是响应式对象,直接返回
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// 创建代理
const proxy = new Proxy(target, reactiveHandler);
// 标记为响应式对象
Object.defineProperty(proxy, ReactiveFlags.IS_REACTIVE, {
value: true, enumerable: false, configurable: false, writable: false
});
return proxy;
}
// 辅助函数和常量
const ReactiveFlags = { IS_REACTIVE: '__v_isReactive' };
function isObject(v) { return v !== null && typeof v === 'object'; }
核心概念解释
-
Proxy:使用 ES6 Proxy 来拦截对象操作(get、set、deleteProperty 等)
-
依赖收集 (track) :在属性被访问时收集当前活动的 effect
-
触发更新 (trigger) :在属性被修改时触发所有相关的 effect 执行
-
WeakMap 和 Map:用于高效地存储和查找依赖关系
-
递归响应式:当访问嵌套对象时,会自动将其转换为响应式对象
4、解释 watch和watchEffect的区别及适用场景
在 Vue 3 的组合式 API 中,watch 和 watchEffect 都用于响应式地执行副作用(如数据获取、DOM 操作等),但它们在行为和使用方式上有显著区别。
核心区别
1. 触发时机
-
watch:明确指定要监听的响应式依赖,只有当依赖项发生变化时才会触发回调 -
watchEffect:立即执行一次副作用函数,然后自动追踪其依赖,当依赖项变化时重新执行
2. 依赖追踪
-
watch:需要显式声明要监听的依赖项 -
watchEffect:自动追踪函数内部使用的所有响应式依赖
3. 清理机制
-
watch:可以通过返回一个清理函数来处理副作用的清理 -
watchEffect:同样支持返回清理函数,且会在每次重新执行前自动调用上一次的清理函数
4. 执行时机控制
-
watch:可以配置立即执行(immediate: true)和深度监听(deep: true) -
watchEffect:总是立即执行一次,无法配置深度监听(因为它自动追踪依赖)
代码示例对比
watch 示例
import { ref, watch } from 'vue';
const count = ref(0);
const double = ref(0);
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`);
double.value = newVal * 2;
});
// 只有当 count 变化时才会触发
count.value++; // 触发回调
count.value++; // 再次触发回调
watchEffect 示例
import { ref, watchEffect } from 'vue';
const count = ref(0);
const double = ref(0);
watchEffect(() => {
console.log(`count is ${count.value}`);
double.value = count.value * 2;
});
// 立即执行一次,然后每次 count 变化时重新执行
count.value++; // 触发重新执行
count.value++; // 再次触发重新执行
适用场景
适合使用 watch 的场景
-
需要明确监听特定依赖:当你只关心某些特定响应式数据的变化时
-
需要配置选项:如需要立即执行(
immediate)、深度监听(deep)或自定义比较函数 -
需要访问新旧值:回调函数提供了新旧值参数
-
需要更精确的控制:可以精确指定哪些依赖触发回调 示例:
// 监听多个响应式数据
watch([count, anotherRef], ([newCount, newAnother], [oldCount, oldAnother]) => {
console.log(`count changed from ${oldCount} to ${newCount}`);
console.log(`anotherRef changed from ${oldAnother} to ${newAnother}`);
});
// 监听对象属性变化(需要 deep: true)
watch(
() => obj.value.nestedProperty,
(newVal) => {
console.log('nestedProperty changed:', newVal);
},
{ deep: true }
);
适合使用 watchEffect 的场景
- 副作用依赖于多个响应式数据:当副作用函数内部使用了多个响应式数据时
- 不需要访问新旧值:只关心依赖变化时重新执行
- 希望代码更简洁:不需要显式声明依赖项
- 依赖关系可能动态变化:当副作用函数内部使用的响应式数据可能变化时 示例:
// 副作用依赖于多个响应式数据
watchEffect(() => {
console.log('count:', count.value);
console.log('another:', another.value);
// 当 count 或 another 变化时都会重新执行
});
// 依赖关系可能变化
const condition = ref(true);
const data1 = ref(0);
const data2 = ref(0);
watchEffect(() => {
if (condition.value) {
console.log('data1:', data1.value);
// 当 condition 为 true 时依赖 data1
} else {
console.log('data2:', data2.value);
// 当 condition 为 false 时依赖 data2
}
});
性能考虑
-
watch:更精确的依赖追踪,适合性能敏感的场景,可以避免不必要的重新执行 -
watchEffect:自动追踪所有依赖,可能导致更多的重新执行,但在简单场景下代码更简洁
总结
| 特性 | watch | watchEffect |
|---|---|---|
| 依赖声明 | 显式声明 | 自动追踪 |
| 触发时机 | 依赖变化时 | 立即执行一次,然后依赖变化时重新执行 |
| 访问新旧值 | 支持 | 不支持 |
| 配置选项 | 支持(immediate, deep 等) | 不支持 |
| 适用场景 | 精确监听特定依赖 | 副作用依赖多个响应式数据 |
| 代码简洁性 | 较低 | 较高 |
选择建议
- 当需要精确控制哪些依赖触发回调时,使用
watch - 当副作用函数内部使用了多个响应式数据且不需要访问新旧值时,使用
watchEffect - 在大多数情况下,
watch提供了更精确的控制,而watchEffect提供了更简洁的语法
四、性能与设计
1、分析并优化以下代码
当前代码分析
当前的 DataCache 类实现了一个简单的缓存系统,具有以下特点:
- 使用
Map存储缓存数据 - 使用对象
timers存储定时器 - 提供了
set方法来设置缓存项及其过期时间(TTL)
潜在问题
- 内存泄漏风险:当
DataCache实例被销毁时,定时器不会被清除 - 键冲突:
timers使用普通对象存储定时器,如果键是数字或包含特殊字符可能会有问题 - 缺乏错误处理:没有处理可能的异常情况
- 缺乏其他缓存方法:如
get、has、delete等常用缓存操作 - 定时器管理:当缓存项被手动删除时,对应的定时器不会被清除
优化建议
class DataCache {
constructor() {
this.cache = new Map();
// 使用 Map 替代普通对象存储定时器,避免键冲突
this.timers = new Map();
}
set(key, value, ttl) {
// 清除已有的定时器(如果存在)
this.clearTimer(key);
// 设置缓存值
this.cache.set(key, value);
// 设置新的定时器
const timer = setTimeout(() => {
this.delete(key);
}, ttl);
// 存储定时器
this.timers.set(key, timer);
}
get(key) {
return this.cache.get(key);
}
has(key) {
return this.cache.has(key);
}
delete(key) {
// 清除缓存
this.cache.delete(key);
// 清除定时器
this.clearTimer(key);
}
clear() {
// 清除所有缓存
this.cache.clear();
// 清除所有定时器
for (const timer of this.timers.values()) {
clearTimeout(timer);
}
this.timers.clear();
}
clearTimer(key) {
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
this.timers.delete(key);
}
}
// 可选:添加大小限制功能
setSize(maxSize) {
this.maxSize = maxSize;
// 实现LRU或其他淘汰策略
}
}
优化说明
-
定时器管理改进:
- 使用
Map替代普通对象存储定时器,避免键冲突问题 - 添加了
clearTimer方法来统一处理定时器清除 - 在
delete和clear方法中确保清除定时器
- 使用
-
添加常用缓存方法:
get(key):获取缓存值has(key):检查缓存是否存在delete(key):删除缓存项及其定时器clear():清空所有缓存和定时器
-
内存管理:
- 添加了
clear方法来清理所有缓存和定时器 - 确保在删除缓存项时也删除对应的定时器
- 添加了
-
扩展性:
- 添加了
setSize方法占位符,便于未来实现缓存大小限制和淘汰策略
- 添加了