当前位置: 首页 > news >正文

前端面试题总结

浏览器缓存机制详解

浏览器缓存机制通过本地存储资源(如HTML、CSS、JavaScript、图片等)优化网页加载速度、降低服务器压力并减少网络流量消耗。以下是其核心原理与技术实现:


缓存的作用

  • 加速页面加载:本地读取资源比远程下载更快。
  • 降低服务器负载:减少重复请求,节省服务器资源。
  • 节省用户流量:避免重复下载相同内容。

缓存的分类

浏览器缓存分为两类:

  1. 强缓存:直接使用本地资源,无需与服务器通信。
  2. 协商缓存:向服务器验证资源有效性,若未更新则返回304状态码,继续使用缓存。

强缓存的实现方式

通过以下HTTP响应头控制:

  • Expires
    指定资源的绝对过期时间(如Expires: Wed, 21 Oct 2023 07:28:00 GMT)。
    缺点:依赖客户端时间,若时间不准可能导致缓存失效。

  • Cache-Control
    优先级高于Expires,支持更灵活的指令:

    • max-age=<seconds>:相对时间(如Cache-Control: max-age=3600缓存1小时)。
    • no-cache:跳过强缓存,强制协商缓存。
    • no-store:完全禁用缓存。
    • public:允许代理服务器缓存资源。
    • private:仅允许客户端缓存。

协商缓存的实现方式

当强缓存失效时触发:

  • Last-Modified / If-Modified-Since

    • 服务器返回Last-Modified响应头(如Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT)。
    • 浏览器后续请求携带If-Modified-Since,服务器比对时间决定返回304或200。
  • ETag / If-None-Match

    • 服务器返回资源唯一标识(如ETag: "123456")。
    • 浏览器后续请求携带If-None-Match,服务器比对ETag决定返回304或200。

完整缓存流程

  1. 检查强缓存:若资源未过期,直接使用本地缓存。
  2. 验证协商缓存:若强缓存失效,携带If-Modified-SinceIf-None-Match向服务器发起请求。
  3. 服务器决策:资源未更新时返回304,否则返回200及新资源。

缓存存储位置

  • Memory Cache:内存缓存,速度快但随页面关闭失效。
  • Disk Cache:磁盘缓存,容量大且持久化。

缓存策略建议

  • 静态资源(CSS/JS/图片)
    设置较长max-age(如1年),并通过文件名哈希(如style.v1.css)确保更新生效。
  • 动态资源(API数据)
    使用no-cache或短max-age保证数据新鲜度。

用户行为影响

  • 正常刷新:可能触发协商缓存。
  • 强制刷新(Ctrl+F5):忽略缓存,直接请求服务器。
  • 地址栏回车:优先使用强缓存。

TCP和HTTP的区别

层级
TCP属于传输层协议,主要负责端到端的可靠数据传输。
HTTP属于应用层协议,建立在TCP之上,定义数据格式和通信规则。

功能
TCP确保数据包的顺序、完整性及重传机制,提供基础通信通道。
HTTP定义客户端与服务器之间的交互语义(如请求方法、状态码、头部字段)。

连接方式
TCP通过三次握手建立持久连接,通信结束后四次挥手释放连接。
HTTP默认无状态(早期版本),每个请求独立处理,现代HTTP/1.1后支持长连接复用TCP通道。

数据内容
TCP传输原始字节流,不解析内容含义,仅保证数据准确送达。
HTTP传输有语义的结构化数据(如HTML、JSON),包含头部和正文,供应用层解析。

典型应用
TCP作为基础协议支撑HTTP、FTP、SMTP等应用层协议。
HTTP专为Web设计,浏览器通过HTTP协议获取服务器资源。

协同关系
HTTP依赖TCP实现可靠传输,TCP为HTTP提供字节流传输能力。例如,HTTP请求会被TCP分割为数据包,确保无误到达后重组为完整消息。

同步任务和异步任务的区别

同步任务
同步任务按照代码书写顺序依次执行,任务执行时会阻塞主线程,直到当前任务完成才能继续执行下一个任务。适合处理简单、快速完成的操作,如变量赋值、简单的数学计算等。

异步任务
异步任务不会阻塞主线程,任务提交后立即返回,允许程序继续执行后续代码。耗时操作(如网络请求、文件读写)通常在后台线程中执行,完成后通过回调、Promise 或事件通知主线程。这种方式能提升程序的响应速度和用户体验。

