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

前端面试核心考点解析:ES6 跨域 Vue3 响应式与大数据渲染优化

前端面试高频考点梳理,涵盖 ES6 新特性、跨域解决方案、Vue2 与 Vue3 响应式原理差异、v-if/v-show 机制对比、网页加载优化策略及浏览器渲染流程。重点解析 10 万级数据虚拟列表实现与时间切片技术,探讨闭包内存管理、事件循环机制及 TypeScript 装饰器应用,为前端开发者提供系统化的知识梳理与实战参考。

狂少发布于 2026/3/21更新于 2026/6/919 浏览

前端面试题详细解答

1. ES6 新特性详解(重要 10 个)

核心特性
// 1. let 和 const - 块级作用域
let x = 10; // 可重新赋值
const y = 20; // 不可重新赋值,但对象属性可修改

// 2. 箭头函数
const add = (a, b) => a + b;
// 特点:没有自己的 this,继承父级 this;不能作为构造函数

// 3. 模板字符串
const name = "John";
const greeting = `Hello, ${name}!`;
// 支持多行字符串

// 4. 解构赋值
const [a, b] = [1, 2]; // 数组解构
const {name, age} = {name:"John", age:25}; // 对象解构

// 5. 默认参数
function greet(name = "Guest") {
    return `Hello, ${name}`;
}

// 6. 扩展运算符
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4]; // [1, 2, 3, 4]
const obj1 = {a: };
 obj2 = {...obj1, : }; 


 x = , y = ;
 point = {x, y}; 


 promise =  ( {
    ( (), );
});


  () {
     data =  promise;
     data;
}


  {
    () {
        . = name;
    }
    () {
         ;
    }
     () {
          (name);
    }
}
1
const
b
2
// {a: 1, b: 2}
// 7. 对象简写
const
10
20
const
// 等同于 {x: x, y: y}
// 8. Promise
const
new
Promise
(resolve, reject) =>
setTimeout
() =>
resolve
"Done"
1000
// 9. async/await(ES8,但通常归为 ES6+)
async
function
fetchData
const
await
return
// 10. 类(Class)
class
Person
constructor
name
this
name
greet
return
`Hello, I'm ${this.name}`
static
create
name
return
new
Person
其他重要特性
  • 模块化:import/export
  • Symbol:唯一标识符
  • Set/Map:新的数据结构
  • for…of 循环:遍历可迭代对象
  • 生成器函数:function*
  • Proxy/Reflect:元编程
  • 可选链操作符:?.(ES2020)
  • 空值合并运算符:??(ES2020)

2. 什么是跨域

同源策略
// 同源条件:协议、域名、端口必须完全相同
// 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 (端口不同)
跨域解决方案
1. 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'); // 允许携带 cookie

// 简单请求 vs 预检请求
// 简单请求:GET/HEAD/POST,Content-Type 为特定值
// 预检请求:非简单请求前会发送 OPTIONS 请求
2. JSONP
// 客户端
function handleResponse(data) {
    console.log(data);
}

// 动态创建 script 标签
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);

// 服务器响应
handleResponse({data: "some data"});
3. 代理服务器
// 开发环境代理(webpack 配置)
module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'http://api.example.com',
                changeOrigin: true,
                pathRewrite: {'^/api': ''}
            }
        }
    }
};
4. WebSocket
// WebSocket 不受同源策略限制
const socket = new WebSocket('ws://api.example.com');
5. Nginx 反向代理
# nginx 配置
location /api/ {
    proxy_pass http://api.example.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

3. 监听数组变化

Vue2 的实现原理

Vue2 使用 Object.defineProperty 无法直接监听数组索引或长度的变化,因此采用了重写数组原型方法的方式。

// 1. 重写数组方法
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

methodsToPatch.forEach(function(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;
        },
        enumerable: false,
        writable: true,
        configurable: true
    });
});

// 2. 替换数组的原型
const arr = [1, 2, 3];
Object.setPrototypeOf(arr, arrayMethods);
Vue3 的实现原理

Vue3 引入 Proxy,可以直接拦截整个对象的操作,包括数组的索引访问和长度修改。

