快手三面(准备二)

快手用户增长部门正积极拥抱AI技术,通过“技术驱动+生态协同”的策略来推动用户规模和活跃度的提升。其核心是利用AI优化内容生态、提升用户体验、赋能创作者和商家,并积极拓展海外市场。
在这里插入图片描述

下面是一个主要布局方向的表格总结:

布局方向具体应用与举措预期效果/现状
📊 用户数据基本盘2025年Q1平均日活跃用户(DAU)4.08亿,月活跃用户(MAU)7.12亿,DAU创历史新高。用户增长虽放缓,但基本盘稳固,寻求通过AI提升存量用户价值。
🤖 核心AI引擎:可灵可灵AI(视频生成大模型)是核心驱动。成立可灵AI事业部(一级部门),迭代快(发布以来超20次迭代,如2.0、2.1版本)。提供更具性价比的视频生成服务(如降价65%)。全球用户超2200万,月活增长25倍。Q1收入超1.5亿元70%收入来自海外付费用户
🛍️ 电商场景深度融合AI试衣:提升购物体验和转化。AI快捷评论:基于ASR模型生成互动评论,提升直播间氛围。智能客服:问题解决率提升至80%,大幅降本增效。直播切片:AI自动生成商品讲解等切片视频,日均GMV同比增长超300%。提升用户购买决策效率购物体验,2025年Q1电商GMV同比增长15.4%至3323亿元。
📈 内容推荐与广告提效生成式推荐大模型OneRec:端到端模型,更精准的内容分发。覆盖主站约25%流量,未来计划提升至50%-60%。测试中带来用户时长超1%的增长AIGC营销素材:帮助商家低成本生成广告素材,日均广告消耗达3000万元。提升用户粘性和使用时长,优化广告投放效率,2025年Q线上营销服务收入180亿元(同比增8%)。
🌍 全球化拓展可灵AI海外表现强劲:70%收入来自海外用户。重点市场:巴西是核心市场之一,正拓展中东和东南亚(如印尼SnackVideo)。本地化运营:加大投入,计划扩展多语言市场。2025年Q1海外收入13.2亿元(同比增32.7%),经营利润实现扭亏为盈。可灵AI成为全球化竞争的利器。
⚙️ 赋能创作者与商家降低创作门槛:可灵帮助用户轻松生成高质量视频。智能经营工具:如“智能开播”工具帮助新商家生成直播话术。新商扶持:AI智能顾问提供全链路陪伴服务,新入驻商家数量同比增长超30%。繁荣平台生态,吸引更多创作者和商家入驻,形成良性循环。

💡 底层逻辑与挑战

快手用户增长部门的AI布局,其底层逻辑是**“三重螺旋”增长范式**:

  1. 技术螺旋:以可灵等大模型矩阵为引擎。
  2. 经营螺旋:AI赋能商家全面提升经营效率。
  3. 生态螺旋:AI最终反哺并重塑整个平台生态,驱动业务增长。

当然,也面临一些挑战:

  • 用户增长天花板:国内用户增速放缓,存量竞争激烈。
  • AI贡献占比仍小:尽管增速快,但AI业务收入占总营收比例仍较低(约1%),需要时间成长为核心收入来源。
  • 平衡“AI”与“人情味”:需警惕过度依赖AI可能导致内容同质化,如何保持快手独特的“老铁”社区氛围和真实感是一大考验。
  • 激烈的市场竞争:不仅来自传统短视频平台,也面临其他AI大模型厂商的竞争。

✅ 总结

总的来说,快手用户增长部门正从追求用户数量的高速增长,转向依托AI技术驱动用户价值和商业价值的深度增长。通过可灵这样的AI基础设施,快手不仅在优化内部效率和用户体验,更在构建一个对B端和C端都更具吸引力和效率的生态系统,并为全球化扩张提供了新的利器。


一面

1. animation 和 transition 的区别

特性transition (过渡)animation (动画)
功能用于实现状态变化的过渡效果。用于创建复杂的关键帧动画
控制相对简单,需要触发(如:hover, 添加类)。功能强大,可自动播放、控制循环次数、方向、暂停等。
关键帧隐式定义(从A状态到B状态)。通过 @keyframes 显式定义多个关键帧和中间状态。
循环不能循环播放。可以无限循环 (infinite)。
中间状态只能定义开始和结束两个状态。可以定义任意多个关键帧和中间状态。
触发需要事件触发(如鼠标悬停、JS添加类)。可以自动立即播放,也可由事件触发。

总结transition 适合简单的、由事件触发的状态转变动画。animation 适合复杂的、自动的、需要精细控制多个关键帧的动画。

2. 手撕动画:div先下移100px,再右移100px

核心思路是使用 animation@keyframes,通过关键帧控制位移顺序。

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    .box {
      width: 100px;
      height: 100px;
      background-color: red;
      /* 应用动画 */
      animation: move 2s ease-in-out forwards;
    }

    @keyframes move {
      0% {
        transform: translate(0, 0);
      }
      50% {
        /* 先下移 */
        transform: translate(0, 100px);
      }
      100% {
        /* 再右移(同时保持下移的状态) */
        transform: translate(100px, 100px);
      }
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>
  • forwards 属性使得动画在结束后保持在最后一帧的状态。
  • 通过设置 50%100% 两个关键帧,精确控制了移动的顺序。

3. 手撕:倒计时组件

这是一个基于 React Hooks 的功能完整的倒计时组件。

import React, { useState, useEffect, useRef } from 'react';

const CountdownTimer = ({ initialTime = 60, onEnd }) => {
  const [timeLeft, setTimeLeft] = useState(initialTime);
  const [isRunning, setIsRunning] = useState(false);
  const timerRef = useRef(null);

  // 效果:计时器逻辑
  useEffect(() => {
    if (isRunning && timeLeft > 0) {
      timerRef.current = setInterval(() => {
        setTimeLeft(prevTime => prevTime - 1);
      }, 1000);
    } else if (timeLeft === 0) {
      clearInterval(timerRef.current);
      onEnd?.(); // 可选的回调,计时结束时触发
    }

    // 清理函数:组件卸载或依赖变化时清除定时器,防止内存泄漏
    return () => clearInterval(timerRef.current);
  }, [isRunning, timeLeft, onEnd]);

  const start = () => setIsRunning(true);
  const pause = () => setIsRunning(false);
  const reset = () => {
    setIsRunning(false);
    setTimeLeft(initialTime);
  };

  // 格式化时间显示 (e.g., 65 -> "01:05")
  const formatTime = (seconds) => {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  };

  return (
    <div>
      <h1>{formatTime(timeLeft)}</h1>
      <button onClick={start} disabled={isRunning || timeLeft === 0}>
        开始
      </button>
      <button onClick={pause} disabled={!isRunning}>
        暂停
      </button>
      <button onClick={reset}>重置</button>
    </div>
  );
};

export default CountdownTimer;

4. CDN 为什么可以加速?

CDN(内容分发网络)通过以下方式加速网站访问:

  1. 地理分布:将你的静态资源(JS, CSS, 图片等)缓存到全球各地的边缘节点服务器上。
  2. 就近访问:用户请求资源时,CDN会智能调度,将请求重定向到离用户地理距离最近、网络路径最优的边缘节点,极大减少网络传输延迟(RTT)。
  3. 减轻源站负载:大多数请求由边缘节点响应,大大降低了源服务器的带宽和计算压力。
  4. 优化传输:大型CDN提供商拥有优质的骨干网和跨网互联资源,优化了网络传输路径。

5. splitchunks 怎么配置?

SplitChunksPlugin 是 Webpack 用于代码分割的插件。其核心配置在 optimization.splitChunks 中:

// webpack.config.js
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有类型的chunk进行处理(async, initial, all)
      cacheGroups: {
        // 1. 分离第三方库(node_modules)
        vendor: {
          test: /[\\/]node_modules[\\/]/, // 匹配路径
          name: 'vendors', //  chunk的名称
          priority: 10, // 优先级,防止同时满足多个条件时被打包到优先级低的组里
        },
        // 2. 分离公共代码(被多个chunk引用的模块)
        common: {
          name: 'common',
          minChunks: 2, // 被2个及以上chunk引用才提取
          minSize: 0, // 生成chunk的最小大小
          priority: 5,
        },
      },
    },
  },
};

