跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端

前端面试深度解析:核心概念与代码实践

整理了前端面试中高频出现的概念与手写代码实现,包括ES6常用特性、跨域方案、Vue响应式原理、v-if/v-show差异、性能优化手段、浏览器渲染流程、防抖节流、闭包应用、事件循环机制、Vue3与Vue2对比、new操作符、call/apply/bind、TypeScript装饰器、10万数据虚拟列表渲染,以及时间切片等,均附有可运行的代码示例。

SecGuard发布于 2026/6/300 浏览

面试中常常会遇到一些反复被问及的知识点,它们背后考察的往往不是死记硬背,而是对语言特性、运行机制和工程实践的真正理解。这里整理了十五个高频考题,结合代码和简短的说明,希望能帮你理清思路。

ES6 常用特性速览

刚接触 ES6 时,let 和 const 让我告别了 var 的变量提升烦恼。let 有自己的块级作用域,const 保证引用不变,不过对象属性仍然可以修改。

let x = 10;
const y = 20;
// y = 30; // 报错,但 y.a = 1 可以

箭头函数不仅写法更短,还解决了 this 的动态绑定问题——它本身没有 this,会直接捕获定义时的外层 this,在回调里特别好用。

const add = (a, b) => a + b;

模板字符串让字符串拼接和换行不再难受:

const name = "John";
const greeting = `Hello, ${name}!`;

解构赋值省掉了很多临时变量:

const [a, b] = [1, 2];
const { name, age } = { name: "John", age: 25 };

默认参数让函数更清晰:

function greet(name = "Guest") {
  return `Hello, ${name}`;
}

扩展运算符在数组合并、对象浅拷贝中几乎取代了 concat 和 Object.assign:

const arr2 = [...[1,2], 3, 4];
const obj2 = { ...{ a:1 }, b: 2 };

对象属性简写也很方便:

const x = 10, y = 20;
const point = { x, y }; // { x: 10, y: 20 }

Promise 的出现让异步代码告别了回调地狱,后面 async/await 更是让代码看起来像同步:

const promise = new Promise((resolve) => setTimeout(() => resolve("Done"), 1000));

async function fetchData() {
  const data = await promise;
  return data;
}

ES6 还带来了 class 语法糖,底层仍然是原型链,但写起来舒服多了:

class Person {
  constructor(name) { this.name = name; }
  greet() { return `Hello, I'm ${this.name}`; }
  static create(name) { return new Person(name); }
}

除此之外,模块化的 import/export、新的数据结构 Set/Map、for...of 循环、生成器函数、Proxy/Reflect 以及后来 ES2020 的可选链 ?. 和空值合并 ?? 也都值得关注。

跨域问题与解决方案

浏览器的同源策略(协议、域名、端口任一不同即跨域)是安全基石,但在开发中经常需要绕开它。

http://example.com:80 → https://example.com:80 (协议不同)
http://example.com:80 → http://api.example.com:80 (域名不同)
http://example.com:80 → http://example.com:8080 (端口不同)

最常见的跨域方案是 CORS。服务器设置几个响应头就能让特定来源的请求被允许:

res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Credentials', 'true');

CORS 的请求分为简单请求和需要预检的请求。简单请求直接发 GET/POST/HEAD,且 Content-Type 有限制;其他情况(如自定义头、PUT 等)会先发一个 OPTIONS 探路。

JSONP 是历史方案,利用 <script> 标签不受跨域限制的特性,动态加载一个带回调的脚本。现在用得很少,但面试还是会问:

function handleResponse(data) { console.log(data); }
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
// 服务器返回:handleResponse({data:"some data"});

开发时最常用的还是代理。webpack 的 devServer 可以轻松把 /api 转发到后端:

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://api.example.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
};

WebSocket 天生不受同源策略限制,可以做实时通信;生产环境常用 Nginx 反向代理,配置类似:

location /api/ {
  proxy_pass http://api.example.com/;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
}

Vue 中的响应式原理