实际开发中的应用

同步任务的使用场景
涉及简单逻辑或需要立即获取结果的操作,如数据验证、状态检查等。同步代码更容易编写和调试,但滥用会导致界面卡顿。

异步任务的使用场景
处理耗时操作,例如:

  • 网络请求(API 调用)
  • 文件 I/O 操作
  • 数据库查询
    通过回调、Promise 或 async/await 管理异步流程,避免阻塞 UI 渲染。

结合使用的示例

// 同步:快速验证输入
function validateInput(input) {return input !== '';
}// 异步:发起网络请求
async function fetchData(url) {try {const response = await fetch(url);return response.json();} catch (error) {console.error('Fetch failed:', error);}
}// 结合使用
const userInput = 'example';
if (validateInput(userInput)) {  // 同步fetchData('https://api.example.com/data')  // 异步.then(data => console.log(data));
}

性能与用户体验优化

  • 减少同步阻塞:将耗时任务异步化,确保主线程流畅。
  • 合理分工:同步处理即时反馈,异步处理后台任务。
  • 错误处理:异步操作需妥善捕获异常,避免静默失败。

通过合理搭配两种模式,可兼顾代码效率与用户体验。

浏览器的事件循环机制

为什么需要事件循环?

JavaScript 是单线程语言,同一时间只能执行一个任务。为了避免耗时任务(如网络请求、定时器)阻塞主线程,浏览器引入了异步任务和事件循环机制。这种机制使得 JavaScript 能够非阻塞地处理任务,同时保持代码的执行顺序。


事件循环的核心概念

调用栈(Call Stack)

调用栈用于存储同步任务的执行上下文(函数调用)。它遵循“后进先出”(LIFO)原则,任务按顺序执行,执行完毕后从栈中弹出。当调用栈为空时,事件循环会开始处理任务队列中的任务。

任务队列(Task Queue)

任务队列用于存储异步任务的回调函数。任务队列分为两类:

  • 宏任务队列(MacroTask Queue):包含 setTimeoutsetInterval、I/O 操作、UI 渲染等。
  • 微任务队列(MicroTask Queue):包含 Promise.thenMutationObserverqueueMicrotask 等。
事件循环(Event Loop)

事件循环负责协调任务的执行顺序。它会不断检查调用栈和任务队列的状态。当调用栈为空时,事件循环会依次执行以下步骤:

  1. 从微任务队列中取出所有任务并执行,直到微任务队列为空。
  2. 从宏任务队列中取出一个任务执行。
  3. 重复上述过程。

执行顺序规则

  1. 同步任务:直接进入调用栈执行。
  2. 微任务:在当前宏任务执行完毕后立即执行,优先级高于宏任务。
  3. 宏任务:在微任务队列清空后执行,每次事件循环只处理一个宏任务。

例如:

console.log('同步任务 1');setTimeout(() => {console.log('宏任务 1');
}, 0);Promise.resolve().then(() => {console.log('微任务 1');
});console.log('同步任务 2');

输出顺序:

同步任务 1
同步任务 2
微任务 1
宏任务 1

关键点总结

  • 微任务的优先级高于宏任务。
  • 每次宏任务执行后,事件循环会清空微任务队列。
  • 事件循环通过任务队列和调用栈的协作,实现了非阻塞的异步执行。

理解事件循环的机制有助于优化异步代码逻辑,避免性能瓶颈和意外行为。

宏任务与微任务的对比分析

任务来源差异
宏任务通常由浏览器API触发,例如setTimeoutsetInterval、DOM事件回调或I/O操作。微任务则通过Promise的.then()/.catch()/.finally()方法或MutationObserver API生成。

执行时机与顺序
当JavaScript引擎执行完一个宏任务后,会立即检查微任务队列并执行其中所有任务,直到队列清空。只有微任务队列为空时,才会继续执行下一个宏任务。这种机制导致微任务具有“插队”特性。

优先级表现
微任务优先级始终高于宏任务。例如,即便在宏任务中嵌套了setTimeout,其回调仍需等待当前所有微任务执行完毕。这种设计确保高时效性操作(如Promise状态更新)能优先处理。