常用配置项

  • chunks: 选择哪些代码块进行优化。
  • minSize: 生成 chunk 的最小体积(字节)。
  • minChunks: 被引用次数超过该值才会被提取。
  • maxAsyncRequests: 按需加载时的最大并行请求数。
  • maxInitialRequests: 入口点的最大并行请求数。
  • cacheGroups: 定义如何对模块进行分组。

6. 前端如何进行缓存?

前端缓存主要分为以下几类:

  1. HTTP 缓存(浏览器缓存):

    • 强缓存Cache-Control (max-age), Expires。浏览器直接读取本地缓存,不发起请求。
    • 协商缓存Last-Modified / If-Modified-Since, ETag / If-None-Match。浏览器询问服务器资源是否过期,未过期则返回304状态码,使用本地缓存。
  2. Service Worker 缓存

    • 可以拦截网络请求,实现复杂的离线缓存策略(Cache First, Network First等)。
  3. 数据存储缓存

    • localStorage / sessionStorage:存储简单的键值对数据。
    • IndexedDB:存储大量结构化数据。
    • Cookie:通常用于身份验证等小数据存储。

7. React 和 Vue 的区别

方面ReactVue
设计理念函数式编程,推崇“单向数据流”和“不可变性”。响应式编程,基于可变数据的“双向绑定”(在表单等特定元素上)。
核心语法JSX(JavaScript XML),在JS中写类HTML结构。单文件组件(SFC),将模板、逻辑、样式封装在一个.vue文件中。
状态管理使用 useState, useReducer Hooks 或状态管理库(Redux)。使用 data 返回响应式对象,或 Pinia/Vuex。
更新机制状态更新后,需要通过 setStatedispatch 触发重新渲染。直接修改状态即可,响应式系统自动触发相关组件更新。
生态系统更灵活,社区提供了大量选择,但需要自行决策和集成(如路由、状态管理)。集成化,官方维护了路由(Vue Router)、状态管理(Pinia)等,开箱即用。
学习曲线对JavaScript基础要求更高,概念相对更少但更抽象。模板更易上手,API设计对初学者更友好,高级特性需要更多学习。

8. ESM 和 CMJ 的区别

特性ESM (ECMAScript Modules)CMJ (CommonJS)
语法import / exportrequire() / module.exports
加载方式静态编译时加载,支持Tree Shaking。动态运行时加载。
运行环境浏览器原生支持和Node.js(v12+)。Node.js环境。
值引用动态只读引用。导入的是值的引用,模块内修改会影响外部。值的拷贝(浅拷贝)。导入的是导出值的副本。
顶层位置只能在模块顶层使用。可以在代码任何地方使用。
异步原生支持异步加载。同步加载。
this指向顶层thisundefined顶层this指向当前模块。

二面

1. i18n 原理

国际化(i18n)的核心原理是资源替换

  1. 定义语言包:为每种支持的语言创建一个资源文件(通常是JSON或JS对象),包含所有需要翻译的文本键值对。
    // en.json
    { "welcome": "Welcome", "button_text": "Submit" }
    
    // zh-CN.json
    { "welcome": "欢迎", "button_text": "提交" }
    
  2. 建立Provider/Context:在应用顶层提供一个国际化上下文,管理当前语言和切换语言的方法。
  3. 翻译函数:提供一个函数(如 t('welcome')),它根据当前选择的语言,从对应的语言包中查找键(welcome)并返回对应的翻译值("Welcome""欢迎")。
  4. 组件集成:UI组件使用这个翻译函数来渲染文本,而不是硬编码字符串。
  5. 切换语言:当用户切换语言时,更新Context中的语言状态,触发组件重新渲染,从而加载新的语言包资源。

2. 强缓存和协商缓存

  • 强缓存:浏览器不向服务器发送请求,直接使用本地缓存。通过 Cache-ControlExpires HTTP响应头控制。

    • Cache-Control: max-age=3600 表示资源在3600秒内有效。
    • Expires: Wed, 21 Oct 2025 07:28:00 GMT 是一个绝对时间戳(HTTP/1.0,优先级低于Cache-Control)。
  • 协商缓存:浏览器向服务器发送请求,询问缓存是否依然有效。如果有效,服务器返回304 Not Modified,浏览器使用本地缓存。通过 Last-Modified / If-Modified-SinceETag / If-None-Match 控制。

    • Last-Modified / If-Modified-Since:基于文件的修改时间。
    • ETag / If-None-Match:基于文件内容的哈希值,更精确(例如,文件内容没变但修改时间变了,ETag不会变)。

缓存流程:浏览器发起请求 -> 检查强缓存是否过期 -> 未过期,直接用缓存 -> 已过期,则发起协商缓存请求 -> 缓存有效(304),用缓存 -> 无效(200),返回新资源。

3. 强缓存什么情况下会失效?

  1. 时间到期Cache-Control 设置的 max-ages-maxage 时间到期。
  2. 用户行为
    • 用户强制刷新(Ctrl + F5)会忽略所有缓存,直接向服务器请求。
    • 用户清除了浏览器缓存
  3. 缓存策略变化:服务器更新了资源,并更改了资源的URL(例如通过添加哈希值到文件名:app.[hash].js)。这是最常用、最有效的缓存策略。

4. 兼容性的问题一般怎么看?

  1. MDN Web Docs:查询每个API或CSS属性的兼容性表格,清晰列出了各浏览器及其版本的支持情况。
  2. Can I use:权威的网站兼容性查询工具,输入特性名称即可查看详细的浏览器支持范围。
  3. Babel:使用Babel编译器,它可以将新的ES6+语法转换为旧版本浏览器兼容的ES5语法。
  4. PostCSS + Autoprefixer:处理CSS兼容性,自动添加浏览器厂商前缀(如 -webkit-, -moz-)。
  5. Polyfill:对于无法通过编译转换的API(如 Promise, fetch),使用 core-js 等库提供实现。

5. 有没有了解过为什么兼容性会造成高度塌陷的问题?

