【前端】前端面试题

【前端】前端面试题

前端面试题

闭包

1. 定义:

闭包(Closure) 是指一个函数能够访问并记住其外部作用域中的变量,即使外部函数已经执行完毕。闭包由两部分组成:

  • 一个函数(通常是内部函数)。
  • 该函数被创建时所在的作用域(即外部函数的变量环境)
functionouter(){let count =0;// 外部函数的变量functioninner(){ count++;// 内部函数访问外部变量 console.log(count);}return inner;}const counter =outer();counter();// 输出 1counter();// 输出 2

2. 闭包的核心原理

  • 作用域链:函数在定义时,会记住自己的词法环境(即外部作用域)。当内部函数访问变量时,会沿着作用域链向上查找。
  • 变量持久化:闭包使得外部函数的变量不会被垃圾回收,因为内部函数仍持有对它们的引用

3. 闭包的常见用途

3.1 私有变量封装

通过闭包隐藏内部变量,仅暴露操作接口:

functioncreateCounter(){let count =0;// 私有变量return{increment:()=> count++,getValue:()=> count };}const counter =createCounter(); counter.increment(); console.log(counter.getValue());// 输出 1// 无法直接访问 count,避免被外部修改
3.2 函数柯里化(Currying)

将多参数函数转换为单参数链式调用:

functionadd(a){returnfunction(b){return a + b;};}const add5 =add(5); console.log(add5(3));// 输出 8
3.3 事件处理与回调

在事件监听中保留上下文变量:

functionsetupButton(){const button = document.getElementById('myButton');let clicks =0; button.addEventListener('click',function(){ clicks++; console.log(`按钮被点击了 ${clicks} 次`);});}// 每次点击都会更新同一个 clicks 变量

4. 闭包的陷阱与解决方案

4.1 循环中的闭包问题

问题:循环中创建的闭包共享同一个变量,导致意外结果。

for(var i =0; i <3; i++){setTimeout(function(){ console.log(i);// 输出 3, 3, 3},100);}

解决方案:使用 IIFE​ 或 let​ 创建块级作用域。

// 使用 IIFEfor(var i =0; i <3; i++){(function(j){setTimeout(function(){ console.log(j);// 输出 0, 1, 2},100);})(i);}// 使用 let(块级作用域)for(let i =0; i <3; i++){setTimeout(function(){ console.log(i);// 输出 0, 1, 2},100);}
4.2 内存泄漏

问题:闭包长期持有外部变量引用,导致内存无法释放。

functionheavyProcess(){const largeData =newArray(1000000).fill('data');returnfunction(){ console.log(largeData.length);};}const leak =heavyProcess();// largeData 被闭包引用,无法回收

解决方案:在不需要时解除引用。

leak =null;// 手动解除对闭包的引用

5. 闭包面试题示例

题目:以下代码输出什么?为什么?

functioncreateFunctions(){const result =[];for(var i =0; i <3; i++){ result.push(function(){ console.log(i);});}return result;}const funcs =createFunctions(); funcs[0]();// 输出 3 funcs[1]();// 输出 3 funcs[2]();// 输出 3

答案:所有函数都输出 3​,因为它们共享同一个变量 i​(var​ 声明的变量在函数作用域中)。
改进方法:使用 let​ 或闭包隔离作用域。

事件循环(Event Loop)

定义

事件循环是JavaScript处理异步任务的核心机制。由于JavaScript是单线程语言,事件循环通过任务队列(Task Queue)和调用栈(Call Stack)的协作,实现了非阻塞的异步执行模型。

核心概念

  1. 调用栈(Call Stack)
    • 用于存储函数调用的栈结构,遵循“后进先出”原则。
    • 当函数执行时,会被推入调用栈;执行完毕后,会从调用栈中弹出。
  2. 任务队列(Task Queue)
    • 用于存储异步任务的回调函数。
    • 任务队列分为宏任务队列微任务队列
  3. 宏任务(Macro Task)与微任务(Micro Task)
    • 宏任务:包括setTimeout​、setInterval​、I/O​操作、UI渲染​等。
    • 微任务:包括Promise.then​、MutationObserver​、process.nextTick​(Node.js)等。

事件循环的执行流程

  1. 同步任务执行
    • 同步任务直接进入调用栈执行,直到调用栈为空。
  2. 微任务执行
    • 调用栈为空后,事件循环会检查微任务队列,依次执行所有微任务,直到微任务队列为空。
  3. 宏任务执行
    • 每次从宏任务队列中取出一个任务执行,执行完毕后再次检查微任务队列并执行所有微任务。
  4. UI渲染
    • 如果需要进行UI渲染,浏览器会在宏任务执行后执行渲染操作。
  5. 循环继续
    • 重复上述步骤,直到所有任务队列为空。

事件循环示例

console.log('1');// 同步任务 setTimeout(()=>{ console.log('2');// 宏任务 },0); Promise.resolve().then(()=>{ console.log('3');// 微任务 }); console.log('4');// 同步任务 // 输出顺序:1 → 4 → 3 → 2 

执行步骤解析

  1. 同步任务console.log('1')​和console.log('4')​依次执行。
  2. 微任务Promise.then​回调执行,输出3​。
  3. 宏任务setTimeout​回调执行,输出2​。

应用场景

  1. 异步编程
    • 通过Promise​、async/await​处理异步任务,避免回调地狱。
  2. 性能优化
    • 将耗时任务拆分为多个微任务,避免阻塞主线程。
  3. 动画与渲染
    • 使用requestAnimationFrame​结合事件循环实现流畅的动画效果。

注意事项

  1. 微任务优先级高于宏任务
    • 微任务会在当前宏任务执行完毕后立即执行,而宏任务需要等待下一轮事件循环。
  2. 避免阻塞主线程
    • 长时间运行的同步任务会导致页面卡顿,应将其拆分为异步任务。
  3. 理解不同环境的事件循环
    • 浏览器与Node.js的事件循环机制略有不同,Node.js引入了process.nextTick​和setImmediate​。

总结

事件循环是JavaScript异步编程的核心机制,通过调用栈、任务队列和事件循环的协作,实现了非阻塞的执行模型。理解事件循环的执行顺序(同步任务 → 微任务 → 宏任务)是掌握异步编程的关键。在实际开发中,合理利用事件循环机制可以提升代码性能和用户体验。

BFC(块级格式化上下文)

定义

BFC(Block Formatting Context,块级格式化上下文)是Web页面渲染时的一种布局环境。它是一个独立的渲染区域,内部的元素布局不会影响外部元素。

触发条件

以下CSS属性可以触发BFC:

  1. 根元素(<html>​)。
  2. float​ 值不为 none​。
  3. position​ 值为 absolute​ 或 fixed​。
  4. display​ 值为 inline-block​、table-cell​、table-caption​、flex​、inline-flex​、grid​、inline-grid​。
  5. overflow​ 值不为 visible​(如 hidden​、auto​、scroll​)。

特性

  1. 内部元素垂直排列: BFC内部的块级元素会按照垂直方向依次排列。
  2. 避免外边距重叠: BFC可以阻止相邻元素的外边距(margin)重叠。
  3. 包含浮动元素: BFC可以包含浮动元素,避免父元素高度塌陷。
  4. 阻止元素被浮动元素覆盖: BFC区域不会与浮动元素重叠。

应用场景

  1. 隔离布局BFC内部的布局不会影响外部元素,适合实现独立的UI组件。

实现多栏布局利用BFC阻止元素被浮动元素覆盖,实现多栏布局:

.left{float: left;width: 200px;}.right{overflow: hidden;/* 触发BFC */}

避免外边距重叠相邻元素的外边距会重叠,通过触发BFC可以避免:

.box{display: inline-block;/* 触发BFC */}

清除浮动当父元素包含浮动子元素时,父元素的高度会塌陷。通过触发BFC可以解决此问题:

.parent{overflow: hidden;/* 触发BFC */}

代码示例

实现多栏布局

<divclass="left">左侧栏</div><divclass="right">右侧栏</div><style>.left{float: left;width: 200px;background: #ccc;}.right{overflow: hidden;/* 触发BFC */background: #f0f0f0;}</style>

避免外边距重叠

<divclass="box"style="margin: 20px;">元素1</div><divclass="box"style="margin: 20px;">元素2</div><style>.box{display: inline-block;/* 触发BFC */width: 100%;}</style>

清除浮动

<divclass="parent"><divclass="child"style="float: left;">浮动元素</div></div><style>.parent{overflow: hidden;/* 触发BFC */border: 1px solid #000;}</style>

注意事项

  1. 性能影响: 过度使用BFC(如大量使用overflow: hidden​)可能会导致性能问题。
  2. 兼容性: 不同浏览器对BFC的实现可能略有差异,需进行兼容性测试。
  3. 合理使用: BFC适用于特定场景,不应滥用。例如,清除浮动优先使用clearfix​方法。

总结

BFC是CSS布局中的重要概念,通过触发BFC可以解决浮动、外边距重叠等问题,实现更灵活的布局。理解BFC的触发条件和特性,有助于编写更健壮和可维护的CSS代码。在实际开发中,应根据需求合理使用BFC,避免过度依赖。

内存泄漏

定义

内存泄漏(Memory Leak)是指程序中已不再使用的内存未被释放,导致内存占用持续增加,最终可能引发性能下降甚至崩溃。在JavaScript中,内存泄漏通常由不当的引用管理引起。

内存管理机制

  1. 垃圾回收(GC) JavaScript通过垃圾回收机制自动管理内存,主要算法包括:
    • 引用计数:记录对象的引用次数,当引用数为0时回收内存。
    • 标记清除:从根对象(如window​)出发,标记所有可达对象,清除未标记的对象。
  2. 常见回收策略
    • 新生代:存放短生命周期对象,使用Scavenge算法。
    • 老生代:存放长生命周期对象,使用标记清除或标记整理算法。

常见的内存泄漏场景

未释放的缓存或 Map缓存或Map中存储的对象未及时清理,会导致内存占用持续增加。

let cache =newMap();functionsetCache(key, value){ cache.set(key, value);}// 未清理cache,内存泄漏 

DOM 引用未清除保存了DOM元素的引用,即使元素被移除,内存也无法释放。

let elements ={button: document.getElementById('button'),}; document.body.removeChild(elements.button);// DOM已移除,但引用仍在 

闭包引用闭包会保留对外部作用域的引用,如果闭包未释放,相关内存也无法回收。

functioncreateClosure(){let largeData =newArray(1000000).fill('data');return()=> console.log(largeData);// largeData一直被引用 }const closure =createClosure();

未清理的定时器或回调函数定时器或事件监听器未及时清除,会导致相关对象无法回收。

let data =fetchData();setInterval(()=>{process(data);// data一直被引用,无法回收 },1000);

意外全局变量未使用var​、let​或const​声明的变量会挂载到全局对象(如window​),导致内存无法释放。

functionleak(){ globalVar ='这是一个全局变量';// 未使用var/let/const }

内存泄漏的检测与排查

  1. 浏览器开发者工具
    • Memory面板:使用Heap Snapshot分析内存占用,查找未释放的对象。
    • Performance面板:记录内存使用情况,观察内存占用是否持续增加。
  2. 工具库
    • Chrome DevTools:提供内存分析功能。
    • Node.js内存分析工具:如node --inspect​结合Chrome DevTools。
  3. 代码检查
    • 检查全局变量、定时器、闭包、DOM引用等常见问题。

避免内存泄漏的最佳实践

    • 清除定时器、事件监听器、DOM引用等。
    • 使用WeakMap​或WeakSet​存储临时数据,避免强引用导致内存无法释放。
    • 避免在闭包中保留不必要的引用。
    • 定期清理缓存或设置缓存失效策略。

清理缓存

let cache =newMap();functionsetCache(key, value, ttl){ cache.set(key, value);setTimeout(()=> cache.delete(key), ttl);// 设置缓存失效时间 }

优化闭包

functioncreateClosure(){let largeData =newArray(1000000).fill('data');return()=>{ console.log('Closure executed');// 不引用largeData };}

使用弱引用

let weakMap =newWeakMap();let key ={}; weakMap.set(key,'data');// key被回收时,数据也会被回收 

及时清除引用

let timer =setInterval(()=>{},1000);clearInterval(timer);// 清除定时器 

总结

内存泄漏是JavaScript开发中的常见问题,通常由不当的引用管理引起。通过理解垃圾回收机制、熟悉常见的内存泄漏场景,并借助开发者工具进行排查,可以有效避免内存泄漏问题。在实际开发中,遵循最佳实践(如及时清除引用、使用弱引用等)是保证应用性能的关键。

Vue 的虚拟 DOM

定义

虚拟 DOM(Virtual DOM)是一种用 JavaScript 对象表示真实 DOM 结构的技术。Vue 通过虚拟 DOM 实现高效的 DOM 更新,减少直接操作真实 DOM 的开销。

工作原理

  1. 生成虚拟 DOM
    • Vue 将模板编译为渲染函数,渲染函数执行后生成虚拟 DOM 树。
    • 虚拟 DOM 树是一个 JavaScript 对象,描述了真实 DOM 的结构和属性。
  2. Diff 算法
    • 当数据变化时,Vue 会生成新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行对比(Diff 算法)。
    • Diff 算法找出两棵树之间的差异,只更新发生变化的部分。
  3. 更新真实 DOM
    • 根据 Diff 算法的结果,Vue 将变化应用到真实 DOM 上,完成视图更新。

优点

  1. 性能优化
    • 减少直接操作真实 DOM 的次数,避免频繁的 DOM 操作带来的性能损耗。
    • 通过批量更新和差异更新,提高渲染效率。
  2. 跨平台能力
    • 虚拟 DOM 是一个抽象层,可以映射到不同的平台(如浏览器、移动端、服务器端)。
  3. 简化开发
    • 开发者只需关注数据逻辑,无需手动操作 DOM,提高开发效率。

实现细节

  1. Diff 算法核心逻辑
    • 同层比较:只比较同一层级的节点,不跨层级比较。
    • Key 值优化:通过 key​ 属性识别节点,避免不必要的节点销毁和重建。
    • 节点复用:如果节点类型和 key​ 值相同,则复用节点,只更新属性或子节点。
  2. 批量更新Vue 将多次数据变化合并为一次更新,减少重复渲染。

虚拟 DOM 的结构虚拟 DOM 是一个 JavaScript 对象,包含标签名、属性、子节点等信息。

const vnode ={tag:'div',attrs:{id:'app'},children:[{tag:'p',attrs:{},children:['Hello, Vue!']}]};

应用场景

  1. 复杂视图更新在数据频繁变化的场景中,虚拟 DOM 可以显著提升性能。
  2. 跨平台开发虚拟 DOM 可以映射到不同的平台,如通过 Weex​ 开发移动端应用。
  3. 服务端渲染(SSR) 虚拟 DOM 可以在服务器端生成 HTML 字符串,提高首屏加载速度。

局限性

  1. 首次渲染性能虚拟 DOM 需要在首次渲染时生成虚拟 DOM 树,相比直接操作真实 DOM,会有一定的性能开销。
  2. 内存占用虚拟 DOM 需要维护一份虚拟 DOM 树,会增加内存占用。
  3. 不适合简单场景在简单的静态页面中,虚拟 DOM 的优势不明显,反而会增加复杂性。

MVVM

定义

MVVM(Model-View-ViewModel)是一种软件架构模式,主要用于分离 UI 逻辑与业务逻辑。它将应用程序分为三个核心部分:

  1. Model:数据模型,负责管理应用程序的数据和业务逻辑。
  2. View:视图,负责呈现用户界面(UI)。
  3. ViewModel:视图模型,负责连接 View 和 Model,处理 UI 逻辑和数据绑定。

核心概念

  1. 数据绑定
    • View 和 ViewModel 之间通过数据绑定机制实现双向通信。
    • 当 Model 数据变化时,ViewModel 自动更新 View;当用户操作 View 时,ViewModel 自动更新 Model。
  2. 命令绑定
    • View 中的用户操作(如点击按钮)通过命令绑定触发 ViewModel 中的方法。
  3. 依赖注入
    • ViewModel 可以依赖外部服务(如 API 调用、数据存储),通过依赖注入实现解耦。

工作流程

  1. 用户操作 View用户在 View 中进行操作(如输入文本、点击按钮)。
  2. ViewModel 处理逻辑ViewModel 接收用户操作,更新 Model 或执行相关逻辑。
  3. Model 更新数据Model 负责处理业务逻辑和数据更新。
  4. View 自动更新ViewModel 将 Model 的变化通过数据绑定同步到 View,更新 UI。

优点

  1. 分离关注点
    • 将 UI 逻辑与业务逻辑分离,提高代码的可维护性和可测试性。
  2. 双向数据绑定
    • 自动同步 View 和 Model 的数据,减少手动操作 DOM 的代码。
  3. 提高开发效率
    • 通过数据绑定和命令绑定,减少重复代码,简化开发流程。
  4. 易于测试
    • ViewModel 可以独立于 View 进行测试,提高测试覆盖率。

缺点

  1. 学习成本高
    • MVVM 涉及的概念(如数据绑定、命令绑定)需要一定的学习成本。
  2. 性能开销
    • 数据绑定机制可能会带来一定的性能开销,特别是在大规模数据更新时。
  3. 框架依赖
    • MVVM 通常依赖于特定的框架(如 Vue、Angular),增加了项目的技术栈复杂度。

应用场景

  1. 前端框架
    • Vue、Angular、Knockout 等前端框架都采用了 MVVM 模式。
  2. 复杂 UI 应用
    • 在需要频繁更新 UI 的复杂应用中,MVVM 可以提高开发效率和性能。
  3. 跨平台开发
    • 通过 MVVM 模式,可以实现代码的跨平台复用(如 Web、移动端)。

Vue2 与 Vue3 响应式原理

Vue2 的响应式原理

Vue2 使用 Object.defineProperty​ 实现响应式,其核心机制如下:

  1. 数据劫持
    • 通过 Object.defineProperty​ 劫持对象的属性,定义 getter​ 和 setter​。
    • 当访问属性时触发 getter​,当修改属性时触发 setter​。
  2. 依赖收集
    • getter​ 中收集依赖(Watcher),在 setter​ 中通知依赖更新。
  3. 数组处理
    • Vue2 无法直接劫持数组的变化,因此通过重写数组方法(如 push​、pop​ 等)实现响应式。

示例

const data ={name:'Vue2'}; Object.defineProperty(data,'name',{get(){ console.log('获取 name');returnthis._name;},set(newValue){ console.log('更新 name');this._name = newValue;}});

局限性

  1. 无法检测新增/删除属性: 使用 Vue.set​ 或 Vue.delete​ 解决。
  2. 数组响应式局限性: 无法检测通过索引直接修改数组元素(如 arr = 1​)。

Vue3 的响应式原理

Vue3 使用 Proxy​ 实现响应式,其核心机制如下:

  1. 数据代理
    • 通过 Proxy​ 代理整个对象,拦截对对象的所有操作(如读取、赋值、删除等)。
  2. 依赖收集
    • get​ 拦截器中收集依赖(Effect),在 set​ 拦截器中通知依赖更新。
  3. 数组处理
    • Proxy​ 可以直接拦截数组的变化,无需重写数组方法。

示例

const data ={name:'Vue3'};const proxy =newProxy(data,{get(target, key){ console.log('获取', key);return target[key];},set(target, key, newValue){ console.log('更新', key); target[key]= newValue;returntrue;}});

优势

  1. 全面拦截: 支持新增/删除属性、数组索引操作等。
  2. 性能提升Proxy​ 是语言层面的特性,性能优于 Object.defineProperty​。
  3. 简化代码: 无需处理特殊 API(如 Vue.set​)。

Vue2 与 Vue3 响应式原理的对比

特性Vue2(Object.defineProperty)Vue3(Proxy)
数据劫持方式劫持对象的属性代理整个对象
新增/删除属性不支持支持
数组响应式需重写数组方法直接拦截数组操作
性能较低较高
代码复杂度较高较低

Vue3 的其他响应式优化

  1. Ref 和 Reactive
    • ref​:用于包装基本数据类型,通过 .value​ 访问。
    • reactive​:用于包装对象,直接访问属性。
  2. Effect 代替 Watcher
    • Vue3 使用 Effect​ 作为依赖单元,更加灵活和高效。
  3. Tree-shaking 支持
    • Vue3 的响应式模块支持 Tree-shaking,减少打包体积。

代码示例对比

Vue3 响应式

const data ={name:'Vue3'};const proxy =newProxy(data,{get(target, key){ console.log('获取', key);return target[key];},set(target, key, newValue){ console.log('更新', key); target[key]= newValue;returntrue;}});

Vue2 响应式

const data ={name:'Vue2'}; Object.defineProperty(data,'name',{get(){ console.log('获取 name');returnthis._name;},set(newValue){ console.log('更新 name');this._name = newValue;}});

深拷贝与浅拷贝

拷贝的定义

  1. 浅拷贝(Shallow Copy)
    • 只复制对象的第一层属性,如果属性是引用类型(如对象、数组),则复制引用地址,新旧对象共享引用类型的属性。
  2. 深拷贝(Deep Copy)
    • 递归复制对象的所有层级,新旧对象完全独立,不共享任何属性。

浅拷贝的实现方式

  1. 数组的浅拷贝方法

slice​、concat​:

const arr =[1,2,{a:3}];const shallowCopy = arr.slice();

Object.assign

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

**扩展运算符(**​ ...

const obj ={a:1,b:{c:2}};const shallowCopy ={...obj };

深拷贝的实现方式

    • 通过 JSON 序列化实现深拷贝,但有以下局限性:
      • 无法复制函数、undefined​、Symbol​等特殊类型。
      • 无法处理循环引用。
    • 手动实现深拷贝,支持所有数据类型并处理循环引用。
  1. 使用第三方库

lodash​ 的 cloneDeep​ 方法:

import _ from'lodash';const obj ={a:1,b:{c:2}};const deepCopy = _.cloneDeep(obj);

递归实现深拷贝

functiondeepClone(obj, map =newMap()){if(typeof obj !=='object'|| obj ===null)return obj;if(map.has(obj))return map.get(obj);// 处理循环引用 const clone = Array.isArray(obj)?[]:{}; map.set(obj, clone);for(let key in obj){if(obj.hasOwnProperty(key)){ clone[key]=deepClone(obj[key], map);}}return clone;}

JSON.parse(JSON.stringify(obj))

const obj ={a:1,b:{c:2}};const deepCopy =JSON.parse(JSON.stringify(obj));

浅拷贝与深拷贝的对比

特性浅拷贝深拷贝
拷贝层级仅第一层所有层级
引用类型属性共享引用地址完全独立
性能较快较慢(递归或序列化开销)
适用场景简单对象,无需深层复制复杂对象,需完全独立副本

代码示例

深拷贝示例

const obj ={a:1,b:{c:2}};const deepCopy =JSON.parse(JSON.stringify(obj)); deepCopy.b.c =3; console.log(obj.b.c);// 输出:2(完全独立) 

浅拷贝示例

const obj ={a:1,b:{c:2}};const shallowCopy ={...obj }; shallowCopy.b.c =3; console.log(obj.b.c);// 输出:3(共享引用) 

深拷贝的注意事项

  1. 循环引用问题如果对象存在循环引用(如 obj.a = obj​),需要额外处理,否则会导致栈溢出。
  2. 特殊数据类型Function​、RegExp​、Map​、Set​ 等,需要特殊处理。
  3. 性能问题深拷贝的性能开销较大,尤其是在处理大型对象时,需谨慎使用。

总结

  • 浅拷贝:适用于简单对象,仅复制第一层属性,性能较高。
  • 深拷贝:适用于复杂对象,递归复制所有层级,确保对象完全独立,但性能较低。
  • 选择建议
    • 如果对象结构简单且无需深层复制,使用浅拷贝。
    • 如果对象结构复杂或需要完全独立副本,使用深拷贝。
    • 处理循环引用或特殊数据类型时,建议使用递归实现或第三方库(如 lodash​)。

npm 开发依赖与生产依赖

npm​ 项目中,依赖包分为 开发依赖生产依赖,分别用于不同的场景。以下是它们的定义、区别及使用方式:

开发依赖(devDependencies​)

  1. 定义
    • 仅在开发环境中使用的依赖包,例如构建工具、测试框架、代码格式化工具等。
    • 这些依赖不会随项目发布到生产环境。
  2. 安装方式
  3. 常见开发依赖
    • 测试工具:jest​、mocha​、chai
    • 构建工具:webpack​、vite​、babel
    • 代码格式化:eslint​、prettier
    • 类型检查:typescript

示例package.json​ 中:

"devDependencies":{"eslint":"^8.0.0","webpack":"^5.0.0","jest":"^27.0.0"}

使用 --save-dev​ 或 -D​ 选项将包安装为开发依赖:

npminstall<package> --save-dev 

npminstall<package>-D

生产依赖(dependencies​)

  1. 定义
    • 项目运行所必需的依赖包,例如框架、库、工具等。
    • 这些依赖会随项目发布到生产环境。
  2. 安装方式
  3. 常见生产依赖
    • 框架:express​、koa​、nestjs
    • UI 库:react​、vue​、angular
    • 工具库:lodash​、axios​、moment

示例package.json​ 中:

"dependencies":{"express":"^4.0.0","lodash":"^4.0.0","react":"^18.0.0"}

如果省略选项,默认安装为生产依赖:

npminstall<package>

使用 --save​ 或 -S​ 选项将包安装为生产依赖:

npminstall<package>--save

npminstall<package>-S

开发依赖与生产依赖的区别

特性开发依赖(devDependencies​)生产依赖(dependencies​)
使用环境仅用于开发环境用于生产环境
是否发布到生产
安装命令npm install <package> --save-devnpm install <package> --save
示例工具eslint​、webpack​、jestexpress​、react​、lodash

如何选择依赖类型

  1. 开发依赖
    • 如果包仅在开发阶段使用(如测试、构建、格式化等),则安装为开发依赖。
  2. 生产依赖
    • 如果包是项目运行所必需的(如框架、库、工具等),则安装为生产依赖。
  3. 特殊情况
    • peerDependencies​:用于插件或库开发,指定兼容的宿主包。
    • optionalDependencies​:可选依赖,安装失败不影响项目运行。

最佳实践

  1. 明确区分依赖类型
    • 开发依赖与生产依赖应严格区分,避免将不必要的包发布到生产环境。
  2. 使用​package-lock.json
    • 锁定依赖版本,确保团队环境一致。
  3. 定期更新依赖
    • 使用 npm outdated​ 检查过期依赖,定期更新以修复漏洞。
  4. 清理无用依赖
    • 使用 npm prune​ 或 npm uninstall​ 清理未使用的依赖。

package.json​ 中 ^​ 和 ~​ 的区别

package.json​ 中,^​ 和 ~​ 是用于定义依赖版本范围的符号。它们决定了 npm​ 或 yarn​ 在安装或更新依赖时允许的版本范围。以下是它们的详细区别:

版本号格式

  1. 语义化版本号(SemVer)
    • 格式:主版本号.次版本号.修订版本号
    • 示例:1.2.3
      • 1​:主版本号(Major),不兼容的 API 变更。
      • 2​:次版本号(Minor),向后兼容的功能新增。
      • 3​:修订版本号(Patch),向后兼容的问题修复。
  2. 版本范围符号
    • ^​ 和 ~​ 是定义版本范围的前缀符号。

^​ 的含义

  1. 定义
    • 允许更新到最新的次版本和修订版本,但不更新主版本。
    • 即:保持主版本号不变,次版本号和修订版本号可以更新。
  2. 规则
    • 如果版本号是 ^1.2.3​,则允许更新的版本范围是 >=1.2.3 <2.0.0​。
  3. 示例
    • ^1.2.3​:允许更新到 1.3.0​、1.4.0​,但不允许更新到 2.0.0​。
    • ^0.2.3​:允许更新到 0.2.4​、0.3.0​,但不允许更新到 1.0.0​。
    • ^0.0.3​:仅允许更新到 0.0.4​,不允许更新到 0.1.0​。

~​ 的含义

  1. 定义
    • 允许更新到最新的修订版本,但不更新次版本和主版本。
    • 即:保持主版本号和次版本号不变,修订版本号可以更新。
  2. 规则
    • 如果版本号是 ~1.2.3​,则允许更新的版本范围是 >=1.2.3 <1.3.0​。
  3. 示例
    • ~1.2.3​:允许更新到 1.2.4​、1.2.5​,但不允许更新到 1.3.0​。
    • ~0.2.3​:允许更新到 0.2.4​、0.2.5​,但不允许更新到 0.3.0​。
    • ~0.0.3​:仅允许更新到 0.0.4​,不允许更新到 0.1.0​。

^​ 和 ~​ 的区别

特性^​(兼容版本)~​(修订版本)
更新范围主版本不变,次版本和修订版本可更新主版本和次版本不变,仅修订版本可更新
示例^1.2.3​ 允许更新到 1.3.0~1.2.3​ 允许更新到 1.2.4
适用场景希望自动获取新功能,但避免破坏性变更仅希望修复问题,不引入新功能

如何选择

  1. 使用​^的场景
    • 希望获取新功能和问题修复,但避免不兼容的变更。
    • 适用于大多数依赖包。
  2. 使用​~的场景
    • 仅希望获取问题修复,不引入新功能。
    • 适用于对稳定性要求较高的项目。
  3. 固定版本
    • 如果希望完全锁定版本,避免任何更新,可以直接指定版本号(如 1.2.3​)。

示例

    • 允许更新的版本范围:>=4.17.21 <5.0.0​。
    • 允许更新的版本范围:>=4.17.21 <4.18.0​。
    • 仅允许使用 4.17.21​ 版本。

固定版本示例

"dependencies":{"lodash":"4.17.21"}

~示例

"dependencies":{"lodash":"~4.17.21"}

^示例

"dependencies":{"lodash":"^4.17.21"}

package.json​ 中的各种 dependencies

package.json​ 是 Node.js 项目的核心配置文件,用于管理项目的依赖、脚本、版本等信息。其中,dependencies​ 是定义项目依赖的关键部分。以下是 package.json​ 中各种依赖类型的详细说明:

主要依赖类型

  1. dependencies
    • 定义:项目运行所必需的依赖包。
    • 安装方式npm install <package>​ 或 yarn add <package>​。
  2. devDependencies
    • 定义:仅用于开发环境的依赖包(如测试工具、构建工具等)。
    • 安装方式npm install <package> --save-dev​ 或 yarn add <package> --dev​。
  3. peerDependencies
    • 定义:与当前包兼容的宿主包,通常用于插件或库的开发。
    • 特点:不会自动安装,需要用户手动安装。
  4. optionalDependencies
    • 定义:可选的依赖包,即使安装失败也不会影响项目运行。
    • 特点:优先级高于 dependencies​,如果安装失败会静默忽略。
  5. bundledDependencies
    • 定义:打包发布时需要包含的依赖包(通常是一个数组)。
    • 特点:与 dependencies​ 和 devDependencies​ 不同,需要手动列出包名。

示例

"bundledDependencies":["lodash","express"]

示例

"optionalDependencies":{"fsevents":"^2.3.2"}

示例

"peerDependencies":{"react":">=16.8.0"}

示例

"devDependencies":{"eslint":"^7.32.0","webpack":"5.51.1"}

示例

"dependencies":{"lodash":"^4.17.21","express":"4.17.1"}

ES6 模块化

ES6(ECMAScript 2015)引入了官方的模块化语法,提供了 import​ 和 export​ 关键字来实现模块的导入和导出。以下是 ES6 模块化的核心概念和用法:

定义

模块化是将代码拆分为独立模块的开发方式,每个模块具有独立的作用域,通过导入和导出实现模块间的依赖管理。ES6 模块化具有以下特点:

  1. 静态加载:模块的依赖关系在编译时确定,支持静态分析和优化。
  2. 独立作用域:每个模块拥有独立的作用域,避免全局污染。
  3. 严格模式:模块默认运行在严格模式下。

模块的导出(export​)

    • 使用 export​ 关键字导出变量、函数或类。
    • 导入时需要使用相同的名称。
    • 使用 export default​ 导出模块的默认值。
    • 导入时可以使用任意名称。
    • 同时使用命名导出和默认导出。

混合导出

// module.js exportconst name ='ES6';exportdefaultfunctiongreet(){ console.log('Hello, ES6!');}

默认导出

// module.js const name ='ES6';exportdefault name;

命名导出

// module.js exportconst name ='ES6';exportfunctiongreet(){ console.log('Hello, ES6!');}

模块的导入(import​)

    • 使用 import { ... }​ 导入命名导出。
    • 使用 import ... from​ 导入默认导出。
    • 使用 import * as ...​ 导入模块的所有导出。
    • 使用 import()​ 动态加载模块,返回一个 Promise。

动态导入

// app.js import('./module.js').then(module=>{ console.log(module.name);// 输出:ES6 });

导入全部导出

// app.js import*as module from'./module.js'; console.log(module.name);// 输出:ES6  module.greet();// 输出:Hello, ES6! 

导入默认导出

// app.js import myModule from'./module.js'; console.log(myModule);// 输出:ES6 

导入命名导出

// app.js import{ name, greet }from'./module.js'; console.log(name);// 输出:ES6 greet();// 输出:Hello, ES6! 

模块化的优势

  1. 代码组织
    • 将代码拆分为独立模块,提高代码的可读性和可维护性。
  2. 依赖管理
    • 明确模块间的依赖关系,避免全局变量污染。
  3. 静态分析
    • 支持静态分析和优化,如 Tree-shaking(移除未使用的代码)。
  4. 复用性
    • 模块可以复用,减少重复代码。

模块化的使用场景

  1. 前端开发
    • 使用 ES6 模块化组织前端代码,结合打包工具(如 Webpack、Vite)进行构建。
  2. Node.js 开发
    • 在 Node.js 中使用 ES6 模块化(需将文件扩展名改为 .mjs​ 或在 package.json​ 中设置 "type": "module"​)。
  3. 库开发
    • 开发独立的库或工具,通过模块化提供清晰的 API。

注意事项

    • 现代浏览器支持原生 ES6 模块化,但需在 <script>​ 标签中添加 type="module"​ 属性。
  1. Node.js 支持
    • 在 Node.js 中使用 ES6 模块化时,需将文件扩展名改为 .mjs​ 或在 package.json​ 中设置 "type": "module"​。
  2. 文件路径
    • 导入模块时需使用完整的文件路径(包括扩展名),或使用构建工具处理路径别名。

浏览器支持

<scripttype="module"src="app.js"></script>

Vue 中 key​ 的作用及为什么不能用 index​ 作为 key​ 详解

key​ 的作用

在 Vue 中,key​ 是用于标识虚拟 DOM 元素的特殊属性,其主要作用包括:

  1. 唯一标识
    • key​ 用于唯一标识每个虚拟 DOM 节点,帮助 Vue 识别哪些节点是新增的、删除的或需要更新的。
  2. 优化渲染性能
    • 在列表渲染时,key​ 可以帮助 Vue 更高效地复用和更新 DOM 节点,减少不必要的 DOM 操作。
  3. 维持组件状态
    • 在动态组件或条件渲染中,key​ 可以确保组件在切换时正确销毁和重建,避免状态混乱。

key​ 的使用场景

    • v-for​ 中使用 key​ 标识每个列表项。
    • 在动态组件中使用 key​ 确保组件正确切换。
    • 在条件渲染中使用 key​ 确保元素正确销毁和重建。

条件渲染

<template> <div v-if="show" key="a">内容A</div> <div v-else key="b">内容B</div> </template> 

动态组件

<template> <component :is="currentComponent" :key="currentComponent"></component> </template> 

列表渲染

<template> <ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul> </template> 

为什么不能用 index​ 作为 key

在列表渲染中,使用 index​ 作为 key​ 可能会导致以下问题:

  1. 无法正确复用节点
    • index​ 是数组的下标,当列表顺序变化时,index​ 会重新分配,导致 Vue 无法正确复用 DOM 节点,降低渲染性能。
  2. 状态混乱
    • 如果列表项是带有状态的组件,使用 index​ 作为 key​ 会导致状态错乱。例如,删除中间项后,后续项的 index​ 会发生变化,状态会被错误地保留。
  3. 示例问题
    • 如果删除 B​,列表变为 ['A', 'C']​,index​ 会重新分配:
      • A​ 的 key​ 仍然是 0​。
      • C​ 的 key​ 从 2​ 变为 1​。
    • 由于 key​ 变化,Vue 会销毁并重新创建 C​ 节点,而不是复用原来的节点。

假设有一个列表 ['A', 'B', 'C']​,使用 index​ 作为 key​:

<template> <ul> <li v-for="(item, index) in items" :key="index">{{ item }}</li> </ul> </template> 

正确的 key​ 使用方式

    • 使用列表项的唯一标识(如 id​)作为 key​。
  1. 避免动态生成​key
    • 避免使用随机数或时间戳作为 key​,否则会导致节点频繁销毁和重建。

使用唯一标识

<template> <ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul> </template> 

CSS 选择器优先级

CSS 选择器优先级决定了当多个规则应用于同一个元素时,哪条规则会生效。以下是优先级规则及其计算方式的详细说明:

优先级规则

CSS 选择器优先级由 4 个级别组成,按权重从高到低依次为:

    • 直接在 HTML 元素中通过 style​ 属性定义的样式。
    • 使用 #​ 定义的 ID 选择器。
    • 类选择器(.class​)、属性选择器([type="text"]​)、伪类选择器(:hover​)。
    • 元素选择器(div​)、伪元素选择器(::before​)。

元素选择器、伪元素选择器(权重:1)

div{color: black;}::before{content:' ';}

类选择器、属性选择器、伪类选择器(权重:10)

.myClass{color: green;}[type="text"]{color: yellow;}a:hover{color: purple;}

ID 选择器(权重:100)

#myId{color: blue;}

内联样式(权重:1000)

<divstyle="color: red;">Hello</div>

优先级计算方式

  1. 计算规则
    • 将选择器的权重相加,得到总权重值。
    • 总权重值越高,优先级越高。
    • 第一条规则的总权重最高,因此会生效。
  2. 注意事项
    • 相同权重:如果优先级相同,后定义的样式会覆盖前面的样式。

!important​:在样式声明后添加 !important​ 可以覆盖所有优先级。

div{color: red !important;}

示例

#myId .myClass div{color: red;}/* 权重:100 + 10 + 1 = 111 */.myClass div{color: blue;}/* 权重:10 + 1 = 11 */div{color: green;}/* 权重:1 */

优先级示例

    • 最终颜色为 blue​,因为 ID 选择器的优先级最高。
    • 最终颜色为 green​,因为类选择器的优先级高于元素选择器。
    • 最终颜色为 red​,因为内联样式的优先级最高。

示例 3

<divstyle="color: red;">Hello</div>
div{color: green;}/* 权重:1 */

示例 2

<divclass="myClass">Hello</div>
.myClass{color: green;}/* 权重:10 */div{color: red;}/* 权重:1 */

示例 1

<divid="myId"class="myClass">Hello</div>
#myId{color: blue;}/* 权重:100 */.myClass{color: green;}/* 权重:10 */div{color: red;}/* 权重:1 */

优先级优化建议

  1. 避免过度使用​!important
    • !important​ 会破坏优先级规则,增加维护难度。
  2. 减少 ID 选择器的使用
    • ID 选择器的优先级过高,可能导致样式难以覆盖。
  3. 使用类选择器
    • 类选择器的优先级适中,适合大多数场景。
  4. 遵循 CSS 层叠规则
    • 相同优先级时,后定义的样式会覆盖前面的样式。

JavaScript 阻塞问题

JavaScript 是单线程语言,意味着它一次只能执行一个任务。如果某个任务耗时较长,可能会导致阻塞,影响页面的响应和渲染。以下是 JavaScript 阻塞问题的详细说明:

JavaScript 阻塞的表现

  1. 页面渲染阻塞
    • 当 JavaScript 代码执行时,浏览器会暂停页面渲染,直到代码执行完毕。
    • 例如,长时间运行的脚本会导致页面卡顿或无响应。
  2. 事件处理阻塞
    • 如果 JavaScript 代码执行时间过长,用户的操作(如点击、滚动)可能无法及时响应。
  3. 网络请求阻塞
    • JavaScript 代码的执行会阻塞网络请求的处理,影响数据的加载和显示。

JavaScript 阻塞的原因

    • 同步代码会逐行执行,如果某行代码耗时较长,后续代码会被阻塞。
  1. DOM 操作
    • 复杂的 DOM 操作(如大量元素插入、样式计算)会导致页面重排和重绘,影响性能。
  2. CPU 密集型任务
    • 如加密解密、图像处理等任务会占用大量 CPU 资源,导致阻塞。

同步代码执行

for(let i =0; i <1000000000; i++){}// 长时间运行的循环  console.log('执行完毕');// 被阻塞 

解决 JavaScript 阻塞的方法

    • 使用异步 API(如 setTimeout​、Promise​、async/await​)将耗时任务放到事件循环中执行,避免阻塞主线程。
    • 使用 Web Workers 将耗时任务放到后台线程中执行,避免阻塞主线程。
    • 将大任务拆分为多个小任务,分批执行,避免长时间占用主线程。
    • 使用 DocumentFragment​ 减少 DOM 操作次数,避免频繁重排和重绘。
    • 将动画或渲染任务放到 requestAnimationFrame​ 中执行,确保与浏览器的渲染周期同步。

使用​requestAnimationFrame

functionanimate(){// 执行动画逻辑 requestAnimationFrame(animate);}animate();

优化 DOM 操作

const fragment = document.createDocumentFragment();for(let i =0; i <1000; i++){const div = document.createElement('div'); fragment.appendChild(div);} document.body.appendChild(fragment);

任务拆分

functionprocessChunk(start, end){for(let i = start; i < end; i++){// 处理任务 }if(end <1000000){setTimeout(()=>processChunk(end, end +1000),0);}}processChunk(0,1000);

Web Workers

// 主线程 const worker =newWorker('worker.js'); worker.postMessage('开始任务'); worker.onmessage=(event)=>{ console.log('收到消息:', event.data);};// worker.js  self.onmessage=(event)=>{ console.log('收到消息:', event.data); self.postMessage('任务完成');};

异步编程

console.log('开始');setTimeout(()=>{ console.log('异步任务');},0); console.log('结束');// 输出顺序:开始 → 结束 → 异步任务 

JavaScript 阻塞的示例

异步代码非阻塞

console.log('开始');setTimeout(()=>{for(let i =0; i <1000000000; i++){}// 长时间运行的循环  console.log('异步任务');},0); console.log('结束');// 页面不卡顿,用户操作可响应 

同步代码阻塞

console.log('开始');for(let i =0; i <1000000000; i++){}// 长时间运行的循环  console.log('结束');// 页面卡顿,用户操作无响应 

总结

JavaScript 的阻塞问题主要由同步代码、复杂 DOM 操作和 CPU 密集型任务引起。通过以下方法可以避免阻塞:

  1. 使用异步编程(如 Promise​、async/await​)。
  2. 使用 Web Workers 将耗时任务放到后台线程。
  3. 将大任务拆分为多个小任务,分批执行。
  4. 优化 DOM 操作,减少重排和重绘。
  5. 使用 requestAnimationFrame​ 确保动画与渲染同步。

Promise 功能与用法

Promise 是 JavaScript 中用于处理异步操作的对象,它解决了传统回调函数嵌套过深(“回调地狱”)的问题,提供了更清晰和可读性更高的代码结构。

核心概念

  1. 状态Promise 有三种状态:
    • Pending(进行中) :初始状态,表示异步操作尚未完成。
    • Fulfilled(已成功) :异步操作成功完成,返回结果值。
    • Rejected(已失败) :异步操作失败,返回错误信息。
  2. 特点
    • 不可逆:状态一旦从 Pending 变为 Fulfilled 或 Rejected,就不能再改变。
    • 链式调用:通过 .then()​ 和 .catch()​ 方法实现链式调用,避免回调嵌套。

基本用法

    • 使用 .then()​ 处理成功状态(Fulfilled)。
    • 使用 .catch()​ 处理失败状态(Rejected)。

最终处理使用 .finally()​ 在 Promise 完成后执行清理操作,无论成功或失败。

promise .then((result)=> console.log('成功:', result)).catch((error)=> console.log('失败:', error)).finally(()=> console.log('操作完成'));

链式调用每个 .then()​ 返回一个新的 Promise,可以继续调用 .then()​ 或 .catch()​。

promise .then((result)=>{ console.log('第一步:', result);return'第二步';}).then((result)=>{ console.log('第二步:', result);}).catch((error)=>{ console.log('失败:', error);});

处理结果

promise .then((result)=>{ console.log('成功:', result);}).catch((error)=>{ console.log('失败:', error);});

创建 Promise使用 new Promise()​ 构造函数创建 Promise 对象,传入一个执行器函数(executor)。

const promise =newPromise((resolve, reject)=>{// 异步操作 if(/* 操作成功 */){resolve('成功结果');}else{reject('失败原因');}});

Promise 的静态方法

Promise.allSettled() ​接收一个 Promise 数组,返回所有 Promise 的结果(无论成功或失败)。

const promises =[ Promise.resolve('任务1'), Promise.reject('任务2失败'),]; Promise.allSettled(promises).then((results)=> console.log('所有结果:', results));

Promise.race() ​接收一个 Promise 数组,返回第一个完成(无论成功或失败)的 Promise 的结果。

const promises =[newPromise((resolve)=>setTimeout(()=>resolve('任务1'),1000)),newPromise((resolve)=>setTimeout(()=>resolve('任务2'),500)),]; Promise.race(promises).then((result)=> console.log('第一个完成:', result));

Promise.all() ​接收一个 Promise 数组,当所有 Promise 都成功时返回结果数组,如果有一个失败则立即返回失败原因。

const promises =[ Promise.resolve('任务1'), Promise.resolve('任务2'),]; Promise.all(promises).then((results)=> console.log('全部成功:', results)).catch((error)=> console.log('失败:', error));

Promise.reject() ​返回一个已失败的 Promise 对象。

const rejectedPromise = Promise.reject('失败'); rejectedPromise.catch((error)=> console.log(error));// 输出:失败 

Promise.resolve() ​返回一个已成功的 Promise 对象。

const resolvedPromise = Promise.resolve('成功'); resolvedPromise.then((result)=> console.log(result));// 输出:成功 

Promise 的实际应用

避免回调地狱使用 Promise 链式调用代替嵌套回调。

functionstep1(){return Promise.resolve('第一步');}functionstep2(data){return Promise.resolve(data +' → 第二步');}step1().then(step2).then((result)=> console.log(result));

处理异步操作如网络请求、定时器、文件读取等。

functionfetchData(){returnnewPromise((resolve, reject)=>{setTimeout(()=>{resolve('数据加载完成');},1000);});}fetchData().then((result)=> console.log(result)).catch((error)=> console.log(error));

JavaScript 处理大量数据的策略

在处理大量数据时,JavaScript 可能会面临性能瓶颈,如内存占用过高、页面卡顿等问题。以下是处理大量数据的常用策略和优化方法:

优化数据加载

    • 将数据分页加载,每次只加载当前页的数据,减少一次性加载的数据量。
    • 在用户滚动到页面底部时动态加载更多数据。
  1. 按需加载
    • 只加载用户当前需要的数据,例如根据搜索条件过滤数据。

懒加载

window.addEventListener('scroll',()=>{if(window.scrollY + window.innerHeight >= document.body.offsetHeight){loadMoreData();}});

分页加载

functionloadData(page, pageSize){const start =(page -1)* pageSize;const end = start + pageSize;return data.slice(start, end);}const pageData =loadData(1,100);// 加载第一页的100条数据 

优化数据处理

    • 将数据处理的逻辑放到后台线程中执行,避免阻塞主线程。
    • 将大数据集拆分为多个小批次处理,避免一次性处理过多数据。
  1. 使用高效算法
    • 选择时间复杂度更低的算法,例如用哈希表(Map​)替代数组查找。

分批处理

functionprocessInBatches(data, batchSize, processFn){let index =0;functionnextBatch(){const batch = data.slice(index, index + batchSize);processFn(batch); index += batchSize;if(index < data.length){setTimeout(nextBatch,0);// 下一批次 }}nextBatch();}processInBatches(data,1000,(batch)=>{ console.log('处理批次:', batch);});

使用 Web Workers

// 主线程 const worker =newWorker('worker.js'); worker.postMessage(data); worker.onmessage=(event)=>{ console.log('处理结果:', event.data);};// worker.js  self.onmessage=(event)=>{const result =processData(event.data);// 处理数据  self.postMessage(result);};

优化数据存储

    • 对于数值型数据,使用 TypedArray​ 或 DataView​ 减少内存占用。
    • 对数据进行压缩,减少内存占用和网络传输时间。
    • 对于需要在客户端存储的大量数据,使用 IndexedDB 代替 localStorage​。

使用 IndexedDB

const request = indexedDB.open('myDatabase',1); request.onsuccess=(event)=>{const db = event.target.result;const transaction = db.transaction('myStore','readwrite');const store = transaction.objectStore('myStore'); store.add(data,'key');};

压缩数据

const compressedData =JSON.stringify(data);const decompressedData =JSON.parse(compressedData);

使用 TypedArray

const buffer =newArrayBuffer(1024);// 1KB 缓冲区 const intArray =newInt32Array(buffer);// 32位整数数组 

优化数据渲染

    • 只渲染可见区域的数据,减少 DOM 元素的数量。
    • 将渲染任务放到 requestAnimationFrame​ 中执行,确保与浏览器的渲染周期同步。
    • 使用 DocumentFragment​ 或 innerHTML​ 减少 DOM 操作次数。

减少 DOM 操作

const fragment = document.createDocumentFragment(); data.forEach((item)=>{const div = document.createElement('div'); div.textContent = item; fragment.appendChild(div);}); container.appendChild(fragment);

使用​requestAnimationFrame

functionrender(){// 渲染逻辑 requestAnimationFrame(render);}render();

虚拟列表

functionrenderVirtualList(data, container, itemHeight){const visibleCount = Math.ceil(container.clientHeight / itemHeight);const start = Math.floor(container.scrollTop / itemHeight);const end = start + visibleCount;const visibleData = data.slice(start, end);renderItems(visibleData);} window.addEventListener('scroll',()=>{renderVirtualList(data, container,50);});

工具和库

  1. Lodash
    • 提供高性能的数据处理函数,如 _.chunk​(分批处理)、_.debounce​(防抖)。
  2. Immutable.js
    • 提供不可变数据结构,减少数据操作的内存开销。
  3. D3.js
    • 用于处理大规模数据可视化的库。

总结

处理大量数据时,JavaScript 可以通过以下策略优化性能:

  1. 优化数据加载:分页加载、懒加载、按需加载。
  2. 优化数据处理:使用 Web Workers、分批处理、高效算法。
  3. 优化数据存储:使用 TypedArray​、压缩数据、IndexedDB。
  4. 优化数据渲染:虚拟列表、requestAnimationFrame​、减少 DOM 操作。

前端项目兼容性处理指南

在前端开发中,兼容性问题是一个常见的挑战。为了确保项目在不同浏览器、设备和操作系统上正常运行,以下是兼容性处理的详细策略和方法:

浏览器兼容性

  1. 明确目标浏览器
    • 根据项目需求,确定需要支持的浏览器及其版本。
    • 使用工具(如 Can I Use)检查浏览器对特定特性的支持情况。
    • 对于不支持的新特性,使用 Polyfill 提供兼容性支持。
    • 使用 Autoprefixer 自动添加 CSS 前缀,确保样式在不同浏览器中一致。
    • 使用 Babel 将 ES6+ 代码转换为 ES5,确保在旧版浏览器中正常运行。
    • 针对 IE 浏览器使用条件注释加载特定样式或脚本。

条件注释(仅限 IE)

<!--[if IE]> <link rel="stylesheet" href="ie.css"> <![endif]-->

JavaScript 语法降级

// .babelrc {"presets":["@babel/preset-env"]}

CSS 前缀

// package.json "browserslist":["last 2 versions","> 1%","not dead"]

使用 Polyfill

// 示例:使用 core-js 提供 Promise 的 Polyfill import'core-js/features/promise';

设备兼容性

    • 使用媒体查询(Media Queries)适配不同屏幕尺寸。
    • 使用现代布局方案(如 Flexbox 和 Grid)替代传统的浮动布局。
    • 在 HTML 中设置视口(Viewport),确保页面在移动设备上正常显示。

视口设置

<metaname="viewport"content="width=device-width, initial-scale=1.0">

Flexbox 和 Grid 布局

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

响应式设计

@media(max-width: 768px){.container{width: 100%;}}

操作系统兼容性

    • 使用跨平台字体(如 Arial、Helvetica、sans-serif)确保字体在不同操作系统上一致。
  1. 文件路径兼容性
    • 在 Windows 和 Unix 系统中,使用正斜杠 /​ 作为文件路径分隔符。
  2. 键盘和输入兼容性
    • 确保表单和输入控件在不同操作系统上正常工作。

字体兼容性

body{font-family: Arial, Helvetica, sans-serif;}

性能兼容性

  1. 代码压缩
    • 使用工具(如 Webpack、Vite)对 JavaScript 和 CSS 进行压缩和混淆。
    • 使用 WebP 格式图片,并为不支持 WebP 的浏览器提供回退方案。
    • 对图片和资源进行懒加载,减少初始页面加载时间。

懒加载

<imgdata-src="image.jpg"alt="Lazy Loaded Image"class="lazyload">

图片优化

<picture><sourcesrcset="image.webp"type="image/webp"><imgsrc="image.jpg"alt="Fallback Image"></picture>

测试和调试

  1. 浏览器测试
    • 使用浏览器开发者工具(如 Chrome DevTools)进行调试。
    • 使用工具(如 BrowserStack)测试不同浏览器和设备上的兼容性。
  2. 自动化测试
    • 使用工具(如 Jest、Cypress)编写自动化测试脚本,确保功能在不同环境中正常工作。
  3. 用户反馈
    • 收集用户反馈,及时修复兼容性问题。

总结

前端项目兼容性处理的核心策略包括:

  1. 浏览器兼容性:使用 Polyfill、CSS 前缀、Babel 等工具。
  2. 设备兼容性:采用响应式设计、Flexbox 和 Grid 布局。
  3. 操作系统兼容性:注意字体、文件路径和输入兼容性。
  4. 性能兼容性:优化代码、图片和资源加载。
  5. 测试和调试:通过自动化测试和用户反馈确保兼容性。

使用 CSS 绘制椭圆的详细方法

在 CSS 中,可以通过 border-radius​ 属性绘制椭圆。以下是实现椭圆的几种方法:

使用 border-radius​ 绘制椭圆

border-radius​ 是绘制椭圆的关键属性。它的值可以是百分比或长度单位,用于定义元素的圆角半径。

    • width​ 和 height​ 分别定义椭圆的宽度和高度。
    • border-radius: 50%​ 将矩形变为椭圆。
    • 通过调整 width​ 和 height​ 的比例,可以绘制不同方向的椭圆。
    • 使用 transform: rotate()​ 旋转椭圆。

倾斜椭圆

.ellipse{width: 200px;height: 100px;background-color: #ff6347;border-radius: 50%;transform:rotate(45deg);}

垂直椭圆

.ellipse{width: 100px;height: 200px;background-color: #ff6347;border-radius: 50%;}

水平椭圆

.ellipse{width: 200px;height: 100px;background-color: #ff6347;border-radius: 50%;}

使用 border-radius​ 的四个值绘制椭圆

border-radius​ 可以分别设置四个角的半径,通过调整值可以绘制更复杂的椭圆。

.ellipse{width: 200px;height: 100px;background-color: #ff6347;border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;}
  • border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%​ 分别定义水平和垂直方向的圆角半径。

使用 clip-path​ 绘制椭圆

clip-path​ 是另一种绘制椭圆的方法,通过裁剪路径实现。

.ellipse{width: 200px;height: 100px;background-color: #ff6347;clip-path:ellipse(50% 50% at 50% 50%);}
  • ellipse(50% 50% at 50% 50%)​:定义椭圆的水平和垂直半径,以及中心点位置。

使用 SVG 绘制椭圆

SVG 是一种矢量图形格式,适合绘制复杂的椭圆。

<svgwidth="200"height="100"><ellipsecx="100"cy="50"rx="100"ry="50"fill="#ff6347"/></svg>
  • cx​ 和 cy​:椭圆中心点的坐标。
  • rx​ 和 ry​:椭圆的水平和垂直半径。

总结

在 CSS 中绘制椭圆的主要方法包括:

  1. border-radius​:通过设置 border-radius: 50%​ 将矩形变为椭圆。
  2. clip-path​:使用 clip-path: ellipse()​ 裁剪路径绘制椭圆。
  3. SVG:使用 SVG 的 <ellipse>​ 元素绘制椭圆。

setTimeout​ 和 setInterval

setTimeout​ 和 setInterval​ 是 JavaScript 中用于定时执行代码的两个核心 API。以下是它们的区别、使用场景以及 setTimeout​ 设置为 0​ 时的行为分析。

setTimeout​ 和 setInterval​ 的区别

特性setTimeoutsetInterval
功能在指定延迟后执行一次回调函数。每隔指定时间重复执行回调函数。
语法setTimeout(callback, delay)setInterval(callback, delay)
停止方法clearTimeout(timeoutId)clearInterval(intervalId)
适用场景延迟执行、单次任务。重复执行、轮询任务。

setTimeout​ 设置为 0​ 的行为

  1. 现象setTimeout​ 的延迟时间设置为 0​ 时,回调函数不会立即执行,而是会被放入事件队列中,等待当前调用栈清空后再执行。
  2. 原因
    • JavaScript 是单线程语言,使用事件循环机制处理异步任务。
    • setTimeout​ 的回调函数属于宏任务,即使延迟时间为 0​,也会被放入宏任务队列中,等待当前同步代码和微任务执行完毕后再执行。
  3. 应用场景
    • 将代码延迟到下一个事件循环执行,避免阻塞当前任务。
    • 在 DOM 操作后执行回调,确保 DOM 更新完成。

示例

console.log('开始');setTimeout(()=>{ console.log('setTimeout');},0); console.log('结束');// 输出顺序:开始 → 结束 → setTimeout 

为什么会有 setTimeout​ 和 setInterval​ 这两个 API

  1. setTimeout的作用
    • 用于延迟执行一次性任务。
    • 例如:延迟显示提示信息、延迟执行动画等。
  2. setInterval的作用
    • 用于重复执行周期性任务。
    • 例如:轮询服务器数据、定时更新 UI 等。
  3. 设计初衷
    • setTimeout​ 和 setInterval​ 分别针对单次任务和重复任务,提供了更灵活的定时器功能。
    • 它们的实现基于事件循环机制,确保了异步任务的执行顺序和可控性。

注意事项

    • 如果回调函数执行时间超过延迟时间,会导致多个回调函数堆积执行。
    • 解决方法:使用 setTimeout​ 递归调用替代 setInterval​。
  1. 性能优化
    • 避免在 setTimeout​ 或 setInterval​ 中执行耗时操作,以免阻塞主线程。
    • 使用 requestAnimationFrame​ 替代 setTimeout​ 执行动画,确保与浏览器的渲染周期同步。

setInterval的问题

functionrepeat(){ console.log('重复执行');setTimeout(repeat,1000);}repeat();

总结

  • setTimeout​:用于延迟执行一次性任务,延迟时间为 0​ 时会在下一个事件循环执行。
  • setInterval​:用于重复执行周期性任务,但需注意回调函数执行时间过长的问题。
  • 设计原因setTimeout​ 和 setInterval​ 分别针对单次任务和重复任务,提供了灵活的定时器功能。

判断项目性能好不好的方法

一、核心性能指标

  1. 加载性能
    • 首次内容绘制(FCP) :页面首次渲染文本、图片等内容的耗时。
    • 最大内容绘制(LCP) :页面最大内容元素渲染的耗时。
    • 首字节时间(TTFB) :从请求发出到收到第一个字节的时间。
  2. 交互性能
    • 首次输入延迟(FID) :用户首次与页面交互(如点击按钮)到页面响应的耗时。
    • 总阻塞时间(TBT) :页面主线程被长时间任务阻塞的总时间。
  3. 视觉稳定性
    • 累积布局偏移(CLS) :页面布局变化的频率和幅度,衡量视觉稳定性。
  4. 资源优化
    • 资源加载时间(如 JavaScript、CSS、图片)。
    • 资源压缩率(如 Gzip、Brotli)。
  5. 内存占用
    • 内存泄漏检测,确保内存使用量在合理范围内。

二、性能检测工具

  1. Lighthouse
    • 集成在 Chrome DevTools 中,提供全面的性能分析报告。
    • 使用步骤:
      1. 打开 Chrome DevTools(F12)。
      2. 切换到 Lighthouse 标签页。
      3. 点击 Generate report 生成报告。
  2. WebPageTest
  3. Chrome DevTools
    • Performance 面板:记录页面运行时性能,分析任务耗时和内存占用。
    • Network 面板:分析资源加载时间和网络请求。
  4. Google Analytics
    • 监控用户实际访问页面的性能数据,如页面加载时间、交互延迟等。

三、检测某个功能或组件的性能

    • 在代码中插入计时器,测量某个函数的执行时间。
    • 高精度计时,测量代码片段的执行时间。
  1. 使用 Chrome DevTools 的 Performance 面板
    • 记录页面运行时性能,定位某个功能或组件的性能瓶颈。
    • 使用步骤:
      1. 打开 Chrome DevTools(F12)。
      2. 切换到 Performance 面板。
      3. 点击 Record 按钮,执行需要测试的功能或组件。
      4. 停止记录后,分析火焰图中的任务耗时。
  2. 使用 React Profiler(React 项目)
    • 分析 React 组件的渲染性能。
    • 使用步骤:
      1. 在 React 开发者工具中切换到 Profiler 面板。
      2. 点击 Record 按钮,执行需要测试的组件。
      3. 停止记录后,分析组件的渲染时间和次数。
  3. 使用​why-did-you-render(React 项目)
    • 检测 React 组件的不必要渲染。

示例:

import whyDidYouRender from'@welldone-software/why-did-you-render';whyDidYouRender(React);

使用​performance.now()

const start = performance.now();myFunction();// 需要测试的函数 const end = performance.now(); console.log(`耗时:${end - start} 毫秒`);

使用​console.time和​console.timeEnd

console.time('myFunction');myFunction();// 需要测试的函数  console.timeEnd('myFunction');

四、优化性能的建议

  1. 减少主线程任务
    • 将耗时任务放到 Web Workers 中执行,避免阻塞主线程。
  2. 优化资源加载
    • 使用懒加载、分块加载(Code Splitting)减少初始加载时间。
  3. 减少重排和重绘
    • 使用 transform​ 和 opacity​ 替代直接修改布局属性。
  4. 缓存数据
    • 使用 Service Worker 或 IndexedDB 缓存数据,减少网络请求。
  5. 压缩资源
    • 使用 Gzip 或 Brotli 压缩 JavaScript、CSS 和图片资源。

五、总结

判断项目性能好不好的核心指标包括加载性能、交互性能、视觉稳定性和资源优化。通过 Lighthouse、WebPageTest 等工具可以全面评估项目性能,而 console.time​、performance.now()​ 和 Chrome DevTools 的 Performance 面板则适合检测某个功能或组件的性能。结合优化建议,可以显著提升项目的性能和用户体验。

单独检测某个功能或组件性能的方法

在开发过程中,有时需要针对某个功能或组件进行性能检测,以定位瓶颈并优化代码。以下是详细的检测方法和工具:

一、使用 console.time​ 和 console.timeEnd

  1. 作用
    • 测量代码片段的执行时间。
    • 输出结果:myFunction: 0.123ms
  2. 适用场景
    • 快速测量函数的执行时间,适合简单的性能检测。

示例

console.time('myFunction');myFunction();// 需要测试的函数  console.timeEnd('myFunction');

二、使用 performance.now()

  1. 作用
    • 提供高精度计时,测量代码片段的执行时间。
  2. 适用场景
    • 需要高精度计时的性能检测。

示例

const start = performance.now();myFunction();// 需要测试的函数 const end = performance.now(); console.log(`耗时:${end - start} 毫秒`);

三、使用 Chrome DevTools 的 Performance 面板

  1. 作用
    • 记录页面运行时性能,定位某个功能或组件的性能瓶颈。
  2. 使用步骤
    1. 打开 Chrome DevTools(F12)。
    2. 切换到 Performance 面板。
    3. 点击 Record 按钮,执行需要测试的功能或组件。
    4. 停止记录后,分析火焰图中的任务耗时。
  3. 适用场景
    • 需要详细分析任务耗时、内存占用和渲染性能的复杂场景。

四、使用 React Profiler(React 项目)

  1. 作用
    • 分析 React 组件的渲染性能。
  2. 使用步骤
    1. 在 React 开发者工具中切换到 Profiler 面板。
    2. 点击 Record 按钮,执行需要测试的组件。
    3. 停止记录后,分析组件的渲染时间和次数。
  3. 示例
    • 输出结果:组件的渲染时间、渲染次数和性能瓶颈。
  4. 适用场景
    • 检测 React 组件的渲染性能,优化不必要的渲染。

五、使用 why-did-you-render​(React 项目)

  1. 作用
    • 检测 React 组件的不必要渲染。
  2. 适用场景
    • 检测和优化 React 组件的渲染性能。

配置

import whyDidYouRender from'@welldone-software/why-did-you-render';whyDidYouRender(React);

安装

npminstall @welldone-software/why-did-you-render --save-dev 

六、使用自定义性能检测工具

  1. 作用
    • 根据项目需求,自定义性能检测逻辑。
  2. 适用场景
    • 需要灵活自定义性能检测逻辑的场景。

示例

classPerformanceTracker{constructor(){this.metrics ={};}start(name){this.metrics[name]= performance.now();}end(name){const duration = performance.now()-this.metrics[name]; console.log(`${name} 耗时:${duration} 毫秒`);}}const tracker =newPerformanceTracker(); tracker.start('myFunction');myFunction();// 需要测试的函数  tracker.end('myFunction');

七、总结

检测某个功能或组件性能的常用方法包括:

  1. console.time和​console.timeEnd​:快速测量函数执行时间。
  2. performance.now() ​:高精度计时。
  3. Chrome DevTools 的 Performance 面板:详细分析任务耗时和内存占用。
  4. React Profiler:检测 React 组件的渲染性能。
  5. why-did-you-render​:检测 React 组件的不必要渲染。
  6. 自定义性能检测工具:根据需求灵活实现。

常见的网络请求状态码

HTTP 状态码是服务器对客户端请求的响应结果,用于表示请求的处理状态。以下是常见的状态码及其含义:

一、状态码分类

HTTP 状态码由 3 位数字组成,分为 5 大类:

  1. 1xx(信息性状态码)
    • 表示请求已被接收,需要继续处理。
  2. 2xx(成功状态码)
    • 表示请求已成功被服务器接收、理解并处理。
  3. 3xx(重定向状态码)
    • 表示需要客户端进一步操作以完成请求。
  4. 4xx(客户端错误状态码)
    • 表示客户端发送的请求有错误。
  5. 5xx(服务器错误状态码)
    • 表示服务器在处理请求时发生错误。

二、常见状态码

  1. 1xx 信息性状态码
    • 100 Continue:服务器已收到请求头,客户端应继续发送请求体。
    • 101 Switching Protocols:服务器同意切换协议(如升级到 WebSocket)。
  2. 2xx 成功状态码
    • 200 OK:请求成功,服务器返回了所需的数据。
    • 201 Created:请求成功,并创建了新资源(如 POST 请求)。
    • 204 No Content:请求成功,但响应中没有返回内容。
  3. 3xx 重定向状态码
    • 301 Moved Permanently:请求的资源已永久移动到新位置。
    • 302 Found:请求的资源临时移动到新位置。
    • 304 Not Modified:资源未修改,客户端可使用缓存版本。
  4. 4xx 客户端错误状态码
    • 400 Bad Request:请求语法错误,服务器无法理解。
    • 401 Unauthorized:请求需要身份验证。
    • 403 Forbidden:服务器拒绝请求,客户端无访问权限。
    • 404 Not Found:请求的资源不存在。
  5. 5xx 服务器错误状态码
    • 500 Internal Server Error:服务器内部错误,无法完成请求。
    • 502 Bad Gateway:服务器作为网关或代理时,从上游服务器收到无效响应。
    • 503 Service Unavailable:服务器暂时无法处理请求(如过载或维护)。
    • 504 Gateway Timeout:服务器作为网关或代理时,未及时从上游服务器收到响应。

三、状态码的应用场景

  1. 调试和排查问题
    • 通过状态码快速定位请求失败的原因。
  2. 优化用户体验
    • 根据状态码显示友好的错误提示(如 404 页面)。
  3. 监控和报警
    • 监控服务器返回的状态码,及时发现异常并报警。

四、总结

HTTP 状态码是网络请求的重要组成部分,分为 1xx(信息性)、2xx(成功)、3xx(重定向)、4xx(客户端错误)和 5xx(服务器错误)五大类。理解常见的状态码及其含义,有助于开发、调试和优化网络请求。

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

箭头函数(Arrow Function)是 ES6 引入的一种简洁的函数语法,与普通函数(Function)在语法、行为和作用域上有显著区别。以下是两者的详细对比:

一、语法区别

    • 语法简洁,适合单行函数。
    • 使用 function​ 关键字定义。

普通函数

functionadd(a, b){return a + b;}

箭头函数

constadd=(a, b)=> a + b;

二、this​ 指向区别

    • 没有自己的 this​,this​ 继承自外层作用域(词法作用域)。
    • 有自己的 this​,this​ 指向调用该函数的对象。

普通函数

const obj ={value:42,getValue:function(){ console.log(this.value);// 输出:42 }}; obj.getValue();

箭头函数

const obj ={value:42,getValue:()=>{ console.log(this.value);// 输出:undefined }}; obj.getValue();

三、arguments​ 对象区别

    • 没有 arguments​ 对象,需使用剩余参数(Rest Parameters)获取参数。
    • arguments​ 对象,包含所有传入的参数。

普通函数

functionshowArgs(){ console.log(arguments);}showArgs(1,2,3);// 输出:{ 0: 1, 1: 2, 2: 3 } 

箭头函数

constshowArgs=(...args)=>{ console.log(args);};showArgs(1,2,3);// 输出:[1, 2, 3] 

四、构造函数区别

    • 不能作为构造函数使用,不能使用 new​ 关键字。
    • 可以作为构造函数使用,可以使用 new​ 关键字。

普通函数

functionFoo(){}const foo =newFoo();// 正常执行 

箭头函数

constFoo=()=>{};const foo =newFoo();// 报错:Foo is not a constructor 

五、原型链区别

    • 没有 prototype​ 属性,不能作为构造函数。
    • prototype​ 属性,可以作为构造函数。

普通函数

functionFoo(){} console.log(Foo.prototype);// 输出:{ constructor: Foo } 

箭头函数

constFoo=()=>{}; console.log(Foo.prototype);// 输出:undefined 

六、适用场景

    • 适合短小简单的函数,如回调函数、数组方法等。
    • 适合需要 this​、arguments​ 或作为构造函数的场景。

普通函数

functionPerson(name){this.name = name;}const person =newPerson('Alice');

箭头函数

const numbers =[1,2,3];const doubled = numbers.map(n=> n *2);

七、总结

特性箭头函数普通函数
语法简洁,适合单行函数使用 function​ 关键字定义
this指向继承自外层作用域指向调用函数的对象
arguments对象无,需使用剩余参数arguments​ 对象
构造函数不能作为构造函数可以作为构造函数
原型链prototype​ 属性prototype​ 属性

根据需求选择合适的函数类型,可以提升代码的可读性和可维护性。

防抖(Debounce)与节流(Throttle)的区别

防抖和节流是两种常见的性能优化技术,用于控制函数的执行频率,避免高频触发导致的性能问题。以下是它们的详细区别和应用场景:

一、防抖(Debounce)

  1. 定义
    • 在事件被触发后,等待一段时间再执行函数。如果在这段时间内事件再次被触发,则重新计时。
  2. 实现原理
    • 使用 setTimeout​ 延迟执行函数,每次触发事件时清除之前的定时器并重新计时。
  3. 应用场景
    • 搜索框输入:用户停止输入后再触发搜索请求。
    • 窗口调整大小:用户停止调整窗口大小后再重新计算布局。

代码实现

functiondebounce(func, delay){let timer;returnfunction(...args){clearTimeout(timer); timer =setTimeout(()=>func.apply(this, args), delay);};}

二、节流(Throttle)

  1. 定义
    • 在事件被触发后,每隔一段时间执行一次函数,忽略中间的事件触发。
  2. 实现原理
    • 使用时间戳或 setTimeout​ 控制函数的执行频率。
  3. 应用场景
    • 滚动事件:每隔一段时间触发一次滚动处理函数。
    • 按钮点击:防止用户频繁点击按钮导致多次提交。

代码实现

functionthrottle(func, delay){let lastTime =0;returnfunction(...args){const now = Date.now();if(now - lastTime >= delay){func.apply(this, args); lastTime = now;}};}

三、防抖与节流的区别

特性防抖(Debounce)节流(Throttle)
执行时机事件停止触发后执行每隔一段时间执行一次
触发频率高频触发时只执行最后一次高频触发时按固定频率执行
实现原理使用 setTimeout​ 延迟执行使用时间戳或 setTimeout​ 控制频率
应用场景搜索框输入、窗口调整大小滚动事件、按钮点击

四、代码示例对比

节流示例

const throttledScroll =throttle(()=>{ console.log('滚动事件处理');},500);// 用户滚动时触发  window.addEventListener('scroll', throttledScroll);

防抖示例

const debouncedSearch =debounce(()=>{ console.log('搜索请求已发送');},500);// 用户输入时触发  input.addEventListener('input', debouncedSearch);

五、总结

  • 防抖:适合在事件停止触发后执行一次的场景,如搜索框输入、窗口调整大小。
  • 节流:适合按固定频率执行一次的场景,如滚动事件、按钮点击。

JavaScript 宏任务与微任务的详解

JavaScript 是单线程语言,通过事件循环(Event Loop)机制处理异步任务。为了更高效地管理任务,JavaScript 将异步任务分为宏任务(Macro Task)和微任务(Micro Task)。以下是宏任务和微任务的存在原因及其区别:

一、宏任务与微任务的定义

  1. 宏任务(Macro Task)
    • 代表较大的任务单元,通常包括:
      • setTimeout​、setInterval
      • I/O 操作(如文件读取、网络请求)
      • UI 渲染
      • script​(整体代码)
  2. 微任务(Micro Task)
    • 代表较小的任务单元,通常包括:
      • Promise.then​、Promise.catch​、Promise.finally
      • MutationObserver
      • queueMicrotask

二、事件循环中的执行顺序

  1. 事件循环流程
    • 执行一个宏任务(如 script​ 整体代码)。
    • 执行所有微任务。
    • 渲染页面(如果需要)。
    • 执行下一个宏任务。
    • 输出顺序:开始 → 结束 → Promise → setTimeout

示例

console.log('开始');setTimeout(()=>{ console.log('setTimeout');},0); Promise.resolve().then(()=>{ console.log('Promise');}); console.log('结束');

三、为什么要有宏任务和微任务

  1. 任务优先级
    • 微任务的优先级高于宏任务,确保高优先级的任务(如 Promise 回调)能及时执行。
  2. 任务粒度
    • 宏任务代表较大的任务单元(如定时器、I/O 操作),适合处理耗时较长的任务。
    • 微任务代表较小的任务单元(如 Promise 回调),适合处理轻量级的任务。
  3. 用户体验
    • 通过微任务优先执行,可以更快地响应用户操作(如 Promise 回调),提升用户体验。
  4. 任务调度
    • 宏任务和微任务的划分,使得 JavaScript 可以更灵活地调度任务,避免长时间阻塞主线程。

四、宏任务与微任务的区别

特性宏任务(Macro Task)微任务(Micro Task)
任务类型setTimeout​、setInterval​、I/O 操作Promise.then​、MutationObserver
执行顺序在微任务之后执行在宏任务之后、渲染之前执行
优先级
任务粒度较大较小

五、总结

JavaScript 引入宏任务和微任务的原因包括:

  1. 优先级管理:微任务优先执行,确保高优先级任务及时处理。
  2. 任务粒度:宏任务处理耗时较长的任务,微任务处理轻量级任务。
  3. 用户体验:通过微任务优先执行,提升用户体验。
  4. 任务调度:灵活调度任务,避免长时间阻塞主线程。

跨域问题

跨域问题是由浏览器的 同源策略(Same-Origin Policy)引起的安全机制,用于防止不同源的网站之间进行恶意数据交互。以下是跨域问题的原因、表现及解决方案:

一、什么是跨域

  1. 同源策略
    • 同源策略要求协议、域名和端口完全相同,否则视为跨域。
    • 示例:
      • https://www.example.com​ 和 https://api.example.com​ 不同源(域名不同)。
      • http://example.com​ 和 https://example.com​ 不同源(协议不同)。
      • http://example.com:80​ 和 http://example.com:8080​ 不同源(端口不同)。
  2. 跨域的表现
    • 浏览器阻止跨域请求,导致请求失败。
    • 常见的错误信息: Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://www.example.com' has been blocked by CORS policy.

二、跨域问题的解决方案

  1. CORS(跨域资源共享)
    • 服务器通过设置响应头允许跨域请求。
    • 常用响应头:
      • Access-Control-Allow-Origin​:允许的源(如 *​ 或 https://www.example.com​)。
      • Access-Control-Allow-Methods​:允许的 HTTP 方法(如 GET, POST​)。
      • Access-Control-Allow-Headers​:允许的请求头(如 Content-Type​)。
  2. JSONP(JSON with Padding)
    • 利用 <script>​ 标签不受同源策略限制的特性,通过回调函数获取跨域数据。
    • 限制:仅支持 GET​ 请求。
  3. 代理服务器
    • 通过同源的后端服务器代理跨域请求。
    • 示例:
      • 前端请求:https://www.example.com/api/data
      • 后端服务器转发请求:https://api.example.com/data
  4. WebSocket
    • WebSocket 不受同源策略限制,可用于跨域通信。
  5. Nginx 反向代理
    • 使用 Nginx 配置反向代理,将跨域请求转发到目标服务器。
  6. PostMessage
    • 用于跨窗口通信,支持跨域。

示例:

// 发送消息  window.postMessage('Hello','https://www.example.com');// 接收消息  window.addEventListener('message',(event)=>{if(event.origin !=='https://www.example.com')return; console.log(event.data);});

示例配置:

server { location /api { proxy_pass https://api.example.com; } } 

示例:

const socket =newWebSocket('wss://api.example.com'); socket.onmessage=(event)=>{ console.log(event.data);};

示例:

<script>functionhandleResponse(data){ console.log(data);}</script><scriptsrc="https://api.example.com/data?callback=handleResponse"></script>

示例:

Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: Content-Type 

三、常见的跨域场景

  1. 前端调用后端 API
    • 前端:https://www.example.com
    • 后端:https://api.example.com
    • 解决方案:CORS、代理服务器、Nginx 反向代理。
  2. 跨域资源共享
    • 不同域名之间的资源访问(如图片、字体)。
    • 解决方案:CORS、JSONP。
  3. 跨窗口通信
    • 不同窗口或 iframe 之间的通信。
    • 解决方案:PostMessage。

四、总结

跨域问题是由浏览器的同源策略引起的,常见解决方案包括:

  1. CORS:通过服务器设置响应头允许跨域请求。
  2. JSONP:利用 <script>​ 标签获取跨域数据。
  3. 代理服务器:通过后端服务器代理跨域请求。
  4. WebSocket:用于跨域实时通信。
  5. Nginx 反向代理:通过 Nginx 配置转发跨域请求。
  6. PostMessage:用于跨窗口通信。

提高前端项目加载速度的方法

前端项目的加载速度直接影响用户体验和 SEO 排名。以下是提高前端项目加载速度的常用方法:

一、优化资源加载

  1. 压缩资源
    • 使用工具(如 Webpack、Vite)压缩 JavaScript、CSS 和 HTML 文件。
  2. 使用 Gzip/Brotli 压缩
    • 在服务器端启用 Gzip 或 Brotli 压缩,减少资源传输体积。
  3. 图片优化
    • 使用 WebP 格式图片,并为不支持 WebP 的浏览器提供回退方案。
    • 使用工具(如 ImageOptim、TinyPNG)压缩图片。
  4. 使用 CDN
    • 将静态资源(如 JavaScript、CSS、图片)托管到 CDN,加速资源加载。

示例:

<picture><sourcesrcset="image.webp"type="image/webp"><imgsrc="image.jpg"alt="Fallback Image"></picture>

示例(Nginx 配置):

gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 

示例:

npminstall terser-webpack-plugin --save-dev 

二、减少请求次数

  1. 合并文件
    • 将多个 JavaScript 或 CSS 文件合并为一个文件,减少 HTTP 请求次数。
  2. 使用雪碧图(Sprite)
    • 将多个小图标合并为一张雪碧图,减少图片请求次数。
  3. HTTP/2
    • 使用 HTTP/2 协议,支持多路复用,减少请求开销。

示例:

.icon{background-image:url('sprite.png');background-position: -10px -20px;}

三、代码优化

  1. 代码分割(Code Splitting)
    • 使用动态导入(Dynamic Import)将代码拆分为多个小块,按需加载。
  2. Tree Shaking
    • 使用工具(如 Webpack、Rollup)移除未使用的代码。
  3. 延迟加载(Lazy Load)
    • 延迟加载非关键资源(如图片、视频)。

示例:

<imgdata-src="image.jpg"alt="Lazy Loaded Image"class="lazyload">

示例:

import{ func1 }from'module';func1();

示例:

import('./module').then(module=>{ module.default();});

四、缓存优化

  1. 浏览器缓存
    • 设置 Cache-Control​ 和 ETag​ 响应头,利用浏览器缓存静态资源。
  2. Service Worker
    • 使用 Service Worker 缓存资源,支持离线访问。

示例:

self.addEventListener('install',(event)=>{ event.waitUntil( caches.open('v1').then((cache)=>{return cache.addAll(['/','/styles.css','/app.js']);}));});

示例(Nginx 配置):

location /static { expires 1y; add_header Cache-Control "public"; } 

五、渲染优化

  1. 减少重排和重绘
    • 使用 transform​ 和 opacity​ 替代直接修改布局属性。
  2. 使用​requestAnimationFrame
    • 将动画逻辑放到 requestAnimationFrame​ 中执行,确保与浏览器的渲染周期同步。
  3. 关键渲染路径优化
    • 将关键 CSS 内联到 HTML 中,优先加载关键资源。

示例:

<style>/* 关键 CSS */</style>

示例:

functionanimate(){// 动画逻辑 requestAnimationFrame(animate);}animate();

示例:

.element{transform:translateX(100px);opacity: 0.5;}

六、工具和监控

  1. 性能分析工具
    • 使用 Lighthouse、WebPageTest 等工具分析页面性能。
  2. 性能监控
    • 使用 Google Analytics、New Relic 等工具监控页面加载速度。

七、总结

提高前端项目加载速度的核心方法包括:

  1. 优化资源加载:压缩资源、使用 CDN、优化图片。
  2. 减少请求次数:合并文件、使用雪碧图、HTTP/2。
  3. 代码优化:代码分割、Tree Shaking、延迟加载。
  4. 缓存优化:设置浏览器缓存、使用 Service Worker。
  5. 渲染优化:减少重排和重绘、使用 requestAnimationFrame​。
  6. 工具和监控:使用性能分析工具和监控工具持续优化。

页签之间消息传递的方法

在浏览器中,多个页签之间可以通过以下几种方式进行消息传递:

一、使用 localStorage​ 或 sessionStorage

  1. 原理
    • localStorage​ 和 sessionStorage​ 是浏览器提供的存储机制,可以在同一源的不同页签之间共享数据。
  2. 实现步骤
    • 在页签 A 中设置数据并触发 storage​ 事件。
    • 在页签 B 中监听 storage​ 事件,获取数据。
  3. 注意事项
    • 只能在同一源的不同页签之间共享数据。
    • storage​ 事件不会在当前页签触发。

示例代码

// 页签 A:设置数据  localStorage.setItem('message','Hello from Tab A');// 页签 B:监听 storage 事件  window.addEventListener('storage',(event)=>{if(event.key ==='message'){ console.log('收到消息:', event.newValue);}});

二、使用 BroadcastChannel

  1. 原理
    • BroadcastChannel​ 是浏览器提供的 API,允许同一源的不同页签之间通过消息通道进行通信。
  2. 实现步骤
    • 在页签 A 和页签 B 中创建相同的 BroadcastChannel​。
    • 在页签 A 中发送消息,在页签 B 中接收消息。
  3. 注意事项
    • 只能在同一源的不同页签之间通信。
    • 需要手动关闭 BroadcastChannel​。

示例代码

// 页签 A:发送消息 const channel =newBroadcastChannel('myChannel'); channel.postMessage('Hello from Tab A');// 页签 B:接收消息 const channel =newBroadcastChannel('myChannel'); channel.onmessage=(event)=>{ console.log('收到消息:', event.data);};

三、使用 SharedWorker

  1. 原理
    • SharedWorker​ 是浏览器提供的 Web Worker,允许同一源的不同页签之间共享一个后台线程进行通信。
  2. 实现步骤
    • 在页签 A 和页签 B 中创建相同的 SharedWorker​。
    • 在页签 A 中发送消息,在页签 B 中接收消息。
  3. 注意事项
    • 只能在同一源的不同页签之间通信。
    • SharedWorker​ 需要额外的 JavaScript 文件。

示例代码

// sharedWorker.js const connections =[]; self.onconnect=(event)=>{const port = event.ports; connections.push(port); port.onmessage=(event)=>{ connections.forEach((conn)=>{if(conn !== port) conn.postMessage(event.data);});};};// 页签 A:发送消息 const worker =newSharedWorker('sharedWorker.js'); worker.port.postMessage('Hello from Tab A');// 页签 B:接收消息 const worker =newSharedWorker('sharedWorker.js'); worker.port.onmessage=(event)=>{ console.log('收到消息:', event.data);};

四、使用 window.open​ 和 postMessage

  1. 原理
    • 通过 window.open​ 打开新页签,并使用 postMessage​ 进行跨页签通信。
  2. 实现步骤
    • 在页签 A 中使用 window.open​ 打开页签 B,并保存页签 B 的引用。
    • 在页签 A 中使用 postMessage​ 发送消息,在页签 B 中监听 message​ 事件。
  3. 注意事项
    • 可以在不同源的页签之间通信,但需要处理跨域问题。
    • 需要手动管理页签的引用。

示例代码

// 页签 A:打开页签 B 并发送消息 const tabB = window.open('tabB.html');setTimeout(()=>{ tabB.postMessage('Hello from Tab A','*');},1000);// 页签 B:接收消息  window.addEventListener('message',(event)=>{ console.log('收到消息:', event.data);});

五、总结

方法优点缺点适用场景
localStorage简单易用,无需额外 API只能在同一源页签之间通信简单数据共享
BroadcastChannel支持消息通道,通信更灵活只能在同一源页签之间通信需要频繁通信的场景
SharedWorker支持后台线程通信,性能更好需要额外的 JavaScript 文件复杂数据共享和通信
window.open​ + postMessage支持跨域通信,灵活性强需要手动管理页签引用跨域页签通信

根据具体需求选择合适的方法,可以实现页签之间的高效消息传递。

使元素水平居中的方法

在前端开发中,实现元素水平居中是常见的需求。以下是多种实现方法及其适用场景:

一、行内元素水平居中

    • 适用于行内元素(如文本、图片)。
    • 适用于单行文本元素。

使用​line-height

.container{height: 100px;line-height: 100px;}

使用​text-align: center

.container{text-align: center;}

二、块级元素水平居中

    • 适用于固定宽度的块级元素。
    • 适用于任意宽度的块级元素。
    • 适用于任意宽度的块级元素。
    • 适用于未知宽度的块级元素。
    • 适用于需要兼容旧浏览器的场景。

使用表格布局

.container{display: table;margin: 0 auto;}

使用绝对定位 +​transform

.element{position: absolute;left: 50%;transform:translateX(-50%);}

使用 Grid

.container{display: grid;place-items: center;}

使用 Flexbox

.container{display: flex;justify-content: center;}

使用​margin: 0 auto

.element{width: 200px;margin: 0 auto;}

三、行内块级元素水平居中

    • 适用于行内块级元素。
    • 适用于行内块级元素。

使用 Flexbox

.container{display: flex;justify-content: center;}

使用​text-align: center

.container{text-align: center;}.element{display: inline-block;}

四、浮动元素水平居中

    • 适用于浮动元素。

使用​margin: 0 auto+ 清除浮动

.element{float: left;width: 200px;margin: 0 auto;}.container::after{content:'';display: table;clear: both;}

五、总结

方法适用场景示例代码
text-align: center行内元素、行内块级元素.container { text-align: center; }
line-height单行文本元素.container { line-height: 100px; }
margin: 0 auto固定宽度的块级元素.element { margin: 0 auto; }
Flexbox任意宽度的块级元素.container { display: flex; justify-content: center; }
Grid任意宽度的块级元素.container { display: grid; place-items: center; }
绝对定位 + transform未知宽度的块级元素.element { position: absolute; left: 50%; transform: translateX(-50%); }
表格布局需要兼容旧浏览器的场景.container { display: table; margin: 0 auto; }

根据具体需求选择合适的方法,可以轻松实现元素水平居中。

let​、const​ 和 var​ 的区别详解

在 JavaScript 中,let​、const​ 和 var​ 是用于声明变量的关键字,它们在作用域、提升和重复声明等方面有显著区别。以下是它们的详细对比:

一、作用域

  1. var
    • 函数作用域:在函数内部声明的变量,只能在函数内部访问。
    • 全局作用域:在函数外部声明的变量,可以在全局访问。
  2. let和​const
    • 块级作用域:在 {}​ 块内部声明的变量,只能在块内部访问。

示例

if(true){var a =1;let b =2;const c =3;} console.log(a);// 输出:1  console.log(b);// 报错:b is not defined  console.log(c);// 报错:c is not defined 

二、变量提升

  1. var
    • 变量会被提升到函数或全局作用域的顶部,但赋值不会被提升。
  2. let和​const
    • 变量会被提升到块级作用域的顶部,但在声明之前访问会触发“暂时性死区”(Temporal Dead Zone,TDZ)。

示例:

console.log(b);// 报错:Cannot access 'b' before initialization let b =2;

示例:

console.log(a);// 输出:undefined var a =1;

三、重复声明

  1. var
    • 允许重复声明,后面的声明会覆盖前面的声明。
  2. let和​const
    • 不允许重复声明,会报错。

示例:

let b =1;let b =2;// 报错:Identifier 'b' has already been declared 

示例:

var a =1;var a =2; console.log(a);// 输出:2 

四、值的修改

  1. var和​let
    • 声明的变量可以重新赋值。
  2. const
    • 声明的变量不可重新赋值(常量),但对象或数组的属性可以修改。

示例:

const c =5; c =6;// 报错:Assignment to constant variable const obj ={key:'value'}; obj.key ='newValue';// 允许修改属性 

示例:

var a =1; a =2;let b =3; b =4;

五、总结

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升提升(赋值不提升)提升(存在 TDZ)提升(存在 TDZ)
重复声明允许不允许不允许
值的修改允许允许不允许(对象属性可修改)

六、使用建议

  1. 优先使用​const
    • 如果变量的值不需要修改,使用 const​ 可以避免意外赋值。
  2. 其次使用​let
    • 如果变量的值需要修改,使用 let​ 可以避免 var​ 的作用域问题。
  3. 避免使用​var
    • var​ 的作用域和提升机制容易导致代码难以维护,建议使用 let​ 和 const​ 替代。

作用域与作用域链

作用域(Scope)和作用域链(Scope Chain)是 JavaScript 中理解变量和函数访问规则的核心概念。以下是它们的详细说明:

一、作用域(Scope)

  1. 定义
    • 作用域是指变量和函数的可访问范围,决定了代码中哪些部分可以访问特定的变量或函数。
  2. 类型
    • 全局作用域(Global Scope) :在函数和代码块外部声明的变量,可以在整个程序中访问。
    • 函数作用域(Function Scope) :在函数内部声明的变量,只能在函数内部访问。
    • 块级作用域(Block Scope) :在 {}​ 块内部声明的变量(使用 let​ 或 const​),只能在块内部访问。

示例

// 全局作用域 const globalVar ='Global';functionmyFunction(){// 函数作用域 const functionVar ='Function'; console.log(globalVar);// 输出:Global }if(true){// 块级作用域 const blockVar ='Block'; console.log(globalVar);// 输出:Global } console.log(blockVar);// 报错:blockVar is not defined 

二、作用域链(Scope Chain)

  1. 定义
    • 作用域链是 JavaScript 查找变量和函数的一种机制,它由当前作用域和所有父级作用域组成。
    • 当访问一个变量时,JavaScript 会从当前作用域开始查找,如果找不到,则逐级向上查找父级作用域,直到全局作用域。
  2. 形成过程
    • 每次调用函数时,都会创建一个新的作用域,并将其添加到作用域链中。
    • 作用域链的顶端是当前作用域,底端是全局作用域。
    • innerFunction​ 中访问变量时,作用域链的顺序为: innerFunction​ → outerFunction​ → global

示例

const globalVar ='Global';functionouterFunction(){const outerVar ='Outer';functioninnerFunction(){const innerVar ='Inner'; console.log(globalVar);// 输出:Global  console.log(outerVar);// 输出:Outer  console.log(innerVar);// 输出:Inner }innerFunction();}outerFunction();

三、作用域链的作用

  1. 变量查找
    • JavaScript 通过作用域链查找变量,确保变量在正确的作用域内访问。
  2. 闭包(Closure)
    • 闭包是函数与其词法作用域的组合,通过作用域链可以访问父级作用域的变量。

示例:

functionouterFunction(){const outerVar ='Outer';functioninnerFunction(){ console.log(outerVar);// 输出:Outer }return innerFunction;}const closure =outerFunction();closure();

四、总结

  1. 作用域
    • 决定了变量和函数的可访问范围,分为全局作用域、函数作用域和块级作用域。
  2. 作用域链
    • 是 JavaScript 查找变量和函数的机制,由当前作用域和所有父级作用域组成。
    • 通过作用域链,可以实现变量查找和闭包功能。

JavaScript 继承的几种方式

JavaScript 是基于原型的语言,没有类的概念,但可以通过多种方式实现继承。以下是常见的继承方式及其优缺点:

一、原型链继承

  1. 原理
    • 将子类的原型对象指向父类的实例,从而继承父类的属性和方法。
  2. 优点
    • 简单易用。
  3. 缺点
    • 父类的引用类型属性会被所有子类实例共享。
    • 无法向父类构造函数传参。

实现

functionParent(){this.name ='Parent';}Parent.prototype.sayHello=function(){ console.log('Hello from '+this.name);};functionChild(){}Child.prototype =newParent();const child =newChild(); child.sayHello();// 输出:Hello from Parent 

二、构造函数继承

  1. 原理
    • 在子类构造函数中调用父类构造函数,从而继承父类的属性。
  2. 优点
    • 解决了原型链继承中引用类型属性共享的问题。
    • 可以向父类构造函数传参。
  3. 缺点
    • 无法继承父类原型上的方法。

实现

functionParent(name){this.name = name;}functionChild(name){Parent.call(this, name);}const child =newChild('Child'); console.log(child.name);// 输出:Child 

三、组合继承

  1. 原理
    • 结合原型链继承和构造函数继承,既继承父类的属性,又继承父类原型上的方法。
  2. 优点
    • 解决了原型链继承和构造函数继承的缺点。
  3. 缺点
    • 父类构造函数被调用了两次,存在性能开销。

实现

functionParent(name){this.name = name;}Parent.prototype.sayHello=function(){ console.log('Hello from '+this.name);};functionChild(name){Parent.call(this, name);}Child.prototype =newParent();const child =newChild('Child'); child.sayHello();// 输出:Hello from Child 

四、原型式继承

  1. 原理
    • 基于已有对象创建新对象,类似于原型链继承。
  2. 优点
    • 简单易用,适合基于已有对象创建新对象。
  3. 缺点
    • 引用类型属性会被所有子对象共享。

实现

const parent ={name:'Parent',sayHello:function(){ console.log('Hello from '+this.name);}};const child = Object.create(parent); child.name ='Child'; child.sayHello();// 输出:Hello from Child 

五、寄生式继承

  1. 原理
    • 在原型式继承的基础上,增强对象的功能。
  2. 优点
    • 可以在继承的基础上增强对象的功能。
  3. 缺点
    • 引用类型属性会被所有子对象共享。

实现

functioncreateChild(parent){const child = Object.create(parent); child.sayHi=function(){ console.log('Hi from '+this.name);};return child;}const parent ={name:'Parent'};const child =createChild(parent); child.sayHi();// 输出:Hi from Parent 

六、寄生组合式继承

  1. 原理
    • 结合寄生式继承和组合继承,优化组合继承的性能问题。
  2. 优点
    • 解决了组合继承的性能问题,是最理想的继承方式。
  3. 缺点
    • 实现较为复杂。

实现

functioninheritPrototype(Child, Parent){const prototype = Object.create(Parent.prototype); prototype.constructor = Child;Child.prototype = prototype;}functionParent(name){this.name = name;}Parent.prototype.sayHello=function(){ console.log('Hello from '+this.name);};functionChild(name){Parent.call(this, name);}inheritPrototype(Child, Parent);const child =newChild('Child'); child.sayHello();// 输出:Hello from Child 

七、ES6 类继承

  1. 原理
    • 使用 class​ 和 extends​ 关键字实现继承。
  2. 优点
    • 语法简洁,易于理解。
    • 解决了传统继承方式的缺点。
  3. 缺点
    • 需要支持 ES6 的浏览器或环境。

实现

classParent{constructor(name){this.name = name;}sayHello(){ console.log('Hello from '+this.name);}}classChildextendsParent{constructor(name){super(name);}}const child =newChild('Child'); child.sayHello();// 输出:Hello from Child 

八、总结

继承方式优点缺点
原型链继承简单易用引用类型属性共享,无法传参
构造函数继承解决引用类型属性共享,支持传参无法继承父类原型上的方法
组合继承结合原型链和构造函数继承父类构造函数被调用两次
原型式继承简单易用,适合基于已有对象创建新对象引用类型属性共享
寄生式继承增强对象功能引用类型属性共享
寄生组合式继承解决组合继承的性能问题实现复杂
ES6 类继承语法简洁,解决传统继承方式的缺点需要支持 ES6 的浏览器或环境

根据需求选择合适的继承方式,可以编写出更高效、更易维护的 JavaScript 代码。

前端缓存与数据持久化的理解

前端缓存和数据持久化是提升应用性能、优化用户体验的重要手段。以下是它们的详细说明和应用场景:

一、前端缓存

前端缓存是指将数据或资源存储在客户端(如浏览器),以减少网络请求、加快资源加载速度。

  1. HTTP 缓存
    • 使用 Service Worker 拦截网络请求,实现离线缓存和资源预加载。
  2. LocalStorage 和 SessionStorage
    • 提供结构化数据的存储,适合存储大量数据。

IndexedDB

const request = indexedDB.open('myDatabase',1); request.onsuccess=(event)=>{const db = event.target.result;const transaction = db.transaction('myStore','readwrite');const store = transaction.objectStore('myStore'); store.add({id:1,name:'John'});};

SessionStorage:会话级存储数据,关闭浏览器后数据清空。

sessionStorage.setItem('key','value'); console.log(sessionStorage.getItem('key'));// 输出:value 

LocalStorage:持久化存储数据,关闭浏览器后数据仍然存在。

localStorage.setItem('key','value'); console.log(localStorage.getItem('key'));// 输出:value 

Service Worker 缓存

self.addEventListener('install',(event)=>{ event.waitUntil( caches.open('v1').then((cache)=>{return cache.addAll(['/','/styles.css','/app.js']);}));});

协商缓存:通过 ETag​ 和 Last-Modified​ 响应头验证资源是否过期。

ETag: "abc123" Last-Modified: Wed, 01 Jan 2020 00:00:00 GMT 

强缓存:通过 Cache-Control​ 和 Expires​ 响应头控制资源的缓存时间。

Cache-Control: max-age=3600 Expires: Wed, 01 Jan 2025 00:00:00 GMT 

二、前端数据持久化

前端数据持久化是指将数据存储在客户端,即使页面刷新或关闭浏览器后数据仍然存在。

    • 适合存储简单的键值对数据。
    • 适合存储会话级数据,关闭浏览器后数据清空。
    • 适合存储大量结构化数据,支持复杂查询。
    • 适合存储小量数据,支持设置过期时间。
    • 使用 FileReader​ 和 Blob​ 缓存文件数据。

文件缓存

const file =newBlob(['Hello, world!'],{type:'text/plain'});const reader =newFileReader(); reader.onload=(event)=>{ console.log(event.target.result);// 输出:Hello, world! }; reader.readAsText(file);

Cookies

document.cookie ='username=John; expires=Wed, 01 Jan 2025 00:00:00 GMT; path=/'; console.log(document.cookie);// 输出:username=John 

IndexedDB

const request = indexedDB.open('myDatabase',1); request.onsuccess=(event)=>{const db = event.target.result;const transaction = db.transaction('myStore','readwrite');const store = transaction.objectStore('myStore'); store.add({id:1,name:'John'});};

SessionStorage

sessionStorage.setItem('token','abc123'); console.log(sessionStorage.getItem('token'));// 输出:abc123 

LocalStorage

localStorage.setItem('user',JSON.stringify({name:'Alice'}));const user =JSON.parse(localStorage.getItem('user')); console.log(user);// 输出:{ name: 'Alice' } 

三、应用场景

  1. 前端缓存
    • 减少网络请求,加快资源加载速度。
    • 实现离线访问和资源预加载。
  2. 数据持久化
    • 保存用户偏好设置(如主题、语言)。
    • 存储用户登录状态(如 Token)。
    • 缓存 API 响应数据,减少服务器请求。

四、总结

  1. 前端缓存
    • 通过 HTTP 缓存、Service Worker、LocalStorage 等方式缓存资源,提升性能。
  2. 数据持久化
    • 使用 LocalStorage、SessionStorage、IndexedDB 等方式持久化数据,确保数据在页面刷新或关闭后仍然存在。

require​ 和 import​ 的区别详解

require​ 和 import​ 是 JavaScript 中用于加载模块的两种方式,分别用于 CommonJS 和 ES6 模块系统。以下是它们的详细对比:

一、语法和使用方式

  1. require(CommonJS)
    • 动态加载:可以在代码的任何位置使用 require​。
  2. import(ES6 Modules)
    • 静态加载:必须在模块的顶层使用 import​,不能在代码块中动态加载。

示例:

import fs from'fs';const data = fs.readFileSync('file.txt','utf8');

语法:

import module from'module-name';

示例:

const fs =require('fs');const data = fs.readFileSync('file.txt','utf8');

语法:

const module =require('module-name');

二、加载方式

  1. require
    • 同步加载:模块加载是同步执行的,会阻塞后续代码的执行。
    • 运行时加载:模块在运行时加载并执行。
  2. import
    • 异步加载:模块加载是异步的,不会阻塞后续代码的执行。
    • 编译时加载:模块在代码编译时加载,支持静态分析。

三、模块导出

  1. require
    • 使用 module.exports​ 或 exports​ 导出模块。
  2. import
    • 使用 export​ 或 export default​ 导出模块。

示例:

// module.js exportconst foo ='bar';// app.js import{ foo }from'./module'; console.log(foo);// 输出:bar 

示例:

// module.js  module.exports ={foo:'bar'};// app.js const module =require('./module'); console.log(module.foo);// 输出:bar 

四、适用场景

  1. require
    • 适用于 Node.js 环境,主要用于后端开发。
    • 支持动态加载,适合需要根据条件加载模块的场景。
  2. import
    • 适用于现代浏览器和前端开发,支持静态分析和 Tree Shaking。
    • 适合模块化开发和代码优化。

五、兼容性

  1. require
    • 在 Node.js 环境中原生支持,但在浏览器中需要使用打包工具(如 Webpack、Browserify)进行转换。
  2. import
    • 在现代浏览器中支持,但在 Node.js 中需要使用 .mjs​ 文件或在 package.json​ 中设置 "type": "module"​。

六、总结

特性require​(CommonJS)import​(ES6 Modules)
语法const module = require('module')import module from 'module'
加载方式同步加载,运行时加载异步加载,编译时加载
模块导出module.exports​ 或 exportsexport​ 或 export default
适用场景Node.js 环境,动态加载现代浏览器,模块化开发
兼容性Node.js 原生支持现代浏览器支持,Node.js 需配置

px​、em​ 和 rem​ 的区别详解

px​、em​ 和 rem​ 是 CSS 中常用的长度单位,它们在网页布局和响应式设计中扮演着重要角色。以下是它们的详细对比:

一、px​(像素)

  1. 定义
    • px​ 是绝对单位,表示屏幕上的一个像素点。
  2. 特点
    • 固定大小,不受父元素或根元素的影响。
    • 适合需要精确控制尺寸的场景。

示例

.element{width: 100px;height: 50px;}

二、em

  1. 定义
    • em​ 是相对单位,基于当前元素的字体大小(font-size​)。
  2. 特点
    • 默认情况下,1em​ 等于当前元素的 font-size​ 值。
    • 如果当前元素未设置 font-size​,则继承父元素的 font-size​。
    • 适合需要根据字体大小调整布局的场景。

示例

.parent{font-size: 16px;}.child{font-size: 1.5em;/* 16px * 1.5 = 24px */padding: 1em;/* 24px */}

三、rem

  1. 定义
    • rem​ 是相对单位,基于根元素(<html>​)的字体大小(font-size​)。
  2. 特点
    • 默认情况下,1rem​ 等于根元素的 font-size​ 值(通常为 16px​)。
    • 不受父元素 font-size​ 的影响,适合全局统一的布局调整。

示例

html{font-size: 16px;}.element{font-size: 1.5rem;/* 16px * 1.5 = 24px */padding: 1rem;/* 16px */}

四、对比与总结

特性pxemrem
定义绝对单位,表示像素相对单位,基于当前元素的 font-size相对单位,基于根元素的 font-size
特点固定大小,不受其他元素影响受当前元素和父元素的 font-size​ 影响仅受根元素的 font-size​ 影响
适用场景需要精确控制尺寸的场景需要根据字体大小调整布局的场景全局统一的布局调整

五、使用建议

  1. px
    • 适合需要精确控制尺寸的场景,如边框、阴影等。
  2. em
    • 适合需要根据字体大小调整布局的场景,如按钮、标题等。
  3. rem
    • 适合全局统一的布局调整,如间距、容器宽度等。

六、示例

html{font-size: 16px;}.container{width: 100%;padding: 1rem;/* 16px */}.title{font-size: 2rem;/* 32px */margin-bottom: 1em;/* 32px */}.button{font-size: 1.25em;/* 继承父元素的字体大小 */padding: 0.5em 1em;/* 根据字体大小调整 */}

通过理解 px​、em​ 和 rem​ 的区别,可以根据需求选择合适的单位,实现更灵活的布局和响应式设计。

常见的浏览器兼容性问题

在开发前端项目时,浏览器兼容性问题是一个常见的挑战。以下是常见的兼容性问题及其解决方案:

一、CSS 兼容性问题

    • 问题:IE 盒模型的 width​ 和 height​ 包括 padding​ 和 border​,而标准盒模型不包括。
    • 解决方案:使用 box-sizing: border-box​ 统一盒模型。
    • 问题:旧版浏览器(如 IE 10)对 Flexbox 的支持不完善。
    • 解决方案:使用 autoprefixer​ 自动添加浏览器前缀。
    • 问题:旧版浏览器(如 IE 11)不支持 CSS Grid。
    • 解决方案:使用 @supports​ 提供回退方案。

CSS Grid 兼容性

.container{display: grid;}@supportsnot(display: grid){.container{display: flex;}}

Flexbox 兼容性

.container{display: flex;}

盒模型差异

*{box-sizing: border-box;}

二、JavaScript 兼容性问题

    • 问题:旧版浏览器(如 IE 11)不支持 ES6+ 语法(如 let​、const​、箭头函数)。
    • 解决方案:使用 Babel 将 ES6+ 代码转换为 ES5。
    • 问题:旧版浏览器不支持现代 API(如 fetch​、Promise​)。
    • 解决方案:使用 Polyfill 提供兼容性支持。

API 兼容性

npminstall whatwg-fetch --save
import'whatwg-fetch';fetch('https://api.example.com/data').then(response=> response.json()).then(data=> console.log(data));

ES6+ 语法兼容性

npminstall @babel/core @babel/preset-env --save-dev 
// .babelrc {"presets":["@babel/preset-env"]}

三、HTML 兼容性问题

    • 问题:旧版浏览器(如 IE 8)不支持 HTML5 标签(如 <header>​、<footer>​)。
    • 解决方案:使用 html5shiv​ 提供兼容性支持。
  1. 表单控件兼容性
    • 问题:旧版浏览器对 HTML5 表单控件(如 date​、range​)的支持不完善。
    • 解决方案:使用 Polyfill 或 JavaScript 库(如 jQuery UI​)。

HTML5 标签兼容性

<!--[if lt IE 9]> <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script> <![endif]-->

四、其他兼容性问题

    • 问题:不同操作系统和浏览器的默认字体不同,可能导致样式不一致。
    • 解决方案:使用 Web 安全字体或 @font-face​ 加载自定义字体。
    • 问题:旧版浏览器不支持 WebP 格式图片。
    • 解决方案:使用 <picture>​ 标签提供回退方案。

图片兼容性

<picture><sourcesrcset="image.webp"type="image/webp"><imgsrc="image.jpg"alt="Fallback Image"></picture>

字体兼容性

body{font-family: Arial, sans-serif;}

五、总结

问题类型常见问题解决方案
CSS 兼容性盒模型差异、Flexbox、CSS Grid使用 box-sizing​、autoprefixer​、@supports
JavaScript 兼容性ES6+ 语法、现代 API使用 Babel、Polyfill
HTML 兼容性HTML5 标签、表单控件使用 html5shiv​、Polyfill
其他兼容性字体、图片使用 Web 安全字体、<picture>​ 标签

通过理解常见的浏览器兼容性问题及其解决方案,可以有效提升前端项目的兼容性和用户体验。

CSS 盒模型

CSS 盒模型(Box Model)是网页布局的基础,它定义了元素的尺寸、内边距、边框和外边距之间的关系。以下是盒模型的详细说明:

一、盒模型的组成部分

盒模型由以下四部分组成:

  1. 内容区域(Content)
    • 元素的实际内容,如文本、图片等。
    • 通过 width​ 和 height​ 设置内容区域的尺寸。
  2. 内边距(Padding)
    • 内容区域与边框之间的空白区域。
    • 通过 padding​ 设置内边距。
  3. 边框(Border)
    • 内边距与外边距之间的边框。
    • 通过 border​ 设置边框的样式、宽度和颜色。
  4. 外边距(Margin)
    • 元素与其他元素之间的空白区域。
    • 通过 margin​ 设置外边距。

二、标准盒模型与 IE 盒模型

  1. 标准盒模型(Content-Box)
    • 元素的 width​ 和 height​ 仅包括内容区域。
    • 总宽度 = width​ + padding​ + border​ + margin
    • 总高度 = height​ + padding​ + border​ + margin
  2. IE 盒模型(Border-Box)
    • 元素的 width​ 和 height​ 包括内容区域、内边距和边框。
    • 总宽度 = width​ + margin
    • 总高度 = height​ + margin
    • 使用 box-sizing​ 属性切换盒模型。

切换盒模型

/* 标准盒模型 */.element{box-sizing: content-box;}/* IE 盒模型 */.element{box-sizing: border-box;}

三、盒模型的计算

    • 总宽度 = 200px​ + 10px * 2​ + 5px * 2​ + 20px * 2​ = 270px
    • 总高度 = height​ + padding * 2​ + border * 2​ + margin * 2
    • 总宽度 = 200px​ + 20px * 2​ = 240px
    • 总高度 = height​ + margin * 2

IE 盒模型计算

.element{width: 200px;padding: 10px;border: 5px solid black;margin: 20px;box-sizing: border-box;}

标准盒模型计算

.element{width: 200px;padding: 10px;border: 5px solid black;margin: 20px;box-sizing: content-box;}

四、盒模型的应用

    • 使用 padding​ 和 margin​ 调整元素之间的间距。
    • 使用 border​ 设置元素的边框样式。
    • 使用 box-sizing: border-box​ 简化布局计算。

盒模型切换

*{box-sizing: border-box;}

边框样式

.element{border: 2px solid red;border-radius: 10px;}

布局控制

.container{padding: 20px;}.item{margin: 10px;}

五、总结

  1. 盒模型的组成部分
    • 内容区域、内边距、边框和外边距。
  2. 盒模型的类型
    • 标准盒模型:width​ 和 height​ 仅包括内容区域。
    • IE 盒模型:width​ 和 height​ 包括内容区域、内边距和边框。
  3. 盒模型的应用
    • 使用 padding​ 和 margin​ 调整布局,使用 box-sizing​ 切换盒模型。

ES6(ECMAScript 2015)新特性

ES6 是 JavaScript 的重要更新,引入了许多新特性,使代码更简洁、更易读、更强大。以下是 ES6 的主要新特性:

一、变量声明

    • let​ 用于声明块级作用域的变量。
    • const​ 用于声明常量,值不可重新赋值。

let和​const

let x =10;const y =20;

二、箭头函数

    • 使用 =>​ 定义函数,简化函数书写。
  1. 特点
    • 没有自己的 this​,this​ 继承自外层作用域。
    • 不能作为构造函数使用。

语法

constadd=(a, b)=> a + b;

三、模板字符串

    • 使用反引号(`​)定义字符串,支持换行和嵌入变量。

语法

const name ='Alice';const message =`Hello, ${name}!`;

四、解构赋值

对象解构

const{ name, age }={name:'Alice',age:25}; console.log(name);// 输出:Alice 

数组解构

const[a, b]=[1,2]; console.log(a);// 输出:1 

五、默认参数

    • 为函数参数设置默认值。

语法

functiongreet(name ='Guest'){ console.log(`Hello, ${name}!`);}greet();// 输出:Hello, Guest! 

六、扩展运算符

对象扩展

const obj1 ={a:1,b:2};const obj2 ={...obj1,c:3}; console.log(obj2);// 输出:{ a: 1, b: 2, c: 3 } 

数组扩展

const arr1 =[1,2];const arr2 =[...arr1,3,4]; console.log(arr2);// 输出:[1, 2, 3, 4] 

七、类(Class)

    • 使用 class​ 关键字定义类,支持构造函数和继承。

语法

classPerson{constructor(name){this.name = name;}greet(){ console.log(`Hello, ${this.name}!`);}}const person =newPerson('Alice'); person.greet();// 输出:Hello, Alice! 

八、模块化

导入模块

// app.js import{ add }from'./module'; console.log(add(1,2));// 输出:3 

导出模块

// module.js exportconstadd=(a, b)=> a + b;

九、Promise

    • 用于处理异步操作,支持链式调用。

语法

const promise =newPromise((resolve, reject)=>{setTimeout(()=>resolve('Success'),1000);}); promise.then(result=> console.log(result));// 输出:Success 

十、Symbol

    • 创建唯一的标识符,避免属性名冲突。

语法

const id =Symbol('id');const obj ={[id]:123}; console.log(obj[id]);// 输出:123 

十一、迭代器和生成器

    • 使用 Symbol.iterator​ 定义迭代器。
    • 使用 function*​ 定义生成器函数。

生成器

function*generator(){yield1;yield2;}const gen =generator(); console.log(gen.next().value);// 输出:1 

迭代器

const iterable ={[Symbol.iterator](){let count =0;return{next(){return{value: count++,done: count >3};}};}};for(const value of iterable){ console.log(value);// 输出:0, 1, 2 }

十二、Set 和 Map

    • 存储唯一值的集合。
    • 存储键值对的集合,键可以是任意类型。

Map

const map =newMap(); map.set('name','Alice'); console.log(map.get('name'));// 输出:Alice 

Set

const set =newSet([1,2,3]); set.add(4); console.log(set.has(2));// 输出:true 

HTTP 强缓存与协商缓存

HTTP 缓存是提升网页性能的重要手段,分为强缓存和协商缓存。以下是它们的详细说明:

一、强缓存

  1. 定义
    • 强缓存是指浏览器直接从本地缓存中读取资源,不发送请求到服务器。
  2. 实现方式
  3. 特点
    • 缓存期间不发送请求到服务器,直接从缓存中读取资源。
    • 适合缓存静态资源(如 CSS、JavaScript、图片)。

示例

Cache-Control: max-age=3600 Expires: Wed, 01 Jan 2025 00:00:00 GMT 

Expires​:通过指定过期时间设置缓存的有效时间。

Expires: Wed, 01 Jan 2025 00:00:00 GMT 

Cache-Control​:通过 max-age​ 设置缓存的有效时间。

Cache-Control: max-age=3600 

二、协商缓存

  1. 定义
    • 协商缓存是指浏览器发送请求到服务器,由服务器判断资源是否过期,决定是否使用缓存。
  2. 实现方式
      • 服务器返回资源的最后修改时间(Last-Modified​)。
      • 浏览器下次请求时带上 If-Modified-Since​,服务器判断资源是否修改。
      • 服务器返回资源的唯一标识(ETag​)。
      • 浏览器下次请求时带上 If-None-Match​,服务器判断资源是否修改。
  3. 特点
    • 每次请求都会发送到服务器,由服务器判断是否使用缓存。
    • 适合缓存动态资源(如 API 响应)。

示例

Last-Modified: Wed, 01 Jan 2020 00:00:00 GMT ETag: "abc123" 

ETag和​If-None-Match​:

ETag: "abc123" If-None-Match: "abc123" 

Last-Modified和​If-Modified-Since​:

Last-Modified: Wed, 01 Jan 2020 00:00:00 GMT If-Modified-Since: Wed, 01 Jan 2020 00:00:00 GMT 

三、强缓存与协商缓存的区别

特性强缓存协商缓存
是否发送请求
缓存判断方式浏览器直接判断服务器判断
实现机制Cache-Control​、ExpiresLast-Modified​、ETag
适用场景静态资源动态资源

四、缓存策略建议

  1. 静态资源
    • 使用强缓存,设置较长的缓存时间(如 max-age=31536000​)。
    • 通过文件哈希或版本号更新缓存(如 style.[hash].css​)。
  2. 动态资源
    • 使用协商缓存,确保数据的最新性。
    • 设置较短的缓存时间(如 max-age=60​)。

五、总结

  1. 强缓存
    • 通过 Cache-Control​ 和 Expires​ 实现,适合缓存静态资源。
  2. 协商缓存
    • 通过 Last-Modified​ 和 ETag​ 实现,适合缓存动态资源。

Vue Router 的两种模式

Vue Router 是 Vue.js 官方的路由管理器,支持两种模式:Hash 模式History 模式。以下是它们的详细说明:

一、Hash 模式

  1. 定义
    • Hash 模式使用 URL 的哈希部分(#​)来实现路由。
  2. 特点
    • URL 示例:http://example.com/#/home
    • 哈希部分的变化不会导致页面刷新。
    • 兼容性好,支持所有浏览器。
  3. 优点
    • 无需服务器配置,适合静态站点。
    • 兼容性好,支持旧版浏览器。
  4. 缺点
    • URL 中包含 #​,不够美观。

配置

const router =newVueRouter({mode:'hash',routes:[{path:'/home',component: Home },{path:'/about',component: About }]});

二、History 模式

  1. 定义
    • History 模式使用 HTML5 的 history.pushState​ API 来实现路由。
  2. 特点
    • URL 示例:http://example.com/home
    • URL 更美观,不包含 #​。
    • 需要服务器配置,避免刷新时返回 404 错误。
  3. 服务器配置
  4. 优点
    • URL 更美观,不包含 #​。
    • 支持 SEO,适合动态站点。
  5. 缺点
    • 需要服务器配置,兼容性较差。

Apache

<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] </IfModule> 

Nginx

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

配置

const router =newVueRouter({mode:'history',routes:[{path:'/home',component: Home },{path:'/about',component: About }]});

三、Hash 模式与 History 模式的对比

特性Hash 模式History 模式
URL 示例http://example.com/#/homehttp://example.com/home
是否需要服务器配置
兼容性兼容所有浏览器需要 HTML5 支持
SEO 支持不支持支持
适用场景静态站点动态站点

四、总结

  1. Hash 模式
    • 适合静态站点,无需服务器配置,兼容性好。
  2. History 模式
    • 适合动态站点,URL 更美观,支持 SEO,但需要服务器配置。

JavaScript 的缺点

JavaScript 是一种强大的脚本语言,广泛应用于前端和后端开发。然而,它也存在一些缺点,以下是主要的缺点及其影响:

一、单线程模型

  1. 问题
    • JavaScript 是单线程的,同一时间只能执行一个任务。
    • 长时间运行的任务会阻塞主线程,导致页面卡顿或无响应。
  2. 解决方案
    • 使用异步编程(如 Promise​、async/await​)。
    • 使用 Web Workers 在后台执行耗时任务。

二、弱类型语言

  1. 问题
    • JavaScript 是弱类型语言,变量的类型可以动态改变,容易引发运行时错误。
  2. 解决方案
    • 使用 TypeScript 提供静态类型检查。
    • 使用工具(如 ESLint)进行代码检查。

示例:

let x =10; x ='Hello';// 类型改变,可能导致错误 

三、浏览器兼容性问题

  1. 问题
    • 不同浏览器对 JavaScript 的支持不一致,可能导致代码在不同浏览器中表现不同。
    • 示例:IE 浏览器不支持 ES6+ 语法。
  2. 解决方案
    • 使用 Babel 将 ES6+ 代码转换为 ES5。
    • 使用 Polyfill 提供兼容性支持。

四、安全问题

  1. 问题
    • JavaScript 代码在客户端执行,容易被恶意用户篡改或攻击。
    • 常见安全问题:XSS(跨站脚本攻击)、CSRF(跨站请求伪造)。
  2. 解决方案
    • 对用户输入进行严格的验证和过滤。
    • 使用 HTTPS 加密数据传输。

五、性能问题

  1. 问题
    • JavaScript 是解释型语言,执行速度比编译型语言慢。
    • 复杂的 DOM 操作和频繁的重绘/重排会影响性能。
  2. 解决方案
    • 使用虚拟 DOM(如 React)优化 DOM 操作。
    • 使用 requestAnimationFrame​ 优化动画性能。

六、调试困难

  1. 问题
    • JavaScript 的错误提示不够明确,调试起来比较困难。
    • 异步代码的调试更加复杂。
  2. 解决方案
    • 使用开发者工具(如 Chrome DevTools)进行调试。
    • 使用 console.log​ 或 debugger​ 语句辅助调试。

七、生态系统碎片化

  1. 问题
    • JavaScript 的生态系统庞大且碎片化,工具和库的选择过多,学习成本高。
    • 示例:前端框架有 React、Vue、Angular 等。
  2. 解决方案
    • 根据项目需求选择合适的工具和库。
    • 关注主流技术趋势,避免过度依赖小众工具。

八、总结

缺点问题描述解决方案
单线程模型长时间任务阻塞主线程使用异步编程、Web Workers
弱类型语言变量类型动态改变,容易引发运行时错误使用 TypeScript、ESLint
浏览器兼容性不同浏览器支持不一致使用 Babel、Polyfill
安全问题容易被恶意用户篡改或攻击验证用户输入、使用 HTTPS
性能问题执行速度慢,复杂 DOM 操作影响性能使用虚拟 DOM、优化动画
调试困难错误提示不明确,异步代码调试复杂使用开发者工具、console.log
生态系统碎片化工具和库选择过多,学习成本高根据需求选择工具,关注主流技术趋势

理解 JavaScript 的缺点及其解决方案,可以帮助开发者更好地应对挑战,编写更高效、更安全的代码。

JavaScript 原型

JavaScript 是一种基于原型的语言,原型(Prototype)是 JavaScript 实现继承和共享属性和方法的核心机制。以下是原型的详细说明:

一、原型的概念

  1. 原型对象(Prototype Object)
    • 每个 JavaScript 对象(除了 null​)都有一个原型对象,对象从原型对象继承属性和方法。
  2. 原型链(Prototype Chain)
    • 当访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链向上查找,直到找到为止。

二、原型的使用

    • 每个对象都有一个 __proto__​ 属性,指向其原型对象。
    • 每个函数都有一个 prototype​ 属性,指向该函数的原型对象。
    • 通过构造函数创建的对象,其原型对象为构造函数的 prototype​ 属性。

构造函数与原型

functionPerson(name){this.name = name;}Person.prototype.greet=function(){ console.log(`Hello, ${this.name}!`);};const person =newPerson('Alice'); person.greet();// 输出:Hello, Alice! 

prototype属性

functionPerson(){} console.log(Person.prototype);// 输出:Person {} 

__proto__属性

const obj ={}; console.log(obj.__proto__);// 输出:Object.prototype 

三、原型链的示例

    • obj​ 本身没有 toString​ 方法,但它的原型对象 Object.prototype​ 有 toString​ 方法。
  1. 原型链图示 obj -> Object.prototype -> null

示例

const obj ={}; console.log(obj.toString());// 输出:[object Object] 

四、原型的继承

    • 通过将子类的原型对象指向父类的实例,实现继承。
    • 使用 Object.create()​ 创建新对象,并指定其原型对象。

Object.create()

const parent ={name:'Parent',sayHello:function(){ console.log(`Hello, ${this.name}!`);}};const child = Object.create(parent); child.name ='Child'; child.sayHello();// 输出:Hello, Child! 

原型继承

functionParent(){this.name ='Parent';}Parent.prototype.sayHello=function(){ console.log(`Hello, ${this.name}!`);};functionChild(){}Child.prototype =newParent();const child =newChild(); child.sayHello();// 输出:Hello, Parent! 

五、总结

  1. 原型对象
    • 每个对象都有一个原型对象,对象从原型对象继承属性和方法。
  2. 原型链
    • 当访问对象的属性或方法时,JavaScript 会沿着原型链向上查找。
  3. 原型继承
    • 通过将子类的原型对象指向父类的实例,实现继承。

XSS 和 CSRF 详解及防御措施

XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)是两种常见的 Web 安全漏洞,以下是它们的详细说明及防御措施:

一、XSS(跨站脚本攻击)

  1. 定义
    • XSS 是指攻击者在网页中注入恶意脚本,当其他用户访问该网页时,脚本会在用户浏览器中执行。
  2. 类型
    • 存储型 XSS:恶意脚本存储在服务器中,用户访问时触发。
    • 反射型 XSS:恶意脚本通过 URL 参数传递,用户点击链接时触发。
    • DOM 型 XSS:恶意脚本通过修改 DOM 触发,不依赖服务器响应。
  3. 危害
    • 窃取用户 Cookie 或会话信息。
    • 篡改网页内容,诱导用户进行恶意操作。
  4. 防御措施
    • 输出编码:在输出用户输入时进行编码,防止脚本执行。
      • 使用 Content-Security-Policy​ 限制脚本加载来源。
      • 使用 HttpOnly​ 标记 Cookie,防止 JavaScript 访问。

设置 HTTP 头

Content-Security-Policy: default-src 'self' 
Set-Cookie: sessionId=123; HttpOnly; Secure 

输入过滤:对用户输入进行严格的验证和过滤,移除或转义特殊字符。

functionescapeHTML(str){return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}

二、CSRF(跨站请求伪造)

  1. 定义
    • CSRF 是指攻击者诱导用户在已登录的网站上执行非预期的操作。
  2. 攻击流程
    • 用户登录目标网站,获得认证 Cookie。
    • 用户访问攻击者的网站,攻击者诱导用户发送恶意请求。
    • 目标网站接收到请求,误认为是用户的操作。
  3. 危害
    • 执行非预期的操作(如转账、修改密码)。
  4. 防御措施
      • 检查 Referer​ 头,确保请求来自合法来源。
      • 在表单或请求中添加 CSRF Token,服务器验证 Token 的有效性。
      • 使用 SameSite​ 属性限制 Cookie 的发送范围。

设置 SameSite Cookie

Set-Cookie: sessionId=123; SameSite=Strict; Secure 

使用 CSRF Token

<formaction="/transfer"method="POST"><inputtype="hidden"name="_csrf"value="csrf_token"><buttontype="submit">Submit</button></form>

验证请求来源

if(req.headers.referer !=='https://example.com'){return res.status(403).send('Forbidden');}

三、总结

攻击类型定义防御措施
XSS攻击者在网页中注入恶意脚本输入过滤、输出编码、设置 HTTP 头
CSRF攻击者诱导用户执行非预期的操作验证请求来源、使用 CSRF Token、设置 SameSite Cookie

通过理解 XSS 和 CSRF 的原理及防御措施,可以有效提升 Web 应用的安全性,保护用户数据和隐私。

HTTP 详解

HTTP(HyperText Transfer Protocol,超文本传输协议)是用于在 Web 浏览器和服务器之间传输数据的协议。以下是 HTTP 的详细说明:

一、HTTP 的基本概念

  1. 定义
    • HTTP 是一种应用层协议,用于在客户端(如浏览器)和服务器之间传输超文本(如 HTML、CSS、JavaScript)。
  2. 特点
    • 无状态:每次请求都是独立的,服务器不会保留客户端的状态信息。
    • 无连接:每次请求完成后,连接会关闭(HTTP/1.1 支持持久连接)。
    • 简单快速:协议简单,通信速度快。

二、HTTP 的工作流程

  1. 客户端发送请求
    • 客户端(如浏览器)向服务器发送 HTTP 请求,请求中包含请求方法、URL、请求头和请求体。
  2. 服务器处理请求
    • 服务器接收请求,根据请求内容进行处理,并生成响应。
  3. 服务器返回响应
    • 服务器向客户端发送 HTTP 响应,响应中包含状态码、响应头和响应体。
  4. 客户端处理响应
    • 客户端接收响应,根据响应内容进行处理(如渲染页面、执行脚本)。

三、HTTP 请求和响应

  1. HTTP 请求
    • 请求体:包含请求的数据(如表单数据、JSON)。
  2. HTTP 响应
    • 响应体:包含响应的数据(如 HTML、JSON)。

响应头:包含响应的元信息(如 Content-Type​、Content-Length​)。

Content-Type: text/html Content-Length: 1234 

状态行:包含 HTTP 版本、状态码和状态描述。

HTTP/1.1 200 OK 

请求头:包含请求的元信息(如 Host​、User-Agent​)。

Host: example.com User-Agent: Mozilla/5.0 

请求行:包含请求方法、URL 和 HTTP 版本。

GET /index.html HTTP/1.1 

四、HTTP 方法

    • 请求获取指定资源,没有请求体。
    • 向指定资源提交数据,有请求体。
    • 更新指定资源,有请求体。
    • 删除指定资源,没有请求体。

DELETE

DELETE /delete HTTP/1.1 

PUT

PUT /update HTTP/1.1 Content-Type: application/json {"name": "Bob"} 

POST

POST /submit HTTP/1.1 Content-Type: application/json {"name": "Alice"} 

GET

GET /index.html HTTP/1.1 

五、HTTP 状态码

  1. 1xx(信息性状态码)
    • 表示请求已被接收,需要继续处理。
    • 示例:100 Continue
  2. 2xx(成功状态码)
    • 表示请求已成功被服务器接收、理解并处理。
    • 示例:200 OK​、201 Created
  3. 3xx(重定向状态码)
    • 表示需要客户端进一步操作以完成请求。
    • 示例:301 Moved Permanently​、302 Found
  4. 4xx(客户端错误状态码)
    • 表示客户端发送的请求有错误。
    • 示例:400 Bad Request​、404 Not Found
  5. 5xx(服务器错误状态码)
    • 表示服务器在处理请求时发生错误。
    • 示例:500 Internal Server Error​、503 Service Unavailable

六、HTTP 版本

  1. HTTP/1.0
    • 每次请求完成后,连接会关闭。
  2. HTTP/1.1
    • 支持持久连接,允许多个请求复用同一个连接。
  3. HTTP/2
    • 支持多路复用,减少延迟,提高性能。
  4. HTTP/3
    • 基于 QUIC 协议,进一步优化性能。

七、总结

HTTP 是 Web 开发的基础协议,用于在客户端和服务器之间传输数据。理解 HTTP 的工作流程、请求和响应、方法、状态码和版本,可以帮助开发者更好地进行 Web 开发,优化应用性能

JavaScript 分片(Chunking)

分片(Chunking)是一种将大数据集或任务分割成多个小块(Chunk)进行处理的技术,常用于优化性能、减少内存占用或实现渐进式加载。以下是 JavaScript 中实现分片的常见方法:

一、分片的应用场景

  1. 大数据处理
    • 将大数据集分割成小块,分批处理,避免内存溢出。
  2. 文件上传
    • 将大文件分割成多个小文件上传,提高上传效率和容错性。
  3. 渐进式加载
    • 分批加载数据或资源,提升用户体验。
  4. 任务调度
    • 将耗时任务分割成多个小任务,避免阻塞主线程。

二、分片的实现方法

1. 数组分片

将大数组分割成多个小数组,分批处理。

functionchunkArray(array, size){const chunks =[];for(let i =0; i < array.length; i += size){ chunks.push(array.slice(i, i + size));}return chunks;}const data =[1,2,3,4,5,6,7,8,9];const chunks =chunkArray(data,3); console.log(chunks);// 输出:[[1, 2, 3], [4, 5, 6], [7, 8, 9]] 
2. 文件分片

将大文件分割成多个小文件,用于上传或处理。

functionchunkFile(file, size){const chunks =[];let start =0;while(start < file.size){ chunks.push(file.slice(start, start + size)); start += size;}return chunks;}const file =newFile(['a'.repeat(1024*1024)],'example.txt');const chunks =chunkFile(file,1024*100);// 每片 100KB  console.log(chunks);// 输出:[Blob, Blob, ...] 
3. 任务分片

将耗时任务分割成多个小任务,使用 setTimeout​ 或 requestAnimationFrame​ 分批执行。

functionchunkTask(task, data, size, callback){let index =0;functionrun(){for(let i =0; i < size && index < data.length; i++){task(data[index]); index++;}if(index < data.length){setTimeout(run,0);// 分批执行 }else{callback();}}run();}const data =newArray(1000).fill(0).map((_, i)=> i);chunkTask((item)=> console.log(item), data,100,()=> console.log('任务完成'));
4. 数据流分片

使用 ReadableStream​ 将数据流分割成多个小块,实现渐进式加载或处理。

asyncfunctionchunkStream(stream, size){const reader = stream.getReader();const chunks =[];let result;while(!(result =await reader.read()).done){ chunks.push(result.value.slice(0, size));}return chunks;}const stream =newReadableStream({start(controller){for(let i =0; i <10; i++){ controller.enqueue(newUint8Array([i]));} controller.close();}});chunkStream(stream,2).then(chunks=> console.log(chunks));
5. 使用 Web Workers 分片

将任务分片后交给 Web Workers 并行处理。

// main.js const worker =newWorker('worker.js');const data =newArray(1000).fill(0).map((_, i)=> i);const chunkSize =100;for(let i =0; i < data.length; i += chunkSize){const chunk = data.slice(i, i + chunkSize); worker.postMessage(chunk);} worker.onmessage=(event)=>{ console.log('处理结果:', event.data);};// worker.js  self.onmessage=(event)=>{const result = event.data.map(item=> item *2); self.postMessage(result);};

三、总结

分片类型适用场景实现方法
数组分片处理大数据集使用 slice​ 分割数组
文件分片大文件上传或处理使用 File.slice​ 分割文件
任务分片避免阻塞主线程使用 setTimeout​ 或 requestAnimationFrame
数据流分片渐进式加载或处理数据流使用 ReadableStream​ 分割数据流
Web Workers并行处理任务使用 Web Workers 分片任务

通过分片技术,可以优化 JavaScript 应用的性能、内存占用和用户体验。根据具体需求选择合适的分片方法,实现高效

懒加载(Lazy Loading)的原理

懒加载是一种优化技术,延迟加载非关键资源(如图片、视频、脚本等),直到用户需要访问它们时再加载。以下是懒加载的原理、实现方法和应用场景:

一、懒加载的原理

  1. 延迟加载
    • 懒加载的核心思想是延迟加载非关键资源,减少页面初始加载时间。
  2. 按需加载
    • 当资源进入用户的可视区域(Viewport)时,再加载资源。
  3. 事件监听
    • 通过监听 scroll​、resize​ 等事件,判断资源是否进入可视区域。

二、懒加载的实现方法

1. 图片懒加载

将图片的 src​ 属性替换为 data-src​,当图片进入可视区域时,再将 data-src​ 的值赋给 src​。

<imgdata-src="image.jpg"alt="Lazy Loaded Image"class="lazyload">
document.addEventListener('DOMContentLoaded',()=>{const images = document.querySelectorAll('.lazyload');constlazyLoad=()=>{ images.forEach(img=>{if(img.getBoundingClientRect().top < window.innerHeight &&!img.src){ img.src = img.dataset.src;}});}; window.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad);lazyLoad();});
2. 视频懒加载

将视频的 src​ 属性替换为 data-src​,当视频进入可视区域时,再加载视频。

<videodata-src="video.mp4"controlsclass="lazyload"></video>
document.addEventListener('DOMContentLoaded',()=>{const videos = document.querySelectorAll('.lazyload');constlazyLoad=()=>{ videos.forEach(video=>{if(video.getBoundingClientRect().top < window.innerHeight &&!video.src){ video.src = video.dataset.src; video.load();}});}; window.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad);lazyLoad();});
3. 使用 Intersection Observer API

Intersection Observer API​ 是一种更高效的懒加载实现方式,无需监听 scroll​ 和 resize​ 事件。

document.addEventListener('DOMContentLoaded',()=>{const images = document.querySelectorAll('.lazyload');const observer =newIntersectionObserver((entries, observer)=>{ entries.forEach(entry=>{if(entry.isIntersecting){const img = entry.target; img.src = img.dataset.src; observer.unobserve(img);}});}); images.forEach(img=> observer.observe(img));});

三、懒加载的应用场景

  1. 图片和视频
    • 延迟加载页面中的图片和视频,减少初始加载时间。
  2. 无限滚动
    • 在无限滚动页面中,延迟加载新内容,提升性能。
  3. 模块化加载
    • 延迟加载非关键模块(如脚本、样式),优化页面加载速度。

四、总结

懒加载的核心原理是延迟加载非关键资源,直到用户需要访问它们时再加载。常见的实现方法包括:

  1. 图片和视频懒加载:通过监听 scroll​ 和 resize​ 事件实现。
  2. Intersection Observer API:更高效的懒加载实现方式。

通过懒加载技术,可以显著提升页面加载速度,优化用户体验,减少不必要的资源消耗。

脱离文档流的方法

脱离文档流是指元素不再占据页面布局中的空间,其他元素会忽略其位置。以下是常见的脱离文档流的方法及其特点:

一、position: absolute

  1. 特点
    • 元素脱离文档流,相对于最近的非 static​ 定位的祖先元素定位。
    • 如果没有非 static​ 定位的祖先元素,则相对于 body​ 定位。

示例

.element{position: absolute;top: 10px;left: 10px;}

二、position: fixed

  1. 特点
    • 元素脱离文档流,相对于浏览器窗口定位。
    • 即使页面滚动,元素的位置也不会改变。

示例

.element{position: fixed;top: 10px;left: 10px;}

三、float

  1. 特点
    • 元素脱离文档流,向左或向右浮动,其他内容会环绕其周围。
    • 需要清除浮动,避免影响后续布局。

示例

.element{float: left;}

四、display: none

  1. 特点
    • 元素脱离文档流,并且不占据任何空间。
    • 元素及其子元素都会被隐藏。

示例

.element{display: none;}

五、visibility: hidden

  1. 特点
    • 元素脱离文档流,但仍然占据空间。
    • 元素不可见,但其子元素可以通过 visibility: visible​ 显示。

示例

.element{visibility: hidden;}

六、总结

方法特点示例
position: absolute相对于最近的非 static​ 祖先元素定位position: absolute; top: 10px;
position: fixed相对于浏览器窗口定位position: fixed; top: 10px;
float脱离文档流,内容环绕其周围float: left;
display: none脱离文档流,不占据空间display: none;
visibility: hidden脱离文档流,占据空间visibility: hidden;

通过理解这些脱离文档流的方法,可以更好地控制页面布局和元素行为。

JavaScript 中堆和栈的存储方式

在 JavaScript 中,堆(Heap)和栈(Stack)是两种不同的内存存储方式,用于存储不同类型的数据。以下是它们的详细说明:

一、栈(Stack)

  1. 定义
    • 栈是一种后进先出(LIFO)的数据结构,用于存储基本类型的值和函数调用栈。
  2. 存储内容
    • 基本类型undefined​、null​、boolean​、number​、string​、symbol​、bigint​。
    • 函数调用栈:函数的执行上下文(包括局部变量、参数、返回地址等)。
  3. 特点
    • 内存分配和释放由系统自动管理。
    • 访问速度快,但存储空间有限。
    • 数据大小固定,生命周期短(函数执行完毕后自动释放)。

示例

let a =10;// 基本类型,存储在栈中 functionfoo(){let b =20;// 局部变量,存储在栈中 }foo();

二、堆(Heap)

  1. 定义
    • 堆是一种动态分配内存的区域,用于存储引用类型的值。
  2. 存储内容
    • 引用类型object​、array​、function​、date​ 等。
  3. 特点
    • 内存分配和释放由开发者或垃圾回收机制管理。
    • 访问速度较慢,但存储空间较大。
    • 数据大小不固定,生命周期较长(需要手动释放或由垃圾回收机制回收)。

示例

let obj ={name:'Alice'};// 引用类型,存储在堆中 let arr =[1,2,3];// 引用类型,存储在堆中 

三、堆和栈的存储方式

  1. 基本类型的存储
    • 基本类型的值直接存储在栈中,变量名指向栈中的值。
  2. 引用类型的存储
    • 引用类型的值存储在堆中,变量名指向栈中的内存地址,该地址指向堆中的实际值。

示例

let a =10;// 基本类型,存储在栈中 let b = a;// 复制值,b 也存储在栈中  b =20;// 修改 b,不影响 a let obj1 ={name:'Alice'};// 引用类型,堆中存储对象,栈中存储地址 let obj2 = obj1;// 复制地址,obj2 和 obj1 指向同一对象  obj2.name ='Bob';// 修改 obj2,obj1 也会被修改 

四、总结

特性栈(Stack)堆(Heap)
存储内容基本类型、函数调用栈引用类型
内存管理系统自动管理开发者或垃圾回收机制管理
访问速度
存储空间有限较大
生命周期短(函数执行完毕后自动释放)长(需要手动释放或由垃圾回收机制回收)

理解堆和栈的存储方式,可以帮助开发者更好地管理内存,优化 JavaScript 应用的性能。

v-model​ 原理

v-model​ 是 Vue.js 中用于实现双向数据绑定的指令,常用于表单元素(如 <input>​、<select>​、<textarea>​)。以下是 v-model​ 的实现原理:

一、v-model​ 的基本用法

  1. 效果
    • 当用户输入内容时,message​ 的值会自动更新。
    • message​ 的值改变时,输入框的内容也会自动更新。

语法

<inputv-model="message"/>

二、v-model​ 的实现原理

  1. v-model的本质
    • v-model​ 是 v-bind​ 和 v-on​ 的语法糖,结合了属性绑定和事件监听。
  2. v-model的展开形式
    • 对于 <textarea>​ 和 <select>​ 元素,v-model​ 的展开形式类似。
  3. v-model的工作流程
    • 初始化:将 message​ 的值绑定到输入框的 value​ 属性。
    • 输入事件:监听输入框的 input​ 事件,将用户输入的值赋给 message​。
    • 数据更新:当 message​ 的值改变时,更新输入框的 value​ 属性。

对于 <input>​ 元素,v-model​ 的展开形式如下:

<input:value="message"@input="message = $event.target.value"/>

三、v-model​ 的源码解析

  1. 源码位置
    • v-model​ 的实现位于 Vue 源码的 src/platforms/web/compiler/directives/model.js​。
  2. 核心逻辑
    • 根据不同的表单元素,生成对应的 v-bind​ 和 v-on​ 指令。
    • 监听表单元素的事件(如 input​、change​),更新绑定的数据。

示例代码

functionmodel(el, dir, _warn){const value = dir.value;const modifiers = dir.modifiers;const tag = el.tag;const type = el.attrsMap.type;if(tag ==='input'&& type ==='checkbox'){// 处理复选框 }elseif(tag ==='input'&& type ==='radio'){// 处理单选框 }elseif(tag ==='input'|| tag ==='textarea'){// 处理输入框和文本域 genDefaultModel(el, value, modifiers);}elseif(tag ==='select'){// 处理下拉框 }else{// 其他情况 }}

四、自定义组件的 v-model

  1. 默认实现
    • 默认情况下,v-model​ 在自定义组件中绑定 value​ 属性和 input​ 事件。
    • 使用 model​ 选项自定义 v-model​ 的属性和事件。

自定义​v-model

exportdefault{model:{prop:'checked',event:'change'},props:['checked'],template:` <input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" /> `};

五、总结

  1. v-model的本质
    • v-model​ 是 v-bind​ 和 v-on​ 的语法糖,用于实现双向数据绑定。
  2. v-model的工作流程
    • 初始化时将数据绑定到表单元素的 value​ 属性。
    • 监听表单元素的事件,更新绑定的数据。
  3. 自定义组件的​v-model
    • 使用 model​ 选项自定义 v-model​ 的属性和事件。

Vue 组件通信的多种方式

在 Vue.js 中,组件通信是开发复杂应用的关键。以下是 Vue 组件通信的多种方式及其适用场景:

一、父子组件通信

  1. props和​$emit
    • 实现父子组件的双向数据绑定。

v-model

// 子组件 exportdefault{model:{prop:'value',event:'input'},props:['value']};// 父组件 <Child v-model="message"/>

子组件向父组件传递数据:通过 $emit​ 触发事件。

// 子组件 this.$emit('update', newValue);// 父组件 <Child @update="handleUpdate"/>

父组件向子组件传递数据:通过 props​。

// 父组件 <Child :message="message"/>// 子组件 exportdefault{props:['message']};

二、兄弟组件通信

    • 兄弟组件通过父组件共享数据。
    • 创建一个 Vue 实例作为事件中心,实现组件间的通信。

使用 Event Bus

// eventBus.js import Vue from'vue';exportconst EventBus =newVue();// 组件 A  EventBus.$emit('update', newValue);// 组件 B  EventBus.$on('update',value=>{ console.log(value);});

通过父组件中转

// 父组件 <ChildA @update="handleUpdate"/><ChildB :message="message"/>

三、跨层级组件通信

    • 祖先组件通过 provide​ 提供数据,后代组件通过 inject​ 注入数据。
    • 使用 Vuex 进行全局状态管理,实现组件间的数据共享。

Vuex

// store.js exportdefaultnewVuex.Store({state:{message:'Hello'},mutations:{updateMessage(state, newValue){ state.message = newValue;}}});// 组件 A this.$store.commit('updateMessage', newValue);// 组件 B this.$store.state.message;

provide和​inject

// 祖先组件 exportdefault{provide(){return{message:this.message };}};// 后代组件 exportdefault{inject:['message']};

四、其他通信方式

    • 通过 $refs​ 直接访问子组件的属性和方法。
    • 通过 $parent​ 访问父组件,通过 $children​ 访问子组件。

$parent和​$children

this.$parent.methodName();this.$children.methodName();

$refs

// 父组件 <Child ref="child"/>this.$refs.child.methodName();

五、总结

通信方式适用场景示例
props$emit父子组件通信<Child :message="message" @update="handleUpdate" />
v-model父子组件双向绑定<Child v-model="message" />
Event Bus兄弟组件或任意组件通信EventBus.$emit('update', newValue)
provideinject跨层级组件通信provide: { message }, inject: ['message']
Vuex全局状态管理this.$store.commit('updateMessage', newValue)
$refs直接访问子组件this.$refs.child.methodName()
$parent$children访问父组件或子组件this.$parent.methodName()

通过理解这些组件通信方式,可以根据具体需求选择最合适的方案,实现高效的组件交互和数据共享。

Diff 算法

Diff 算法是虚拟 DOM(Virtual DOM)的核心算法,用于高效地比较两个虚拟 DOM 树的差异,并最小化实际 DOM 的操作。以下是 Diff 算法的详细说明:

一、Diff 算法的基本概念

  1. 虚拟 DOM
    • 虚拟 DOM 是一个轻量级的 JavaScript 对象,用于描述真实 DOM 的结构和属性。
  2. Diff 算法的目标
    • 比较新旧虚拟 DOM 树的差异,找出需要更新的部分,最小化 DOM 操作。
  3. Diff 算法的策略
    • 只进行同层级比较,不跨层级比较。
    • 通过唯一标识(如 key​)优化列表项的对比。

二、Diff 算法的实现步骤

1. 同层级节点比较
  1. 节点类型不同
    • 如果节点类型不同(如 div​ 变为 span​),直接替换整个节点。
  2. 节点类型相同
    • 如果节点类型相同,比较节点的属性和子节点。
2. 属性比较
  1. 更新属性
    • 比较新旧节点的属性,更新变化的属性。
  2. 删除属性
    • 如果旧节点有而新节点没有的属性,删除该属性。
  3. 添加属性
    • 如果新节点有而旧节点没有的属性,添加该属性。
3. 子节点比较
  1. 无子节点
    • 如果新旧节点都没有子节点,无需处理。
  2. 有子节点
    • 如果新旧节点都有子节点,使用列表对比算法(如 key​ 对比)进行优化。

三、列表对比算法

  1. 无​key的情况
    • 按顺序对比子节点,性能较差。
  2. 有​key的情况
    • 使用 key​ 作为唯一标识,优化列表项的对比。
    • 通过 key​ 找到新旧节点中相同的节点,进行更新。

示例

const oldChildren =[{key:'a',value:'A'},{key:'b',value:'B'},{key:'c',value:'C'}];const newChildren =[{key:'c',value:'C'},{key:'a',value:'A'},{key:'d',value:'D'}];// 使用 key 对比,更新节点 

四、Diff 算法的优化

  1. 唯一标识(​​key
    • 通过 key​ 优化列表项的对比,减少不必要的 DOM 操作。
  2. 批量更新
    • 将多次 DOM 操作合并为一次,提升性能。
  3. 虚拟 DOM 的分层比较
    • 只进行同层级比较,不跨层级比较,简化算法复杂度。

五、Diff 算法的源码解析

  1. 源码位置
    • Vue 的 Diff 算法实现位于 src/core/vdom/patch.js​。
  2. 核心逻辑
    • 通过 patchVnode​ 函数比较新旧节点。
    • 通过 updateChildren​ 函数比较子节点列表。

示例代码

functionpatchVnode(oldVnode, vnode){if(oldVnode === vnode)return;const elm = vnode.elm = oldVnode.elm;const oldCh = oldVnode.children;const ch = vnode.children;if(vnode.data){updateAttrs(oldVnode, vnode);}if(isUndef(vnode.text)){if(isDef(oldCh)&&isDef(ch)){if(oldCh !== ch)updateChildren(elm, oldCh, ch);}elseif(isDef(ch)){addVnodes(elm,null, ch,0, ch.length -1);}elseif(isDef(oldCh)){removeVnodes(elm, oldCh,0, oldCh.length -1);}}elseif(oldVnode.text !== vnode.text){setTextContent(elm, vnode.text);}}

六、总结

  1. Diff 算法的目标
    • 找出新旧虚拟 DOM 树的差异,最小化 DOM 操作。
  2. Diff 算法的策略
    • 只进行同层级比较,不跨层级比较。
    • 通过 key​ 优化列表项的对比。
  3. Diff 算法的优化
    • 使用 key​ 作为唯一标识,批量更新 DOM 操作。

理解 Diff 算法的原理和实现,可以帮助开发者更好地优化 Vue 应用的性能。

Webpack 打包配置

Webpack 是一个模块打包工具,用于将 JavaScript、CSS、图片等资源打包成静态文件。以下是 Webpack 的基本配置及其详细说明:

一、Webpack 的基本概念

  1. 入口(Entry)
    • 指定打包的入口文件,Webpack 从入口文件开始构建依赖图。
  2. 输出(Output)
    • 指定打包后的文件输出路径和文件名。
  3. 加载器(Loader)
    • 用于处理非 JavaScript 文件(如 CSS、图片),将其转换为模块。
  4. 插件(Plugin)
    • 用于执行更复杂的任务(如代码压缩、资源优化)。
  5. 模式(Mode)
    • 指定打包模式(如开发模式 development​ 或生产模式 production​)。

二、Webpack 的基本配置

1. 安装 Webpack
npminstall webpack webpack-cli --save-dev 
2. 创建配置文件

在项目根目录下创建 webpack.config.js​ 文件。

3. 基本配置示例
const path =require('path'); module.exports ={mode:'development',// 打包模式 entry:'./src/index.js',// 入口文件 output:{filename:'bundle.js',// 输出文件名 path: path.resolve(__dirname,'dist')// 输出路径 },module:{rules:[{test:/\.css$/,// 匹配 CSS 文件 use:['style-loader','css-loader']// 使用加载器 },{test:/\.(png|jpg|gif)$/,// 匹配图片文件 use:['file-loader']// 使用加载器 }]},plugins:[]// 插件配置 };

三、Webpack 的常用配置

1. 多入口配置
module.exports ={entry:{app:'./src/app.js',admin:'./src/admin.js'},output:{filename:'[name].bundle.js',// 使用 [name] 占位符 path: path.resolve(__dirname,'dist')}};
2. 加载器配置

图片加载器

npminstall file-loader --save-dev 
module.exports ={module:{rules:[{test:/\.(png|jpg|gif)$/,use:['file-loader']}]}};

Babel 加载器

npminstall babel-loader @babel/core @babel/preset-env --save-dev 
module.exports ={module:{rules:[{test:/\.js$/,exclude:/node_modules/,use:{loader:'babel-loader',options:{presets:['@babel/preset-env']}}}]}};

CSS 加载器

npminstall style-loader css-loader --save-dev 
module.exports ={module:{rules:[{test:/\.css$/,use:['style-loader','css-loader']}]}};
3. 插件配置

代码压缩插件

const TerserPlugin =require('terser-webpack-plugin'); module.exports ={optimization:{minimizer:[newTerserPlugin()]}};

HTML 插件

npminstall html-webpack-plugin --save-dev 
const HtmlWebpackPlugin =require('html-webpack-plugin'); module.exports ={plugins:[newHtmlWebpackPlugin({template:'./src/index.html'// 指定模板文件 })]};
4. 开发服务器配置
npminstall webpack-dev-server --save-dev 
module.exports ={devServer:{contentBase:'./dist',// 服务器根目录 port:8080,// 端口号 hot:true// 启用热更新 }};

四、总结

  1. Webpack 的基本配置
    • 入口、输出、加载器、插件、模式。
  2. 常用配置
    • 多入口、CSS 加载器、Babel 加载器、图片加载器、HTML 插件、代码压缩插件、开发服务器。

通过理解 Webpack 的配置和常用插件,可以高效地打包和管理前端项目。

浏览器输入 URL 到网页呈现的详细过程

当用户在浏览器中输入 URL 并按下回车键后,浏览器会执行一系列操作,最终将网页呈现给用户。以下是这一过程的详细步骤:

一、URL 解析

  1. 解析协议、域名、端口和路径
    • 浏览器解析 URL,提取协议(如 http​ 或 https​)、域名(如 www.example.com​)、端口(默认 80​ 或 443​)和路径(如 /index.html​)。
  2. 检查缓存
    • 浏览器检查缓存中是否有该 URL 对应的资源,如果有且未过期,则直接使用缓存资源。

二、DNS 解析

  1. 查询 DNS 缓存
    • 浏览器检查本地 DNS 缓存(如浏览器缓存、操作系统缓存),如果有域名对应的 IP 地址,则直接使用。
  2. DNS 递归查询
    • 如果本地缓存中没有,浏览器向 DNS 服务器发起递归查询,获取域名对应的 IP 地址。

三、建立 TCP 连接

  1. 三次握手
    • 浏览器与服务器通过 TCP 协议建立连接,进行三次握手:
      • 客户端发送 SYN​ 报文。
      • 服务器回复 SYN-ACK​ 报文。
      • 客户端发送 ACK​ 报文。
  2. HTTPS 加密(可选)
    • 如果使用 HTTPS 协议,浏览器与服务器进行 TLS 握手,协商加密算法和密钥,建立安全连接。

四、发送 HTTP 请求

  1. 构造 HTTP 请求
    • 浏览器构造 HTTP 请求报文,包括请求行(如 GET /index.html HTTP/1.1​)、请求头(如 Host​、User-Agent​)和请求体(如 POST 数据)。
  2. 发送请求
    • 浏览器通过 TCP 连接将 HTTP 请求发送到服务器。

五、服务器处理请求

  1. 解析请求
    • 服务器解析 HTTP 请求,确定请求的资源和方法。
  2. 处理请求
    • 服务器根据请求内容执行相应的操作(如读取文件、查询数据库)。
  3. 生成响应
    • 服务器生成 HTTP 响应,包括状态行(如 HTTP/1.1 200 OK​)、响应头(如 Content-Type​、Content-Length​)和响应体(如 HTML 内容)。

六、接收 HTTP 响应

  1. 接收响应
    • 浏览器接收服务器返回的 HTTP 响应。
  2. 检查状态码
    • 浏览器检查状态码(如 200​ 表示成功,404​ 表示未找到),决定后续操作。

七、解析和渲染页面

  1. 解析 HTML
    • 浏览器解析 HTML 文档,构建 DOM 树。
  2. 加载外部资源
    • 浏览器加载 CSS、JavaScript、图片等外部资源。
  3. 构建 CSSOM 树
    • 浏览器解析 CSS,构建 CSSOM 树。
  4. 构建渲染树
    • 浏览器将 DOM 树和 CSSOM 树合并,构建渲染树。
  5. 布局和绘制
    • 浏览器计算渲染树中每个节点的位置和大小(布局),然后将页面绘制到屏幕上(绘制)。

八、执行 JavaScript

  1. 解析和执行 JavaScript
    • 浏览器解析并执行 JavaScript 代码,可能修改 DOM 或 CSSOM,触发重绘或重排。
  2. 触发事件
    • 浏览器触发 DOMContentLoaded​ 和 load​ 事件,表示页面加载完成。

九、总结

步骤描述
URL 解析解析协议、域名、端口和路径,检查缓存
DNS 解析查询 DNS 缓存,递归查询域名对应的 IP 地址
建立 TCP 连接通过三次握手建立连接,HTTPS 进行 TLS 握手
发送 HTTP 请求构造并发送 HTTP 请求到服务器
服务器处理请求解析请求,处理请求,生成 HTTP 响应
接收 HTTP 响应接收并检查服务器返回的 HTTP 响应
解析和渲染页面构建 DOM 树、CSSOM 树、渲染树,布局和绘制
执行 JavaScript解析和执行 JavaScript,触发页面事件

前端的优化方法

前端优化是提升网页性能、用户体验和 SEO 效果的关键。以下是常见的前端优化方法及其详细说明:

一、减少 HTTP 请求

  1. 合并文件
    • 将多个 CSS 或 JavaScript 文件合并为一个文件,减少请求次数。
  2. 使用雪碧图
    • 将多个小图标合并为一张雪碧图,通过 background-position​ 显示不同图标。
  3. 内联资源
    • 将小型的 CSS 或 JavaScript 代码直接内联到 HTML 中,减少请求次数。

二、使用缓存

    • 通过设置 Cache-Control​ 和 Expires​ 头,缓存静态资源。
    • 使用 Service Worker 缓存资源,实现离线访问。

Service Worker 缓存

self.addEventListener('install',(event)=>{ event.waitUntil( caches.open('v1').then((cache)=>{return cache.addAll(['/','/styles.css','/app.js']);}));});

浏览器缓存

Cache-Control: max-age=31536000 

三、优化资源加载

    • 将非关键资源(如图片、视频)延迟加载,提升页面初始加载速度。
    • 使用 async​ 或 defer​ 属性异步加载 JavaScript 文件。
    • 使用 <link rel="preload">​ 预加载关键资源。

预加载关键资源

<linkrel="preload"href="styles.css"as="style">

异步加载脚本

<scriptsrc="app.js"async></script>

延迟加载

<imgdata-src="image.jpg"alt="Lazy Loaded Image"class="lazyload">

四、优化代码

  1. 压缩代码
    • 使用工具(如 Webpack、Gulp)压缩 JavaScript、CSS 和 HTML 代码。
  2. 移除未使用的代码
    • 使用 Tree Shaking 移除未使用的 JavaScript 代码。
  3. 优化 CSS
    • 避免使用 @import​,减少 CSS 嵌套层级,移除未使用的样式。

五、优化图片

  1. 使用合适的格式
    • 使用 WebP、JPEG XR 等现代图片格式,减少文件大小。
  2. 压缩图片
    • 使用工具(如 TinyPNG、ImageOptim)压缩图片。
    • 使用 <picture>​ 和 srcset​ 提供不同分辨率的图片。

响应式图片

<picture><sourcesrcset="image.webp"type="image/webp"><imgsrc="image.jpg"alt="Fallback Image"></picture>

六、优化渲染性能

  1. 减少重绘和重排
    • 使用 transform​ 和 opacity​ 替代 top​、left​ 等属性,减少重排。
    • 使用 requestAnimationFrame​ 优化动画性能。
  2. 使用虚拟 DOM
    • 使用虚拟 DOM(如 React)优化 DOM 操作。

使用​requestAnimationFrame

functionanimate(){// 动画逻辑 requestAnimationFrame(animate);}requestAnimationFrame(animate);

七、优化 SEO

  1. 语义化 HTML
    • 使用语义化标签(如 <header>​、<article>​)提升 SEO 效果。
    • 设置 title​、description​ 和 keywords​ 等 meta​ 标签。
    • 使用 JSON-LD 或 Microdata 添加结构化数据。

使用结构化数据

<scripttype="application/ld+json">{"@context":"https://schema.org","@type":"Article","headline":"Article Title"}</script>

优化​meta标签

<metaname="description"content="Page description">

八、总结

优化方法描述
减少 HTTP 请求合并文件、使用雪碧图、内联资源
使用缓存浏览器缓存、Service Worker 缓存
优化资源加载延迟加载、异步加载脚本、预加载关键资源
优化代码压缩代码、移除未使用的代码、优化 CSS
优化图片使用合适的格式、压缩图片、响应式图片
优化渲染性能减少重绘和重排、使用 requestAnimationFrame​、虚拟 DOM
优化 SEO语义化 HTML、优化 meta​ 标签、结构化数据

通过理解这些前端优化方法,可以显著提升网页性能、用户体验和 SEO 效果。

Read more

保姆级教程:Windows/Mac/Linux三平台OpenClaw部署,90%的坑我帮你踩完了

保姆级教程:Windows/Mac/Linux三平台OpenClaw部署,90%的坑我帮你踩完了

做OpenClaw企业落地的这大半年,我被问得最多的问题就是: 为什么我跟着网上的教程部署,要么Docker启动失败,要么面板访问不了? Windows部署WSL2报错怎么解决?Mac M芯片跑不起来是什么原因?Linux服务器部署完了外网访问不了? 毫不夸张地说,OpenClaw的部署本身极简,但90%的问题都不是OpenClaw本身的问题,而是环境配置、权限、端口、依赖兼容这些基础坑。我自己在三平台都反复部署过几十次,踩过的坑能凑成一本手册,小到中文路径导致的启动失败,大到企业内网环境的镜像拉取失败,几乎都遇见过。 这篇文章,我就把Windows/Mac/Linux三平台的部署流程,拆成保姆级的一步步操作,每一步都标注踩坑点,新手跟着走,99%能一次部署成功。同时把90%的人会遇到的问题,整理成「踩坑合集」,直接给原因+现成的解决方案,不用你再到处搜教程。 部署前必看:先搞懂这3点,少走90%的弯路 1. 硬件最低要求 很多人上来就部署,结果自己的电脑/服务器根本带不动,先看清楚硬件门槛: 配置类型最低配置推荐配置说明CPU2核4线程4核8线程纯指令执行用最低配

By Ne0inhk
打工人摸鱼新姿势!轻量斗地主服务器,内网穿透让同事远程联机不翻车

打工人摸鱼新姿势!轻量斗地主服务器,内网穿透让同事远程联机不翻车

Ratel 斗地主服务器是一款基于 Netty 和 Protobuf 开发的轻量级服务端软件,核心功能是搭建斗地主游戏服务,适配 Windows、Linux、macOS 多系统,适合职场上班族、学生群体这类想利用碎片时间休闲的人群,它的核心优点是资源占用极低,CPU 仅占 3%,内存消耗也少,还支持 AI 对手和隐藏进程,日常使用不会给设备带来负担。 使用这款软件时也有一些小细节需要注意,比如在办公场景下启动服务要注意隐藏会话,避免被察觉;和 AI 对战时不同难度模式的出牌节奏有差异,新手可以先从简单模式上手,而且软件启动后需要保持终端窗口运行,不小心关闭就会中断游戏。 不过这款软件仅靠局域网使用时,会遇到不少实际问题:比如上班族想和异地的同事联机,却因为不在同一局域网无法连接;学生在宿舍搭建好服务器,放假回家后就没法和室友继续玩,只能局限在小范围的网络环境里,大大降低了使用的灵活性。 而将 Ratel 斗地主服务器和 cpolar 内网穿透结合后,这些问题就能迎刃而解。cpolar 无需公网 IP 就能把本地的游戏服务映射到公网,

By Ne0inhk

Ubuntu新手必看:如何快速更换国内源(阿里/清华/中科大源对比)

Ubuntu 新手的第一道“加速”关:国内镜像源深度解析与实战指南 刚装好 Ubuntu,那种清爽的桌面和开箱即用的感觉确实不错。但当你兴冲冲地打开终端,准备用 apt install 装点东西时,进度条那慢如蜗牛的爬行速度,是不是瞬间浇灭了一半的热情?别急着怀疑自己的网络,这几乎是每个国内 Ubuntu 用户都会遇到的“新手墙”。问题的核心,往往不在于你的宽带,而在于系统默认连接的软件仓库服务器远在海外,网络延迟和带宽限制成了最大的瓶颈。 解决这个问题的方法,就是“换源”——将系统的软件源地址,更换为位于国内的镜像服务器。这听起来像是个简单的操作,但背后其实有不少门道:国内有哪些可靠的镜像站?阿里云、清华大学、中国科学技术大学(USTC)的源有什么区别?为什么别人的源换上去飞快,你的却报了一堆错?今天,我们就来彻底拆解这个问题。这不仅仅是复制粘贴几行命令,而是帮你理解原理、掌握选择、并能在遇到问题时自己动手排查。无论你是刚接触 Linux 的开发新手,还是希望优化工作流效率的资深用户,一个配置得当的软件源,

By Ne0inhk
时序数据库选型指南:在大数据浪潮中把握未来,为何Apache IoTDB值得关注?

时序数据库选型指南:在大数据浪潮中把握未来,为何Apache IoTDB值得关注?

文章目录 * 1 -> 引言 * 2 -> 时序数据的挑战与选型的重要性 * 3 -> 核心选型维度:超越性能参数的综合考量 * 4 -> 深入聚焦:Apache IoTDB的差异化优势 * 5 -> 选型建议与总结 1 -> 引言 在当今这个万物互联、数据驱动的时代,从工业传感器到智能电网,从车联网到金融交易,每一秒都在产生海量带有时间戳的数据——时序数据。这类数据不仅是企业运营的“脉搏”,更是驱动智能决策、优化效率、预测未来的核心燃料。面对汹涌而至的时序数据洪流,如何选择一款合适的时序数据库(Time-Series Database, TSDB),已成为大数据架构师、物联网(IoT)平台开发者和数据分析师面临的关键决策。本文将站在大数据技术演进和国产基础软件发展的视角,为您梳理时序数据库的选型要点,

By Ne0inhk