典型应用场景
宏任务适合处理非紧急的后台任务,如延迟逻辑(setTimeout)或大数据量I/O。微任务适用于需要快速响应的操作,例如UI状态同步、Promise链式调用或Vue的异步DOM更新。

代码示例验证

console.log('Script start'); // 宏任务1开始setTimeout(() => console.log('Timeout'), 0); // 宏任务2入队Promise.resolve().then(() => {console.log('Promise 1'); // 微任务1
}).then(() => console.log('Promise 2')); // 微任务2console.log('Script end'); // 宏任务1结束// 输出顺序:
// Script start → Script end → Promise 1 → Promise 2 → Timeout

该示例清晰展示了微任务在宏任务之间优先执行的特性,即使setTimeout的延迟设置为0。

闭包的定义与原理

闭包指在函数内部定义另一个函数(内部函数),且内部函数引用了外部函数的变量。即使外部函数执行完毕,这些变量仍保留在内存中,因为内部函数可能在未来被调用。闭包的核心在于作用域链引用环境:内部函数通过作用域链访问外部函数的变量,即使外部函数已销毁,其变量仍被内部函数持有。


闭包的应用场景

缓存与优化计算
通过闭包保存计算结果,避免重复执行耗时操作。例如实现一个记忆化函数:

function memoize(fn) {const cache = {};return function(...args) {const key = JSON.stringify(args);if (cache[key]) return cache[key];cache[key] = fn(...args);return cache[key];};
}

封装私有数据
闭包模拟私有变量,外部无法直接访问:

function createCounter() {let count = 0;return {increment: () => count++,getValue: () => count};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 1

装饰器
在不修改原函数的基础上扩展功能,本质是闭包的应用:

def logger(func):def wrapper(*args):print(f"Calling {func.__name__}")return func(*args)return wrapper@logger
def add(a, b):return a + b

事件处理与回调
在异步编程中,闭包保留上下文数据:

function setupButton(buttonId, message) {document.getElementById(buttonId).onclick = function() {alert(message); // 闭包保留message的引用};
}

函数工厂
动态生成定制化函数:

function multiplier(factor) {return function(x) {return x * factor;};
}
const double = multiplier(2);
console.log(double(5)); // 10

闭包的优缺点

优点

  • 数据隔离:通过闭包实现私有变量,避免全局污染。
  • 状态保持:函数多次调用间可共享特定状态(如计数器)。
  • 灵活复用:通过参数生成不同行为的函数实例。

缺点

  • 内存泄漏:闭包长期持有外部变量引用,可能导致垃圾回收失效。需手动解除引用(如置null)。
  • 性能开销:作用域链的查找比直接访问局部变量更耗时。

注意事项

  • 避免循环引用(如闭包引用DOM元素,同时DOM元素引用闭包)。
  • 在需要释放资源时,主动断开闭包对外部变量的引用。

JavaScript 垃圾回收机制

JavaScript 的垃圾回收机制(Garbage Collection,GC)是一种自动内存管理方式,用于检测和释放不再使用的内存对象,防止内存泄漏。

核心工作原理

JavaScript 的垃圾回收器通过可达性(Reachability)判断对象是否可回收。如果一个对象无法通过引用链从根对象访问,则被视为垃圾并被回收。

  • 根对象(Roots):包括全局变量(如 window)、当前执行栈中的变量、DOM 节点等。
  • 引用链:从根对象出发,通过引用关系遍历所有可达对象。
  • 不可达对象:无法通过任何引用链访问的对象,会被垃圾回收器自动清理。
主要垃圾回收算法
  1. 标记-清除(Mark-and-Sweep)

    • 第一阶段(标记):遍历所有可达对象并标记为活动对象。
    • 第二阶段(清除):回收未被标记的不可达对象。
  2. 引用计数(Reference Counting)(较少使用)

    • 记录每个对象的引用次数,当引用次数为 0 时回收。
    • 缺点:无法处理循环引用问题,如两个对象互相引用但无外部引用时仍无法回收。
优化垃圾回收的策略
  1. 减少全局变量

    • 全局变量始终可达,直到程序结束才会被回收,容易导致内存浪费。
    • 使用模块化或闭包限制变量作用域。
  2. 手动清理引用

    • 对于定时器(setTimeoutsetInterval)、事件监听器等,使用后应及时清除:
      const timer = setTimeout(() => {}, 1000);
      clearTimeout(timer); // 避免内存泄漏
      
    • 移除不再需要的 DOM 事件监听器:
      element.removeEventListener('click', handler);
      
  3. 使用弱引用(WeakMap / WeakSet)

    • WeakMapWeakSet 不会阻止垃圾回收,适合存储临时或辅助数据:
      const weakMap = new WeakMap();
      weakMap.set(obj, 'tempData'); // 当 obj 被回收,weakMap 中的条目自动删除
      
  4. 避免频繁创建短期对象

    • 减少在循环或高频操作中创建临时对象,如拼接字符串时改用数组 + join()
    • 复用对象池(Object Pooling)优化性能。
常见内存泄漏场景
  • 意外全局变量:未声明的变量会变成全局变量。
    function leak() {leakedVar = '全局泄漏'; // 未使用 let/const/var
    }
    
  • 闭包未释放:内部函数持有外部变量引用。
    function outer() {const bigData = new Array(1000000);return function inner() {console.log(bigData.length); // bigData 无法释放};
    }
    
  • DOM 引用未清理:移除 DOM 节点后仍保留其引用。
    const elements = {button: document.getElementById('button'),
    };
    document.body.removeChild(elements.button); // 仍需 elements.button = null
    
调试工具
  • Chrome DevTools 的 Memory 面板可分析内存快照,检测泄漏对象。
  • performance.memory API 监控内存使用情况。

通过合理管理引用和避免常见陷阱,可显著优化 JavaScript 应用的性能和内存效率。

防抖的实现

防抖的核心逻辑是延迟执行函数,若在延迟期间重复触发事件,则重置延迟计时。适用于输入框搜索、窗口调整等场景。

function debounce(func, delay) {let timer = null;return function (...args) {if (timer) clearTimeout(timer);timer = setTimeout(() => {func.apply(this, args);}, delay);};
}// 示例:输入框防抖
const input = document.getElementById("input");
input.addEventListener("input", debounce(() => {console.log("输入框内容:", input.value);
}, 300));

节流的实现

节流通过时间戳或标志位限制函数执行频率,确保固定时间间隔内只执行一次。适用于滚动加载、高频点击等场景。

function throttle(func, delay) {let lastTime = 0;return function (...args) {const now = Date.now();if (now - lastTime >= delay) {func.apply(this, args);lastTime = now;}};
}// 示例:滚动节流
window.addEventListener("scroll", throttle(() => {console.log("滚动事件触发");
}, 200));

防抖与节流的区别

特性防抖(Debounce)节流(Throttle)
触发时机事件停止触发后执行固定时间间隔执行
执行次数最后仅一次间隔内可能多次
典型场景输入框搜索、窗口调整滚动加载、按钮防重复点击

结合防抖与节流

在需要兼顾即时响应和终止操作的场景中(如滚动结束检测),可结合两者特性:

function debounceThrottle(func, delay) {let lastTime = 0;let timer = null;return function (...args) {const now = Date.now();if (now - lastTime < delay) {if (timer) clearTimeout(timer);timer = setTimeout(() => {func.apply(this, args);lastTime = now;}, delay);} else {func.apply(this, args);lastTime = now;}};
}// 示例:滚动事件结合
window.addEventListener("scroll", debounceThrottle(() => {console.log("滚动优化执行");
}, 200));

使用 Lodash 简化

Lodash 提供现成的防抖(_.debounce)和节流(_.throttle)方法:

import _ from "lodash";// 防抖示例
const debouncedFunc = _.debounce(() => {console.log("防抖执行");
}, 300);// 节流示例
const throttledFunc = _.throttle(() => {console.log("节流执行");
}, 300);

Vue 响应式原理详解

Vue的响应式系统通过自动追踪数据变化并更新视图,极大简化了前端开发。其实现机制在Vue 2.x和3.x中有显著差异。

Vue 2.x实现机制

数据劫持通过Object.defineProperty实现:

function defineReactive(obj, key, val) {const dep = new Dep(); Object.defineProperty(obj, key, {get() {if (Dep.target) dep.depend();return val;},set(newVal) {if (newVal === val) return;val = newVal;dep.notify();}});
}

依赖收集与派发更新流程:

  • 组件渲染时触发getter收集依赖
  • 数据变更时触发setter通知Watcher
  • Watcher执行更新操作

局限性

  • 无法检测对象属性的动态添加/删除
  • 无法直接检测数组索引变更
  • 需使用Vue.set/Vue.delete处理新增属性
Vue 3.x改进方案

采用Proxy API实现更完善的响应式:

function reactive(target) {return new Proxy(target, {get(target, key, receiver) {track(target, key);return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver);trigger(target, key);return result;}});
}

核心API:

  • reactive(): 创建响应式对象
  • ref(): 处理基本类型数据
  • computed(): 声明计算属性

优势

  • 完整支持对象/数组操作拦截
  • 自动处理动态属性变更
  • 更好的TypeScript集成
  • 更优的tree-shaking支持
响应式系统组成

Observer

  • 递归转换普通对象为响应式对象
  • 处理嵌套属性响应式

Dep

  • 管理依赖关系
  • 维护订阅者列表

Watcher

  • 渲染Watcher:处理视图更新
  • 计算Watcher:处理计算属性
  • 侦听器Watcher:处理watch选项

Scheduler

  • 异步更新队列管理
  • 合并同周期内的多次更新
工作流程

初始化阶段

  • 转换data/props为响应式对象
  • 创建组件级Watcher

依赖收集

  • 渲染过程访问响应式数据
  • 触发getter建立依赖关系

更新阶段

  • 数据变更触发setter
  • 通知相关Watcher入队
  • 下一事件循环执行更新

该机制通过细粒度的依赖追踪,实现精确的按需更新,避免了不必要的DOM操作。同时异步批处理策略优化了性能表现,使开发者能专注于数据逻辑而非视图更新。

var、let、const 区别详解

作用域差异
var 声明的变量具有函数作用域,仅在声明它的函数内部有效。
letconst 声明的变量具有块级作用域,只在 {}iffor 等代码块内有效。

变量提升表现
var 声明的变量会提升到作用域顶部,声明前访问会返回 undefined
letconst 存在暂时性死区(TDZ),声明前访问会抛出 ReferenceError

重复声明规则
var 允许在同一作用域内多次声明同名变量,后者覆盖前者。
letconst 禁止重复声明,同一作用域内同名变量会触发 SyntaxError

可变性区别
varlet 声明的变量可重新赋值。
const 声明的常量不可重新赋值,但对引用类型(如对象、数组),可修改其属性或元素,但不可重新分配内存地址。

初始化要求
varlet 允许声明时不初始化(默认值为 undefined)。
const 必须声明时初始化,否则会报错。

使用建议

  • 优先使用 const,确保变量不可变,减少意外修改风险。
  • 需要重新赋值的变量使用 let,如循环计数器或状态变量。
  • 避免使用 var,因其作用域和提升行为易导致逻辑错误。
  • 循环中推荐 let,因 const 会导致每次迭代创建新变量(可能违反预期)。

不可变对象处理

若需完全冻结对象(包括嵌套属性),可结合 constObject.freeze()

const obj = Object.freeze({ prop: 'value' });
obj.prop = 'new'; // 严格模式下报错,非严格模式静默失败

浅拷贝与深拷贝的概念

浅拷贝创建一个新对象,但仅复制原始对象属性值的引用。对于基本类型,拷贝的是值;对于引用类型,拷贝的是内存地址。原始对象和拷贝对象会共享引用类型的属性。

深拷贝会完整复制对象及其嵌套对象,新对象与原对象不共享内存。任何修改都不会相互影响,确保数据完全独立。

浅拷贝实现方式

使用Object.assign()

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);

使用扩展运算符:

const obj3 = { ...obj1 };

数组浅拷贝方法:

const arr1 = [1, [2, 3]];
const arr2 = arr1.slice();
const arr3 = [].concat(arr1);

深拷贝实现方式

使用JSON.parse(JSON.stringify())

const obj = { a: 1, b: { c: 2 }, d: function() {} };
const deepCopy = JSON.parse(JSON.stringify(obj)); // 注意会丢失函数、undefined等

递归实现:

function deepClone(obj) {if (obj === null || typeof obj !== 'object') return obj;if (obj instanceof Date) return new Date(obj);if (obj instanceof RegExp) return new RegExp(obj);const clonedObj = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {clonedObj[key] = deepClone(obj[key]);}}return clonedObj;
}