“高度塌陷”是CSS布局中的一个经典问题,通常由浮动(float) 引起,与浏览器旧版本对标准的实现差异有关。

  • 原因:当一个元素内部只包含浮动元素时,该元素的高度在旧式浏览器中会计算为0,就好像里面没有内容一样,这就是“高度塌陷”。
  • 原理:浮动元素会脱离正常的文档流,父容器无法感知其存在,因此无法被浮动子元素“撑开”。
  • 解决方案(清除浮动):
    1. 额外标签法:在浮动元素末尾添加一个空标签,设置 clear: both;
    2. 伪元素法(推荐):给父容器添加一个清除浮动的伪元素。这是Bootstrap等框架使用的方法。
      .clearfix::after {
        content: "";
        display: table;
        clear: both;
      }
      
    3. 触发BFC:给父容器设置属性,触发其BFC(块级格式化上下文),使其可以包含浮动元素。例如 overflow: hidden;display: flow-root;(现代、专为此设计)。

6. App 和 H5 具体怎么调试?

  1. Android
    • Chrome DevTools:在Chrome中打开 chrome://inspect,启用USB调试后,可以看到连接的设备和App内的WebView,可以直接调试。
  2. iOS
    • Safari Web Inspector:在Safari偏好设置中开启“开发”菜单。用USB连接iOS设备后,在“开发”菜单中选择你的设备或App内的WebView进行调试。
  3. 通用方案
    • 代理调试(Charles/Fiddler):抓取H5页面发出的网络请求,分析问题。
    • VConsole/Eruda:在页面中引入这些轻量级库,在手机屏幕上生成一个类似DevTools的控制台,方便查看日志、网络请求等信息。
    • Weinre:远程调试工具,在项目中引入脚本,即可在PC浏览器上调试手机页面。

7. 手撕:LRU 缓存

LRU(Least Recently Used)缓存淘汰算法。我们使用 Map 数据结构来实现,因为 Map 能记住键的原始插入顺序。

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity; // 缓存容量
    this.cache = new Map(); // 使用Map来存储键值对
  }

  get(key) {
    if (!this.cache.has(key)) return -1; // 如果key不存在,返回-1

    const value = this.cache.get(key);
    this.cache.delete(key); // 删除原有键
    this.cache.set(key, value); // 重新设置,将其变为最新使用的
    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key); // 如果key已存在,先删除
    } else if (this.cache.size >= this.capacity) {
      // 如果缓存已满,删除最久未使用的(即Map中的第一个键)
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }
    this.cache.set(key, value); // 设置新的键值对
  }
}

// 测试
const lruCache = new LRUCache(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
console.log(lruCache.get(1)); // 返回 1
lruCache.put(3, 3); // 该操作会使得密钥 2 作废
console.log(lruCache.get(2)); // 返回 -1 (未找到)
lruCache.put(4, 4); // 该操作会使得密钥 1 作废
console.log(lruCache.get(1)); // 返回 -1 (未找到)
console.log(lruCache.get(3)); // 返回 3
console.log(lruCache.get(4)); // 返回 4

8. 手撕:聊天框

这是一个简化的React聊天框组件,包含消息列表和发送框。

import React, { useState, useRef, useEffect } from 'react';

const ChatBox = () => {
  const [messages, setMessages] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const messagesEndRef = useRef(null);

  // 效果:自动滚动到底部
  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };
  useEffect(scrollToBottom, [messages]);

  // 发送消息
  const handleSend = () => {
    if (inputValue.trim()) {
      const newMessage = {
        id: Date.now(),
        text: inputValue,
        timestamp: new Date().toLocaleTimeString(),
        isOwn: true, // 模拟是自己发送的消息
      };
      setMessages(prev => [...prev, newMessage]);
      setInputValue('');

      // 模拟收到回复 (可选)
      setTimeout(() => {
        const replyMessage = {
          id: Date.now() + 1,
          text: `Echo: ${newMessage.text}`,
          timestamp: new Date().toLocaleTimeString(),
          isOwn: false,
        };
        setMessages(prev => [...prev, replyMessage]);
      }, 1000);
    }
  };

  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      handleSend();
    }
  };

  return (
    <div className="chat-container">
      <div className="messages-container">
        {messages.map(msg => (
          <div
            key={msg.id}
            className={`message-bubble ${msg.isOwn ? 'own-message' : 'other-message'}`}
          >
            <div className="message-text">{msg.text}</div>
            <div className="message-time">{msg.timestamp}</div>
          </div>
        ))}
        <div ref={messagesEndRef} /> {/* 用于滚动定位的锚点 */}
      </div>
      <div className="input-area">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={handleKeyPress}
          placeholder="输入消息..."
        />
        <button onClick={handleSend}>发送</button>
      </div>
    </div>
  );
};

export default ChatBox;
/* 简单样式 */
.chat-container {
  display: flex;
  flex-direction: column;
  height: 400px;
  border: 1px solid #ccc;
}

.messages-container {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
}

.message-bubble {
  max-width: 70%;
  margin-bottom: 10px;
  padding: 8px 12px;
  border-radius: 18px;
  word-break: break-word;
}

.own-message {
  background-color: #0084ff;
  color: white;
  margin-left: auto;
}

.other-message {
  background-color: #e4e6eb;
  color: black;
}

.message-time {
  font-size: 0.7em;
  margin-top: 4px;
  text-align: right;
  opacity: 0.7;
}

.input-area {
  display: flex;
  padding: 10px;
  border-top: 1px solid #ccc;
}

.input-area input {
  flex: 1;
  padding: 8px;
  margin-right: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

三面

1. 跨域的情况下如何获取到别的页面 localStorage 里存放的数据?如何进行安全处理?

核心方法:使用 window.postMessagemessage 事件进行跨文档消息传递。

实现原理

  1. a.com 的页面(父页面)中通过 iframe 嵌入 b.com 的页面(子页面)。
  2. 父页面监听 message 事件。
  3. 子页面在加载后,使用 postMessage 将存储在 b.comlocalStorage 中的数据发送给父页面 a.com
  4. 父页面在收到消息后,从中提取数据。

安全处理(至关重要)

  1. 验证来源:在父页面的 message 事件监听器中,必须检查 event.origin,确保消息来自你信任的特定域名(b.com),防止恶意网站发送消息。
  2. 限制目标:在子页面使用 postMessage 时,最好指定精确的目标 originparent.postMessage(data, 'https://a.com')),避免数据发送到未知的、可能恶意的窗口。
  3. 数据验证:对接收到的数据格式和内容进行校验,不要直接信任和使用。

示例代码

<!-- 父页面 (a.com) -->
<iframe src="https://b.com/child.html" id="childFrame"></iframe>
<script>
  window.addEventListener('message', function(event) {
    // 1. 安全检查:验证消息来源
    if (event.origin !== 'https://b.com') {
      return;
    }
    // 2. 处理数据
    console.log('Received data from child:', event.data);
  });
</script>

<!-- 子页面 (b.com/child.html) -->
<script>
  // 从自己的localStorage中读取数据
  const data = localStorage.getItem('myData');
  // 发送数据给父页面,并指定目标origin
  window.parent.postMessage(data, 'https://a.com');
</script>

2. 性能指标怎么看的?FCP 理想状态应该在几秒内?

  • 如何看:使用工具(如 Lighthouse, Chrome DevTools Performance panel, Web Vitals library)来测量和监控这些指标。
  • FCP (First Contentful Paint) - 首次内容绘制:测量页面开始加载到屏幕首次显示任何文本、图片、非白色 canvas/SVG 的时间。
    • 理想状态小于等于 1.5 秒
    • Google 的评级标准:
      • Good (绿色): ≤ 1.5s
      • Needs Improvement (橙色): 1.5s ~ 3.0s
      • Poor (红色): > 3.0s