// 使用 Proxy 代理整个数组
function reactiveArray(arr) {
    return new Proxy(arr, {
        get(target, key) {
            // 处理数组的特殊情况
            if(key === 'length') return target.length;
            const value = target[key];
            // 如果访问的是方法,绑定正确的 this
            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;
        }
    });
}

4. v-if vs v-show

原理对比
<template>
    <!-- v-if:条件渲染 -->
    <div v-if="show">使用 v-if</div>
    <!-- v-show:条件显示 -->
    <div v-show="show">使用 v-show</div>
</template>
<script>
export default {
    data() {
        return {
            show: true
        };
    }
};
</script>
差异对比表
特性v-ifv-show
DOM 操作条件为 false 时,元素从 DOM 移除元素始终在 DOM 中,只切换 display 属性
编译/卸载切换时触发组件的生命周期钩子不触发生命周期钩子
性能初始渲染开销小,切换开销大初始渲染开销大,切换开销小
使用场景不频繁切换,条件很少改变频繁切换,需要保持状态
配合使用可与 v-else、v-else-if 配合没有配套指令
源码分析
// v-if 的编译结果
function render() {
    return show ? createElement('div', 'v-if 内容') : createEmptyVNode();
}

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

5. 网页加载优化

性能指标(Core Web Vitals)
  1. LCP(最大内容绘制) < 2.5 秒
  2. FID(首次输入延迟) < 100 毫秒
  3. CLS(累计布局偏移) < 0.1
优化策略
1. 代码优化
// 代码分割
const LazyComponent = () => import('./LazyComponent.vue');

// 路由懒加载
const routes = [
    {path: '/home', component: () => import('./views/Home.vue')}
];

// Webpack 配置优化
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'all', // 代码分割
            cacheGroups: {
                vendor: {
                    test: /[\/]node_modules[\/]/,
                    name: 'vendors'
                }
            }
        }
    }
};
2. 资源优化
<!-- 图片优化 -->
<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="preload" href="critical.css" as="style">
<link rel="prefetch" href="next-page.html"> <!-- 预取 -->
3. 缓存策略
// Service Worker 缓存
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open('v1').then(cache => {
            return cache.addAll(['/', '/styles/main.css', '/script/main.js']);
        })
    );
});
4. 使用 Lighthouse 分析
# 命令行使用
npx lighthouse https://example.com --view

# 生成报告
npx lighthouse https://example.com --output=json --output-path=report.json

6. 浏览器渲染过程

详细流程
1. 构建 DOM 树
// HTML → DOM 树
<html> → DOM 节点
<body> → DOM 节点
<div> → DOM 节点
2. 构建 CSSOM 树
/* CSS → CSSOM 树 */
body {
    font-size: 16px;
}
div {
    color: red;
}
3. 渲染树构建
  • 结合 DOM 和 CSSOM
  • 排除不可见元素(display: none)
4. 布局(重排)
// 计算每个元素的位置和大小
// 触发重排的操作:
element.style.width = '100px';
element.style.height = '200px';
window.getComputedStyle(element);
5. 绘制(重绘)
// 填充像素
// 触发重绘的操作:
element.style.color = 'red';
element.style.backgroundColor = '#fff';
6. 合成
// GPU 加速的图层合成
element.style.transform = 'translateX(100px)'; // 触发合成层
优化建议
// 避免强制同步布局
function badExample() {
    // 读取 → 写入 → 读取 → 写入
    const width = element.offsetWidth; // 触发重排
    element.style.width = width + 10 + 'px'; // 触发重排
}

function goodExample() {
    // 读取 → 读取 → 写入 → 写入
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    element.style.width = width + 10 + 'px';
    element.style.height = height + 10 + 'px';
}

7. 节流和防抖

防抖(Debounce)
// 原理:在事件被触发 n 秒后再执行,如果在这 n 秒内又被触发,则重新计时
function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
        const context = this;
        // 清除之前的定时器
        clearTimeout(timer);
        // 设置新的定时器
        timer = setTimeout(() => {
            fn.apply(context, args);
        }, delay);
    };
}