使用第三方库:

// const _ = require('lodash');
// const deepCopy = _.cloneDeep(obj);

行为差异示例

浅拷贝修改嵌套属性:

const shallow = { ...original };
shallow.address.city = 'Shanghai';
console.log(original.address.city); // 输出: Shanghai(原对象被修改)

深拷贝修改嵌套属性:

const deep = JSON.parse(JSON.stringify(original));
deep.address.city = 'Guangzhou';
console.log(original.address.city); // 输出: Shanghai(原对象未被修改)

适用场景选择

浅拷贝适合场景:

  • 对象属性均为基本数据类型
  • 不需要完全独立的对象副本
  • 性能要求较高时

深拷贝适合场景:

  • 对象包含嵌套引用类型
  • 需要完全隔离的数据副本
  • 避免意外修改原始数据
  • 数据需要序列化传输时

前端路由的核心原理

前端路由基于URL变化与视图渲染的联动机制。当URL的hash或path部分发生改变时,路由系统会匹配预定义的规则,动态加载对应的组件或模板,无需向服务器发起请求。核心流程分为两步:

  • URL变化:通过修改window.location.hash或调用history.pushState触发路由更新。
  • 视图渲染:路由解析URL后,将匹配的组件渲染到指定容器(如<router-view>)。

Hash模式与History模式对比

