前端核心面试题解析:闭包、事件循环与 Vue 原理
前端开发核心面试题,涵盖 JavaScript 基础(闭包、作用域、原型链)、异步编程(事件循环、Promise)、Vue 框架原理(虚拟 DOM、响应式、组件通信)、CSS 布局(BFC、盒模型)、网络协议(HTTP、跨域)及性能优化(缓存、懒加载、Webpack)等内容。通过代码示例和原理解析,帮助开发者系统复习前端知识体系,提升面试通过率。

前端开发核心面试题,涵盖 JavaScript 基础(闭包、作用域、原型链)、异步编程(事件循环、Promise)、Vue 框架原理(虚拟 DOM、响应式、组件通信)、CSS 布局(BFC、盒模型)、网络协议(HTTP、跨域)及性能优化(缓存、懒加载、Webpack)等内容。通过代码示例和原理解析,帮助开发者系统复习前端知识体系,提升面试通过率。

闭包(Closure) 是指一个函数能够访问并记住其外部作用域中的变量,即使外部函数已经执行完毕。闭包由两部分组成:
function outer() {
let count = 0; // 外部函数的变量
function inner() {
count++; // 内部函数访问外部变量
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 输出 1
counter(); // 输出 2
通过闭包隐藏内部变量,仅暴露操作接口:
function createCounter() {
let count = 0; // 私有变量
return {
increment: () => count++,
getValue: () => count
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 输出 1
// 无法直接访问 count,避免被外部修改
将多参数函数转换为单参数链式调用:
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(3)); // 输出 8
在事件监听中保留上下文变量:
function setupButton() {
const button = document.getElementById('myButton');
let clicks = 0;
button.addEventListener('click', function() {
clicks++;
console.log(`按钮被点击了 ${clicks} 次`);
});
}
// 每次点击都会更新同一个 clicks 变量
问题:循环中创建的闭包共享同一个变量,导致意外结果。
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3, 3, 3
}, 100);
}
解决方案:使用 IIFE 或 let 创建块级作用域。
// 使用 IIFE
for (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);
}
问题:闭包长期持有外部变量引用,导致内存无法释放。
function heavyProcess() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log(largeData.length);
};
}
const leak = heavyProcess(); // largeData 被闭包引用,无法回收
解决方案:在不需要时解除引用。
leak = null; // 手动解除对闭包的引用
题目:以下代码输出什么?为什么?
function createFunctions() {
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 或闭包隔离作用域。
事件循环是 JavaScript 处理异步任务的核心机制。由于 JavaScript 是单线程语言,事件循环通过任务队列(Task Queue)和调用栈(Call Stack)的协作,实现了非阻塞的异步执行模型。
setTimeout、setInterval、I/O 操作、UI 渲染等。Promise.then、MutationObserver、process.nextTick(Node.js)等。console.log('1'); // 同步任务
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步任务
// 输出顺序:1 → 4 → 3 → 2
执行步骤解析:
console.log('1') 和 console.log('4') 依次执行。Promise.then 回调执行,输出 3。setTimeout 回调执行,输出 2。Promise、async/await 处理异步任务,避免回调地狱。requestAnimationFrame 结合事件循环实现流畅的动画效果。process.nextTick 和 setImmediate。事件循环是 JavaScript 异步编程的核心机制,通过调用栈、任务队列和事件循环的协作,实现了非阻塞的执行模型。理解事件循环的执行顺序(同步任务 → 微任务 → 宏任务)是掌握异步编程的关键。在实际开发中,合理利用事件循环机制可以提升代码性能和用户体验。
BFC(Block Formatting Context,块级格式化上下文)是 Web 页面渲染时的一种布局环境。它是一个独立的渲染区域,内部的元素布局不会影响外部元素。
以下 CSS 属性可以触发 BFC:
<html>)。float 值不为 none。position 值为 absolute 或 fixed。display 值为 inline-block、table-cell、table-caption、flex、inline-flex、grid、inline-grid。overflow 值不为 visible(如 hidden、auto、scroll)。.left { float: left; width: 200px; }
.right { overflow: hidden; /* 触发 BFC */ }
.box { display: inline-block; /* 触发 BFC */ }
.parent { overflow: hidden; /* 触发 BFC */ }
实现多栏布局
<div class="left">左侧栏</div>
<div class="right">右侧栏</div>
<style>
.left { float: left; width: 200px; background: #ccc; }
.right { overflow: hidden; /* 触发 BFC */ background: #f0f0f0; }
</style>
避免外边距重叠
<div class="box" style="margin: 20px;">元素 1</div>
<div class="box" style="margin: 20px;">元素 2</div>
<style>
.box { display: inline-block; /* 触发 BFC */ width: 100%; }
</style>
清除浮动
<div class="parent">
<div class="child" style="float: left;">浮动元素</div>
</div>
<style>
.parent { overflow: hidden; /* 触发 BFC */ border: 1px solid #000; }
</style>
overflow: hidden)可能会导致性能问题。clearfix 方法。BFC 是 CSS 布局中的重要概念,通过触发 BFC 可以解决浮动、外边距重叠等问题,实现更灵活的布局。理解 BFC 的触发条件和特性,有助于编写更健壮和可维护的 CSS 代码。在实际开发中,应根据需求合理使用 BFC,避免过度依赖。
内存泄漏(Memory Leak)是指程序中已不再使用的内存未被释放,导致内存占用持续增加,最终可能引发性能下降甚至崩溃。在 JavaScript 中,内存泄漏通常由不当的引用管理引起。
window)出发,标记所有可达对象,清除未标记的对象。未释放的缓存或 Map:缓存或 Map 中存储的对象未及时清理,会导致内存占用持续增加。
let cache = new Map();
function setCache(key, value) {
cache.set(key, value);
}
// 未清理 cache,内存泄漏
DOM 引用未清除:保存了 DOM 元素的引用,即使元素被移除,内存也无法释放。
let elements = {
button: document.getElementById('button'),
};
document.body.removeChild(elements.button); // DOM 已移除,但引用仍在
闭包引用:闭包会保留对外部作用域的引用,如果闭包未释放,相关内存也无法回收。
function createClosure() {
let largeData = new Array(1000000).fill('data');
return () => console.log(largeData); // largeData 一直被引用
}
const closure = createClosure();
未清理的定时器或回调函数:定时器或事件监听器未及时清除,会导致相关对象无法回收。
let data = fetchData();
setInterval(() => {
process(data); // data 一直被引用,无法回收
}, 1000);
意外全局变量:未使用 var、let 或 const 声明的变量会挂载到全局对象(如 window),导致内存无法释放。
function leak() {
globalVar = '这是一个全局变量'; // 未使用 var/let/const
}
node --inspect 结合 Chrome DevTools。WeakMap 或 WeakSet 存储临时数据,避免强引用导致内存无法释放。清理缓存
let cache = new Map();
function setCache(key, value, ttl) {
cache.set(key, value);
setTimeout(() => cache.delete(key), ttl); // 设置缓存失效时间
}
优化闭包
function createClosure() {
let largeData = new Array(1000000).fill('data');
return () => {
console.log('Closure executed'); // 不引用 largeData
};
}
使用弱引用
let weakMap = new WeakMap();
let key = {};
weakMap.set(key, 'data'); // key 被回收时,数据也会被回收
及时清除引用
let timer = setInterval(() => {}, 1000);
clearInterval(timer); // 清除定时器
内存泄漏是 JavaScript 开发中的常见问题,通常由不当的引用管理引起。通过理解垃圾回收机制、熟悉常见的内存泄漏场景,并借助开发者工具进行排查,可以有效避免内存泄漏问题。在实际开发中,遵循最佳实践(如及时清除引用、使用弱引用等)是保证应用性能的关键。
虚拟 DOM(Virtual DOM)是一种用 JavaScript 对象表示真实 DOM 结构的技术。Vue 通过虚拟 DOM 实现高效的 DOM 更新,减少直接操作真实 DOM 的开销。
key 属性识别节点,避免不必要的节点销毁和重建。key 值相同,则复用节点,只更新属性或子节点。虚拟 DOM 的结构:虚拟 DOM 是一个 JavaScript 对象,包含标签名、属性、子节点等信息。
const vnode = {
tag: 'div',
attrs: { id: 'app' },
children: [
{ tag: 'p', attrs: {}, children: ['Hello, Vue!'] }
]
};
Weex 开发移动端应用。MVVM(Model-View-ViewModel)是一种软件架构模式,主要用于分离 UI 逻辑与业务逻辑。它将应用程序分为三个核心部分:
Vue2 使用 Object.defineProperty 实现响应式,其核心机制如下:
Object.defineProperty 劫持对象的属性,定义 getter 和 setter。当访问属性时触发 getter,当修改属性时触发 setter。getter 中收集依赖(Watcher),在 setter 中通知依赖更新。push、pop 等)实现响应式。示例:
const data = { name: 'Vue2' };
Object.defineProperty(data, 'name', {
get() {
console.log('获取 name');
return this._name;
},
set(newValue) {
console.log('更新 name');
this._name = newValue;
}
});
局限性:
Vue.set 或 Vue.delete 解决。arr[0] = 1)。Vue3 使用 Proxy 实现响应式,其核心机制如下:
Proxy 代理整个对象,拦截对对象的所有操作(如读取、赋值、删除等)。get 拦截器中收集依赖(Effect),在 set 拦截器中通知依赖更新。Proxy 可以直接拦截数组的变化,无需重写数组方法。示例:
const data = { name: 'Vue3' };
const proxy = new Proxy(data, {
get(target, key) {
console.log('获取', key);
return target[key];
},
set(target, key, newValue) {
console.log('更新', key);
target[key] = newValue;
return true;
}
});
优势:
Proxy 是语言层面的特性,性能优于 Object.defineProperty。Vue.set)。| 特性 | Vue2(Object.defineProperty) | Vue3(Proxy) |
|---|---|---|
| 数据劫持方式 | 劫持对象的属性 | 代理整个对象 |
| 新增/删除属性 | 不支持 | 支持 |
| 数组响应式 | 需重写数组方法 | 直接拦截数组操作 |
| 性能 | 较低 | 较高 |
| 代码复杂度 | 较高 | 较低 |
ref 用于包装基本数据类型,通过 .value 访问。reactive 用于包装对象,直接访问属性。Effect 作为依赖单元,更加灵活和高效。Vue3 响应式
const data = { name: 'Vue3' };
const proxy = new Proxy(data, {
get(target, key) {
console.log('获取', key);
return target[key];
},
set(target, key, newValue) {
console.log('更新', key);
target[key] = newValue;
return true;
}
});
Vue2 响应式
const data = { name: 'Vue2' };
Object.defineProperty(data, 'name', {
get() {
console.log('获取 name');
return this._name;
},
set(newValue) {
console.log('更新 name');
this._name = newValue;
}
});
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 };
undefined、Symbol 等特殊类型;无法处理循环引用。lodash 的 cloneDeep 方法:import _ from 'lodash';
const obj = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(obj);
递归实现深拷贝
function deepClone(obj, map = new Map()) {
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(共享引用)
obj.a = obj),需要额外处理,否则会导致栈溢出。Function、RegExp、Map、Set 等,需要特殊处理。lodash)。在 npm 项目中,依赖包分为 开发依赖 和 生产依赖,分别用于不同的场景。以下是它们的定义、区别及使用方式:
devDependencies)--save-dev 或 -D 选项将包安装为开发依赖:npm install <package> --save-dev
# 或
npm install <package> -D
jest、mocha、chaiwebpack、vite、babeleslint、prettiertypescript示例在 package.json 中:
"devDependencies": {
"eslint": "^8.0.0",
"webpack": "^5.0.0",
"jest": "^27.0.0"
}
dependencies)npm install <package>
# 或使用 --save 或 -S 选项
npm install <package> --save
express、koa、nestjsreact、vue、angularlodash、axios、moment示例在 package.json 中:
"dependencies": {
"express": "^4.0.0",
"lodash": "^4.0.0",
"react": "^18.0.0"
}
| 特性 | 开发依赖(devDependencies) | 生产依赖(dependencies) |
|---|---|---|
| 使用环境 | 仅用于开发环境 | 用于生产环境 |
| 是否发布到生产 | 否 | 是 |
| 安装命令 | npm install <package> --save-dev | npm install <package> --save |
| 示例工具 | eslint、webpack、jest | express、react、lodash |
peerDependencies:用于插件或库开发,指定兼容的宿主包。optionalDependencies:可选依赖,安装失败不影响项目运行。package-lock.json:锁定依赖版本,确保团队环境一致。npm outdated 检查过期依赖,定期更新以修复漏洞。npm prune 或 npm uninstall 清理未使用的依赖。package.json 中 ^ 和 ~ 的区别在 package.json 中,^ 和 ~ 是用于定义依赖版本范围的符号。它们决定了 npm 或 yarn 在安装或更新依赖时允许的版本范围。以下是它们的详细区别:
主版本号。次版本号。修订版本号。示例:1.2.3。
1:主版本号(Major),不兼容的 API 变更。2:次版本号(Minor),向后兼容的功能新增。3:修订版本号(Patch),向后兼容的问题修复。^ 和 ~ 是定义版本范围的前缀符号。^ 的含义^1.2.3,则允许更新的版本范围是 >=1.2.3 <2.0.0。^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.3,则允许更新的版本范围是 >=1.2.3 <1.3.0。~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)。>=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 中的各种 dependenciespackage.json 是 Node.js 项目的核心配置文件,用于管理项目的依赖、脚本、版本等信息。其中,dependencies 是定义项目依赖的关键部分。以下是 package.json 中各种依赖类型的详细说明:
dependencies:项目运行所必需的依赖包。安装方式:npm install <package> 或 yarn add <package>。devDependencies:仅用于开发环境的依赖包(如测试工具、构建工具等)。安装方式:npm install <package> --save-dev 或 yarn add <package> --dev。peerDependencies:与当前包兼容的宿主包,通常用于插件或库的开发。特点:不会自动安装,需要用户手动安装。optionalDependencies:可选的依赖包,即使安装失败也不会影响项目运行。特点:优先级高于 dependencies,如果安装失败会静默忽略。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(ECMAScript 2015)引入了官方的模块化语法,提供了 import 和 export 关键字来实现模块的导入和导出。以下是 ES6 模块化的核心概念和用法:
模块化是将代码拆分为独立模块的开发方式,每个模块具有独立的作用域,通过导入和导出实现模块间的依赖管理。ES6 模块化具有以下特点:
export)export 关键字导出变量、函数或类。导入时需要使用相同的名称。export default 导出模块的默认值。导入时可以使用任意名称。混合导出
// module.js
export const name = 'ES6';
export default function greet() {
console.log('Hello, ES6!');
}
默认导出
// module.js
const name = 'ES6';
export default name;
命名导出
// module.js
export const name = 'ES6';
export function greet() {
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!
.mjs 或在 package.json 中设置 "type": "module")。<script> 标签中添加 type="module" 属性。.mjs 或在 package.json 中设置 "type": "module"。浏览器支持
<script type="module" src="app.js"></script>
key 的作用及为什么不能用 index 作为 key 详解key 的作用在 Vue 中,key 是用于标识虚拟 DOM 元素的特殊属性,其主要作用包括:
key 用于唯一标识每个虚拟 DOM 节点,帮助 Vue 识别哪些节点是新增的、删除的或需要更新的。key 可以帮助 Vue 更高效地复用和更新 DOM 节点,减少不必要的 DOM 操作。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 可能会导致以下问题:
index 是数组的下标,当列表顺序变化时,index 会重新分配,导致 Vue 无法正确复用 DOM 节点,降低渲染性能。index 作为 key 会导致状态错乱。例如,删除中间项后,后续项的 index 会发生变化,状态会被错误地保留。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。key:避免使用随机数或时间戳作为 key,否则会导致节点频繁销毁和重建。使用唯一标识
<template>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</template>
CSS 选择器优先级决定了当多个规则应用于同一个元素时,哪条规则会生效。以下是优先级规则及其计算方式的详细说明:
CSS 选择器优先级由 4 个级别组成,按权重从高到低依次为:
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)
<div style="color: red;">Hello</div>
!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
<div style="color: red;">Hello</div>
div { color: green; } /* 权重:1 */
示例 2
<div class="myClass">Hello</div>
.myClass { color: green; } /* 权重:10 */
div { color: red; } /* 权重:1 */
示例 1
<div id="myId" class="myClass">Hello</div>
#myId { color: blue; } /* 权重:100 */
.myClass { color: green; } /* 权重:10 */
div { color: red; } /* 权重:1 */
!important:!important 会破坏优先级规则,增加维护难度。JavaScript 是单线程语言,意味着它一次只能执行一个任务。如果某个任务耗时较长,可能会导致阻塞,影响页面的响应和渲染。以下是 JavaScript 阻塞问题的详细说明:
同步代码执行
for (let i = 0; i < 1000000000; i++) {}
// 长时间运行的循环
console.log('执行完毕'); // 被阻塞
setTimeout、Promise、async/await)将耗时任务放到事件循环中执行,避免阻塞主线程。DocumentFragment 减少 DOM 操作次数,避免频繁重排和重绘。requestAnimationFrame 中执行,确保与浏览器的渲染周期同步。使用 requestAnimationFrame
function animate() {
// 执行动画逻辑
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);
任务拆分
function processChunk(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 = new Worker('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('结束');
// 输出顺序:开始 → 结束 → 异步任务
异步代码非阻塞
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 密集型任务引起。通过以下方法可以避免阻塞:
Promise、async/await)。requestAnimationFrame 确保动画与渲染同步。Promise 是 JavaScript 中用于处理异步操作的对象,它解决了传统回调函数嵌套过深('回调地狱')的问题,提供了更清晰和可读性更高的代码结构。
.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 = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve('成功结果');
} else {
reject('失败原因');
}
});
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 = [
new Promise((resolve) => setTimeout(() => resolve('任务 1'), 1000)),
new Promise((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 链式调用代替嵌套回调。
function step1() {
return Promise.resolve('第一步');
}
function step2(data) {
return Promise.resolve(data + ' → 第二步');
}
step1().then(step2).then((result) => console.log(result));
处理异步操作:如网络请求、定时器、文件读取等。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('数据加载完成');
}, 1000);
});
}
fetchData()
.then((result) => console.log(result))
.catch((error) => console.log(error));
在处理大量数据时,JavaScript 可能会面临性能瓶颈,如内存占用过高、页面卡顿等问题。以下是处理大量数据的常用策略和优化方法:
懒加载
window.addEventListener('scroll', () => {
if (window.scrollY + window.innerHeight >= document.body.offsetHeight) {
loadMoreData();
}
});
分页加载
function loadData(page, pageSize) {
const start = (page - 1) * pageSize;
const end = start + pageSize;
return data.slice(start, end);
}
const pageData = loadData(1, 100); // 加载第一页的 100 条数据
Map)替代数组查找。分批处理
function processInBatches(data, batchSize, processFn) {
let index = 0;
function nextBatch() {
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 = new Worker('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 减少内存占用。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 = new ArrayBuffer(1024); // 1KB 缓冲区
const intArray = new Int32Array(buffer); // 32 位整数数组
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
function render() {
// 渲染逻辑
requestAnimationFrame(render);
}
render();
虚拟列表
function renderVirtualList(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);
});
_.chunk(分批处理)、_.debounce(防抖)。处理大量数据时,JavaScript 可以通过以下策略优化性能:
TypedArray、压缩数据、IndexedDB。requestAnimationFrame、减少 DOM 操作。在前端开发中,兼容性问题是一个常见的挑战。为了确保项目在不同浏览器、设备和操作系统上正常运行,以下是兼容性处理的详细策略和方法:
条件注释(仅限 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';
视口设置
<meta name="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%; }
}
/ 作为文件路径分隔符。字体兼容性
body {
font-family: Arial, Helvetica, sans-serif;
}
懒加载
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
图片优化
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback Image">
</picture>
前端项目兼容性处理的核心策略包括:
在 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 width="200" height="100">
<ellipse cx="100" cy="50" rx="100" ry="50" fill="#ff6347"/>
</svg>
cx 和 cy:椭圆中心点的坐标。rx 和 ry:椭圆的水平和垂直半径。在 CSS 中绘制椭圆的主要方法包括:
border-radius:通过设置 border-radius: 50% 将矩形变为椭圆。clip-path:使用 clip-path: ellipse() 裁剪路径绘制椭圆。<ellipse> 元素绘制椭圆。setTimeout 和 setIntervalsetTimeout 和 setInterval 是 JavaScript 中用于定时执行代码的两个核心 API。以下是它们的区别、使用场景以及 setTimeout 设置为 0 时的行为分析。
setTimeout 和 setInterval 的区别| 特性 | setTimeout | setInterval |
|---|---|---|
| 功能 | 在指定延迟后执行一次回调函数。 | 每隔指定时间重复执行回调函数。 |
| 语法 | setTimeout(callback, delay) | setInterval(callback, delay) |
| 停止方法 | clearTimeout(timeoutId) | clearInterval(intervalId) |
| 适用场景 | 延迟执行、单次任务。 | 重复执行、轮询任务。 |
setTimeout 设置为 0 的行为setTimeout 的延迟时间设置为 0 时,回调函数不会立即执行,而是会被放入事件队列中,等待当前调用栈清空后再执行。setTimeout 的回调函数属于宏任务,即使延迟时间为 0,也会被放入宏任务队列中,等待当前同步代码和微任务执行完毕后再执行。示例
console.log('开始');
setTimeout(() => {
console.log('setTimeout');
}, 0);
console.log('结束');
// 输出顺序:开始 → 结束 → setTimeout
setTimeout 和 setInterval 这两个 APIsetTimeout 的作用:用于延迟执行一次性任务。例如:延迟显示提示信息、延迟执行动画等。setInterval 的作用:用于重复执行周期性任务。例如:轮询服务器数据、定时更新 UI 等。setTimeout 和 setInterval 分别针对单次任务和重复任务,提供了更灵活的定时器功能。它们的实现基于事件循环机制,确保了异步任务的执行顺序和可控性。setTimeout 递归调用替代 setInterval。setTimeout 或 setInterval 中执行耗时操作,以免阻塞主线程。使用 requestAnimationFrame 替代 setTimeout 执行动画,确保与浏览器的渲染周期同步。setInterval 的问题
function repeat() {
console.log('重复执行');
setTimeout(repeat, 1000);
}
repeat();
setTimeout:用于延迟执行一次性任务,延迟时间为 0 时会在下一个事件循环执行。setInterval:用于重复执行周期性任务,但需注意回调函数执行时间过长的问题。setTimeout 和 setInterval 分别针对单次任务和重复任务,提供了灵活的定时器功能。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');
transform 和 opacity 替代直接修改布局属性。判断项目性能好不好的核心指标包括加载性能、交互性能、视觉稳定性和资源优化。通过 Lighthouse、WebPageTest 等工具可以全面评估项目性能,而 console.time、performance.now() 和 Chrome DevTools 的 Performance 面板则适合检测某个功能或组件的性能。结合优化建议,可以显著提升项目的性能和用户体验。
在开发过程中,有时需要针对某个功能或组件进行性能检测,以定位瓶颈并优化代码。以下是详细的检测方法和工具:
console.time 和 console.timeEndmyFunction: 0.123ms。示例
console.time('myFunction');
myFunction(); // 需要测试的函数
console.timeEnd('myFunction');
performance.now()示例
const start = performance.now();
myFunction(); // 需要测试的函数
const end = performance.now();
console.log(`耗时:${end - start} 毫秒`);
why-did-you-render(React 项目)配置
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React);
安装
npm install @welldone-software/why-did-you-render --save-dev
示例
class PerformanceTracker {
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 = new PerformanceTracker();
tracker.start('myFunction');
myFunction(); // 需要测试的函数
tracker.end('myFunction');
检测某个功能或组件性能的常用方法包括:
console.time 和 console.timeEnd:快速测量函数执行时间。performance.now():高精度计时。why-did-you-render:检测 React 组件的不必要渲染。HTTP 状态码是服务器对客户端请求的响应结果,用于表示请求的处理状态。以下是常见的状态码及其含义:
HTTP 状态码由 3 位数字组成,分为 5 大类:
HTTP 状态码是网络请求的重要组成部分,分为 1xx(信息性)、2xx(成功)、3xx(重定向)、4xx(客户端错误)和 5xx(服务器错误)五大类。理解常见的状态码及其含义,有助于开发、调试和优化网络请求。
箭头函数(Arrow Function)是 ES6 引入的一种简洁的函数语法,与普通函数(Function)在语法、行为和作用域上有显著区别。以下是两者的详细对比:
function 关键字定义。普通函数
function add(a, b) {
return a + b;
}
箭头函数
const add = (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 对象,包含所有传入的参数。arguments 对象,需使用剩余参数(Rest Parameters)获取参数。普通函数
function showArgs() {
console.log(arguments);
}
showArgs(1, 2, 3); // 输出:{ 0: 1, 1: 2, 2: 3 }
箭头函数
const showArgs = (...args) => {
console.log(args);
};
showArgs(1, 2, 3); // 输出:[1, 2, 3]
new 关键字。new 关键字。普通函数
function Foo() {}
const foo = new Foo(); // 正常执行
箭头函数
const Foo = () => {};
const foo = new Foo(); // 报错:Foo is not a constructor
prototype 属性,可以作为构造函数。prototype 属性,不能作为构造函数。普通函数
function Foo() {}
console.log(Foo.prototype); // 输出:{ constructor: Foo }
箭头函数
const Foo = () => {};
console.log(Foo.prototype); // 输出:undefined
this、arguments 或作为构造函数的场景。普通函数
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
箭头函数
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
| 特性 | 箭头函数 | 普通函数 |
|---|---|---|
| 语法 | 简洁,适合单行函数 | 使用 function 关键字定义 |
this 指向 | 继承自外层作用域 | 指向调用函数的对象 |
arguments 对象 | 无,需使用剩余参数 | 有 arguments 对象 |
| 构造函数 | 不能作为构造函数 | 可以作为构造函数 |
| 原型链 | 无 prototype 属性 | 有 prototype 属性 |
根据需求选择合适的函数类型,可以提升代码的可读性和可维护性。
防抖和节流是两种常见的性能优化技术,用于控制函数的执行频率,避免高频触发导致的性能问题。以下是它们的详细区别和应用场景:
setTimeout 延迟执行函数,每次触发事件时清除之前的定时器并重新计时。代码实现
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
setTimeout 控制函数的执行频率。代码实现
function throttle(func, delay) {
let lastTime = 0;
return function(...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 是单线程语言,通过事件循环(Event Loop)机制处理异步任务。为了更高效地管理任务,JavaScript 将异步任务分为宏任务(Macro Task)和微任务(Micro Task)。以下是宏任务和微任务的存在原因及其区别:
setTimeout、setInterval、I/O 操作(如文件读取、网络请求)、UI 渲染、script(整体代码)。Promise.then、Promise.catch、Promise.finally、MutationObserver、queueMicrotask。script 整体代码)。开始 → 结束 → Promise → setTimeout示例
console.log('开始');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('结束');
| 特性 | 宏任务(Macro Task) | 微任务(Micro Task) |
|---|---|---|
| 任务类型 | setTimeout、setInterval、I/O 操作 | Promise.then、MutationObserver |
| 执行顺序 | 在微任务之后执行 | 在宏任务之后、渲染之前执行 |
| 优先级 | 低 | 高 |
| 任务粒度 | 较大 | 较小 |
JavaScript 引入宏任务和微任务的原因包括:
跨域问题是由浏览器的 同源策略(Same-Origin Policy)引起的安全机制,用于防止不同源的网站之间进行恶意数据交互。以下是跨域问题的原因、表现及解决方案:
https://www.example.com 和 https://api.example.com 不同源(域名不同)。http://example.com 和 https://example.com 不同源(协议不同)。http://example.com:80 和 http://example.com:8080 不同源(端口不同)。Access-Control-Allow-Origin:允许的源(如 * 或 https://www.example.com)。Access-Control-Allow-Methods:允许的 HTTP 方法(如 GET, POST)。Access-Control-Allow-Headers:允许的请求头(如 Content-Type)。<script> 标签不受同源策略限制的特性,通过回调函数获取跨域数据。限制:仅支持 GET 请求。https://www.example.com/api/data,后端服务器转发请求:https://api.example.com/data。示例:
// 发送消息
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 = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
console.log(event.data);
};
示例:
<script>
function handleResponse(data) {
console.log(data);
}
</script>
<script src="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
https://www.example.com,后端:https://api.example.com。解决方案:CORS、代理服务器、Nginx 反向代理。跨域问题是由浏览器的同源策略引起的,常见解决方案包括:
<script> 标签获取跨域数据。前端项目的加载速度直接影响用户体验和 SEO 排名。以下是提高前端项目加载速度的常用方法:
示例:
<picture>
<source srcset="image.webp" type="image/webp">
<img src="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;
示例:
npm install terser-webpack-plugin --save-dev
示例:
.icon {
background-image: url('sprite.png');
background-position: -10px -20px;
}
示例:
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
示例:
import { func1 } from 'module';
func1();
示例:
import('./module').then(module => {
module.default();
});
Cache-Control 和 ETag 响应头,利用浏览器缓存静态资源。示例:
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";
}
transform 和 opacity 替代直接修改布局属性。requestAnimationFrame:将动画逻辑放到 requestAnimationFrame 中执行,确保与浏览器的渲染周期同步。示例:
<style>
/* 关键 CSS */
</style>
示例:
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
animate();
示例:
.element {
transform: translateX(100px);
opacity: 0.5;
}
提高前端项目加载速度的核心方法包括:
requestAnimationFrame。在浏览器中,多个页签之间可以通过以下几种方式进行消息传递:
localStorage 或 sessionStoragelocalStorage 和 sessionStorage 是浏览器提供的存储机制,可以在同一源的不同页签之间共享数据。storage 事件。在页签 B 中监听 storage 事件,获取数据。storage 事件不会在当前页签触发。示例代码
// 页签 A:设置数据
localStorage.setItem('message', 'Hello from Tab A');
// 页签 B:监听 storage 事件
window.addEventListener('storage', (event) => {
if (event.key === 'message') {
console.log('收到消息:', event.newValue);
}
});
BroadcastChannelBroadcastChannel 是浏览器提供的 API,允许同一源的不同页签之间通过消息通道进行通信。BroadcastChannel。在页签 A 中发送消息,在页签 B 中接收消息。BroadcastChannel。示例代码
// 页签 A:发送消息
const channel = new BroadcastChannel('myChannel');
channel.postMessage('Hello from Tab A');
// 页签 B:接收消息
const channel = new BroadcastChannel('myChannel');
channel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
SharedWorkerSharedWorker 是浏览器提供的 Web Worker,允许同一源的不同页签之间共享一个后台线程进行通信。SharedWorker。在页签 A 中发送消息,在页签 B 中接收消息。SharedWorker 需要额外的 JavaScript 文件。示例代码
// sharedWorker.js
const connections = [];
self.onconnect = (event) => {
const port = event.ports[0];
connections.push(port);
port.onmessage = (event) => {
connections.forEach((conn) => {
if (conn !== port) conn.postMessage(event.data);
});
};
};
// 页签 A:发送消息
const worker = new SharedWorker('sharedWorker.js');
worker.port.postMessage('Hello from Tab A');
// 页签 B:接收消息
const worker = new SharedWorker('sharedWorker.js');
worker.port.onmessage = (event) => {
console.log('收到消息:', event.data);
};
window.open 和 postMessagewindow.open 打开新页签,并使用 postMessage 进行跨页签通信。window.open 打开页签 B,并保存页签 B 的引用。在页签 A 中使用 postMessage 发送消息,在页签 B 中监听 message 事件。示例代码
// 页签 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 是用于声明变量的关键字,它们在作用域、提升和重复声明等方面有显著区别。以下是它们的详细对比:
var:函数作用域:在函数内部声明的变量,只能在函数内部访问。全局作用域:在函数外部声明的变量,可以在全局访问。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
var:变量会被提升到函数或全局作用域的顶部,但赋值不会被提升。let 和 const:变量会被提升到块级作用域的顶部,但在声明之前访问会触发'暂时性死区'(Temporal Dead Zone,TDZ)。示例:
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 2;
示例:
console.log(a); // 输出:undefined
var a = 1;
var:允许重复声明,后面的声明会覆盖前面的声明。let 和 const:不允许重复声明,会报错。示例:
let b = 1;
let b = 2; // 报错:Identifier 'b' has already been declared
示例:
var a = 1;
var a = 2;
console.log(a); // 输出:2
var 和 let:声明的变量可以重新赋值。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;
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 提升(赋值不提升) | 提升(存在 TDZ) | 提升(存在 TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 值的修改 | 允许 | 允许 | 不允许(对象属性可修改) |
const:如果变量的值不需要修改,使用 const 可以避免意外赋值。let:如果变量的值需要修改,使用 let 可以避免 var 的作用域问题。var:var 的作用域和提升机制容易导致代码难以维护,建议使用 let 和 const 替代。作用域(Scope)和作用域链(Scope Chain)是 JavaScript 中理解变量和函数访问规则的核心概念。以下是它们的详细说明:
{} 块内部声明的变量(使用 let 或 const),只能在块内部访问。示例
// 全局作用域
const globalVar = 'Global';
function myFunction() {
// 函数作用域
const functionVar = 'Function';
console.log(globalVar); // 输出:Global
}
if (true) {
// 块级作用域
const blockVar = 'Block';
console.log(globalVar); // 输出:Global
}
console.log(blockVar); // 报错:blockVar is not defined
innerFunction 中访问变量时,作用域链的顺序为:innerFunction → outerFunction → global示例
const globalVar = 'Global';
function outerFunction() {
const outerVar = 'Outer';
function innerFunction() {
const innerVar = 'Inner';
console.log(globalVar); // 输出:Global
console.log(outerVar); // 输出:Outer
console.log(innerVar); // 输出:Inner
}
innerFunction();
}
outerFunction();
示例:
function outerFunction() {
const outerVar = 'Outer';
function innerFunction() {
console.log(outerVar); // 输出:Outer
}
return innerFunction;
}
const closure = outerFunction();
closure();
JavaScript 是基于原型的语言,没有类的概念,但可以通过多种方式实现继承。以下是常见的继承方式及其优缺点:
实现
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child() {}
Child.prototype = new Parent();
const child = new Child();
child.sayHello(); // 输出:Hello from Parent
实现
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
}
const child = new Child('Child');
console.log(child.name); // 输出:Child
实现
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
const child = new Child('Child');
child.sayHello(); // 输出:Hello from Child
实现
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
实现
function createChild(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
实现
function inheritPrototype(Child, Parent) {
const prototype = Object.create(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name) {
Parent.call(this, name);
}
inheritPrototype(Child, Parent);
const child = new Child('Child');
child.sayHello(); // 输出:Hello from Child
class 和 extends 关键字实现继承。实现
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello from ' + this.name);
}
}
class Child extends Parent {
constructor(name) {
super(name);
}
}
const child = new Child('Child');
child.sayHello(); // 输出:Hello from Child
| 继承方式 | 优点 | 缺点 |
|---|---|---|
| 原型链继承 | 简单易用 | 引用类型属性共享,无法传参 |
| 构造函数继承 | 解决引用类型属性共享,支持传参 | 无法继承父类原型上的方法 |
| 组合继承 | 结合原型链和构造函数继承 | 父类构造函数被调用两次 |
| 原型式继承 | 简单易用,适合基于已有对象创建新对象 | 引用类型属性共享 |
| 寄生式继承 | 增强对象功能 | 引用类型属性共享 |
| 寄生组合式继承 | 解决组合继承的性能问题 | 实现复杂 |
| ES6 类继承 | 语法简洁,解决传统继承方式的缺点 | 需要支持 ES6 的浏览器或环境 |
根据需求选择合适的继承方式,可以编写出更高效、更易维护的 JavaScript 代码。
前端缓存和数据持久化是提升应用性能、优化用户体验的重要手段。以下是它们的详细说明和应用场景:
前端缓存是指将数据或资源存储在客户端(如浏览器),以减少网络请求、加快资源加载速度。
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 = new Blob(['Hello, world!'], { type: 'text/plain' });
const reader = new FileReader();
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' }
require 和 import 的区别详解require 和 import 是 JavaScript 中用于加载模块的两种方式,分别用于 CommonJS 和 ES6 模块系统。以下是它们的详细对比:
require(CommonJS):动态加载:可以在代码的任何位置使用 require。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');
require:同步加载:模块加载是同步执行的,会阻塞后续代码的执行。运行时加载:模块在运行时加载并执行。import:异步加载:模块加载是异步的,不会阻塞后续代码的执行。编译时加载:模块在代码编译时加载,支持静态分析。require:使用 module.exports 或 exports 导出模块。import:使用 export 或 export default 导出模块。示例:
// module.js
export const 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
require:适用于 Node.js 环境,主要用于后端开发。支持动态加载,适合需要根据条件加载模块的场景。import:适用于现代浏览器和前端开发,支持静态分析和 Tree Shaking。适合模块化开发和代码优化。require:在 Node.js 环境中原生支持,但在浏览器中需要使用打包工具(如 Webpack、Browserify)进行转换。import:在现代浏览器中支持,但在 Node.js 中需要使用 .mjs 文件或在 package.json 中设置 "type": "module"。| 特性 | require(CommonJS) | import(ES6 Modules) |
|---|---|---|
| 语法 | const module = require('module') | import module from 'module' |
| 加载方式 | 同步加载,运行时加载 | 异步加载,编译时加载 |
| 模块导出 | module.exports 或 exports | export 或 export default |
| 适用场景 | Node.js 环境,动态加载 | 现代浏览器,模块化开发 |
| 兼容性 | Node.js 原生支持 | 现代浏览器支持,Node.js 需配置 |
px、em 和 rem 的区别详解px、em 和 rem 是 CSS 中常用的长度单位,它们在网页布局和响应式设计中扮演着重要角色。以下是它们的详细对比:
px(像素)px 是绝对单位,表示屏幕上的一个像素点。示例
.element {
width: 100px;
height: 50px;
}
emem 是相对单位,基于当前元素的字体大小(font-size)。1em 等于当前元素的 font-size 值。如果当前元素未设置 font-size,则继承父元素的 font-size。适合需要根据字体大小调整布局的场景。示例
.parent {
font-size: 16px;
}
.child {
font-size: 1.5em; /* 16px * 1.5 = 24px */
padding: 1em; /* 24px */
}
remrem 是相对单位,基于根元素(<html>)的字体大小(font-size)。1rem 等于根元素的 font-size 值(通常为 16px)。不受父元素 font-size 的影响,适合全局统一的布局调整。示例
html {
font-size: 16px;
}
.element {
font-size: 1.5rem; /* 16px * 1.5 = 24px */
padding: 1rem; /* 16px */
}
| 特性 | px | em | rem |
|---|---|---|---|
| 定义 | 绝对单位,表示像素 | 相对单位,基于当前元素的 font-size | 相对单位,基于根元素的 font-size |
| 特点 | 固定大小,不受其他元素影响 | 受当前元素和父元素的 font-size 影响 | 仅受根元素的 font-size 影响 |
| 适用场景 | 需要精确控制尺寸的场景 | 需要根据字体大小调整布局的场景 | 全局统一的布局调整 |
px:适合需要精确控制尺寸的场景,如边框、阴影等。em:适合需要根据字体大小调整布局的场景,如按钮、标题等。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 的区别,可以根据需求选择合适的单位,实现更灵活的布局和响应式设计。
在开发前端项目时,浏览器兼容性问题是一个常见的挑战。以下是常见的兼容性问题及其解决方案:
width 和 height 包括 padding 和 border,而标准盒模型不包括。解决方案:使用 box-sizing: border-box 统一盒模型。autoprefixer 自动添加浏览器前缀。@supports 提供回退方案。CSS Grid 兼容性
.container {
display: grid;
}
@supports not (display: grid) {
.container {
display: flex;
}
}
Flexbox 兼容性
.container {
display: flex;
}
盒模型差异
* {
box-sizing: border-box;
}
let、const、箭头函数)。解决方案:使用 Babel 将 ES6+ 代码转换为 ES5。fetch、Promise)。解决方案:使用 Polyfill 提供兼容性支持。API 兼容性
npm install whatwg-fetch --save
import 'whatwg-fetch';
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
ES6+ 语法兼容性
npm install @babel/core @babel/preset-env --save-dev
// .babelrc
{
"presets": ["@babel/preset-env"]
}
<header>、<footer>)。解决方案:使用 html5shiv 提供兼容性支持。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]-->
@font-face 加载自定义字体。<picture> 标签提供回退方案。图片兼容性
<picture>
<source srcset="image.webp" type="image/webp">
<img src="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 盒模型(Box Model)是网页布局的基础,它定义了元素的尺寸、内边距、边框和外边距之间的关系。以下是盒模型的详细说明:
盒模型由以下四部分组成:
width 和 height 设置内容区域的尺寸。padding 设置内边距。border 设置边框的样式、宽度和颜色。margin 设置外边距。width 和 height 仅包括内容区域。width + padding + border + marginheight + padding + border + marginwidth 和 height 包括内容区域、内边距和边框。width + marginheight + marginbox-sizing 属性切换盒模型。切换盒模型
/* 标准盒模型 */
.element {
box-sizing: content-box;
}
/* IE 盒模型 */
.element {
box-sizing: border-box;
}
200px + 10px * 2 + 5px * 2 + 20px * 2 = 270pxheight + padding * 2 + border * 2 + margin * 2200px + 20px * 2 = 240pxheight + margin * 2IE 盒模型计算
.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;
}
width 和 height 仅包括内容区域。width 和 height 包括内容区域、内边距和边框。padding 和 margin 调整布局,使用 box-sizing 切换盒模型。ES6 是 JavaScript 的重要更新,引入了许多新特性,使代码更简洁、更易读、更强大。以下是 ES6 的主要新特性:
let 用于声明块级作用域的变量。const 用于声明常量,值不可重新赋值。let 和 const
let x = 10;
const y = 20;
=> 定义函数,简化函数书写。this,this 继承自外层作用域。不能作为构造函数使用。语法
const add = (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
语法
function greet(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 Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
const person = new Person('Alice');
person.greet(); // 输出:Hello, Alice!
导入模块
// app.js
import { add } from './module';
console.log(add(1, 2)); // 输出:3
导出模块
// module.js
export const add = (a, b) => a + b;
语法
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Success'), 1000);
});
promise.then(result => console.log(result)); // 输出:Success
语法
const id = Symbol('id');
const obj = { [id]: 123 };
console.log(obj[id]); // 输出:123
Symbol.iterator 定义迭代器。function* 定义生成器函数。生成器
function* generator() {
yield 1;
yield 2;
}
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
}
Map
const map = new Map();
map.set('name', 'Alice');
console.log(map.get('name')); // 输出:Alice
Set
const set = new Set([1, 2, 3]);
set.add(4);
console.log(set.has(2)); // 输出:true
HTTP 缓存是提升网页性能的重要手段,分为强缓存和协商缓存。以下是它们的详细说明:
Expires:通过指定过期时间设置缓存的有效时间。Expires: Wed, 01 Jan 2025 00:00:00 GMT
Cache-Control:通过 max-age 设置缓存的有效时间。Cache-Control: max-age=3600
Last-Modified)。浏览器下次请求时带上 If-Modified-Since,服务器判断资源是否修改。ETag)。浏览器下次请求时带上 If-None-Match,服务器判断资源是否修改。示例
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、Expires | Last-Modified、ETag |
| 适用场景 | 静态资源 | 动态资源 |
max-age=31536000)。通过文件哈希或版本号更新缓存(如 style.[hash].css)。max-age=60)。Cache-Control 和 Expires 实现,适合缓存静态资源。Last-Modified 和 ETag 实现,适合缓存动态资源。Vue Router 是 Vue.js 官方的路由管理器,支持两种模式:Hash 模式和History 模式。以下是它们的详细说明:
#)来实现路由。http://example.com/#/home。哈希部分的变化不会导致页面刷新。兼容性好,支持所有浏览器。#,不够美观。配置
const router = new VueRouter({
mode: 'hash',
routes: [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
});
history.pushState API 来实现路由。http://example.com/home。URL 更美观,不包含 #。需要服务器配置,避免刷新时返回 404 错误。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;
}
#。支持 SEO,适合动态站点。配置
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
});
| 特性 | Hash 模式 | History 模式 |
|---|---|---|
| URL 示例 | http://example.com/#/home | http://example.com/home |
| 是否需要服务器配置 | 否 | 是 |
| 兼容性 | 兼容所有浏览器 | 需要 HTML5 支持 |
| SEO 支持 | 不支持 | 支持 |
| 适用场景 | 静态站点 | 动态站点 |
JavaScript 是一种强大的脚本语言,广泛应用于前端和后端开发。然而,它也存在一些缺点,以下是主要的缺点及其影响:
Promise、async/await)。使用 Web Workers 在后台执行耗时任务。示例:
let x = 10;
x = 'Hello'; // 类型改变,可能导致错误
requestAnimationFrame 优化动画性能。console.log 或 debugger 语句辅助调试。| 缺点 | 问题描述 | 解决方案 |
|---|---|---|
| 单线程模型 | 长时间任务阻塞主线程 | 使用异步编程、Web Workers |
| 弱类型语言 | 变量类型动态改变,容易引发运行时错误 | 使用 TypeScript、ESLint |
| 浏览器兼容性 | 不同浏览器支持不一致 | 使用 Babel、Polyfill |
| 安全问题 | 容易被恶意用户篡改或攻击 | 验证用户输入、使用 HTTPS |
| 性能问题 | 执行速度慢,复杂 DOM 操作影响性能 | 使用虚拟 DOM、优化动画 |
| 调试困难 | 错误提示不明确,异步代码调试复杂 | 使用开发者工具、console.log |
| 生态系统碎片化 | 工具和库选择过多,学习成本高 | 根据需求选择工具,关注主流技术趋势 |
理解 JavaScript 的缺点及其解决方案,可以帮助开发者更好地应对挑战,编写更高效、更安全的代码。
JavaScript 是一种基于原型的语言,原型(Prototype)是 JavaScript 实现继承和共享属性和方法的核心机制。以下是原型的详细说明:
null)都有一个原型对象,对象从原型对象继承属性和方法。__proto__ 属性,指向其原型对象。prototype 属性,指向该函数的原型对象。prototype 属性。构造函数与原型
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, ${this.name}!`);
};
const person = new Person('Alice');
person.greet(); // 输出:Hello, Alice!
prototype 属性
function Person() {}
console.log(Person.prototype); // 输出:Person {}
__proto__ 属性
const obj = {};
console.log(obj.__proto__); // 输出:Object.prototype
obj 本身没有 toString 方法,但它的原型对象 Object.prototype 有 toString 方法。示例
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!
原型继承
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child() {}
Child.prototype = new Parent();
const child = new Child();
child.sayHello(); // 输出:Hello, Parent!
XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)是两种常见的 Web 安全漏洞,以下是它们的详细说明及防御措施:
Content-Security-Policy 限制脚本加载来源。HttpOnly 标记 Cookie,防止 JavaScript 访问。设置 HTTP 头:
Content-Security-Policy: default-src 'self'
Set-Cookie: sessionId=123; HttpOnly; Secure
输入过滤:对用户输入进行严格的验证和过滤,移除或转义特殊字符。
function escapeHTML(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Referer 头,确保请求来自合法来源。SameSite 属性限制 Cookie 的发送范围。设置 SameSite Cookie:
Set-Cookie: sessionId=123; SameSite=Strict; Secure
使用 CSRF Token:
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="csrf_token">
<button type="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(HyperText Transfer Protocol,超文本传输协议)是用于在 Web 浏览器和服务器之间传输数据的协议。以下是 HTTP 的详细说明:
响应头:包含响应的元信息(如 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
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
100 Continue。200 OK、201 Created。301 Moved Permanently、302 Found。400 Bad Request、404 Not Found。500 Internal Server Error、503 Service Unavailable。HTTP 是 Web 开发的基础协议,用于在客户端和服务器之间传输数据。理解 HTTP 的工作流程、请求和响应、方法、状态码和版本,可以帮助开发者更好地进行 Web 开发,优化应用性能。
分片(Chunking)是一种将大数据集或任务分割成多个小块(Chunk)进行处理的技术,常用于优化性能、减少内存占用或实现渐进式加载。以下是 JavaScript 中实现分片的常见方法:
将大数组分割成多个小数组,分批处理。
function chunkArray(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]]
将大文件分割成多个小文件,用于上传或处理。
function chunkFile(file, size) {
const chunks = [];
let start = 0;
while (start < file.size) {
chunks.push(file.slice(start, start + size));
start += size;
}
return chunks;
}
const file = new File(['a'.repeat(1024 * 1024)], 'example.txt');
const chunks = chunkFile(file, 1024 * 100); // 每片 100KB
console.log(chunks); // 输出:[Blob, Blob, ...]
将耗时任务分割成多个小任务,使用 setTimeout 或 requestAnimationFrame 分批执行。
function chunkTask(task, data, size, callback) {
let index = 0;
function run() {
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 = new Array(1000).fill(0).map((_, i) => i);
chunkTask((item) => console.log(item), data, 100, () => console.log('任务完成'));
使用 ReadableStream 将数据流分割成多个小块,实现渐进式加载或处理。
async function chunkStream(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 = new ReadableStream({
start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(new Uint8Array([i]));
}
controller.close();
}
});
chunkStream(stream, 2).then(chunks => console.log(chunks));
将任务分片后交给 Web Workers 并行处理。
// main.js
const worker = new Worker('worker.js');
const data = new Array(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 应用的性能、内存占用和用户体验。根据具体需求选择合适的分片方法,实现高效处理。
懒加载是一种优化技术,延迟加载非关键资源(如图片、视频、脚本等),直到用户需要访问它们时再加载。以下是懒加载的原理、实现方法和应用场景:
scroll、resize 等事件,判断资源是否进入可视区域。将图片的 src 属性替换为 data-src,当图片进入可视区域时,再将 data-src 的值赋给 src。
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
document.addEventListener('DOMContentLoaded', () => {
const images = document.querySelectorAll('.lazyload');
const lazyLoad = () => {
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();
});
将视频的 src 属性替换为 data-src,当视频进入可视区域时,再加载视频。
<video data-src="video.mp4" controls class="lazyload"></video>
document.addEventListener('DOMContentLoaded', () => {
const videos = document.querySelectorAll('.lazyload');
const lazyLoad = () => {
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();
});
Intersection Observer API 是一种更高效的懒加载实现方式,无需监听 scroll 和 resize 事件。
document.addEventListener('DOMContentLoaded', () => {
const images = document.querySelectorAll('.lazyload');
const observer = new IntersectionObserver((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));
});
懒加载的核心原理是延迟加载非关键资源,直到用户需要访问它们时再加载。常见的实现方法包括:
scroll 和 resize 事件实现。通过懒加载技术,可以显著提升页面加载速度,优化用户体验,减少不必要的资源消耗。
脱离文档流是指元素不再占据页面布局中的空间,其他元素会忽略其位置。以下是常见的脱离文档流的方法及其特点:
position: absolutestatic 定位的祖先元素定位。如果没有非 static 定位的祖先元素,则相对于 body 定位。示例
.element {
position: absolute;
top: 10px;
left: 10px;
}
position: fixed示例
.element {
position: fixed;
top: 10px;
left: 10px;
}
float示例
.element {
float: left;
}
display: none示例
.element {
display: none;
}
visibility: hiddenvisibility: 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 中,堆(Heap)和栈(Stack)是两种不同的内存存储方式,用于存储不同类型的数据。以下是它们的详细说明:
undefined、null、boolean、number、string、symbol、bigint。示例
let a = 10; // 基本类型,存储在栈中
function foo() {
let b = 20; // 局部变量,存储在栈中
}
foo();
object、array、function、date 等。示例
let obj = { name: 'Alice' }; // 引用类型,存储在堆中
let arr = [1, 2, 3]; // 引用类型,存储在堆中
示例
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 的基本用法message 的值会自动更新。当 message 的值改变时,输入框的内容也会自动更新。语法
<input v-model="message" />
v-model 的实现原理v-model 的本质:v-model 是 v-bind 和 v-on 的语法糖,结合了属性绑定和事件监听。v-model 的展开形式:对于 <textarea> 和 <select> 元素,v-model 的展开形式类似。v-model 的工作流程:
message 的值绑定到输入框的 value 属性。input 事件,将用户输入的值赋给 message。message 的值改变时,更新输入框的 value 属性。对于 <input> 元素,v-model 的展开形式如下:
<input :value="message" @input="message = $event.target.value" />
v-model 的源码解析v-model 的实现位于 Vue 源码的 src/platforms/web/compiler/directives/model.js。v-bind 和 v-on 指令。监听表单元素的事件(如 input、change),更新绑定的数据。示例代码
function model(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') {
// 处理复选框
} else if (tag === 'input' && type === 'radio') {
// 处理单选框
} else if (tag === 'input' || tag === 'textarea') {
// 处理输入框和文本域
genDefaultModel(el, value, modifiers);
} else if (tag === 'select') {
// 处理下拉框
} else {
// 其他情况
}
}
v-modelv-model 在自定义组件中绑定 value 属性和 input 事件。使用 model 选项自定义 v-model 的属性和事件。自定义 v-model
export default {
model: {
prop: 'checked',
event: 'change'
},
props: ['checked'],
template: `
<input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" />
`
};
v-model 的本质:v-model 是 v-bind 和 v-on 的语法糖,用于实现双向数据绑定。v-model 的工作流程:初始化时将数据绑定到表单元素的 value 属性。监听表单元素的事件,更新绑定的数据。v-model:使用 model 选项自定义 v-model 的属性和事件。在 Vue.js 中,组件通信是开发复杂应用的关键。以下是 Vue 组件通信的多种方式及其适用场景:
props 和 $emit:实现父子组件的双向数据绑定。v-model
// 子组件
export default {
model: {
prop: 'value',
event: 'input'
},
props: ['value']
};
// 父组件
<Child v-model="message" />
子组件向父组件传递数据:通过 $emit 触发事件。
// 子组件
this.$emit('update', newValue);
// 父组件
<Child @update="handleUpdate" />
父组件向子组件传递数据:通过 props。
// 父组件
<Child :message="message" />
// 子组件
export default {
props: ['message']
};
使用 Event Bus
// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();
// 组件 A
EventBus.$emit('update', newValue);
// 组件 B
EventBus.$on('update', value => {
console.log(value);
});
通过父组件中转
// 父组件
<ChildA @update="handleUpdate" />
<ChildB :message="message" />
provide 提供数据,后代组件通过 inject 注入数据。Vuex
// store.js
export default new Vuex.Store({
state: {
message: 'Hello'
},
mutations: {
updateMessage(state, newValue) {
state.message = newValue;
}
}
});
// 组件 A
this.$store.commit('updateMessage', newValue);
// 组件 B
this.$store.state.message;
provide 和 inject
// 祖先组件
export default {
provide() {
return {
message: this.message
};
}
};
// 后代组件
export default {
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) |
provide 和 inject | 跨层级组件通信 | provide: { message }, inject: ['message'] |
| Vuex | 全局状态管理 | this.$store.commit('updateMessage', newValue) |
$refs | 直接访问子组件 | this.$refs.child.methodName() |
$parent 和 $children | 访问父组件或子组件 | this.$parent.methodName() |
通过理解这些组件通信方式,可以根据具体需求选择最合适的方案,实现高效的组件交互和数据共享。
Diff 算法是虚拟 DOM(Virtual DOM)的核心算法,用于高效地比较两个虚拟 DOM 树的差异,并最小化实际 DOM 的操作。以下是 Diff 算法的详细说明:
key)优化列表项的对比。div 变为 span),直接替换整个节点。key 对比)进行优化。key 的情况:按顺序对比子节点,性能较差。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 对比,更新节点
key):通过 key 优化列表项的对比,减少不必要的 DOM 操作。src/core/vdom/patch.js。patchVnode 函数比较新旧节点。通过 updateChildren 函数比较子节点列表。示例代码
function patchVnode(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);
} else if (isDef(ch)) {
addVnodes(elm, null, ch, 0, ch.length - 1);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
} else if (oldVnode.text !== vnode.text) {
setTextContent(elm, vnode.text);
}
}
key 优化列表项的对比。key 作为唯一标识,批量更新 DOM 操作。理解 Diff 算法的原理和实现,可以帮助开发者更好地优化 Vue 应用的性能。
Webpack 是一个模块打包工具,用于将 JavaScript、CSS、图片等资源打包成静态文件。以下是 Webpack 的基本配置及其详细说明:
development 或生产模式 production)。npm install webpack webpack-cli --save-dev
在项目根目录下创建 webpack.config.js 文件。
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: [] // 插件配置
};
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
output: {
filename: '[name].bundle.js', // 使用 [name] 占位符
path: path.resolve(__dirname, 'dist')
}
};
图片加载器
npm install file-loader --save-dev
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: ['file-loader']
}
]
}
};
Babel 加载器
npm install 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 加载器
npm install style-loader css-loader --save-dev
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
代码压缩插件
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new TerserPlugin()]
}
};
HTML 插件
npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html' // 指定模板文件
})
]
};
npm install webpack-dev-server --save-dev
module.exports = {
devServer: {
contentBase: './dist', // 服务器根目录
port: 8080, // 端口号
hot: true // 启用热更新
}
};
通过理解 Webpack 的配置和常用插件,可以高效地打包和管理前端项目。
当用户在浏览器中输入 URL 并按下回车键后,浏览器会执行一系列操作,最终将网页呈现给用户。以下是这一过程的详细步骤:
http 或 https)、域名(如 www.example.com)、端口(默认 80 或 443)和路径(如 /index.html)。SYN 报文。SYN-ACK 报文。ACK 报文。GET /index.html HTTP/1.1)、请求头(如 Host、User-Agent)和请求体(如 POST 数据)。HTTP/1.1 200 OK)、响应头(如 Content-Type、Content-Length)和响应体(如 HTML 内容)。200 表示成功,404 表示未找到),决定后续操作。DOMContentLoaded 和 load 事件,表示页面加载完成。| 步骤 | 描述 |
|---|---|
| URL 解析 | 解析协议、域名、端口和路径,检查缓存 |
| DNS 解析 | 查询 DNS 缓存,递归查询域名对应的 IP 地址 |
| 建立 TCP 连接 | 通过三次握手建立连接,HTTPS 进行 TLS 握手 |
| 发送 HTTP 请求 | 构造并发送 HTTP 请求到服务器 |
| 服务器处理请求 | 解析请求,处理请求,生成 HTTP 响应 |
| 接收 HTTP 响应 | 接收并检查服务器返回的 HTTP 响应 |
| 解析和渲染页面 | 构建 DOM 树、CSSOM 树、渲染树,布局和绘制 |
| 执行 JavaScript | 解析和执行 JavaScript,触发页面事件 |
前端优化是提升网页性能、用户体验和 SEO 效果的关键。以下是常见的前端优化方法及其详细说明:
background-position 显示不同图标。Cache-Control 和 Expires 头,缓存静态资源。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"> 预加载关键资源。预加载关键资源
<link rel="preload" href="styles.css" as="style">
异步加载脚本
<script src="app.js" async></script>
延迟加载
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
@import,减少 CSS 嵌套层级,移除未使用的样式。<picture> 和 srcset 提供不同分辨率的图片。响应式图片
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback Image">
</picture>
transform 和 opacity 替代 top、left 等属性,减少重排。使用 requestAnimationFrame 优化动画性能。使用 requestAnimationFrame
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
<header>、<article>)提升 SEO 效果。设置 title、description 和 keywords 等 meta 标签。使用 JSON-LD 或 Microdata 添加结构化数据。使用结构化数据
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"Article","headline":"Article Title"}
</script>
优化 meta 标签
<meta name="description" content="Page description">
| 优化方法 | 描述 |
|---|---|
| 减少 HTTP 请求 | 合并文件、使用雪碧图、内联资源 |
| 使用缓存 | 浏览器缓存、Service Worker 缓存 |
| 优化资源加载 | 延迟加载、异步加载脚本、预加载关键资源 |
| 优化代码 | 压缩代码、移除未使用的代码、优化 CSS |
| 优化图片 | 使用合适的格式、压缩图片、响应式图片 |
| 优化渲染性能 | 减少重绘和重排、使用 requestAnimationFrame、虚拟 DOM |
| 优化 SEO | 语义化 HTML、优化 meta 标签、结构化数据 |
通过理解这些前端优化方法,可以显著提升网页性能、用户体验和 SEO 效果。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online