// 使用场景:搜索框输入、窗口 resize
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function() {
    console.log('搜索:', this.value);
}, 500));
节流(Throttle)
// 原理:规定在一个单位时间内,只能触发一次函数

// 时间戳实现
function throttle(fn, delay) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        const context = this;
        if(now - lastTime >= delay) {
            fn.apply(context, args);
            lastTime = now;
        }
    };
}

// 定时器实现
function throttle(fn, delay) {
    let timer = null;
    let lastArgs = null;
    return function(...args) {
        lastArgs = args;
        if(!timer) {
            timer = setTimeout(() => {
                fn.apply(this, lastArgs);
                timer = null;
            }, delay);
        }
    };
}

// 使用场景:滚动加载、按钮点击
window.addEventListener('scroll', throttle(function() {
    console.log('滚动位置:', window.scrollY);
}, 100));
结合版(加强版节流)
function enhancedThrottle(fn, delay, options = {}) {
    const {leading = true, trailing = true} = options;
    let timer = null;
    let lastTime = 0;
    let lastArgs = null;
    return function(...args) {
        const context = this;
        const now = Date.now();
        // 计算剩余时间
        const remaining = delay - (now - lastTime);
        // 更新参数
        lastArgs = args;
        if(remaining <= 0) {
            // 时间到了,执行函数
            if(timer) {
                clearTimeout(timer);
                timer = null;
            }
            if(leading) {
                fn.apply(context, args);
            }
            lastTime = now;
        } else if(!timer && trailing) {
            // 设置定时器,确保最后一次执行
            timer = setTimeout(() => {
                fn.apply(context, lastArgs);
                timer = null;
                lastTime = Date.now();
            }, remaining);
        }
    };
}

8. 闭包