Hash模式

实现方式

  • 依赖URL中#后的部分(如example.com/#/home)。
  • 通过hashchange事件监听变化,使用window.location.hash主动修改。

优点

  • 兼容所有浏览器(包括IE8)。
  • 无需服务器配置,适合静态部署。

缺点

  • URL含#,视觉上不简洁。
  • 不利于SEO,搜索引擎可能忽略hash部分。
History模式

实现方式

  • 基于HTML5的history.pushStatepopstate事件。
  • URL无#(如example.com/home),更像传统路径。

优点

  • URL简洁,对SEO友好。
  • 支持完整的路径嵌套和参数传递。

缺点

  • 需服务器支持:刷新页面时需重定向到index.html,否则返回404。
  • 兼容性限制(IE10以下不支持)。

主流路由库实现

Vue Router
  • 模式配置:通过mode选项指定hashhistory
  • 示例代码
    const router = new VueRouter({mode: 'history', // 或 'hash'routes: [{ path: '/home', component: Home }]
    });
    
React Router
  • 组件区分
    • HashRouter:使用URL hash。
    • BrowserRouter:依赖History API。
  • 示例代码
    import { BrowserRouter as Router, Route } from 'react-router-dom';
    <Router><Route path="/home" component={Home} />
    </Router>
    

应用场景与选型建议

  • 快速原型开发:选择Hash模式,避免服务器配置。
  • 生产环境SPA:优先History模式,需配置服务器(如Nginx的try_files)。
  • 动态加载优化:结合路由懒加载(如Vue的() => import('./Home.vue'))。
  • 权限控制:在路由守卫中校验权限(如Vue的beforeEach钩子)。

服务器配置示例(History模式)

Nginx配置

location / {try_files $uri $uri/ /index.html;
}

Express配置

app.get('*', (req, res) => {res.sendFile(path.resolve(__dirname, 'public/index.html'));
});

注意事项

  • History模式404问题:确保所有路径回退到入口文件。
  • SSR兼容性:服务端渲染需特殊处理路由匹配。
  • 滚动行为:部分场景需手动管理滚动位置(如Vue Router的scrollBehavior)。

浏览器渲染流程

浏览器渲染页面分为多个阶段:解析HTML生成DOM树,解析CSS生成CSSOM树,合并为渲染树(Render Tree),计算布局(Layout/Reflow),绘制(Paint),最后合成(Composite)显示。

重排(Reflow)

重排是浏览器重新计算元素几何属性的过程,触发条件包括:

  • 修改元素的尺寸(width/height)、边距(margin)、内边距(padding)、边框(border)。
  • 调整元素位置属性(position/top/left)。
  • 增删可见DOM元素或修改文本/图片内容。
  • 窗口resize事件或读取几何属性(如offsetWidth)。

性能影响:

  • 重排涉及渲染树重新计算,可能引发连锁反应(如父元素尺寸变化导致子元素重排)。
  • 现代浏览器优化为局部重排,但仍需避免高频触发。

重绘(Repaint)

重绘是重新绘制元素到屏幕的过程,不涉及几何计算,触发条件包括:

  • 修改颜色相关属性(color/background-color/border-color)。
  • 调整透明度(opacity)或可见性(visibility)。