3. 哪些优化会对 FCP 这个指标产生影响?

优化FCP的核心是尽快呈现初始内容

  1. 优化关键渲染路径
    • 消除渲染阻塞资源:延迟加载非关键CSS/JS,使用 asyncdefer
    • 缩小CSS:压缩CSS文件,移除未使用的CSS。
    • 内联关键CSS:将首屏内容所需的关键CSS直接内联在HTML的 <head> 中。
  2. 优化服务器响应
    • 减少TTFB(Time to First Byte):使用CDN、优化后端逻辑、使用缓存。
    • 启用压缩(如Gzip/Brotli)。
  3. 资源优化
    • 优化图片:压缩、使用现代格式(WebP/AVIF)、响应式图片(srcset)。
    • 预加载关键资源:使用 <link rel="preload"> 提示浏览器尽早获取重要资源。
  4. 缓存:合理配置HTTP缓存(强缓存),让浏览器快速从本地加载资源。

4. 性能指标好和坏的标准

Google 使用 Core Web Vitals 作为关键用户体验指标,其标准如下:

指标定义好 (Green)需要改进 (Orange)差 (Red)
LCP
(Largest Contentful Paint)
最大内容元素绘制时间≤ 2.5s2.5s ~ 4.0s> 4.0s
FIDINP
(Interaction to Next Paint)
响应下一次用户交互的延迟≤ 100ms100ms ~ 200ms> 200ms
CLS
(Cumulative Layout Shift)
累计布局偏移分数≤ 0.10.1 ~ 0.25> 0.25

(注:FID已被INP取代,成为新的核心指标)


前端面试题目详解

1. 自我介绍

面试官您好,我叫刘警,是南京师范大学地图学与地理信息系统专业的26届硕士毕业生。我专注于前端开发领域,有扎实的前端工程化经验和全栈开发能力。

在我的硕士期间,我作为负责人参与了国家重点研发项目"城市灾害应急管理集成系统"的开发,使用Vue+SpringBoot技术栈。我还有在得物集团的实习经历,参与开发了B端低代码落地页编辑器和C端性能优化工作,使首屏秒开率从15%提升至65%。

我建立了个人技术博客,是CSDN前端优质创作者,总访问量140w+,粉丝2.5w+,持续输出前端技术内容。已主导完成5项软件著作权登记和1项发明专利。

我热爱前端技术,有较强的学习能力和解决问题的能力,期待能在前端开发岗位上贡献价值。

2. 为什么学前端,学了多久

我学习前端开发已经3年多了。最初接触前端是因为被网页的视觉交互效果所吸引,后来发现前端技术能将创意快速实现并呈现给用户,这种即时反馈的成就感让我深深着迷。

随着深入学习,我认识到前端开发不仅是UI实现,更涉及性能优化、工程化、用户体验等深层次技术,这激发了我持续探索的热情。特别是在研究生阶段参与国家级项目开发后,我更加坚定了在前端领域深耕的决心。

3. HTML相关问题

HTML学习的重点有哪些

HTML学习的重点包括:

  • 语义化标签:正确使用header、nav、main、article、section等语义化标签
  • 表单与输入控件:掌握各种input类型和表单验证
  • 可访问性(ARIA):确保页面对所有用户都可访问
  • 元数据和SEO:合理使用meta标签优化搜索引擎收录
  • 多媒体元素:audio、video、canvas等标签的使用
  • 性能优化:preload、prefetch等资源加载优化

HTML里如何实现飞书文档字体变化的效果

飞书文档的字体变化效果可以通过contenteditable属性结合CSS实现:

<div contenteditable="true" class="document-editor">
  这里是可以编辑的文本内容
</div>

<style>
  .toolbar button {
    /* 工具栏按钮样式 */
  }
  
  .document-editor {
    min-height: 300px;
    border: 1px solid #ddd;
    padding: 10px;
  }
  
  .bold {
    font-weight: bold;
  }
  
  .italic {
    font-style: italic;
  }
  
  .underline {
    text-decoration: underline;
  }
</style>

<script>
  // 通过document.execCommand实现文本格式控制
  function formatText(command, value = null) {
    document.execCommand(command, false, value);
  }
  
  // 或者使用现代API
  function toggleBold() {
    document.querySelector('.document-editor').classList.toggle('bold-mode');
  }
</script>

现代实现可能使用自定义编辑器或基于Canvas的渲染技术,但基本原理是通过控制CSS样式或使用文档编辑API。

4. CSS相关问题

CSS怎么学的

我通过多途径系统学习CSS:

  1. 基础知识:通过MDN文档和教程掌握核心概念
  2. 实践项目:在真实项目中应用和解决布局问题
  3. 案例分析:研究知名网站的CSS实现方式
  4. 新技术跟进:学习Grid、Flexbox等现代布局技术
  5. 性能优化:掌握渲染原理和优化技巧

CSS选择器优先级