基本概念
// 闭包:能够访问其他函数作用域中变量的函数
function outer() {
    const name = "John"; // 局部变量
    function inner() {
        console.log(name); // 访问外部函数的变量
    }
    return inner; // 返回内部函数
}
const closureFn = outer();
closureFn(); // "John"
实际应用
1. 数据私有化
function createCounter() {
    let count = 0; // 私有变量
    return {
        increment() {
            count++;
            return count;
        },
        decrement() {
            count--;
            return count;
        },
        getCount() {
            return count;
        }
    };
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
2. 函数工厂
function createMultiplier(multiplier) {
    return function(x) {
        return x * multiplier;
    };
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. 模块模式
const myModule = (function() {
    let privateVar = 0;
    function privateMethod() {
        return privateVar;
    }
    return {
        publicMethod() {
            privateVar++;
            return privateMethod();
        }
    };
})();
console.log(myModule.publicMethod()); // 1
内存管理
// 闭包可能导致内存泄漏
function leakMemory() {
    const largeArray = new Array(1000000).fill('data');
    return function() {
        console.log('仍然持有 largeArray 的引用'); // largeArray 不会被垃圾回收
    };
}

// 解决方案:及时释放引用
function noLeak() {
    let largeArray = new Array(1000000).fill('data');
    const result = largeArray.length; // 使用完后设为 null
    largeArray = null;
    return function() {
        console.log('不持有 largeArray 的引用');
        return result;
    };
}

9. 浏览器线程

主要线程
1. 主线程(UI 线程)
// 负责:DOM 渲染、JavaScript 执行、用户交互
// 注意:JavaScript 是单线程的
console.log(1);
setTimeout(() => console.log(2), 0);
console.log(3);
// 输出:1, 3, 2(因为有事件循环)
2. 浏览器内核线程
  • GUI 渲染线程:解析 HTML/CSS,构建 DOM 树
  • JavaScript 引擎线程:执行 JavaScript 代码
  • 事件触发线程:管理事件队列
  • 定时器触发线程:setTimeout/setInterval
  • 异步 HTTP 请求线程:XMLHttpRequest
事件循环机制
// 微任务(Microtask)vs 宏任务(Macrotask)
console.log('script start'); // 同步任务
setTimeout(() => {
    console.log('setTimeout'); // 宏任务
}, 0);
Promise.resolve().then(() => {
    console.log('promise1'); // 微任务
}).then(() => {
    console.log('promise2'); // 微任务
});
console.log('script end');

// 执行顺序:
// 1. script start
// 2. script end
// 3. promise1
// 4. promise2
// 5. setTimeout
Web Workers
// 主线程
const worker = new Worker('worker.js');
worker.postMessage({data: 'Hello'});
worker.onmessage = function(event) {
    console.log('收到 worker 消息:', event.data);
};

// worker.js(另一个线程)
self.onmessage = function(event) {
    const result = heavyComputation(event.data);
    self.postMessage(result);
};

function heavyComputation(data) {
    // 耗时操作
    return data.toUpperCase();
}

10. Vue3 vs Vue2 区别

核心差异对比表
特性Vue2Vue3
响应式原理Object.definePropertyProxy
Composition APIOptions APIComposition API + Options API
性能较慢更快(打包体积小 40%)
TypeScript 支持一般完善
Fragment不支持支持多根节点
Teleport不支持支持
Suspense不支持支持
Tree-shaking有限更好的支持
响应式系统
// Vue2 响应式
const data = {count: 0};
Object.defineProperty(data, 'count', {
    get() {
        console.log('获取 count');
        return value;
    },
    set(newValue) {
        console.log('设置 count');
        value = newValue;
    }
});

// Vue3 响应式
const data = {count: 0};
const proxy = new Proxy(data, {
    get(target, key) {
        console.log(`获取${key}`);
        return target[key];
    },
    set(target, key, value) {
        console.log(`设置${key}`);
        target[key] = value;
        return true;
    }
});
Composition API 示例
<template>
    <div>
        <p>Count: {{ count }}</p>
        <p>Double: {{ doubleCount }}</p>
        <button @click="increment">Increment</button>
    </div>
</template>
<script>
import { ref, computed, onMounted } from 'vue';
export default {
    setup() {
        // 响应式数据
        const count = ref(0);
        // 计算属性
        const doubleCount = computed(() => count.value * 2);
        // 方法
        function increment() {
            count.value++;
        }
        // 生命周期
        onMounted(() => {
            console.log('组件已挂载');
        });
        return { count, doubleCount, increment };
    }
};
</script>
性能优化
// Vue3 的静态提升
const _hoisted_1 = {id: "app"}; // 静态节点提升
const _hoisted_2 = /*#__PURE__*/createVNode("div", null, "静态内容"); // 静态节点

// Vue3 的 Patch Flag
const vnode = {
    type: 'div',
    props: {id: 'app', class: 'container'},
    children: [{
        type: 'span',
        children: '动态内容',
        patchFlag: 1 /* TEXT */
    }]
};

11. new 关键字

实现原理
// new 关键字的作用
function Person(name) {
    this.name = name;
}
const john = new Person('John');

// 模拟 new 的实现
function myNew(constructor, ...args) {
    // 1. 创建一个空对象
    const obj = {};
    // 2. 将对象的原型指向构造函数的 prototype
    Object.setPrototypeOf(obj, constructor.prototype);
    // 3. 将构造函数的 this 绑定到新对象
    const result = constructor.apply(obj, args);
    // 4. 如果构造函数返回对象,则返回该对象,否则返回新对象
    return result instanceof Object ? result : obj;
}
const john2 = myNew(Person, 'John');
详细步骤分析
function Animal(name) {
    this.name = name;
    this.speak = function() {
        return `${this.name} makes a noise`;
    };
    // 如果构造函数返回一个对象,new 表达式会返回这个对象
    // return { custom: 'object' };
}
Animal.prototype.eat = function() {
    return `${this.name} eats food`;
};

// 使用 new
const animal = new Animal('Dog');

// 等同于
const animal = {};
animal.__proto__ = Animal.prototype;
Animal.call(animal, 'Dog');

12. bind、call、apply 的区别

基本用法对比
const person = {
    name: 'John',
    greet: function(greeting, punctuation) {
        return `${greeting}, ${this.name}${punctuation}`;
    }
};
const anotherPerson = {name: 'Jane'};

// call:立即调用,参数逐个传递
person.greet.call(anotherPerson, 'Hello', '!'); // "Hello, Jane!"

// apply:立即调用,参数数组传递
person.greet.apply(anotherPerson, ['Hi', '!!']); // "Hi, Jane!!"

// bind:返回新函数,不立即调用
const boundGreet = person.greet.bind(anotherPerson, 'Hey');
boundGreet('?'); // "Hey, Jane?"
手写实现
// 实现 call
Function.prototype.myCall = function(context, ...args) {
    // 如果 context 是 null 或 undefined,指向全局对象
    context = context || window;
    // 创建唯一 key,避免属性冲突
    const fnKey = Symbol('fn');
    // 将函数作为 context 的方法
    context[fnKey] = this;
    // 执行函数
    const result = context[fnKey](...args);
    // 删除添加的方法
    delete context[fnKey];
    return result;
};

// 实现 apply
Function.prototype.myApply = function(context, argsArray = []) {
    context = context || window;
    const fnKey = Symbol('fn');
    context[fnKey] = this;
    const result = context[fnKey](...argsArray);
    delete context[fnKey];
    return result;
};

// 实现 bind
Function.prototype.myBind = function(context, ...bindArgs) {
    const originalFn = this;
    return function boundFn(...callArgs) {
        // 判断是否作为构造函数调用(使用 new)
        const isConstructorCall = new.target !== undefined;
        if(isConstructorCall) {
            // 如果是 new 调用,忽略绑定的 this
            return new originalFn(...bindArgs, ...callArgs);
        } else {
            // 普通调用,使用绑定的 this
            return originalFn.apply(context, [...bindArgs, ...callArgs]);
        }
    };
};
实际应用场景
// 1. 借用数组方法处理类数组对象
function sum() {
    // arguments 是类数组,没有数组方法
    const args = Array.prototype.slice.call(arguments);
    return args.reduce((acc, val) => acc + val, 0);
}

// 2. 构造函数继承
function Parent(name) {
    this.name = name;
}
function Child(name, age) {
    Parent.call(this, name); // 继承属性
    this.age = age;
}

// 3. 事件处理函数绑定 this
class Button {
    constructor() {
        this.text = 'Click me';
        // 使用 bind 保持 this 指向
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        console.log(this.text);
    }
}

13. TypeScript 装饰器

装饰器类型
// 类装饰器
function ClassDecorator(constructor: Function) {
    console.log('类装饰器执行');
    constructor.prototype.newProperty = 'added by decorator';
}

// 方法装饰器
function MethodDecorator(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    console.log(`方法装饰器:${propertyKey}`);
    const originalMethod = descriptor.value;
    descriptor.value = function(...args: any[]) {
        console.log(`调用方法:${propertyKey}`);
        return originalMethod.apply(this, args);
    };
}

// 属性装饰器
function PropertyDecorator(target: any, propertyKey: string) {
    console.log(`属性装饰器:${propertyKey}`);
}

// 参数装饰器
function ParameterDecorator(
    target: any,
    propertyKey: string,
    parameterIndex: number
) {
    console.log(`参数装饰器:${propertyKey}的第${parameterIndex}个参数`);
}

// 访问器装饰器
function AccessorDecorator(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    console.log(`访问器装饰器:${propertyKey}`);
}

// 装饰器工厂
function LogDecorator(message: string) {
    return function(
        target: any,
        propertyKey: string,
        descriptor?: PropertyDescriptor
    ) {
        console.log(`${message}: ${propertyKey}`);
    };
}
实际应用示例
// 1. 日志装饰器
function log(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const originalMethod = descriptor.value;
    descriptor.value = function(...args: any[]) {
        console.log(`调用 ${propertyKey},参数:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`返回结果:`, result);
        return result;
    };
}

// 2. 自动绑定 this 装饰器
function autobind(
    _target: any,
    _propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const originalMethod = descriptor.value;
    const adjustedDescriptor: PropertyDescriptor = {
        configurable: true,
        enumerable: false,
        get() {
            // 返回绑定 this 的方法
            return originalMethod.bind(this);
        }
    };
    return adjustedDescriptor;
}

// 3. 验证装饰器
function validate(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const originalMethod = descriptor.value;
    descriptor.value = function(...args: any[]) {
        // 参数验证逻辑
        args.forEach((arg, index) => {
            if(arg === undefined || arg === null) {
                throw new Error(`参数 ${index} 不能为空`);
            }
        });
        return originalMethod.apply(this, args);
    };
}

// 使用示例
class UserService {
    private users: string[] = [];

    @log
    @validate
    addUser(@ParameterDecorator name: string) {
        this.users.push(name);
        return this.users;
    }

    @autobind
    getUsers() {
        return this.users;
    }
}

14. 10w 数据展示优化

虚拟列表实现

当数据量达到万级甚至十万级时,一次性渲染所有 DOM 会导致页面卡顿。虚拟列表的核心思想是:只渲染可视区域及其附近的数据。通过计算滚动位置,动态调整渲染范围,并复用 DOM 节点。

<template>
    <div ref="container" @scroll="handleScroll" class="virtual-list-container">
        <!-- 撑开容器,显示滚动条 -->
        <div :style="{ height: totalHeight + 'px' }" class="virtual-list-phantom"></div>
        <!-- 可视区域内容 -->
        <div :style="{ transform: `translateY(${offset}px)` }" class="virtual-list-content">
            <div v-for="item in visibleData" :key="item.id" :style="{ height: itemHeight + 'px' }" class="virtual-list-item">
                {{ item.content }}
            </div>
        </div>
    </div>
</template>

<script>
export default {
    props: {
        data: {
            type: Array,
            required: true
        },
        itemHeight: {
            type: Number,
            default: 50
        },
        buffer: {
            type: Number,
            default: 5
        }
    },
    data() {
        return {
            startIndex: 0, // 开始索引
            endIndex: 0, // 结束索引
            offset: 0 // 偏移量
        };
    },
    computed: {
        // 总高度
        totalHeight() {
            return this.data.length * this.itemHeight;
        },
        // 容器高度
        containerHeight() {
            return this.$refs.container?.clientHeight || 0;
        },
        // 可见区域项数
        visibleCount() {
            return Math.ceil(this.containerHeight / this.itemHeight);
        },
        // 实际渲染的数据
        visibleData() {
            const start = Math.max(0, this.startIndex - this.buffer);
            const end = Math.min(this.data.length, this.endIndex + this.buffer);
            return this.data.slice(start, end);
        }
    },
    mounted() {
        this.updateVisibleData();
    },
    methods: {
        handleScroll() {
            const scrollTop = this.$refs.container.scrollTop;
            // 计算开始索引
            this.startIndex = Math.floor(scrollTop / this.itemHeight);
            // 计算结束索引
            this.endIndex = Math.min(
                this.data.length,
                this.startIndex + this.visibleCount
            );
            // 计算偏移量
            this.offset = this.startIndex * this.itemHeight;
        },
        updateVisibleData() {
            this.startIndex = 0;
            this.endIndex = Math.min(this.data.length, this.visibleCount);
        }
    }
};
</script>

<style>
.virtual-list-container {
    height: 500px;
    overflow-y: auto;
    position: relative;
}
.virtual-list-phantom {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
}
.virtual-list-content {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
}
.virtual-list-item {
    display: flex;
    align-items: center;
    padding: 0 16px;
    box-sizing: border-box;
    border-bottom: 1px solid #eee;
}
</style>
时间分片渲染

除了虚拟列表,还可以利用浏览器的空闲时间分批渲染数据,避免阻塞主线程。

// 使用 requestAnimationFrame 分批渲染
function renderLargeData(data, container, chunkSize = 100) {
    let index = 0;
    function renderChunk() {
        const fragment = document.createDocumentFragment();
        for(let i = 0; i < chunkSize && index < data.length; i++, index++) {
            const item = document.createElement('div');
            item.textContent = data[index];
            fragment.appendChild(item);
        }
        container.appendChild(fragment);
        // 如果还有数据,继续渲染下一批
        if(index < data.length) {
            requestAnimationFrame(renderChunk);
        }
    }
    renderChunk();
}

// 使用 Web Worker 处理数据
const worker = new Worker('data-processor.js');
worker.postMessage({action: 'process', data: largeData});
worker.onmessage = function(event) {
    const processedData = event.data;
    // 在主线程渲染处理后的数据
};

15. 时间切片

基本原理

时间切片(Time Slicing)允许 JavaScript 在执行长时间任务时主动让出控制权,将任务拆分成多个小块,在浏览器空闲时执行。这能有效防止长任务阻塞 UI 渲染。

// 使用 requestIdleCallback 实现时间切片
function timeSlicingWork(unitsOfWork) {
    let currentIndex = 0;
    function doWork(deadline) {
        // 在空闲时间内执行工作单元
        while(currentIndex < unitsOfWork.length && deadline.timeRemaining() > 0) {
            performUnitOfWork(unitsOfWork[currentIndex]);
            currentIndex++;
        }
        // 如果还有工作,继续调度
        if(currentIndex < unitsOfWork.length) {
            requestIdleCallback(doWork);
        } else {
            console.log('所有工作完成');
        }
    }
    requestIdleCallback(doWork);
}

// React 中的时间切片实现(简化版)
function workLoop(deadline) {
    let shouldYield = false;
    while(nextUnitOfWork && !shouldYield) {
        // 执行工作单元
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        // 检查是否需要让出控制权
        shouldYield = deadline.timeRemaining() < 1;
    }
    // 如果还有工作,继续调度
    if(nextUnitOfWork) {
        requestIdleCallback(workLoop);
    }
}
requestIdleCallback(workLoop);
实际应用示例
// 1. 大数据处理
function processBigData(data, processItem, chunkSize = 100) {
    return new Promise((resolve) => {
        let index = 0;
        const results = [];
        function processChunk(deadline) {
            while(index < data.length && deadline.timeRemaining() > 0) {
                const chunk = data.slice(index, index + chunkSize);
                // 处理数据块
                for(const item of chunk) {
                    results.push(processItem(item));
                }
                index += chunkSize;
                // 更新进度
                updateProgress((index / data.length) * 100);
            }
            if(index < data.length) {
                // 还有数据,继续处理
                requestIdleCallback(processChunk);
            } else {
                // 处理完成
                resolve(results);
            }
        }
        requestIdleCallback(processChunk);
    });
}

// 2. 长列表渲染优化
class VirtualScroller {
    constructor(container, items, renderItem) {
        this.container = container;
        this.items = items;
        this.renderItem = renderItem;
        this.renderedItems = new Set();
    }
    renderVisibleItems() {
        const visibleRange = this.getVisibleRange();
        const itemsToRender = [];
        for(let i = visibleRange.start; i < visibleRange.end; i++) {
            if(!this.renderedItems.has(i)) {
                itemsToRender.push(i);
            }
        }
        // 使用时间切片分批渲染
        this.renderWithTimeSlice(itemsToRender);
    }
    renderWithTimeSlice(itemIndices) {
        let index = 0;
        const renderBatch = (deadline) => {
            while(index < itemIndices.length && deadline.timeRemaining() > 0) {
                const itemIndex = itemIndices[index];
                const item = this.items[itemIndex];
                // 渲染单个项目
                const element = this.renderItem(item, itemIndex);
                this.container.appendChild(element);
                this.renderedItems.add(itemIndex);
                index++;
            }
            if(index < itemIndices.length) {
                requestIdleCallback(renderBatch);
            }
        };
        requestIdleCallback(renderBatch);
    }
}
React Concurrent Mode 中的时间切片

React 18+ 引入了并发特性,允许中断和恢复渲染过程。

// React 18+ 中的并发特性
import { startTransition, useDeferredValue } from 'react';

function SearchResults({ query }) {
    const deferredQuery = useDeferredValue(query);
    return (
        <div>
            <SlowList query={deferredQuery}/>
        </div>
    );
}

// 在事件处理中使用 startTransition
function handleChange(e) {
    setInput(e.target.value);
    // 将 setQuery 标记为过渡更新
    startTransition(() => {
        setQuery(e.target.value);
    });
}

// 这会告诉 React 这个更新不是紧急的,可以被中断

目录

  1. 前端面试题详细解答
  2. 1. ES6 新特性详解(重要 10 个)
  3. 核心特性
  4. 其他重要特性
  5. 2. 什么是跨域
  6. 同源策略
  7. 跨域解决方案
  8. 1. CORS(跨域资源共享)
  9. 2. JSONP
  10. 3. 代理服务器
  11. 4. WebSocket
  12. 5. Nginx 反向代理
  13. nginx 配置
  14. 3. 监听数组变化
  15. Vue2 的实现原理
  16. Vue3 的实现原理
  17. 4. v-if vs v-show
  18. 原理对比
  19. 差异对比表
  20. 源码分析
  21. 5. 网页加载优化
  22. 性能指标(Core Web Vitals)
  23. 优化策略
  24. 1. 代码优化
  25. 2. 资源优化
  26. 3. 缓存策略
  27. 4. 使用 Lighthouse 分析
  28. 命令行使用
  29. 生成报告
  30. 6. 浏览器渲染过程
  31. 详细流程
  32. 1. 构建 DOM 树
  33. 2. 构建 CSSOM 树
  34. 3. 渲染树构建
  35. 4. 布局(重排)
  36. 5. 绘制(重绘)
  37. 6. 合成
  38. 优化建议
  39. 7. 节流和防抖
  40. 防抖(Debounce)
  41. 节流(Throttle)
  42. 结合版(加强版节流)
  43. 8. 闭包
  44. 基本概念
  45. 实际应用
  46. 1. 数据私有化
  47. 2. 函数工厂
  48. 3. 模块模式
  49. 内存管理
  50. 9. 浏览器线程
  51. 主要线程
  52. 1. 主线程(UI 线程)
  53. 2. 浏览器内核线程
  54. 事件循环机制
  55. Web Workers
  56. 10. Vue3 vs Vue2 区别
  57. 核心差异对比表
  58. 响应式系统
  59. Composition API 示例
  60. 性能优化
  61. 11. new 关键字
  62. 实现原理
  63. 详细步骤分析
  64. 12. bind、call、apply 的区别
  65. 基本用法对比
  66. 手写实现
  67. 实际应用场景
  68. 13. TypeScript 装饰器
  69. 装饰器类型
  70. 实际应用示例
  71. 14. 10w 数据展示优化
  72. 虚拟列表实现
  73. 时间分片渲染
  74. 15. 时间切片
  75. 基本原理
  76. 实际应用示例
  77. React Concurrent Mode 中的时间切片
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 嵌入式 UART 协议面试题及参考答案
  • Zynq PS 与 PL 数据交互设计及算法加速验证
  • Python 数据分析师 IDE 选型指南
  • Gateway 设备令牌不匹配问题排查指南
  • SpringAI 与 Deepseek 大模型应用开发实战笔记(上)
  • ASP.NET Core Web API 控制器与方法注解属性详解
  • Python 实现 MCP 客户端调用高德地图天气查询示例
  • HarmonyOS 6 Navigation 组件导航生命周期解析
  • 网络安全入门指南:分支方向与学习路线
  • C++ 位运算技巧与常见算法题解
  • 通义万相 2.1 多模态 AI 生成技术解析与应用前景
  • Python 操作 CMD 命令行详解与实战
  • Android 转场动画演进历程与实战解析
  • Open WebUI 下载模型文件的默认存储路径
  • 数据结构入门:顺序表的定义、分类及动态实现
  • Linux 底层核心精讲:环境变量、命令行参数与程序地址空间全解析
  • Python 生成 HTTP 流量 PCAP 报文工具,支持 TCP 握手与四元组递增
  • TS-RAG:基于检索增强的时间序列基础模型零样本预测
  • 零基础网络安全入门指南:学习路径与核心技能解析
  • VS Code Python 解释器选择报错解决方法
  • 相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • RSA密钥对生成器

      生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

    • Mermaid 预览与可视化编辑

      基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

    • 随机西班牙地址生成器

      随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • Keycode 信息

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