性能影响:

  • 重绘性能开销比重排小,但高频触发仍会损耗性能。

两者关系

  • 重排必然导致重绘(几何变化需重新绘制)。
  • 重绘不一定触发重排(如仅颜色变化)。

优化策略

避免频繁DOM操作
使用DocumentFragment批量插入DOM,或临时设置display: none操作后再显示。

CSS动画优化
优先使用transformopacity实现动画,它们触发合成层(Composite),跳过重排和重绘。例如:

.box {transform: translateX(100px); /* 替代left属性 */transition: transform 0.3s;
}

避免强制同步布局
禁止在循环中交替读写几何属性(如先读offsetHeight再改style)。使用requestAnimationFrame拆分读写操作:

function update() {requestAnimationFrame(() => {element.style.width = '200px';});
}

类名切换样式
通过CSS类名批量修改样式,减少逐行JS样式操作:

element.classList.add('active'); /* CSS中预定义.active样式 */

提升为合成层
对动画元素使用will-changetransform: translateZ(0),将其提升为独立图层:

.animated {will-change: transform;
}

工具检测

  • Chrome DevTools
    • Performance面板记录重排/重绘耗时。
    • Rendering面板开启“Paint flashing”高亮重绘区域。

代码示例对比

低效写法(触发多次重排):

element.style.width = '100px';
element.style.height = '100px';

高效写法(单次类名修改):

element.classList.add('resized');

通过上述策略可显著减少重排与重绘,提升页面渲染性能。

TCP与UDP区别

连接方式
TCP是面向连接的协议,通过三次握手建立连接,确保通信双方准备就绪,但会增加延迟。UDP是无连接的协议,直接发送数据,适合实时性要求高的场景,但牺牲了可靠性。

可靠性
TCP通过序列号、确认应答和重传机制保证数据完整性和顺序,适合文件传输、网页浏览等对可靠性要求高的场景。UDP不提供可靠性保障,数据可能丢失或损坏,适用于直播、在线游戏等实时应用。

传输效率
TCP因连接建立、确认和重传机制,延迟较高但稳定性强。UDP无额外机制,延迟低、传输快,适合视频会议、实时游戏等场景。

头部大小
TCP头部为20-40字节,UDP头部固定8字节,更高效处理小数据包。

应用场景
TCP用于文件传输、电子邮件等需高可靠性的场景。UDP用于直播、语音通话等实时性优先的场景。

箭头函数与普通函数的区别

this 绑定

箭头函数的 this 继承自外层作用域,在定义时确定,不会因调用方式改变。
普通函数的 this 动态绑定,取决于调用时的上下文(如对象调用、直接调用等)。

构造函数调用

箭头函数不能作为构造函数使用,尝试用 new 调用会抛出错误。
普通函数可以作为构造函数,通过 new 创建实例对象。

prototype 属性

箭头函数没有 prototype 属性,无法用于原型链继承。
普通函数存在 prototype 属性,支持通过原型扩展方法。

arguments 对象

箭头函数不绑定 arguments 对象,需通过剩余参数(...args)获取参数列表。
普通函数内部可直接访问 arguments 对象。

方法定义

箭头函数作为对象方法时,this 可能意外指向全局(如 Window),因其不绑定对象自身。
普通函数作为方法时,this 通常指向调用该方法的对象。

示例代码:

// 箭头函数示例
const arrowFunc = () => console.log(this); 
arrowFunc(); // 输出外层 this(如 Window)// 普通函数示例
function regularFunc() { console.log(this); 
}
regularFunc.call({ name: "obj" }); // 输出 { name: "obj" }

JS原型和原型链

JavaScript中每个函数都有一个prototype属性,该属性是一个对象。这个对象被称为显式原型

每个对象都有一个__proto__属性,称为隐式原型,指向创建该对象的构造函数的prototype

原型链的形成是通过__proto__的链接实现的:实例对象的__proto__指向构造函数的prototype,构造函数的prototype__proto__又指向Object.prototype,最终Object.prototype.__proto__null,形成原型链的终点。

原型链继承

原型链继承通过将子类的prototype指向父类的实例实现:

function Parent() {this.name = 'parent';
}
Parent.prototype.sayName = function() {console.log(this.name);
};function Child() {}
Child.prototype = new Parent(); // 继承父类实例和原型const child = new Child();
child.sayName(); // 'parent'