CSS选择器优先级从高到低:

  1. !important声明
  2. 内联样式(style属性)
  3. ID选择器(#id)
  4. 类选择器/属性选择器/伪类(.class, [type=“text”], :hover)
  5. 元素选择器/伪元素(div, ::before)
  6. 通配符/关系选择器(*, >, +, ~)

优先级计算通常用(a,b,c,d)表示:

  • a: 是否内联样式
  • b: ID选择器数量
  • c: 类、属性、伪类选择器数量
  • d: 元素、伪元素选择器数量

选中同时有a和b类的元素

/* 只选中同时有a和b类的元素 */
.a.b {
  color: red;
}

/* 忽略单独的a或b */
.a:not(.b), .b:not(.a) {
  color: initial;
}

选择三个子元素的实现

.parent {
  display: flex;
}

/* 选择第一个子元素 */
.parent > :first-child {
  background-color: #ff9999;
}

/* 选择第二个子元素(中间) */
.parent > :nth-child(2) {
  background-color: #99ff99;
}

/* 选择最后一个子元素 */
.parent > :last-child {
  background-color: #9999ff;
}

垂直居中实现

多种垂直居中方法:

  1. Flexbox方案:
.container {
  display: flex;
  align-items: center; /* 垂直居中 */
  justify-content: center; /* 水平居中 */
  height: 300px;
}
  1. Grid方案:
.container {
  display: grid;
  place-items: center;
  height: 300px;
}
  1. 绝对定位方案:
.container {
  position: relative;
  height: 300px;
}

.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
  1. Table-cell方案:
.container {
  display: table;
  width: 100%;
  height: 300px;
}

.centered {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}

Position属性详解

CSS position属性定义元素的定位方式:

  1. static:默认值,元素处于正常文档流中
  2. relative:相对定位,相对于自身原本位置进行偏移
  3. absolute:绝对定位,相对于最近的非static定位祖先元素
  4. fixed:固定定位,相对于视口定位,不随滚动条滚动
  5. sticky:粘性定位,在滚动时保持在特定位置

5. JavaScript相关问题

JS怎么学的

我通过以下方式系统学习JavaScript:

  1. 语言基础:深入理解原型、作用域、闭包、异步等核心概念
  2. 现代特性:学习ES6+新特性如箭头函数、解构、Promise等
  3. 类型系统:掌握TypeScript增强开发体验
  4. 运行机制:研究事件循环、内存管理、V8引擎优化
  5. 设计模式:应用模块化、面向对象等编程范式
  6. 实践项目:在真实项目中应用和优化JS代码

作用域和闭包

作用域决定了变量的可见范围和生命周期。JavaScript有全局作用域、函数作用域和块级作用域。

闭包是指函数能够访问并记住其词法作用域中的变量,即使函数在其作用域外执行。闭包的形成需要两个条件:

  1. 函数嵌套
  2. 内部函数引用外部函数的变量
function createCounter() {
  let count = 0; // 闭包保护的变量
  
  return function() {
    count++; // 内部函数引用外部变量
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

闭包代码输出分析

function createFunctions() {
  var result = [];
  
  for (var i = 0; i < 3; i++) {
    result[i] = function() {
      return i;
    };
  }
  
  return result;
}

var functions = createFunctions();
console.log(functions); // 输出3
console.log(functions); // 输出3
console.log(functions); // 输出3

问题在于使用var声明的变量没有块级作用域,所有函数共享同一个变量i。循环结束后i的值为3。

解决方案:

  1. 使用IIFE创建闭包
for (var i = 0; i < 3; i++) {
  result[i] = (function(num) {
    return function() {
      return num;
    };
  })(i);
}
  1. 使用let声明块级作用域变量
for (let i = 0; i < 3; i++) {
  result[i] = function() {
    return i;
  };
}

6. React相关问题

变量声明方式区别

var a = 1;           // 函数作用域,可重复声明,可重新赋值
let a = 1;           // 块级作用域,不可重复声明,可重新赋值
const a = 1;         // 块级作用域,不可重复声明,不可重新赋值
const [a, setA] = useState(1); // React状态,值变化触发重新渲染
const a = useRef(1).current;   // React引用,值变化不触发重新渲染

useState和useRef区别

特性useStateuseRef
触发渲染值变化触发组件重新渲染值变化不触发重新渲染
返回值返回当前状态和更新函数返回可变ref对象
适用场景需要反映在UI上的数据不需要触发渲染的可变值
持久性重新渲染时保持状态组件生命周期内保持引用

useRef更新是否会触发重新渲染

useRef().current更新后不会触发组件重新渲染。这是useRef与useState的主要区别之一。

触发组件重新渲染的因素

  1. 状态更新:useState/useReducer返回的更新函数被调用
  2. 父组件重新渲染:父组件渲染会导致所有子组件重新渲染
  3. Props变化:组件接收的props发生变化
  4. Context更新:组件订阅的Context值发生变化
  5. Force update:使用forceUpdate方法强制更新

7. 城市下拉框实现

下面是使用React实现的城市下拉框组件:

import React, { useState } from 'react';

// 城市数据
const cityData = {
  '北京市': ['东城区', '西城区', '朝阳区', '丰台区', '石景山区', '海淀区', '门头沟区', '房山区', '通州区', '顺义区', '昌平区', '大兴区', '怀柔区', '平谷区', '密云区', '延庆区'],
  '上海市': ['黄浦区', '徐汇区', '长宁区', '静安区', '普陀区', '虹口区', '杨浦区', '闵行区', '宝山区', '嘉定区', '浦东新区', '金山区', '松江区', '青浦区', '奉贤区', '崇明区'],
  '广州市': ['荔湾区', '越秀区', '海珠区', '天河区', '白云区', '黄埔区', '番禺区', '花都区', '南沙区', '从化区', '增城区'],
  '深圳市': ['罗湖区', '福田区', '南山区', '宝安区', '龙岗区', '盐田区', '龙华区', '坪山区', '光明区', '大鹏新区']
};

const CityDistrictSelector = () => {
  const [selectedCity, setSelectedCity] = useState('');
  const [districts, setDistricts] = useState([]);

  // 处理城市选择变化
  const handleCityChange = (event) => {
    const city = event.target.value;
    setSelectedCity(city);
    setDistricts(cityData[city] || []);
  };

  return (
    <div className="city-selector">
      <h2>城市区域选择器</h2>
      
      <div className="select-group">
        <label htmlFor="city-select">选择城市:</label>
        <select 
          id="city-select"
          value={selectedCity} 
          onChange={handleCityChange}
          className="city-dropdown"
        >
          <option value="">请选择城市</option>
          {Object.keys(cityData).map(city => (
            <option key={city} value={city}>{city}</option>
          ))}
        </select>
      </div>

      {selectedCity && (
        <div className="select-group">
          <label htmlFor="district-select">选择区域:</label>
          <select 
            id="district-select"
            className="district-dropdown"
            disabled={districts.length === 0}
          >
            <option value="">请选择区域</option>
            {districts.map(district => (
              <option key={district} value={district}>{district}</option>
            ))}
          </select>
        </div>
      )}

      <style jsx>{`
        .city-selector {
          max-width: 400px;
          margin: 20px auto;
          padding: 20px;
          border: 1px solid #ddd;
          border-radius: 8px;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        }
        
        h2 {
          margin-top: 0;
          color: #333;
          text-align: center;
        }
        
        .select-group {
          margin-bottom: 15px;
        }
        
        label {
          display: block;
          margin-bottom: 5px;
          font-weight: 500;
          color: #555;
        }
        
        select {
          width: 100%;
          padding: 8px 12px;
          border: 1px solid #ccc;
          border-radius: 4px;
          font-size: 14px;
          background-color: white;
        }
        
        select:focus {
          outline: none;
          border-color: #007bff;
          box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
        }
        
        select:disabled {
          background-color: #f8f9fa;
          color: #6c757d;
        }
      `}</style>
    </div>
  );
};

export default CityDistrictSelector;

这个组件实现了:

  1. 城市选择下拉框,数据来自预定义的城市数据
  2. 根据选择的城市动态加载对应的区域数据
  3. 区域选择下拉框,只在选择城市后启用
  4. 简洁的UI样式和良好的用户体验

您可以将此组件集成到您的React应用中,或根据需求进一步扩展功能,如添加默认值、表单验证等。

在这里插入图片描述


针对你的前端面试准备,我结合简历中提到的项目经验和技术栈,整理了以下详细解答。这些答案不仅解释了概念和原理,还融入了实际应用场景和最佳实践,希望能帮助你更好地展示技能。

🧩 前端核心概念详解

1. Iframe 传参

iframe 传参主要有以下几种方式,根据同域/跨域场景选择:

  • URL 查询参数:适用于简单数据传递,通过 src 属性传递参数,子页面通过 URLSearchParams 解析。

    <!-- 父页面 -->
    <iframe src="child.html?param1=value1&param2=value2"></iframe>
    
    <!-- 子页面 -->
    <script>
      const params = new URLSearchParams(window.location.search);
      console.log(params.get('param1')); // 'value1'
    </script>
    

    优点是实现简单、兼容性好;缺点是参数暴露在URL中,长度受限,且只能单向传递。

  • window.postMessage推荐用于跨域通信,允许安全地跨源传递消息。

    // 父页面发送消息
    iframe.contentWindow.postMessage({ key: 'value' }, 'https://子页面源');
    
    // 子页面接收消息
    window.addEventListener('message', (event) => {
      // 安全验证:检查消息来源
      if (event.origin !== 'https://父页面源') return;
      console.log(event.data); // { key: 'value' }
    });
    

    优点是支持跨域、可传递复杂对象;缺点是需要验证 event.origin 以防止安全风险。

  • 其他方法:同域下还可通过 window.parent 直接访问父窗口属性,或使用 localStorage/sessionStorage 配合 storage 事件进行通信。

🔒 安全注意:使用 postMessage 时务必验证 event.origin,防止数据被恶意站点截获。跨域 iframe 若无法通信,可能是目标页面设置了 X-Frame-Options: DENYSAMEORIGIN 阻止嵌入。

结合简历:在得物实习的“实时预览”功能中, likely 采用了 postMessage 进行父子页面通信,传递状态或配置数据,确保了跨域环境下的安全数据交换。


2. Pinia 原理

Pinia 是 Vue 官方推荐的状态管理库,其核心原理基于 Vue 3 的响应式系统:

  • 核心机制

    • State:通过 reactive()ref() 创建响应式状态。
    • Getters:利用 computed() 实现计算属性,依赖状态自动更新。
    • Actions:普通函数,可直接修改 state(同步或异步),无需 mutation
    • Store 管理:通过 defineStore 定义并注册唯一 Store,底层通过 Map 管理单例。
  • 简易实现

    import { reactive, computed } from 'vue';
    const stores = new Map();
    
    function defineStore(id, setup) {
      return function useStore() {
        if (!stores.has(id)) {
          stores.set(id, setup());
        }
        return stores.get(id);
      };
    }
    
    // 使用
    const useCounter = defineStore('counter', () => {
      const state = reactive({ count: 0 });
      const double = computed(() => state.count * 2);
      const increment = () => { state.count++; };
      return { state, double, increment };
    });
    
  • 与 Vuex 区别

    特性VuexPinia
    API 风格Options APIComposition API
    Mutations必需(同步)无,直接修改 state
    模块化嵌套模块扁平化独立 Store
    TypeScript支持较复杂原生支持更好
    体积较大轻量(约 1KB)

结合简历:在灾害管理系统等 Vue 项目中,你通过 Pinia 管理全局状态(如用户信息、表单数据),利用其类型推断和简洁 API 提升开发效率。


3. 装饰器(Decorator)与 AOP

  • 装饰器(Decorator):是一种特殊类型的声明,用于扩展类或方法的功能,提供一种灵活的方式添加额外行为。

    function log(target: any, key: string, descriptor: PropertyDescriptor) {
      const originalMethod = descriptor.value;
      descriptor.value = function (...args: any[]) {
        console.log(`调用 ${key} 方法,参数: ${JSON.stringify(args)}`);
        return originalMethod.apply(this, args);
      };
      return descriptor;
    }
    
    class Example {
      @log
      greet(name: string) {
        return `Hello, ${name}!`;
      }
    }
    
  • AOP(Aspect-Oriented Programming,面向切面编程):是一种编程范式,旨在将横切关注点(如日志、权限、性能监控)从业务逻辑中分离,使代码更模块化、易于维护。装饰器是实现 AOP 的常见方式。

  • 使用场景

    • 日志记录:记录方法调用参数、返回值。
    • 权限控制:在方法执行前验证用户权限。
    • 性能监控:测量方法执行时间。
    • 异常处理:统一捕获和处理异常。
    • 缓存:缓存方法结果,提升性能。

结合简历:在博客或管理系统中,你可能使用装饰器进行请求封装(如自动添加 Token)、权限校验或性能打点,实现关注点分离。


4. Object.create 使用场景

Object.create(proto, propertiesObject) 用于创建一个新对象,并指定其原型:

  • 原型继承:实现纯净的原型链继承。

    const parent = { a: 1 };
    const child = Object.create(parent); // child.__proto__ === parent
    console.log(child.a); // 1 (通过原型链访问)
    
  • 创建无原型对象:避免原型链污染(如用于 Map 替代品)。

    const obj = Object.create(null); // obj.__proto__ === undefined
    obj.key = 'value';
    // console.log(obj.toString); // undefined (无 Object 方法)
    
  • 属性描述符定义:创建属性时控制 writable/enumerable/configurable

    const obj = Object.create(null, {
      key: {
        value: 'static',
        writable: false,   // 不可写
        enumerable: true   // 可枚举
      }
    });
    

结合简历:在工具库或框架开发中,你可能用其实现继承或创建纯净配置对象。


5. Object.freeze 深度冻结与 Hook

  • 原理Object.freeze() 浅冻结对象,阻止添加、删除、修改属性(非严格模式静默失败,严格模式抛错)。

    const obj = { a: 1, b: { c: 2 } };
    Object.freeze(obj);
    obj.a = 2; // 失败(严格模式报错)
    obj.b.c = 3; // 成功(内部对象未冻结)
    
  • 深度冻结:需递归冻结所有嵌套对象。

    function deepFreeze(obj) {
      Object.freeze(obj);
      for (const key in obj) {
        if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
          deepFreeze(obj[key]);
        }
      }
    }
    
  • Freeze Hook:通过 Proxy 拦截 set/delete 操作,实现修改警告或阻止。

    function createFreezeHook(obj) {
      return new Proxy(obj, {
        set(target, key, value) {
          console.warn(`Cannot set ${key} to ${value}:
    Object is frozen.`);
          return true; // 或 false (严格模式报错)
        },
        deleteProperty(target, key) {
          console.warn(`Cannot delete ${key}: Object is frozen.`);
          return true;
        }
      });
    }
    const frozen = createFreezeHook(Object.freeze({ a: 1 }));
    

结合简历:在状态管理或常量配置中,你可能使用冻结确保数据不可变。


6. public、protected、private 区别

这些是 TypeScript/ES Next 中的访问修饰符,用于控制类成员的可见性:

修饰符访问权限示例代码
public任意位置可访问(默认)class A { public x = 1; }
protected仅类内部和子类可访问class B { protected y = 2; }
private仅类内部可访问(编译后仍可访问)class C { private z = 3; }
  • TS 编译后private 仅编译时检查,运行时仍可通过 obj['key'] 访问。
  • ECMAScript 私有字段:使用 #field 语法(运行时真正私有)。
    class Example {
      #privateField = 'secret';
      getPrivate() { return this.#privateField; }
    }
    

结合简历:在大型 TypeScript 项目中,你常用这些修饰符封装组件类或工具库。


7. Drag API 原理

HTML5 Drag and Drop API 基于事件驱动:

  • 核心事件

    事件触发时机
    dragstart开始拖动时(在拖动元素上)
    drag拖动过程中
    dragenter进入目标区域时(在目标上)
    dragover在目标区域上移动时
    dragleave离开目标区域时
    drop在目标区域释放时
    dragend拖动结束时(在拖动元素上)
  • 数据传输:通过 dataTransfer 对象设置/获取数据。

    // 拖动源
    element.addEventListener('dragstart', (event) => {
      event.dataTransfer.setData('text/plain', 'Hello World');
      event.dataTransfer.effectAllowed = 'copy';
    });
    
    // 放置目标
    target.addEventListener('drop', (event) => {
      event.preventDefault();
      const data = event.dataTransfer.getData('text/plain');
      console.log(data); // 'Hello World'
    });
    target.addEventListener('dragover', (event) => event.preventDefault()); // 需阻止默认行为以允许放置
    

结合简历:在 BPMN 流程设计器或可视化工具中,你可能使用 Drag API 实现节点拖拽布局。


8. Grid 布局 vs Flex 布局

特性Grid(二维布局)Flex(一维布局)
维度行和列(同时控制)单行或单列(主轴方向)
适用场景整体页面布局、卡片网格组件内元素排列、导航栏
容器属性grid-template-rows/columnsflex-direction, flex-wrap
项目属性grid-row, grid-columnflex-grow, flex-shrink
  • Grid 示例

    .container {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      grid-gap: 10px;
    }
    .item { grid-column: 1 / 3; } /* 跨两列 */
    
  • Flex 示例

    .container {
      display: flex;
      justify-content: space-between;
    }
    .item { flex: 1; }
    

结合简历:在响应式项目中,你可能混合使用两者(Grid 用于整体结构,Flex 用于组件内部)。


9. 权限设计

前端权限设计通常基于 RBAC(Role-Based Access Control)模型:

  • 路由权限:动态注册路由(如 Vue Router)。

    // 过滤异步路由表
    const asyncRoutes = [/* 所有路由定义 */];
    const allowedRoutes = asyncRoutes.filter(route => {
      return user.roles.some(role => route.meta?.roles?.includes(role));
    });
    router.addRoutes(allowedRoutes);
    
  • 组件/按钮权限:通过自定义指令或包装组件控制。

    <template>
      <button v-permission="'user:delete'">删除用户</button>
    </template>
    
    // 指令实现
    Vue.directive('permission', {
      inserted(el, binding) {
        const { value } = binding;
        const permissions = store.getters.permissions;
        if (!permissions.includes(value)) {
          el.parentNode?.removeChild(el);
        }
      }
    });
    

结合简历:在灾害管理系统中,你实现了 RBAC 动态路由和按钮级权限控制,确保用户仅访问授权功能。


10. 表单加密解密

  • 传输加密:使用 HTTPS 协议保障传输安全。对敏感字段(如密码)可额外使用 AES 等算法加密。

    import CryptoJS from 'crypto-js';
    const key = '密钥';
    const encrypted = CryptoJS.AES.encrypt('明文数据', key).toString();
    // 发送加密后数据
    
  • 前端解密:通常由后端返回加密数据,前端解密(较少用)。

    const bytes = CryptoJS.AES.decrypt(encryptedData, key);
    const decrypted = bytes.toString(CryptoJS.enc.Utf8);
    

🔒 安全建议:密钥需安全管理(避免硬编码),敏感操作应在后端处理。

结合简历:在金融或管理系统中,你可能对接加密接口或处理敏感数据加密。


11. 上拉加载原理

通过监听滚动事件判断触底,加载更多数据:

  • 滚动监听

    window.addEventListener('scroll', () => {
      const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
      if (scrollTop + clientHeight >= scrollHeight - threshold) {
        loadMore(); // 加载下一页
      }
    });
    
  • 性能优化

    • 防抖:避免频繁触发。
    • Intersection Observer:现代浏览器推荐 API。
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        loadMore();
      }
    });
    observer.observe(document.getElementById('footer'));
    