Vue2 通过 Object.defineProperty 劫持数据属性的 getter/setter,但对于数组无法直接监听索引变化,所以 Vue2 重写了数组的七个方法(push、pop、shift、unshift、splice、sort、reverse),在其中加入依赖通知。

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
  const original = arrayProto[method];
  Object.defineProperty(arrayMethods, method, {
    value: function(...args) {
      const result = original.apply(this, args);
      const ob = this.__ob__;
      let inserted;
      switch(method) {
        case 'push': case 'unshift': inserted = args; break;
        case 'splice': inserted = args.slice(2); break;
      }
      if(inserted) ob.observeArray(inserted);
      ob.dep.notify();
      return result;
    }
  });
});

const arr = [1, 2, 3];
Object.setPrototypeOf(arr, arrayMethods);

Vue3 用 Proxy 彻底解决了这个问题,可以直接代理整个对象和数组,包括索引赋值和 length 变化。

function reactiveArray(arr) {
  return new Proxy(arr, {
    get(target, key) {
      if(key === 'length') return target.length;
      const value = target[key];
      if(typeof value === 'function') return value.bind(target);
      return value;
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      const type = Number(key) < target.length ? 'SET' : 'ADD';
      if(type === 'ADD' || oldValue !== value) {
        trigger(target, key, type);
      }
      return result;
    }
  });
}

v-if 和 v-show 的区别

简单来说,v-if 决定节点是否渲染(DOM 直接移除),v-show 只是切换 display 属性。

<div v-if="show">使用 v-if</div>
<div v-show="show">使用 v-show</div>

这意味着:

  • 初始渲染时,v-if 为 false 不会创建 DOM,开销小;v-show 无论如何都会渲染,开销大。
  • 切换时,v-if 会重建/销毁组件,触发生命周期;v-show 只是样式变化,开销小。

所以频繁切换的场景(如 Tab 切换)用 v-show,条件很少改变时(如权限控制)用 v-if。v-if 还能配合 v-else 和 v-else-if 使用。

从编译结果看:

// v-if
function render() { return show ? createElement('div', '内容') : createEmptyVNode(); }
// v-show
function render() { return createElement('div', {
  directives: [{ name: 'show', value: show }],
  style: { display: show ? '' : 'none' }
}, '内容'); }

网页加载性能优化实战

Core Web Vitals 是现在衡量体验的核心指标:LCP < 2.5s、FID < 100ms、CLS < 0.1。优化可以从这几方面入手。

代码层面:路由懒加载和代码分割是首屏优化的利器。Vue 中() => import() 就能按需加载组件,webpack 的 splitChunks 配置则把第三方库抽离为 vendor。

const LazyComponent = () => import('./LazyComponent.vue');
// webpack.config.js
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors' }
    }
  }
}

资源优化:图片用 srcset 提供不同分辨率,loading="lazy" 实现原生懒加载;关键资源(字体、核心 CSS)用 <link rel="preload"> 提前获取。

<img src="image.jpg"
     srcset="image-320w.jpg 320w, image-480w.jpg 480w"
     sizes="(max-width: 600px) 480px, 800px"
     loading="lazy" alt="描述文本">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="prefetch" href="next-page.html">

缓存策略:Service Worker 可以离线缓存关键资源,配合 Lighthouse 命令行能快速审计。

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache => cache.addAll(['/', '/styles/main.css', '/script/main.js']))
  );
});
npx lighthouse https://example.com --output=json --output-path=report.json

浏览器渲染流程

浏览器拿到 HTML 后依次构建 DOM 树和 CSSOM 树,两者合并成渲染树,然后经历布局(计算位置大小)和绘制(填充像素),最后合成图层。

重排(Reflow)会重新计算几何属性,开销大;重绘(Repaint)只是颜色等变化,开销较小。合成(Composite)则只触发 GPU 层的变化,比如 transform,开销最小。

避免强制同步布局是个好习惯:不要在修改样式后立即读取几何属性,否则浏览器会立即重排以获取最新值。

// 不好
const width = element.offsetWidth; // 读
element.style.width = width + 10 + 'px'; // 写
// 更好:批量读,再批量写
const width = element.offsetWidth;
const height = element.offsetHeight;
element.style.width = width + 10 + 'px';
element.style.height = height + 10 + 'px';

防抖与节流

防抖(Debounce)让高频事件在停止触发 n 秒后才执行一次,适合输入框搜索、窗口 resize 等场景。

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