原型链继承的特点:

  • 子类共享同一个父类实例
  • 父类实例属性会成为子类原型属性
  • 可以继承父类原型链上的方法

构造函数继承

构造函数继承通过在子类中调用父类构造函数实现:

function Parent(name) {this.name = name;this.colors = ['red', 'blue'];
}function Child(name) {Parent.call(this, name); // 继承父类实例属性
}const child1 = new Child('child1');
child1.colors.push('green');const child2 = new Child('child2');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue']

构造函数继承的特点:

  • 每个子类实例都有独立的父类属性副本
  • 无法继承父类原型链上的方法
  • 避免引用类型属性被共享

两种继承的区别

原型链继承基于原型机制,共享父类实例和原型方法。
构造函数继承通过调用父类构造函数,为每个实例创建独立属性副本。

实际开发中常组合使用两种方式,例如:

function Child(name) {Parent.call(this, name); // 构造函数继承属性
}
Child.prototype = Object.create(Parent.prototype); // 原型链继承方法

Vue中Computed和Watchers的区别

计算属性(Computed)
计算属性适用于基于其他响应式数据派生新值的场景,具有缓存机制,仅当依赖项变化时才重新计算。例如,对数据进行格式化或组合多个数据源时,计算属性能自动追踪依赖并高效更新。

特点

  • 声明式:依赖关系自动追踪
  • 缓存:依赖未变化时直接返回缓存值
  • 同步:适合纯计算逻辑
computed: {fullName() {return `${this.firstName} ${this.lastName}`; // 依赖firstName和lastName}
}

观察者(Watchers)
观察者用于在数据变化时执行异步操作或副作用(如API调用、DOM操作)。需手动指定监听目标,无缓存,每次变化均触发。

特点

  • 命令式:需显式定义监听目标
  • 无缓存:每次变动均执行
  • 异步支持:可处理延迟操作
watch: {searchQuery(newVal) {this.fetchResults(newVal); // 搜索词变化时发起API请求}
}

选择建议

  • 派生数据优先用Computed:如过滤列表、计算总价等,利用缓存提升性能。
  • 副作用操作用Watchers:如输入防抖、路由参数监听等需异步处理的场景。

两者可结合使用,例如用Computed生成派生值,再用Watch监听该值触发后续操作。

http://www.dtcms.com/a/553299.html

相关文章:

  • UE5【插件】一键重命名蓝图变量、事件、函数、宏等(实现批量翻译)
  • UE5【C++】中文注释、编辑器乱码解决方法
  • 鸿蒙Flutter三方库适配指南:08.联合插件开发
  • node做网站怎么知道蜘蛛来过桂林人论坛app
  • 什么语言最适合开发 SaaS 系统:从架构视角的全面分析
  • liosam详解
  • 先知社区文章批量爬虫工具
  • 【STM32】电动车报警系统
  • linux kernel struct clk_init_data结构浅解
  • ▲各类通信算法的FPGA开发学习教程——总目录
  • 2025企业秋招:AI笔试监考如何重塑秋招公平性?
  • Rust开发之常用标准库Trait实践(Display、From/Into)
  • XML与HTML
  • 太原做网站需要多少钱网页设计网站怎么放到域名里
  • 网站开发 费用怎么用PS做网站广告图
  • 算法专题十八:FloodFill算法(使用dfs)
  • 【11408学习记录】考研数学速成:n维随机变量分布函数详解(从定义到边缘分布一网打尽)
  • 网络安全应用题3:网络攻击与防范
  • 做网站设计赚钱吗做攻略的网站好
  • 用react和ant.d做的网站例子宣传推广方式
  • 网店网站设计php网站开发教学
  • 鸿蒙元服务深度实践:跨端唤醒与状态共享的设计模式
  • 【Linux】信号机制详解:进程间通信的核心
  • 当一家车企出现在AI顶会
  • 解锁AI交互新范式:MCP(Model Context Protocol)如何重塑模型上下文管理
  • 保定 网站制作网站策划ppt
  • C#知识学习-019(泛型类型约束关键字)
  • ioDraw实测:AI加持的全能图表工具,免费又好用?
  • GD32F407VE天空星开发板的188数码管
  • 时硕科技,隐形冠军的修炼之道