结合简历:在得物商品列表或博客文章列表,你可能实现过分页或无限滚动。


12. 大文件分片上传

将大文件切片,并行上传,支持断点续传:

  • 前端切片

    const chunkSize = 2 * 1024 * 1024; // 2MB/片
    const chunks = [];
    for (let i = 0; i < file.size; i += chunkSize) {
      chunks.push(file.slice(i, i + chunkSize));
    }
    
  • 上传与合并

    • 为每个切片生成哈希(如 SparkMD5),用于标识文件和服务端去重。
    • 并发上传切片(控制并发数)。
    • 服务端接收所有切片后合并文件。
  • 断点续传:服务端记录已上传切片,前端跳过已传切片。

结合简历:在灾害管理系统中,你实现了大文件分片上传、秒传和断点续传功能。


13. Lodash 与 Tree-shaking

  • Lodash 支持 Tree-shaking:需使用 lodash-es(ES Modules 版本)或按需导入。

    import debounce from 'lodash-es/debounce'; // 支持 shaking
    // 或 import { debounce } from 'lodash-es';
    
  • Tree-shaking 原理

    • 基于 ES Modules 的静态分析(编译时确定依赖)。
    • Webpack/Rollup 等工具标记未导出代码,并在打包时移除。
  • 为什么默认 lodash 不支持:传统版本为 CommonJS 格式(动态引用,无法静态分析)。

