跳到主要内容前端面试题精选:ES6 跨域 Vue 性能优化及 10w 数据展示 | 极客日志JavaScriptNode.js大前端
前端面试题精选:ES6 跨域 Vue 性能优化及 10w 数据展示
系统梳理了前端开发核心面试题,涵盖 ES6 新特性、跨域处理方案、Vue2 与 Vue3 响应式原理对比、组件指令差异、页面加载性能优化、浏览器渲染机制、事件循环、闭包应用、浏览器线程模型、TypeScript 装饰器以及大规模数据渲染优化(虚拟列表、时间切片)。文中提供代码示例与源码分析,旨在帮助开发者巩固基础并提升工程化能力。
RustyLab1 浏览 前端面试题详细解答
1. ES6 新特性详解(重要 10 个)
核心特性
x = ;
y = ;
= () => a + b;
name = ;
greeting = ;
[a, b] = [, ];
{ name, age } = { : , : };
() {
;
}
arr1 = [, ];
arr2 = [...arr1, , ];
obj1 = { : };
obj2 = { ...obj1, : };
x = , y = ;
point = { x, y };
promise = ( {
( (), );
});
() {
data = promise;
data;
}
{
() {
. = name;
}
() {
;
}
() {
(name);
}
}
let
10
const
20
const
add
a, b
const
"John"
const
`Hello, ${name}!`
const
1
2
const
name
"John"
age
25
function
greet
name = "Guest"
return
`Hello, ${name}`
const
1
2
const
3
4
const
a
1
const
b
2
const
10
20
const
const
new
Promise
(resolve, reject) =>
setTimeout
() =>
resolve
"Done"
1000
async
function
fetchData
const
await
return
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. 什么是跨域
同源策略
跨域解决方案
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');
2. JSONP
function handleResponse(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
3. 代理服务器
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};
4. 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 的实现原理
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
});
});
const arr = [1, 2, 3];
Object.setPrototypeOf(arr, arrayMethods);
Vue3 的实现原理
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;
}
});
}
4. v-if vs v-show
原理对比
<template>
<div v-if="show">使用 v-if</div>
<div v-show="show">使用 v-show</div>
</template>
<script>
export default {
data() {
return {
show: true
};
}
};
</script>
差异对比表
| 特性 | v-if | v-show |
|---|
| DOM 操作 | 条件为 false 时,元素从 DOM 移除 | 元素始终在 DOM 中,只切换 display 属性 |
| 编译/卸载 | 切换时触发组件的生命周期钩子 | 不触发生命周期钩子 |
| 性能 | 初始渲染开销小,切换开销大 | 初始渲染开销大,切换开销小 |
| 使用场景 | 不频繁切换,条件很少改变 | 频繁切换,需要保持状态 |
| 配合使用 | 可与 v-else、v-else-if 配合 | 没有配套指令 |
源码分析
function render() {
return show ? createElement('div', 'v-if 内容') : createEmptyVNode();
}
function render() {
return createElement('div', {
directives: [{ name: 'show', value: show }],
style: { display: show ? '' : 'none' }
}, 'v-show 内容');
}
5. 网页加载优化
性能指标(Core Web Vitals)
- LCP(最大内容绘制) < 2.5 秒
- FID(首次输入延迟) < 100 毫秒
- CLS(累计布局偏移) < 0.1
优化策略
1. 代码优化
const LazyComponent = () => import('./LazyComponent.vue');
const routes = [
{ path: '/home', component: () => import('./views/Home.vue') }
];
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. 缓存策略
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 树
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. 合成
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)
function debounce(fn, delay) {
let timer = null;
return function(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
}
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();
实际应用
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());
console.log(counter.increment());
console.log(counter.getCount());
2. 函数工厂
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5));
console.log(triple(5));
3. 模块模式
const myModule = (function() {
let privateVar = 0;
function privateMethod() { return privateVar; }
return {
publicMethod() {
privateVar++;
return privateMethod();
}
};
})();
console.log(myModule.publicMethod());
内存管理
function leakMemory() {
const largeArray = new Array(1000000).fill('data');
return function() {
console.log('仍然持有 largeArray 的引用');
};
}
function noLeak() {
let largeArray = new Array(1000000).fill('data');
const result = largeArray.length;
largeArray = null;
return function() {
console.log('不持有 largeArray 的引用');
return result;
};
}
9. 浏览器线程
主要线程
1. 主线程(UI 线程)
console.log(1);
setTimeout(() => console.log(2), 0);
console.log(3);
2. 浏览器内核线程
- GUI 渲染线程:解析 HTML/CSS,构建 DOM 树
- JavaScript 引擎线程:执行 JavaScript 代码
- 事件触发线程:管理事件队列
- 定时器触发线程:setTimeout/setInterval
- 异步 HTTP 请求线程:XMLHttpRequest
事件循环机制
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
}).then(() => {
console.log('promise2');
});
console.log('script end');
Web Workers
const worker = new Worker('worker.js');
worker.postMessage({ data: 'Hello' });
worker.onmessage = function(event) {
console.log('收到 worker 消息:', event.data);
};
self.onmessage = function(event) {
const result = heavyComputation(event.data);
self.postMessage(result);
};
function heavyComputation(data) {
return data.toUpperCase();
}
10. Vue3 vs Vue2 区别
核心差异对比表
| 特性 | Vue2 | Vue3 |
|---|
| 响应式原理 | Object.defineProperty | Proxy |
| Composition API | Options API | Composition API + Options API |
| 性能 | 较慢 | 更快(打包体积小 40%) |
| TypeScript 支持 | 一般 | 完善 |
| Fragment | 不支持 | 支持多根节点 |
| Teleport | 不支持 | 支持 |
| Suspense | 不支持 | 支持 |
| Tree-shaking | 有限 | 更好的支持 |
响应式系统
const data = { count: 0 };
Object.defineProperty(data, 'count', {
get() {
console.log('获取 count');
return value;
},
set(newValue) {
console.log('设置 count');
value = newValue;
}
});
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>
性能优化
const _hoisted_1 = { id: "app" };
const _hoisted_2 = createVNode("div", null, "静态内容");
const vnode = {
type: 'div',
props: { id: 'app', class: 'container' },
children: [
{ type: 'span', children: '动态内容', patchFlag: 1 }
]
};
11. new 关键字
实现原理
function Person(name) {
this.name = name;
}
const john = new Person('John');
function myNew(constructor, ...args) {
const obj = {};
Object.setPrototypeOf(obj, constructor.prototype);
const result = constructor.apply(obj, args);
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`;
};
}
Animal.prototype.eat = function() {
return `${this.name} eats food`;
};
const animal = new Animal('Dog');
12. bind、call、apply 的区别
基本用法对比
const person = {
name: 'John',
greet: function(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
};
const anotherPerson = { name: 'Jane' };
person.greet.call(anotherPerson, 'Hello', '!');
person.greet.apply(anotherPerson, ['Hi', '!!']);
const boundGreet = person.greet.bind(anotherPerson, 'Hey');
boundGreet('?');
手写实现
Function.prototype.myCall = function(context, ...args) {
context = context || window;
const fnKey = Symbol('fn');
context[fnKey] = this;
const result = context[fnKey](...args);
delete context[fnKey];
return result;
};
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;
};
Function.prototype.myBind = function(context, ...bindArgs) {
const originalFn = this;
return function boundFn(...callArgs) {
const isConstructorCall = new.target !== undefined;
if(isConstructorCall) {
return new originalFn(...bindArgs, ...callArgs);
} else {
return originalFn.apply(context, [...bindArgs, ...callArgs]);
}
};
};
实际应用场景
function sum() {
const args = Array.prototype.slice.call(arguments);
return args.reduce((acc, val) => acc + val, 0);
}
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
class Button {
constructor() {
this.text = 'Click me';
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}`);
};
}
实际应用示例
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;
};
}
function autobind(
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const adjustedDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: false,
get() {
return originalMethod.bind(this);
}
};
return adjustedDescriptor;
}
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 数据展示优化
虚拟列表实现
<template>
<div ref="container" @scroll="handleScroll">
<!-- 撑开容器,显示滚动条 -->
<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>
<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>
时间分片渲染
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();
}
const worker = new Worker('data-processor.js');
worker.postMessage({ action: 'process', data: largeData });
worker.onmessage = function(event) {
const processedData = event.data;
};
15. 时间切片
基本原理
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);
}
function workLoop(deadline) {
let shouldYield = false;
while(nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if(nextUnitOfWork) {
requestIdleCallback(workLoop);
}
}
requestIdleCallback(workLoop);
实际应用示例
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);
});
}
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 中的时间切片
import { startTransition, useDeferredValue } from 'react';
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query);
return (
<div>
<SlowList query={deferredQuery}/>
</div>
);
}
function handleChange(e) {
setInput(e.target.value);
startTransition(() => {
setQuery(e.target.value);
});
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 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