节流(Throttle)则保证在一段时间内只执行一次,适合滚动加载、按钮点击等。实现方式有两种:时间戳比较直观,定时器版能保证最后一次也执行。

// 时间戳
function throttle(fn, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if(now - lastTime >= delay) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}
// 定时器
function throttle(fn, delay) {
  let timer = null;
  return function(...args) {
    if(!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

实际更常用的是加强版,能控制首尾触发:

function enhancedThrottle(fn, delay, { leading = true, trailing = true } = {}) {
  let timer = null, lastTime = 0, lastArgs = null;
  return function(...args) {
    const now = Date.now();
    const remaining = delay - (now - lastTime);
    lastArgs = args;
    if(remaining <= 0) {
      if(timer) { clearTimeout(timer); timer = null; }
      if(leading) fn.apply(this, args);
      lastTime = now;
    } else if(!timer && trailing) {
      timer = setTimeout(() => {
        fn.apply(this, lastArgs);
        timer = null;
        lastTime = Date.now();
      }, remaining);
    }
  };
}

理解闭包

闭包就是函数能够访问其外部作用域变量的能力,在 JavaScript 里很常见。它常用于创建私有变量、工厂函数和模块模式。

function outer() {
  const name = "John";
  return function inner() { console.log(name); };
}
const closureFn = outer();
closureFn(); // "John"

一个经典例子是数据私有化:

function createCounter() {
  let count = 0;
  return {
    increment() { return ++count; },
    decrement() { return --count; },
    getCount() { return count; }
  };
}

不过闭包也可能造成内存泄漏,如果内部函数持有大对象的引用且一直不释放。解决办法就是在用完后主动解除引用。

function noLeak() {
  let largeArray = new Array(1000000).fill('data');
  const result = largeArray.length;
  largeArray = null;
  return function() { return result; };
}

浏览器线程与事件循环

浏览器主线程承担着 DOM 渲染、JS 执行和用户交互,而 JS 是单线程的。于是有了事件循环来协调异步任务:微任务先于宏任务执行。

console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
// start, end, promise, timeout

遇到耗时计算可以用 Web Worker 开启另一个线程:

const worker = new Worker('worker.js');
worker.postMessage({ data: 'Hello' });
worker.onmessage = (e) => console.log(e.data);
// worker.js: self.onmessage = (e) => self.postMessage(e.data.toUpperCase());

Vue2 到 Vue3 的核心变化

Vue3 改用 Proxy 实现响应式,性能更好且能监听数组索引。新增 Composition API 让逻辑复用更灵活,对 TypeScript 支持也到位了。

<script setup>
import { ref, computed, onMounted } from 'vue';
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() { count.value++; }
onMounted(() => console.log('mounted'));
return { count, doubleCount, increment };
</script>

体积上,Vue3 支持 tree-shaking,打包能小 40% 左右。它还允许 Fragment(多根节点)、Teleport、Suspense 等新特性。编译时还有静态提升和 patch flag,减少运行时比对开销。

new 操作符做了什么

new Fn() 的过程可以拆解为:创建一个空对象,将其原型指向 Fn.prototype,执行构造函数并绑定 this,最后根据返回值决定是返回那个对象还是本身的 this。

function myNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype);
  const result = constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}

call、apply、bind 的区别

三者都能改变 this 指向。call 和 apply 立即执行,只是传参方式不同(逐个 vs 数组);bind 返回新函数,可以延迟调用,且可以柯里化部分参数。

const person = { name: 'John', greet(greeting, punc) { return `${greeting}, ${this.name}${punc}`; }};
const another = { name: 'Jane' };
person.greet.call(another, 'Hello', '!');      // Hello, Jane!
person.greet.apply(another, ['Hi', '!!']);     // Hi, Jane!!
const bound = person.greet.bind(another, 'Hey');
bound('?'); // Hey, Jane?

手写实现也是面试常考,核心思路是:将原函数临时挂到上下文对象上,用 Symbol 避免属性冲突,执行后再删除。bind 还要处理 new 调用的情况。

TypeScript 装饰器入门

装饰器本质上是对类、方法、属性、参数或访问器进行'劫持'的函数。可以搭配装饰器工厂传递参数。

function log(target: any, key: string, desc: PropertyDescriptor) {
  const original = desc.value;
  desc.value = function(...args: any[]) {
    console.log(`Calling ${key}`);
    return original.apply(this, args);
  };
}

class UserService {
  @log
  addUser(name: string) { /* ... */ }
}

实际项目中,装饰器可用于日志记录、自动绑定 this、参数校验等,让代码更声明式和可维护。

大数据量列表渲染优化

面对 10 万条数据,直接全量渲染浏览器会直接卡死。虚拟列表的思路是只渲染可视区域及上下少量缓冲区,动态计算偏移。

在 Vue 组件中,监听滚动计算 startIndex 和 endIndex,通过 transform: translateY 将可见内容放在正确位置。

<template>
  <div ref="container" @scroll="handleScroll" style="height:500px;overflow:auto;position:relative">
    <div :style="{ height: totalHeight + 'px' }"></div>
    <div :style="{ transform: `translateY(${offset}px)` }">
      <div v-for="item in visibleData" :key="item.id" :style="{ height: itemHeight + 'px' }">
        {{ item.content }}
      </div>
    </div>
  </div>
</template>

另一种思路是时间分片渲染,用 requestAnimationFrame 或 requestIdleCallback 把大数据切割成小批次,避免长时间阻塞主线程。Web Worker 处理数据也是常见手段。

时间切片与浏览器空闲回调

时间切片的本质是在浏览器空闲时执行小段任务,避免长任务影响交互。requestIdleCallback 可以让我们在每一帧有空余时间时处理工作。

function processBigData(data, processItem) {
  return new Promise(resolve => {
    let index = 0;
    function run(deadline) {
      while (index < data.length && deadline.timeRemaining() > 0) {
        processItem(data[index]);
        index++;
      }
      if (index < data.length) requestIdleCallback(run);
      else resolve();
    }
    requestIdleCallback(run);
  });
}

React 18 的 Concurrent Mode 也用类似思想,useDeferredValue 和 startTransition 能标记非紧急更新,这些更新可以被更高优先级的交互中断。

目录

  1. ES6 常用特性速览
  2. 跨域问题与解决方案
  3. Vue 中的响应式原理
  4. v-if 和 v-show 的区别
  5. 网页加载性能优化实战
  6. 浏览器渲染流程
  7. 防抖与节流
  8. 理解闭包
  9. 浏览器线程与事件循环
  10. Vue2 到 Vue3 的核心变化
  11. new 操作符做了什么
  12. call、apply、bind 的区别
  13. TypeScript 装饰器入门
  14. 大数据量列表渲染优化
  15. 时间切片与浏览器空闲回调
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 用 Claude 在 Android Studio 里 10 分钟搭好 WebView 模块
  • Hx0 鹰眼:在浏览器侧栏完成抓包、重放与 AI 审计
  • CVE-2015-7450:WebSphere 反序列化漏洞的攻击链分析
  • Whisper-large-v3 离线部署实战:摆脱 HuggingFace Hub 的网络依赖
  • Qwen3-VL 的双模式是怎么工作的?Instruct 与 Thinking 实践对比
  • 从 J2EE 到 Agentic AI:OpenClaw 如何复现 Spring 的轻量级革命
  • 10 个 Python 脚本让日常重复活自动跑起来
  • PyCharm 断点排查 GLM-4.6V-Flash-WEB 脚本错误
  • 车端部署DeepSeek-R1蒸馏模型:做法与取舍
  • PX4 Offboard 控制开发笔记:ROS 状态机与轨迹自主飞行
  • iOS 18.2 上 Flutter WebView 点击失效的来龙去脉
  • 上手 Llama 3:推理与 LoRA 微调实践
  • 2026年机器人系统架构解析:从运动控制到VLA大模型的技术路径
  • 给 OpenCode 接上 Kimi K2.5:三种方式与一些选择
  • LeetCode 92 区间反转:递归与哨兵节点解法
  • C++ 中 std::list 的常用技巧和坑
  • 2026低代码选型:AI融合下,三个核心维度
  • 给 Wan2.2 装个风格插件:LoRA 微调实战指南
  • 2026 大模型落地观察:国产反超、百万上下文与 Agent 工程化实践
  • Docker部署OnlyOffice社区版,文档保存失败踩坑记录

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online