结合简历:在项目优化中,你通过配置 Tree-shaking 和代码分割减少打包体积。


14. 媒体查询原理

CSS 媒体查询基于设备特性(如宽度、分辨率)应用样式:

  • 原理:浏览器监听视口变化,匹配条件时应用对应样式。

    /* 移动端优先 */
    .container { padding: 10px; }
    
    @media (min-width: 768px) {
      .container { padding: 20px; } /* 平板 */
    }
    
    @media (min-width: 1024px) {
      .container { padding: 30px; } /* 桌面 */
    }
    
  • 常见查询

    • width/height:视口尺寸。
    • orientation:横屏/竖屏。
    • resolution:设备像素比。

结合简历:在响应式项目中,你常用媒体查询适配多端设备。


15. TS 中抽象类和抽象方法

  • 抽象类:用 abstract 声明,不能实例化,只能被继承。
  • 抽象方法:在抽象类中声明,必须由子类实现
    abstract class Animal {
      abstract makeSound(): void; // 抽象方法
      move() { console.log('Moving...'); } // 具体方法
    }
    
    class Dog extends Animal {
      makeSound() { console.log('Woof!'); } // 必须实现
    }
    
    // const a = new Animal(); // 错误:无法实例化抽象类
    const d = new Dog();
    d.makeSound(); // 'Woof!'
    

结合简历:在大型 TypeScript 项目中,你使用抽象类定义通用接口或基础组件。


16. 找字符串中第一次出现的非重复字符

function firstNonRepeatingChar(str) {
  const map = new Map();
  // 第一次遍历:统计字符出现次数
  for (let char of str) {
    map.set(char, (map.get(char) || 0) + 1);
  }
  // 第二次遍历:找到第一个计数为1的字符
  for (let char of str) {
    if (map.get(char) === 1) return char;
  }
  return null; // 无非重复字符
}

// 示例:
console.log(firstNonRepeatingChar("swiss")); // "w"
console.log(firstNonRepeatingChar("aabb"));  // null

时间复杂度:O(n)(两次遍历)。
空间复杂度:O(n)(Map 存储)。


根据你的简历内容,可以看出你在前端开发、工程化、性能优化和全栈方面有扎实的经验。以下是对每个面试问题的详细解答,并结合了你的项目和实践经验进行说明:


1. iframe 传参

方式

  • 通过 URL 查询参数传递(简单数据)
  • 使用 postMessage 进行跨域通信(安全且支持复杂数据)
  • 通过 window.namedocument.domain(较少用)

结合简历:你在得物实习中使用了 Iframe 实现实时预览, likely 使用 postMessage 进行父子页面通信,传递状态或配置数据。


2. Pinia 原理

Pinia 是 Vue 状态管理库,基于 Composition API:

  • 使用 reactive 创建响应式 store
  • 提供 patch 批量更新、DevTools 集成、插件系统
  • 支持服务端渲染(SSR)

结合简历:你在灾害管理系统使用 Vue,可能接触过 Vuex/Pinia 进行状态管理。


