答案

44 阅读9分钟

一、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 的事件循环机制):

  1. 同步代码执行(调用栈):     -   console.log('Start') 立即执行 → 输出 "Start"     -   setTimeout 回调被放入 Web APIs 环境(计时器开始计时,但延迟为 0)     -   第一个 Promise.resolve().then() 将回调放入 微任务队列(Microtask Queue)     -   第二个 Promise.resolve().then() 也将回调放入 微任务队列     -   console.log('End') 立即执行 → 输出 "End"
  2. 微任务队列执行(优先级高于宏任务):     -   调用栈清空后,事件循环先处理所有微任务:         -   第一个 Promise 回调执行 → 输出 "Promise 1",内部的 setTimeout 被放入 Web APIs         -   第二个 Promise 回调执行 → 输出 "Promise 2"
  3. 宏任务队列执行:     -   微任务队列清空后,事件循环处理宏任务:         -   第一个 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 
        {...restrender={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: nullloading: trueerror: 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.statereturn 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'; }

核心概念解释

  1.  Proxy:使用 ES6 Proxy 来拦截对象操作(get、set、deleteProperty 等)

  2.  依赖收集 (track) :在属性被访问时收集当前活动的 effect

  3.  触发更新 (trigger) :在属性被修改时触发所有相关的 effect 执行

  4.  WeakMap 和 Map:用于高效地存储和查找依赖关系

  5.  递归响应式:当访问嵌套对象时,会自动将其转换为响应式对象

 

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 的场景
  1.  需要明确监听特定依赖:当你只关心某些特定响应式数据的变化时

  2.  需要配置选项:如需要立即执行(immediate)、深度监听(deep)或自定义比较函数

  3.  需要访问新旧值:回调函数提供了新旧值参数

  4.  需要更精确的控制:可以精确指定哪些依赖触发回调 示例

 // 监听多个响应式数据
 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 的场景
  1.  副作用依赖于多个响应式数据:当副作用函数内部使用了多个响应式数据时
  2.  不需要访问新旧值:只关心依赖变化时重新执行
  3.  希望代码更简洁:不需要显式声明依赖项
  4.  依赖关系可能动态变化:当副作用函数内部使用的响应式数据可能变化时 示例
 // 副作用依赖于多个响应式数据
 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:自动追踪所有依赖,可能导致更多的重新执行,但在简单场景下代码更简洁

总结

特性watchwatchEffect
依赖声明显式声明自动追踪
触发时机依赖变化时立即执行一次,然后依赖变化时重新执行
访问新旧值支持不支持
配置选项支持(immediate, deep 等)不支持
适用场景精确监听特定依赖副作用依赖多个响应式数据
代码简洁性较低较高

选择建议

  •   当需要精确控制哪些依赖触发回调时,使用 watch
  •   当副作用函数内部使用了多个响应式数据且不需要访问新旧值时,使用 watchEffect
  •   在大多数情况下,watch 提供了更精确的控制,而 watchEffect 提供了更简洁的语法

四、性能与设计

1、分析并优化以下代码

当前代码分析

当前的 DataCache 类实现了一个简单的缓存系统,具有以下特点:

  1. 使用 Map 存储缓存数据
  2. 使用对象 timers 存储定时器
  3. 提供了 set 方法来设置缓存项及其过期时间(TTL)

潜在问题

  1. 内存泄漏风险:当 DataCache 实例被销毁时,定时器不会被清除
  2. 键冲突timers 使用普通对象存储定时器,如果键是数字或包含特殊字符可能会有问题
  3. 缺乏错误处理:没有处理可能的异常情况
  4. 缺乏其他缓存方法:如 gethasdelete 等常用缓存操作
  5. 定时器管理:当缓存项被手动删除时,对应的定时器不会被清除

优化建议

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或其他淘汰策略
  }
}

优化说明

  1. 定时器管理改进

    • 使用 Map 替代普通对象存储定时器,避免键冲突问题
    • 添加了 clearTimer 方法来统一处理定时器清除
    • 在 delete 和 clear 方法中确保清除定时器
  2. 添加常用缓存方法

    • get(key):获取缓存值
    • has(key):检查缓存是否存在
    • delete(key):删除缓存项及其定时器
    • clear():清空所有缓存和定时器
  3. 内存管理

    • 添加了 clear 方法来清理所有缓存和定时器
    • 确保在删除缓存项时也删除对应的定时器
  4. 扩展性

    • 添加了 setSize 方法占位符,便于未来实现缓存大小限制和淘汰策略

2、设计前端监控系统SDK