3. 装饰器 Decorator 使用场景?什么是 AOP?

  • Decorator:用于扩展类或方法的功能(如日志、权限校验)
  • AOP(面向切面编程):将横切关注点(如日志、事务)从业务逻辑中分离

使用场景

  • 日志记录
  • 权限控制
  • 性能监控

结合简历:你在博客项目中可能使用装饰器进行请求封装或权限校验。


4. Object.create 使用场景?

用于创建一个新对象,并指定原型:

const parent = { a: 1 };
const child = Object.create(parent);

使用场景:原型继承、创建纯净对象(无原型链污染)


5. Object.freeze 原理?深层对象可 freeze 吗?如何修改?

  • 原理:冻结对象,禁止添加/删除/修改属性(浅冻结)
  • 深层冻结:需递归冻结嵌套对象
  • 修改冻结对象:无法直接修改,可通过代理(Proxy)或解冻副本实现

Hook 实现:可基于 Proxy 拦截 set/delete 操作并提示警告。


6. public、protected、private 区别

  • public:任意访问
  • protected:仅类和子类可访问
  • private:仅类内部可访问

TS/JS 中:通过 TypeScript 或符号(Symbol)实现。


7. Drag API 原理

基于 HTML5 Drag and Drop API:

  • draggable="true"
  • 事件:dragstart, dragend, dragover, drop
  • 使用 dataTransfer 传递数据

结合简历:可能在可视化项目中使用拖拽交互(如 BPMN 流程设计器)。


8. Grid 布局 vs Flex 布局

  • Flex:一维布局(行或列)
  • Grid:二维布局(行和列)
  • 选择:简单线性布局用 Flex,复杂网格用 Grid

简历关联:你熟悉响应式布局, likely 两者都使用过。


9. 权限设计

常见方案:

  • RBAC(基于角色的访问控制)
  • 前端路由动态注册 + 按钮级权限(自定义指令)

结合简历:你在灾害管理系统中实现了 RBAC 和动态路由,按钮级权限控制。


10. 表单加密解密

  • 加密:使用 HTTPS + 加密算法(如 AES)加密敏感数据
  • 解密:后端解密处理

最佳实践:全程 HTTPS,敏感字段额外加密。


11. 上拉加载原理

监听滚动事件,判断触底:

window.addEventListener('scroll', () => {
  if (scrollTop + clientHeight >= scrollHeight - threshold) {
    loadMore();
  }
});

优化:防抖 + Intersection Observer API


12. 大文件分片上传

  • 前端分片(Blob.slice)
  • 并发上传(Promise.all)
  • 服务端合并
  • 断点续传(基于文件哈希)

结合简历:你在灾害管理系统中实现了大文件分片上传、秒传、断点续传。


13. Lodash 支持 Tree-shaking 吗?为什么?原理是什么?

  • 支持:Lodash-es 或模块化导入方式支持 Tree-shaking
  • 原理:ES Module 静态分析,移除未引用代码
  • 为什么:传统 Lodash 是 CommonJS,无法静态分析

简历关联:你在项目中优化构建配置(Tree Shaking、代码分割)。


14. 媒体查询原理

基于 CSS3 @media,根据设备特性(如宽度、分辨率)应用样式:

@media (max-width: 768px) { ... }

浏览器监听视口变化,动态应用样式。


15. TS 中抽象类和抽象方法

  • 抽象类:不能实例化,只能继承
  • 抽象方法:必须由子类实现
abstract class Animal {
  abstract makeSound(): void;
}
class Dog extends Animal {
  makeSound() { ... }
}

16. 代码题:找字符串中第一次出现的非重复字符

function firstNonRepeatingChar(str) {
  const map = new Map();
  for (let char of str) {
    map.set(char, (map.get(char) || 0) + 1);
  }
  for (let char of str) {
    if (map.get(char) === 1) return char;
  }
  return null;
}
// 示例:firstNonRepeatingChar("swiss") -> "w"

总结回答风格建议:

  • 结合简历中提到的技术(如 Vue/React、工程化、性能优化)举例说明
  • 突出你的实战经验(如得物实习、国家重点研发项目)
  • 简洁清晰,直击要点
快手作为国内头部互联网公司之一,其AI相关的岗位面试流程通常包括多个环节,旨在全面考察候选人的技术能力、项目经验以及综合素质。以下是针对快手AI相关岗位的面试流程、可能遇到的题目类型及准备建议: ### 三、AI面试流程 1. **简历筛选** 快手对简历的要求较高,尤其是对于AI方向,需要有扎实的算法基础和实际项目经验。候选人需在简历中突出自己的机器学习、深度学习、自然语言处理(NLP)或计算机视觉(CV)等相关经历[^2]。 2. **在线笔试/编程测试** 部分岗位会安排LeetCode风格的编程题或算法题,考察基础数据结构与算法能力。也可能包含数学推导、概率统计、优化方法等理论题。 3. **技术初面(一面)** 初面一般由HR或技术面试官进行,内容涵盖基础知识问答、项目深挖和技术细节讨论。例如: - 深度学习模型的基本原理(如CNN、RNN、Transformer) - 常见损失函数及其应用场景 - 模型评估指标(如准确率、召回率、F1值) - 正则化方法(L1/L2)、Dropout的作用[^2] 4. **技术面(面/交叉面)** 这一轮通常更加深入,可能会涉及更复杂的算法设计、模型调优、工程实现等方面的问题。例如: - 如何解决过拟合问题? - 梯度下降的不同变种(SGD、Adam等)有何区别? - 在图像分类任务中如何提升模型性能? - 实际项目中的调参经验和技巧 5. **终面(三面/综合面)** 终面通常由团队负责人或更高层级的技术人员主导,除了技术问题外,还会考察候选人的项目理解、逻辑思维、沟通表达和职业规划等内容。例如: - 讲一个你最有成就感的项目 - 如果给你一个新任务,你会如何入手? - 对AI未来发展的看法? 6. **HR面** HR面主要关注个人动机、团队协作能力、抗压能力等软实力,常见问题包括: - 为什么选择快手? - 你的职业规划是什么? - 你最大的优点和缺点? --- ### 四、典型面试题目示例 - 简述Transformer的结构和工作原理。 - 解释Batch Normalization的作用及其公式。 - 描述一次你在项目中使用深度学习解决实际问题的经历。 - 如何处理类别不平衡的数据? - 什么是ROC曲线?AUC的意义是什么? --- ### 五、准备建议 1. **基础知识巩固** 复习《深度学习》(花书)、《机器学习实战》、《统计学习方法》等书籍,掌握主流模型的原理和应用。 2. **刷题训练** 推荐使用LeetCode、牛客网、Kaggle等平台进行编程训练,重点关注算法题和模型相关的编程题。 3. **项目复盘** 准备2~3个重点项目的详细描述,包括背景、目标、技术方案、实现过程、结果分析和改进空间。 4. **模拟面试** 可以找朋友或使用AI模拟面试工具进行演练,熟悉常见的技术问题和行为问题。 5. **了解快手业务** 快手的核心业务包括短视频推荐、直播、广告系统等,建议提前了解这些领域的AI应用场景和技术挑战。 --- ```python # 示例:计算两个数的最大公约数(GCD),常用于算法题中 def gcd(a, b): while b: a, b = b, a % b return a ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FE_Jinger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值