闭包(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)的协作,实现了非阻塞的异步执行模型。
调用栈(Call Stack)
任务队列(Task Queue)
宏任务(Macro Task)与微任务(Micro Task)
setTimeout
、setInterval
、I/O
操作、UI渲染
等。Promise.then
、MutationObserver
、process.nextTick
(Node.js)等。同步任务执行:
微任务执行:
宏任务执行:
UI渲染:
循环继续:
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:
)。float
值不为 none
。position
值为 absolute
或 fixed
。display
值为 inline-block
、table-cell
、table-caption
、flex
、inline-flex
、grid
、inline-grid
。overflow
值不为 visible
(如 hidden
、auto
、scroll
)。清除浮动当父元素包含浮动子元素时,父元素的高度会塌陷。通过触发BFC可以解决此问题:
.parent {
overflow: hidden; /* 触发BFC */
}
避免外边距重叠相邻元素的外边距会重叠,通过触发BFC可以避免:
.box {
display: inline-block; /* 触发BFC */
}
实现多栏布局利用BFC阻止元素被浮动元素覆盖,实现多栏布局:
.left {
float: left;
width: 200px;
}
.right {
overflow: hidden; /* 触发BFC */
}
隔离布局BFC内部的布局不会影响外部元素,适合实现独立的UI组件。
清除浮动
<div class="parent">
<div class="child" style="float: left;">浮动元素div>
div>
<style>
.parent {
overflow: hidden; /* 触发BFC */
border: 1px solid #000;
}
style>
避免外边距重叠
<div class="box" style="margin: 20px;">元素1div>
<div class="box" style="margin: 20px;">元素2div>
<style>
.box {
display: inline-block; /* 触发BFC */
width: 100%;
}
style>
实现多栏布局
<div class="left">左侧栏div>
<div class="right">右侧栏div>
<style>
.left {
float: left;
width: 200px;
background: #ccc;
}
.right {
overflow: hidden; /* 触发BFC */
background: #f0f0f0;
}
style>
overflow: hidden
)可能会导致性能问题。clearfix
方法。BFC是CSS布局中的重要概念,通过触发BFC可以解决浮动、外边距重叠等问题,实现更灵活的布局。理解BFC的触发条件和特性,有助于编写更健壮和可维护的CSS代码。在实际开发中,应根据需求合理使用BFC,避免过度依赖。
内存泄漏(Memory Leak)是指程序中已不再使用的内存未被释放,导致内存占用持续增加,最终可能引发性能下降甚至崩溃。在JavaScript中,内存泄漏通常由不当的引用管理引起。
垃圾回收(GC) JavaScript通过垃圾回收机制自动管理内存,主要算法包括:
window
)出发,标记所有可达对象,清除未标记的对象。常见回收策略
意外全局变量未使用var
、let
或const
声明的变量会挂载到全局对象(如window
),导致内存无法释放。
function leak() {
globalVar = '这是一个全局变量'; // 未使用var/let/const
}
未清理的定时器或回调函数定时器或事件监听器未及时清除,会导致相关对象无法回收。
let data = fetchData();
setInterval(() => {
process(data); // data一直被引用,无法回收
}, 1000);
闭包引用闭包会保留对外部作用域的引用,如果闭包未释放,相关内存也无法回收。
function createClosure() {
let largeData = new Array(1000000).fill('data');
return () => console.log(largeData); // largeData一直被引用
}
const closure = createClosure();
DOM 引用未清除保存了DOM元素的引用,即使元素被移除,内存也无法释放。
let elements = {
button: document.getElementById('button'),
};
document.body.removeChild(elements.button); // DOM已移除,但引用仍在
未释放的缓存或 Map缓存或Map中存储的对象未及时清理,会导致内存占用持续增加。
let cache = new Map();
function setCache(key, value) {
cache.set(key, value);
}
// 未清理cache,内存泄漏
浏览器开发者工具
工具库
node --inspect
结合Chrome DevTools。代码检查
及时清除引用
let timer = setInterval(() => {}, 1000);
clearInterval(timer); // 清除定时器
使用弱引用
WeakMap
或WeakSet
存储临时数据,避免强引用导致内存无法释放。let weakMap = new WeakMap();
let key = {};
weakMap.set(key, 'data'); // key被回收时,数据也会被回收
优化闭包
function createClosure() {
let largeData = new Array(1000000).fill('data');
return () => {
console.log('Closure executed');
// 不引用largeData
};
}
清理缓存
let cache = new Map();
function setCache(key, value, ttl) {
cache.set(key, value);
setTimeout(() => cache.delete(key), ttl); // 设置缓存失效时间
}
内存泄漏是JavaScript开发中的常见问题,通常由不当的引用管理引起。通过理解垃圾回收机制、熟悉常见的内存泄漏场景,并借助开发者工具进行排查,可以有效避免内存泄漏问题。在实际开发中,遵循最佳实践(如及时清除引用、使用弱引用等)是保证应用性能的关键。
虚拟 DOM(Virtual DOM)是一种用 JavaScript 对象表示真实 DOM 结构的技术。Vue 通过虚拟 DOM 实现高效的 DOM 更新,减少直接操作真实 DOM 的开销。
生成虚拟 DOM
Diff 算法
更新真实 DOM
性能优化
跨平台能力
简化开发
虚拟 DOM 的结构虚拟 DOM 是一个 JavaScript 对象,包含标签名、属性、子节点等信息。
const vnode = {
tag: 'div',
attrs: { id: 'app' },
children: [
{ tag: 'p', attrs: {}, children: ['Hello, Vue!'] }
]
};
Diff 算法核心逻辑
key
属性识别节点,避免不必要的节点销毁和重建。key
值相同,则复用节点,只更新属性或子节点。批量更新Vue 将多次数据变化合并为一次更新,减少重复渲染。
Weex
开发移动端应用。MVVM(Model-View-ViewModel)是一种软件架构模式,主要用于分离 UI 逻辑与业务逻辑。它将应用程序分为三个核心部分:
数据绑定
命令绑定
依赖注入
分离关注点
双向数据绑定
提高开发效率
易于测试
学习成本高
性能开销
框架依赖
前端框架
复杂 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 = 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 和 Reactive
ref
:用于包装基本数据类型,通过 .value
访问。reactive
:用于包装对象,直接访问属性。Effect 代替 Watcher
Effect
作为依赖单元,更加灵活和高效。Tree-shaking 支持
Vue2 响应式
const data = { name: 'Vue2' };
Object.defineProperty(data, 'name', {
get() {
console.log('获取 name');
return this._name;
},
set(newValue) {
console.log('更新 name');
this._name = newValue;
}
});
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;
}
});
浅拷贝(Shallow Copy)
深拷贝(Deep Copy)
**扩展运算符(** ...
)
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
Object.assign
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);
数组的浅拷贝方法
slice
、concat
:
const arr = [1, 2, { a: 3 }];
const shallowCopy = arr.slice();
JSON.parse(JSON.stringify(obj))
通过 JSON 序列化实现深拷贝,但有以下局限性:
undefined
、Symbol
等特殊类型。const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(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;
}
使用第三方库
lodash
的 cloneDeep
方法:
import _ from 'lodash';
const obj = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(obj);
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
拷贝层级 | 仅第一层 | 所有层级 |
引用类型属性 | 共享引用地址 | 完全独立 |
性能 | 较快 | 较慢(递归或序列化开销) |
适用场景 | 简单对象,无需深层复制 | 复杂对象,需完全独立副本 |
浅拷贝示例
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
shallowCopy.b.c = 3;
console.log(obj.b.c); // 输出:3(共享引用)
深拷贝示例
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
deepCopy.b.c = 3;
console.log(obj.b.c); // 输出:2(完全独立)
obj.a = obj
),需要额外处理,否则会导致栈溢出。Function
、RegExp
、Map
、Set
等,需要特殊处理。浅拷贝:适用于简单对象,仅复制第一层属性,性能较高。
深拷贝:适用于复杂对象,递归复制所有层级,确保对象完全独立,但性能较低。
选择建议:
lodash
)。在 npm
项目中,依赖包分为 开发依赖 和 生产依赖,分别用于不同的场景。以下是它们的定义、区别及使用方式:
devDependencies
)定义
安装方式
使用 --save-dev
或 -D
选项将包安装为开发依赖:
npm install <package> --save-dev
或
npm install <package> -D
示例在 package.json
中:
"devDependencies": {
"eslint": "^8.0.0",
"webpack": "^5.0.0",
"jest": "^27.0.0"
}
常见开发依赖
jest
、mocha
、chai
webpack
、vite
、babel
eslint
、prettier
typescript
dependencies
)定义
安装方式
使用 --save
或 -S
选项将包安装为生产依赖:
npm install <package> --save
或
npm install <package> -S
如果省略选项,默认安装为生产依赖:
npm install <package>
示例在 package.json
中:
"dependencies": {
"express": "^4.0.0",
"lodash": "^4.0.0",
"react": "^18.0.0"
}
常见生产依赖
express
、koa
、nestjs
react
、vue
、angular
lodash
、axios
、moment
特性 | 开发依赖(devDependencies ) |
生产依赖(dependencies ) |
---|---|---|
使用环境 | 仅用于开发环境 | 用于生产环境 |
是否发布到生产 | 否 | 是 |
安装命令 | npm install |
npm install |
示例工具 | eslint 、webpack 、jest |
express 、react 、lodash |
开发依赖
生产依赖
特殊情况
peerDependencies
:用于插件或库开发,指定兼容的宿主包。optionalDependencies
:可选依赖,安装失败不影响项目运行。明确区分依赖类型
使用 package-lock.json
定期更新依赖
npm outdated
检查过期依赖,定期更新以修复漏洞。清理无用依赖
npm prune
或 npm uninstall
清理未使用的依赖。package.json
中 ^
和 ~
的区别在 package.json
中,^
和 ~
是用于定义依赖版本范围的符号。它们决定了 npm
或 yarn
在安装或更新依赖时允许的版本范围。以下是它们的详细区别:
语义化版本号(SemVer)
格式:主版本号.次版本号.修订版本号
示例: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
)。 ^
示例
"dependencies": {
"lodash": "^4.17.21"
}
>=4.17.21 <5.0.0
。 ~
示例
"dependencies": {
"lodash": "~4.17.21"
}
>=4.17.21 <4.18.0
。固定版本示例
"dependencies": {
"lodash": "4.17.21"
}
4.17.21
版本。package.json
中的各种 dependencies
package.json
是 Node.js 项目的核心配置文件,用于管理项目的依赖、脚本、版本等信息。其中,dependencies
是定义项目依赖的关键部分。以下是 package.json
中各种依赖类型的详细说明:
dependencies
定义:项目运行所必需的依赖包。
安装方式:npm install
或 yarn add
。
示例:
"dependencies": {
"lodash": "^4.17.21",
"express": "4.17.1"
}
devDependencies
定义:仅用于开发环境的依赖包(如测试工具、构建工具等)。
安装方式:npm install
或 yarn add
。
示例:
"devDependencies": {
"eslint": "^7.32.0",
"webpack": "5.51.1"
}
peerDependencies
定义:与当前包兼容的宿主包,通常用于插件或库的开发。
特点:不会自动安装,需要用户手动安装。
示例:
"peerDependencies": {
"react": ">=16.8.0"
}
optionalDependencies
定义:可选的依赖包,即使安装失败也不会影响项目运行。
特点:优先级高于 dependencies
,如果安装失败会静默忽略。
示例:
"optionalDependencies": {
"fsevents": "^2.3.2"
}
bundledDependencies
定义:打包发布时需要包含的依赖包(通常是一个数组)。
特点:与 dependencies
和 devDependencies
不同,需要手动列出包名。
示例:
"bundledDependencies": ["lodash", "express"]
ES6(ECMAScript 2015)引入了官方的模块化语法,提供了 import
和 export
关键字来实现模块的导入和导出。以下是 ES6 模块化的核心概念和用法:
模块化是将代码拆分为独立模块的开发方式,每个模块具有独立的作用域,通过导入和导出实现模块间的依赖管理。ES6 模块化具有以下特点:
export
)命名导出
export
关键字导出变量、函数或类。// module.js
export const name = 'ES6';
export function greet() {
console.log('Hello, ES6!');
}
默认导出
export default
导出模块的默认值。// module.js
const name = 'ES6';
export default name;
混合导出
// module.js
export const name = 'ES6';
export default function greet() {
console.log('Hello, ES6!');
}
import
)导入命名导出
import { ... }
导入命名导出。// app.js
import { name, greet } from './module.js';
console.log(name); // 输出:ES6
greet(); // 输出:Hello, ES6!
导入默认导出
import ... from
导入默认导出。// app.js
import myModule from './module.js';
console.log(myModule); // 输出:ES6
导入全部导出
import * as ...
导入模块的所有导出。// app.js
import * as module from './module.js';
console.log(module.name); // 输出:ES6
module.greet(); // 输出:Hello, ES6!
动态导入
import()
动态加载模块,返回一个 Promise。// app.js
import('./module.js').then(module => {
console.log(module.name); // 输出:ES6
});
代码组织
依赖管理
静态分析
复用性
前端开发
Node.js 开发
.mjs
或在 package.json
中设置 "type": "module"
)。库开发
浏览器支持
标签中添加 type="module"
属性。<script type="module" src="app.js">script>
Node.js 支持
.mjs
或在 package.json
中设置 "type": "module"
。文件路径
key
的作用及为什么不能用 index
作为 key
详解key
的作用在 Vue 中,key
是用于标识虚拟 DOM 元素的特殊属性,其主要作用包括:
唯一标识
key
用于唯一标识每个虚拟 DOM 节点,帮助 Vue 识别哪些节点是新增的、删除的或需要更新的。优化渲染性能
key
可以帮助 Vue 更高效地复用和更新 DOM 节点,减少不必要的 DOM 操作。维持组件状态
key
可以确保组件在切换时正确销毁和重建,避免状态混乱。key
的使用场景列表渲染
v-for
中使用 key
标识每个列表项。
- {{ item.name }}
动态组件
key
确保组件正确切换。
条件渲染
key
确保元素正确销毁和重建。
内容A
内容B
index
作为 key
在列表渲染中,使用 index
作为 key
可能会导致以下问题:
无法正确复用节点
index
是数组的下标,当列表顺序变化时,index
会重新分配,导致 Vue 无法正确复用 DOM 节点,降低渲染性能。状态混乱
index
作为 key
会导致状态错乱。例如,删除中间项后,后续项的 index
会发生变化,状态会被错误地保留。示例问题
假设有一个列表 ['A', 'B', 'C']
,使用 index
作为 key
:
- {{ item }}
如果删除 B
,列表变为 ['A', 'C']
,index
会重新分配:
A
的 key
仍然是 0
。C
的 key
从 2
变为 1
。由于 key
变化,Vue 会销毁并重新创建 C
节点,而不是复用原来的节点。
key
使用方式使用唯一标识
id
)作为 key
。
- {{ item.name }}
避免动态生成 key
key
,否则会导致节点频繁销毁和重建。CSS 选择器优先级决定了当多个规则应用于同一个元素时,哪条规则会生效。以下是优先级规则及其计算方式的详细说明:
CSS 选择器优先级由 4 个级别组成,按权重从高到低依次为:
内联样式(权重:1000)
style
属性定义的样式。<div style="color: red;">Hellodiv>
ID 选择器(权重:100)
#
定义的 ID 选择器。#myId { color: blue; }
类选择器、属性选择器、伪类选择器(权重:10)
.class
)、属性选择器([type="text"]
)、伪类选择器(:hover
)。.myClass { color: green; }
[type="text"] { color: yellow; }
a:hover { color: purple; }
元素选择器、伪元素选择器(权重:1)
div
)、伪元素选择器(::before
)。div { color: black; }
::before { content: ' '; }
计算规则
示例
#myId .myClass div { color: red; } /* 权重:100 + 10 + 1 = 111 */
.myClass div { color: blue; } /* 权重:10 + 1 = 11 */
div { color: green; } /* 权重:1 */
注意事项
!important
:在样式声明后添加 !important
可以覆盖所有优先级。
div { color: red !important; }
相同权重:如果优先级相同,后定义的样式会覆盖前面的样式。
示例 1
<div id="myId" class="myClass">Hellodiv>
#myId { color: blue; } /* 权重:100 */
.myClass { color: green; } /* 权重:10 */
div { color: red; } /* 权重:1 */
blue
,因为 ID 选择器的优先级最高。示例 2
<div class="myClass">Hellodiv>
.myClass { color: green; } /* 权重:10 */
div { color: red; } /* 权重:1 */
green
,因为类选择器的优先级高于元素选择器。示例 3
<div style="color: red;">Hellodiv>
div { color: green; } /* 权重:1 */
red
,因为内联样式的优先级最高。避免过度使用 !important
!important
会破坏优先级规则,增加维护难度。减少 ID 选择器的使用
使用类选择器
遵循 CSS 层叠规则
JavaScript 是单线程语言,意味着它一次只能执行一个任务。如果某个任务耗时较长,可能会导致阻塞,影响页面的响应和渲染。以下是 JavaScript 阻塞问题的详细说明:
页面渲染阻塞
事件处理阻塞
网络请求阻塞
同步代码执行
for (let i = 0; i < 1000000000; i++) {} // 长时间运行的循环
console.log('执行完毕'); // 被阻塞
DOM 操作
CPU 密集型任务
异步编程
setTimeout
、Promise
、async/await
)将耗时任务放到事件循环中执行,避免阻塞主线程。console.log('开始');
setTimeout(() => {
console.log('异步任务');
}, 0);
console.log('结束');
// 输出顺序:开始 → 结束 → 异步任务
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('任务完成');
};
任务拆分
function processChunk(start, end) {
for (let i = start; i < end; i++) {
// 处理任务
}
if (end < 1000000) {
setTimeout(() => processChunk(end, end + 1000), 0);
}
}
processChunk(0, 1000);
优化 DOM 操作
DocumentFragment
减少 DOM 操作次数,避免频繁重排和重绘。const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
document.body.appendChild(fragment);
使用 requestAnimationFrame
requestAnimationFrame
中执行,确保与浏览器的渲染周期同步。function animate() {
// 执行动画逻辑
requestAnimationFrame(animate);
}
animate();
同步代码阻塞
console.log('开始');
for (let i = 0; i < 1000000000; i++) {} // 长时间运行的循环
console.log('结束');
// 页面卡顿,用户操作无响应
异步代码非阻塞
console.log('开始');
setTimeout(() => {
for (let i = 0; i < 1000000000; i++) {} // 长时间运行的循环
console.log('异步任务');
}, 0);
console.log('结束');
// 页面不卡顿,用户操作可响应
JavaScript 的阻塞问题主要由同步代码、复杂 DOM 操作和 CPU 密集型任务引起。通过以下方法可以避免阻塞:
Promise
、async/await
)。requestAnimationFrame
确保动画与渲染同步。Promise 是 JavaScript 中用于处理异步操作的对象,它解决了传统回调函数嵌套过深(“回调地狱”)的问题,提供了更清晰和可读性更高的代码结构。
状态Promise 有三种状态:
特点
.then()
和 .catch()
方法实现链式调用,避免回调嵌套。创建 Promise使用 new Promise()
构造函数创建 Promise 对象,传入一个执行器函数(executor)。
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve('成功结果');
} else {
reject('失败原因');
}
});
处理结果
.then()
处理成功状态(Fulfilled)。.catch()
处理失败状态(Rejected)。promise
.then((result) => {
console.log('成功:', result);
})
.catch((error) => {
console.log('失败:', error);
});
链式调用每个 .then()
返回一个新的 Promise,可以继续调用 .then()
或 .catch()
。
promise
.then((result) => {
console.log('第一步:', result);
return '第二步';
})
.then((result) => {
console.log('第二步:', result);
})
.catch((error) => {
console.log('失败:', error);
});
最终处理使用 .finally()
在 Promise 完成后执行清理操作,无论成功或失败。
promise
.then((result) => console.log('成功:', result))
.catch((error) => console.log('失败:', error))
.finally(() => console.log('操作完成'));
Promise.resolve()
返回一个已成功的 Promise 对象。
const resolvedPromise = Promise.resolve('成功');
resolvedPromise.then((result) => console.log(result)); // 输出:成功
Promise.reject()
返回一个已失败的 Promise 对象。
const rejectedPromise = Promise.reject('失败');
rejectedPromise.catch((error) => console.log(error)); // 输出:失败
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.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.allSettled()
接收一个 Promise 数组,返回所有 Promise 的结果(无论成功或失败)。
const promises = [
Promise.resolve('任务1'),
Promise.reject('任务2失败'),
];
Promise.allSettled(promises)
.then((results) => console.log('所有结果:', results));
处理异步操作如网络请求、定时器、文件读取等。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('数据加载完成');
}, 1000);
});
}
fetchData()
.then((result) => console.log(result))
.catch((error) => console.log(error));
避免回调地狱使用 Promise 链式调用代替嵌套回调。
function step1() {
return Promise.resolve('第一步');
}
function step2(data) {
return Promise.resolve(data + ' → 第二步');
}
step1()
.then(step2)
.then((result) => console.log(result));
在处理大量数据时,JavaScript 可能会面临性能瓶颈,如内存占用过高、页面卡顿等问题。以下是处理大量数据的常用策略和优化方法:
分页加载
function loadData(page, pageSize) {
const start = (page - 1) * pageSize;
const end = start + pageSize;
return data.slice(start, end);
}
const pageData = loadData(1, 100); // 加载第一页的100条数据
懒加载
window.addEventListener('scroll', () => {
if (window.scrollY + window.innerHeight >= document.body.offsetHeight) {
loadMoreData();
}
});
按需加载
使用 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);
};
分批处理
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);
});
使用高效算法
Map
)替代数组查找。使用 TypedArray
TypedArray
或 DataView
减少内存占用。const buffer = new ArrayBuffer(1024); // 1KB 缓冲区
const intArray = new Int32Array(buffer); // 32位整数数组
压缩数据
const compressedData = JSON.stringify(data);
const decompressedData = JSON.parse(compressedData);
使用 IndexedDB
localStorage
。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');
};
虚拟列表
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);
});
使用 requestAnimationFrame
requestAnimationFrame
中执行,确保与浏览器的渲染周期同步。function render() {
// 渲染逻辑
requestAnimationFrame(render);
}
render();
减少 DOM 操作
DocumentFragment
或 innerHTML
减少 DOM 操作次数。const fragment = document.createDocumentFragment();
data.forEach((item) => {
const div = document.createElement('div');
div.textContent = item;
fragment.appendChild(div);
});
container.appendChild(fragment);
Lodash
_.chunk
(分批处理)、_.debounce
(防抖)。Immutable.js
D3.js
处理大量数据时,JavaScript 可以通过以下策略优化性能:
TypedArray
、压缩数据、IndexedDB。requestAnimationFrame
、减少 DOM 操作。在前端开发中,兼容性问题是一个常见的挑战。为了确保项目在不同浏览器、设备和操作系统上正常运行,以下是兼容性处理的详细策略和方法:
明确目标浏览器
使用 Polyfill
// 示例:使用 core-js 提供 Promise 的 Polyfill
import 'core-js/features/promise';
CSS 前缀
// package.json
"browserslist": [
"last 2 versions",
"> 1%",
"not dead"
]
JavaScript 语法降级
// .babelrc
{
"presets": ["@babel/preset-env"]
}
条件注释(仅限 IE)
响应式设计
@media (max-width: 768px) {
.container {
width: 100%;
}
}
Flexbox 和 Grid 布局
.container {
display: flex;
justify-content: space-between;
}
视口设置
<meta name="viewport" content="width=device-width, initial-scale=1.0">
字体兼容性
body {
font-family: Arial, Helvetica, sans-serif;
}
文件路径兼容性
/
作为文件路径分隔符。键盘和输入兼容性
代码压缩
图片优化
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback Image">
picture>
懒加载
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
浏览器测试
自动化测试
用户反馈
前端项目兼容性处理的核心策略包括:
在 CSS 中,可以通过 border-radius
属性绘制椭圆。以下是实现椭圆的几种方法:
border-radius
绘制椭圆border-radius
是绘制椭圆的关键属性。它的值可以是百分比或长度单位,用于定义元素的圆角半径。
水平椭圆
.ellipse {
width: 200px;
height: 100px;
background-color: #ff6347;
border-radius: 50%;
}
width
和 height
分别定义椭圆的宽度和高度。border-radius: 50%
将矩形变为椭圆。垂直椭圆
.ellipse {
width: 100px;
height: 200px;
background-color: #ff6347;
border-radius: 50%;
}
width
和 height
的比例,可以绘制不同方向的椭圆。倾斜椭圆
.ellipse {
width: 200px;
height: 100px;
background-color: #ff6347;
border-radius: 50%;
transform: rotate(45deg);
}
transform: rotate()
旋转椭圆。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()
裁剪路径绘制椭圆。
元素绘制椭圆。setTimeout
和 setInterval
setTimeout
和 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
的作用
设计初衷
setTimeout
和 setInterval
分别针对单次任务和重复任务,提供了更灵活的定时器功能。setInterval
的问题
setTimeout
递归调用替代 setInterval
。function repeat() {
console.log('重复执行');
setTimeout(repeat, 1000);
}
repeat();
性能优化
setTimeout
或 setInterval
中执行耗时操作,以免阻塞主线程。requestAnimationFrame
替代 setTimeout
执行动画,确保与浏览器的渲染周期同步。setTimeout
:用于延迟执行一次性任务,延迟时间为 0
时会在下一个事件循环执行。setInterval
:用于重复执行周期性任务,但需注意回调函数执行时间过长的问题。setTimeout
和 setInterval
分别针对单次任务和重复任务,提供了灵活的定时器功能。加载性能
交互性能
视觉稳定性
资源优化
内存占用
Lighthouse
集成在 Chrome DevTools 中,提供全面的性能分析报告。
使用步骤:
WebPageTest
Chrome DevTools
Google Analytics
使用 console.time
和 console.timeEnd
console.time('myFunction');
myFunction(); // 需要测试的函数
console.timeEnd('myFunction');
使用 performance.now()
const start = performance.now();
myFunction(); // 需要测试的函数
const end = performance.now();
console.log(`耗时:${end - start} 毫秒`);
使用 Chrome DevTools 的 Performance 面板
记录页面运行时性能,定位某个功能或组件的性能瓶颈。
使用步骤:
使用 React Profiler(React 项目)
分析 React 组件的渲染性能。
使用步骤:
使用 why-did-you-render
(React 项目)
检测 React 组件的不必要渲染。
示例:
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React);
减少主线程任务
优化资源加载
减少重排和重绘
transform
和 opacity
替代直接修改布局属性。缓存数据
压缩资源
判断项目性能好不好的核心指标包括加载性能、交互性能、视觉稳定性和资源优化。通过 Lighthouse、WebPageTest 等工具可以全面评估项目性能,而 console.time
、performance.now()
和 Chrome DevTools 的 Performance 面板则适合检测某个功能或组件的性能。结合优化建议,可以显著提升项目的性能和用户体验。
在开发过程中,有时需要针对某个功能或组件进行性能检测,以定位瓶颈并优化代码。以下是详细的检测方法和工具:
console.time
和 console.timeEnd
作用
示例
console.time('myFunction');
myFunction(); // 需要测试的函数
console.timeEnd('myFunction');
myFunction: 0.123ms
适用场景
performance.now()
作用
示例
const start = performance.now();
myFunction(); // 需要测试的函数
const end = performance.now();
console.log(`耗时:${end - start} 毫秒`);
适用场景
作用
使用步骤
适用场景
作用
使用步骤
示例
适用场景
why-did-you-render
(React 项目)作用
安装
npm install @welldone-software/why-did-you-render --save-dev
配置
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React);
适用场景
作用
示例
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 大类:
1xx(信息性状态码)
2xx(成功状态码)
3xx(重定向状态码)
4xx(客户端错误状态码)
5xx(服务器错误状态码)
1xx 信息性状态码
2xx 成功状态码
3xx 重定向状态码
4xx 客户端错误状态码
5xx 服务器错误状态码
调试和排查问题
优化用户体验
监控和报警
HTTP 状态码是网络请求的重要组成部分,分为 1xx(信息性)、2xx(成功)、3xx(重定向)、4xx(客户端错误)和 5xx(服务器错误)五大类。理解常见的状态码及其含义,有助于开发、调试和优化网络请求。
箭头函数(Arrow Function)是 ES6 引入的一种简洁的函数语法,与普通函数(Function)在语法、行为和作用域上有显著区别。以下是两者的详细对比:
箭头函数
const add = (a, b) => a + b;
普通函数
function
关键字定义。function add(a, b) {
return a + b;
}
this
指向区别箭头函数
this
,this
继承自外层作用域(词法作用域)。const obj = {
value: 42,
getValue: () => {
console.log(this.value); // 输出:undefined
}
};
obj.getValue();
普通函数
this
,this
指向调用该函数的对象。const obj = {
value: 42,
getValue: function() {
console.log(this.value); // 输出:42
}
};
obj.getValue();
arguments
对象区别箭头函数
arguments
对象,需使用剩余参数(Rest Parameters)获取参数。const showArgs = (...args) => {
console.log(args);
};
showArgs(1, 2, 3); // 输出:[1, 2, 3]
普通函数
arguments
对象,包含所有传入的参数。function showArgs() {
console.log(arguments);
}
showArgs(1, 2, 3); // 输出:{ 0: 1, 1: 2, 2: 3 }
箭头函数
new
关键字。const Foo = () => {};
const foo = new Foo(); // 报错:Foo is not a constructor
普通函数
new
关键字。function Foo() {}
const foo = new Foo(); // 正常执行
箭头函数
prototype
属性,不能作为构造函数。const Foo = () => {};
console.log(Foo.prototype); // 输出:undefined
普通函数
prototype
属性,可以作为构造函数。function Foo() {}
console.log(Foo.prototype); // 输出:{ constructor: Foo }
箭头函数
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
普通函数
this
、arguments
或作为构造函数的场景。function Person(name) {
this.name = name;
}
const person = new Person('Alice');
特性 | 箭头函数 | 普通函数 |
---|---|---|
语法 | 简洁,适合单行函数 | 使用 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 debouncedSearch = debounce(() => {
console.log('搜索请求已发送');
}, 500);
// 用户输入时触发
input.addEventListener('input', debouncedSearch);
节流示例
const throttledScroll = throttle(() => {
console.log('滚动事件处理');
}, 500);
// 用户滚动时触发
window.addEventListener('scroll', throttledScroll);
JavaScript 是单线程语言,通过事件循环(Event Loop)机制处理异步任务。为了更高效地管理任务,JavaScript 将异步任务分为宏任务(Macro Task)和微任务(Micro Task)。以下是宏任务和微任务的存在原因及其区别:
宏任务(Macro Task)
代表较大的任务单元,通常包括:
setTimeout
、setInterval
script
(整体代码)微任务(Micro Task)
代表较小的任务单元,通常包括:
Promise.then
、Promise.catch
、Promise.finally
MutationObserver
queueMicrotask
事件循环流程
script
整体代码)。示例
console.log('开始');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('结束');
开始 → 结束 → Promise → setTimeout
任务优先级
任务粒度
用户体验
任务调度
特性 | 宏任务(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 to XMLHttpRequest at 'https://api.example.com' from origin 'https://www.example.com' has been blocked by CORS policy.
CORS(跨域资源共享)
服务器通过设置响应头允许跨域请求。
常用响应头:
Access-Control-Allow-Origin
:允许的源(如 *
或 https://www.example.com
)。Access-Control-Allow-Methods
:允许的 HTTP 方法(如 GET, POST
)。Access-Control-Allow-Headers
:允许的请求头(如 Content-Type
)。示例:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
JSONP(JSON with Padding)
利用 标签不受同源策略限制的特性,通过回调函数获取跨域数据。
示例:
<script>
function handleResponse(data) {
console.log(data);
}
script>
<script src="https://api.example.com/data?callback=handleResponse">script>
限制:仅支持 GET
请求。
代理服务器
通过同源的后端服务器代理跨域请求。
示例:
https://www.example.com/api/data
https://api.example.com/data
WebSocket
WebSocket 不受同源策略限制,可用于跨域通信。
示例:
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
console.log(event.data);
};
Nginx 反向代理
使用 Nginx 配置反向代理,将跨域请求转发到目标服务器。
示例配置:
server {
location /api {
proxy_pass https://api.example.com;
}
}
PostMessage
用于跨窗口通信,支持跨域。
示例:
// 发送消息
window.postMessage('Hello', 'https://www.example.com');
// 接收消息
window.addEventListener('message', (event) => {
if (event.origin !== 'https://www.example.com') return;
console.log(event.data);
});
前端调用后端 API
https://www.example.com
https://api.example.com
跨域资源共享
跨窗口通信
跨域问题是由浏览器的同源策略引起的,常见解决方案包括:
标签获取跨域数据。
前端项目的加载速度直接影响用户体验和 SEO 排名。以下是提高前端项目加载速度的常用方法:
压缩资源
使用工具(如 Webpack、Vite)压缩 JavaScript、CSS 和 HTML 文件。
示例:
npm install terser-webpack-plugin --save-dev
使用 Gzip/Brotli 压缩
在服务器端启用 Gzip 或 Brotli 压缩,减少资源传输体积。
示例(Nginx 配置):
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
图片优化
使用 WebP 格式图片,并为不支持 WebP 的浏览器提供回退方案。
使用工具(如 ImageOptim、TinyPNG)压缩图片。
示例:
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback Image">
picture>
使用 CDN
合并文件
使用雪碧图(Sprite)
将多个小图标合并为一张雪碧图,减少图片请求次数。
示例:
.icon {
background-image: url('sprite.png');
background-position: -10px -20px;
}
HTTP/2
代码分割(Code Splitting)
使用动态导入(Dynamic Import)将代码拆分为多个小块,按需加载。
示例:
import('./module').then(module => {
module.default();
});
Tree Shaking
使用工具(如 Webpack、Rollup)移除未使用的代码。
示例:
import { func1 } from 'module';
func1();
延迟加载(Lazy Load)
延迟加载非关键资源(如图片、视频)。
示例:
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
浏览器缓存
设置 Cache-Control
和 ETag
响应头,利用浏览器缓存静态资源。
示例(Nginx 配置):
location /static {
expires 1y;
add_header Cache-Control "public";
}
Service Worker
使用 Service Worker 缓存资源,支持离线访问。
示例:
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll(['/', '/styles.css', '/app.js']);
})
);
});
减少重排和重绘
使用 transform
和 opacity
替代直接修改布局属性。
示例:
.element {
transform: translateX(100px);
opacity: 0.5;
}
使用 requestAnimationFrame
将动画逻辑放到 requestAnimationFrame
中执行,确保与浏览器的渲染周期同步。
示例:
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
animate();
关键渲染路径优化
将关键 CSS 内联到 HTML 中,优先加载关键资源。
示例:
<style>
/* 关键 CSS */
style>
性能分析工具
性能监控
提高前端项目加载速度的核心方法包括:
requestAnimationFrame
。
在浏览器中,多个页签之间可以通过以下几种方式进行消息传递:
localStorage
或 sessionStorage
原理
localStorage
和 sessionStorage
是浏览器提供的存储机制,可以在同一源的不同页签之间共享数据。实现步骤
storage
事件。storage
事件,获取数据。示例代码
// 页签 A:设置数据
localStorage.setItem('message', 'Hello from Tab A');
// 页签 B:监听 storage 事件
window.addEventListener('storage', (event) => {
if (event.key === 'message') {
console.log('收到消息:', event.newValue);
}
});
注意事项
storage
事件不会在当前页签触发。BroadcastChannel
原理
BroadcastChannel
是浏览器提供的 API,允许同一源的不同页签之间通过消息通道进行通信。实现步骤
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);
};
注意事项
BroadcastChannel
。SharedWorker
原理
SharedWorker
是浏览器提供的 Web Worker,允许同一源的不同页签之间共享一个后台线程进行通信。实现步骤
SharedWorker
。示例代码
// 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 = 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);
};
注意事项
SharedWorker
需要额外的 JavaScript 文件。window.open
和 postMessage
原理
window.open
打开新页签,并使用 postMessage
进行跨页签通信。实现步骤
window.open
打开页签 B,并保存页签 B 的引用。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 |
支持跨域通信,灵活性强 | 需要手动管理页签引用 | 跨域页签通信 |
根据具体需求选择合适的方法,可以实现页签之间的高效消息传递。
在前端开发中,实现元素水平居中是常见的需求。以下是多种实现方法及其适用场景:
使用 text-align: center
.container {
text-align: center;
}
使用 line-height
.container {
height: 100px;
line-height: 100px;
}
使用 margin: 0 auto
.element {
width: 200px;
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;
}
使用 text-align: center
.container {
text-align: center;
}
.element {
display: inline-block;
}
使用 Flexbox
.container {
display: flex;
justify-content: center;
}
使用 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
变量会被提升到函数或全局作用域的顶部,但赋值不会被提升。
示例:
console.log(a); // 输出:undefined
var a = 1;
let
和 const
变量会被提升到块级作用域的顶部,但在声明之前访问会触发“暂时性死区”(Temporal Dead Zone,TDZ)。
示例:
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 2;
var
允许重复声明,后面的声明会覆盖前面的声明。
示例:
var a = 1;
var a = 2;
console.log(a); // 输出:2
let
和 const
不允许重复声明,会报错。
示例:
let b = 1;
let b = 2; // 报错:Identifier 'b' has already been declared
var
和 let
声明的变量可以重新赋值。
示例:
var a = 1;
a = 2;
let b = 3;
b = 4;
const
声明的变量不可重新赋值(常量),但对象或数组的属性可以修改。
示例:
const c = 5;
c = 6; // 报错:Assignment to constant variable
const obj = { key: 'value' };
obj.key = 'newValue'; // 允许修改属性
特性 | 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
定义
形成过程
示例
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();
innerFunction
中访问变量时,作用域链的顺序为: innerFunction
→ outerFunction
→ global
变量查找
闭包(Closure)
闭包是函数与其词法作用域的组合,通过作用域链可以访问父级作用域的变量。
示例:
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 代码。
前端缓存和数据持久化是提升应用性能、优化用户体验的重要手段。以下是它们的详细说明和应用场景:
前端缓存是指将数据或资源存储在客户端(如浏览器),以减少网络请求、加快资源加载速度。
HTTP 缓存
强缓存:通过 Cache-Control
和 Expires
响应头控制资源的缓存时间。
Cache-Control: max-age=3600
Expires: Wed, 01 Jan 2025 00:00:00 GMT
协商缓存:通过 ETag
和 Last-Modified
响应头验证资源是否过期。
ETag: "abc123"
Last-Modified: Wed, 01 Jan 2020 00:00:00 GMT
Service Worker 缓存
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll(['/', '/styles.css', '/app.js']);
})
);
});
LocalStorage 和 SessionStorage
LocalStorage:持久化存储数据,关闭浏览器后数据仍然存在。
localStorage.setItem('key', 'value');
console.log(localStorage.getItem('key')); // 输出:value
SessionStorage:会话级存储数据,关闭浏览器后数据清空。
sessionStorage.setItem('key', 'value');
console.log(sessionStorage.getItem('key')); // 输出:value
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' });
};
前端数据持久化是指将数据存储在客户端,即使页面刷新或关闭浏览器后数据仍然存在。
LocalStorage
localStorage.setItem('user', JSON.stringify({ name: 'Alice' }));
const user = JSON.parse(localStorage.getItem('user'));
console.log(user); // 输出:{ name: 'Alice' }
SessionStorage
sessionStorage.setItem('token', 'abc123');
console.log(sessionStorage.getItem('token')); // 输出:abc123
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' });
};
Cookies
document.cookie = 'username=John; expires=Wed, 01 Jan 2025 00:00:00 GMT; path=/';
console.log(document.cookie); // 输出:username=John
文件缓存
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);
前端缓存
数据持久化
前端缓存
数据持久化
require
和 import
的区别详解require
和 import
是 JavaScript 中用于加载模块的两种方式,分别用于 CommonJS 和 ES6 模块系统。以下是它们的详细对比:
require
(CommonJS)
语法:
const module = require('module-name');
动态加载:可以在代码的任何位置使用 require
。
示例:
const fs = require('fs');
const data = fs.readFileSync('file.txt', 'utf8');
import
(ES6 Modules)
语法:
import module from 'module-name';
静态加载:必须在模块的顶层使用 import
,不能在代码块中动态加载。
示例:
import fs from 'fs';
const data = fs.readFileSync('file.txt', 'utf8');
require
import
require
使用 module.exports
或 exports
导出模块。
示例:
// module.js
module.exports = {
foo: 'bar'
};
// app.js
const module = require('./module');
console.log(module.foo); // 输出:bar
import
使用 export
或 export default
导出模块。
示例:
// module.js
export const foo = 'bar';
// app.js
import { foo } from './module';
console.log(foo); // 输出:bar
require
import
require
import
.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;
}
em
定义
em
是相对单位,基于当前元素的字体大小(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 */
}
rem
定义
rem
是相对单位,基于根元素(
)的字体大小(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
统一盒模型。* {
box-sizing: border-box;
}
Flexbox 兼容性
autoprefixer
自动添加浏览器前缀。.container {
display: flex;
}
CSS Grid 兼容性
@supports
提供回退方案。.container {
display: grid;
}
@supports not (display: grid) {
.container {
display: flex;
}
}
ES6+ 语法兼容性
let
、const
、箭头函数)。npm install @babel/core @babel/preset-env --save-dev
// .babelrc
{
"presets": ["@babel/preset-env"]
}
API 兼容性
fetch
、Promise
)。npm install whatwg-fetch --save
import 'whatwg-fetch';
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
HTML5 标签兼容性
、
)。html5shiv
提供兼容性支持。
表单控件兼容性
date
、range
)的支持不完善。jQuery UI
)。字体兼容性
@font-face
加载自定义字体。body {
font-family: Arial, sans-serif;
}
图片兼容性
标签提供回退方案。<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Fallback Image">
picture>
问题类型 | 常见问题 | 解决方案 |
---|---|---|
CSS 兼容性 | 盒模型差异、Flexbox、CSS Grid | 使用 box-sizing 、autoprefixer 、@supports |
JavaScript 兼容性 | ES6+ 语法、现代 API | 使用 Babel、Polyfill |
HTML 兼容性 | HTML5 标签、表单控件 | 使用 html5shiv 、Polyfill |
其他兼容性 | 字体、图片 | 使用 Web 安全字体、 标签 |
通过理解常见的浏览器兼容性问题及其解决方案,可以有效提升前端项目的兼容性和用户体验。
CSS 盒模型(Box Model)是网页布局的基础,它定义了元素的尺寸、内边距、边框和外边距之间的关系。以下是盒模型的详细说明:
盒模型由以下四部分组成:
内容区域(Content)
width
和 height
设置内容区域的尺寸。内边距(Padding)
padding
设置内边距。边框(Border)
border
设置边框的样式、宽度和颜色。外边距(Margin)
margin
设置外边距。标准盒模型(Content-Box)
width
和 height
仅包括内容区域。width
+ padding
+ border
+ margin
height
+ padding
+ border
+ margin
IE 盒模型(Border-Box)
width
和 height
包括内容区域、内边距和边框。width
+ margin
height
+ margin
切换盒模型
box-sizing
属性切换盒模型。/* 标准盒模型 */
.element {
box-sizing: content-box;
}
/* IE 盒模型 */
.element {
box-sizing: border-box;
}
标准盒模型计算
.element {
width: 200px;
padding: 10px;
border: 5px solid black;
margin: 20px;
box-sizing: content-box;
}
200px
+ 10px * 2
+ 5px * 2
+ 20px * 2
= 270px
height
+ padding * 2
+ border * 2
+ margin * 2
IE 盒模型计算
.element {
width: 200px;
padding: 10px;
border: 5px solid black;
margin: 20px;
box-sizing: border-box;
}
200px
+ 20px * 2
= 240px
height
+ margin * 2
布局控制
padding
和 margin
调整元素之间的间距。.container {
padding: 20px;
}
.item {
margin: 10px;
}
边框样式
border
设置元素的边框样式。.element {
border: 2px solid red;
border-radius: 10px;
}
盒模型切换
box-sizing: border-box
简化布局计算。* {
box-sizing: border-box;
}
盒模型的组成部分
盒模型的类型
width
和 height
仅包括内容区域。width
和 height
包括内容区域、内边距和边框。盒模型的应用
padding
和 margin
调整布局,使用 box-sizing
切换盒模型。ES6 是 JavaScript 的重要更新,引入了许多新特性,使代码更简洁、更易读、更强大。以下是 ES6 的主要新特性:
let
和 const
let
用于声明块级作用域的变量。const
用于声明常量,值不可重新赋值。let x = 10;
const y = 20;
语法
=>
定义函数,简化函数书写。const add = (a, b) => a + b;
特点
this
,this
继承自外层作用域。语法
`
)定义字符串,支持换行和嵌入变量。const name = 'Alice';
const message = `Hello, ${name}!`;
数组解构
const [a, b] = [1, 2];
console.log(a); // 输出:1
对象解构
const { name, age } = { name: 'Alice', age: 25 };
console.log(name); // 输出:Alice
语法
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // 输出:Hello, Guest!
数组扩展
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // 输出:[1, 2, 3, 4]
对象扩展
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2); // 输出:{ a: 1, b: 2, c: 3 }
语法
class
关键字定义类,支持构造函数和继承。class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
const person = new Person('Alice');
person.greet(); // 输出:Hello, Alice!
导出模块
// module.js
export const add = (a, b) => a + b;
导入模块
// app.js
import { add } from './module';
console.log(add(1, 2)); // 输出:3
语法
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
定义迭代器。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
}
生成器
function*
定义生成器函数。function* generator() {
yield 1;
yield 2;
}
const gen = generator();
console.log(gen.next().value); // 输出:1
Set
const set = new Set([1, 2, 3]);
set.add(4);
console.log(set.has(2)); // 输出:true
Map
const map = new Map();
map.set('name', 'Alice');
console.log(map.get('name')); // 输出:Alice
HTTP 缓存是提升网页性能的重要手段,分为强缓存和协商缓存。以下是它们的详细说明:
定义
实现方式
Cache-Control
:通过 max-age
设置缓存的有效时间。
Cache-Control: max-age=3600
Expires
:通过指定过期时间设置缓存的有效时间。
Expires: Wed, 01 Jan 2025 00:00:00 GMT
特点
示例
Cache-Control: max-age=3600
Expires: Wed, 01 Jan 2025 00:00:00 GMT
定义
实现方式
Last-Modified
和 If-Modified-Since
:
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
ETag
和 If-None-Match
:
ETag
)。If-None-Match
,服务器判断资源是否修改。ETag: "abc123"
If-None-Match: "abc123"
特点
示例
Last-Modified: Wed, 01 Jan 2020 00:00:00 GMT
ETag: "abc123"
特性 | 强缓存 | 协商缓存 |
---|---|---|
是否发送请求 | 否 | 是 |
缓存判断方式 | 浏览器直接判断 | 服务器判断 |
实现机制 | 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
#
。配置
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
});
服务器配置
Nginx:
location / {
try_files $uri $uri/ /index.html;
}
Apache:
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
优点
#
。缺点
特性 | Hash 模式 | History 模式 |
---|---|---|
URL 示例 | http://example.com/#/home |
http://example.com/home |
是否需要服务器配置 | 否 | 是 |
兼容性 | 兼容所有浏览器 | 需要 HTML5 支持 |
SEO 支持 | 不支持 | 支持 |
适用场景 | 静态站点 | 动态站点 |
Hash 模式
History 模式
JavaScript 是一种强大的脚本语言,广泛应用于前端和后端开发。然而,它也存在一些缺点,以下是主要的缺点及其影响:
问题
解决方案
Promise
、async/await
)。问题
JavaScript 是弱类型语言,变量的类型可以动态改变,容易引发运行时错误。
示例:
let x = 10;
x = 'Hello'; // 类型改变,可能导致错误
解决方案
问题
解决方案
问题
解决方案
问题
解决方案
requestAnimationFrame
优化动画性能。问题
解决方案
console.log
或 debugger
语句辅助调试。问题
解决方案
缺点 | 问题描述 | 解决方案 |
---|---|---|
单线程模型 | 长时间任务阻塞主线程 | 使用异步编程、Web Workers |
弱类型语言 | 变量类型动态改变,容易引发运行时错误 | 使用 TypeScript、ESLint |
浏览器兼容性 | 不同浏览器支持不一致 | 使用 Babel、Polyfill |
安全问题 | 容易被恶意用户篡改或攻击 | 验证用户输入、使用 HTTPS |
性能问题 | 执行速度慢,复杂 DOM 操作影响性能 | 使用虚拟 DOM、优化动画 |
调试困难 | 错误提示不明确,异步代码调试复杂 | 使用开发者工具、console.log |
生态系统碎片化 | 工具和库选择过多,学习成本高 | 根据需求选择工具,关注主流技术趋势 |
理解 JavaScript 的缺点及其解决方案,可以帮助开发者更好地应对挑战,编写更高效、更安全的代码。
JavaScript 是一种基于原型的语言,原型(Prototype)是 JavaScript 实现继承和共享属性和方法的核心机制。以下是原型的详细说明:
原型对象(Prototype Object)
null
)都有一个原型对象,对象从原型对象继承属性和方法。原型链(Prototype Chain)
__proto__
属性
__proto__
属性,指向其原型对象。const obj = {};
console.log(obj.__proto__); // 输出:Object.prototype
prototype
属性
prototype
属性,指向该函数的原型对象。function Person() {}
console.log(Person.prototype); // 输出:Person {}
构造函数与原型
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!
示例
const obj = {};
console.log(obj.toString()); // 输出:[object Object]
obj
本身没有 toString
方法,但它的原型对象 Object.prototype
有 toString
方法。原型链图示
obj -> Object.prototype -> null
原型继承
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log(`Hello, ${this.name}!`);
};
function Child() {}
Child.prototype = new Parent();
const child = new Child();
child.sayHello(); // 输出:Hello, Parent!
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!
原型对象
原型链
原型继承
XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)是两种常见的 Web 安全漏洞,以下是它们的详细说明及防御措施:
定义
类型
危害
防御措施
输入过滤:对用户输入进行严格的验证和过滤,移除或转义特殊字符。
function escapeHTML(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
输出编码:在输出用户输入时进行编码,防止脚本执行。
设置 HTTP 头:
Content-Security-Policy
限制脚本加载来源。Content-Security-Policy: default-src 'self'
HttpOnly
标记 Cookie,防止 JavaScript 访问。Set-Cookie: sessionId=123; HttpOnly; Secure
定义
攻击流程
危害
防御措施
验证请求来源:
Referer
头,确保请求来自合法来源。if (req.headers.referer !== 'https://example.com') {
return res.status(403).send('Forbidden');
}
使用 CSRF Token:
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="csrf_token">
<button type="submit">Submitbutton>
form>
设置 SameSite Cookie:
SameSite
属性限制 Cookie 的发送范围。Set-Cookie: sessionId=123; SameSite=Strict; Secure
攻击类型 | 定义 | 防御措施 |
---|---|---|
XSS | 攻击者在网页中注入恶意脚本 | 输入过滤、输出编码、设置 HTTP 头 |
CSRF | 攻击者诱导用户执行非预期的操作 | 验证请求来源、使用 CSRF Token、设置 SameSite Cookie |
通过理解 XSS 和 CSRF 的原理及防御措施,可以有效提升 Web 应用的安全性,保护用户数据和隐私。
HTTP(HyperText Transfer Protocol,超文本传输协议)是用于在 Web 浏览器和服务器之间传输数据的协议。以下是 HTTP 的详细说明:
定义
特点
客户端发送请求
服务器处理请求
服务器返回响应
客户端处理响应
HTTP 请求
请求行:包含请求方法、URL 和 HTTP 版本。
GET /index.html HTTP/1.1
请求头:包含请求的元信息(如 Host
、User-Agent
)。
Host: example.com
User-Agent: Mozilla/5.0
请求体:包含请求的数据(如表单数据、JSON)。
HTTP 响应
状态行:包含 HTTP 版本、状态码和状态描述。
HTTP/1.1 200 OK
响应头:包含响应的元信息(如 Content-Type
、Content-Length
)。
Content-Type: text/html
Content-Length: 1234
响应体:包含响应的数据(如 HTML、JSON)。
GET
GET /index.html HTTP/1.1
POST
POST /submit HTTP/1.1
Content-Type: application/json
{"name": "Alice"}
PUT
PUT /update HTTP/1.1
Content-Type: application/json
{"name": "Bob"}
DELETE
DELETE /delete HTTP/1.1
1xx(信息性状态码)
100 Continue
2xx(成功状态码)
200 OK
、201 Created
3xx(重定向状态码)
301 Moved Permanently
、302 Found
4xx(客户端错误状态码)
400 Bad Request
、404 Not Found
5xx(服务器错误状态码)
500 Internal Server Error
、503 Service Unavailable
HTTP/1.0
HTTP/1.1
HTTP/2
HTTP/3
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: absolute
特点
static
定位的祖先元素定位。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: hidden
特点
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 中,堆(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 中用于实现双向数据绑定的指令,常用于表单元素(如 、
、
)。以下是
v-model
的实现原理:
v-model
的基本用法语法
<input v-model="message" />
效果
message
的值会自动更新。message
的值改变时,输入框的内容也会自动更新。v-model
的实现原理v-model
的本质
v-model
是 v-bind
和 v-on
的语法糖,结合了属性绑定和事件监听。v-model
的展开形式
对于 元素,
v-model
的展开形式如下:
<input
:value="message"
@input="message = $event.target.value"
/>
对于 和
元素,
v-model
的展开形式类似。
v-model
的工作流程
message
的值绑定到输入框的 value
属性。input
事件,将用户输入的值赋给 message
。message
的值改变时,更新输入框的 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-model
默认实现
v-model
在自定义组件中绑定 value
属性和 input
事件。自定义 v-model
model
选项自定义 v-model
的属性和事件。export default {
model: {
prop: 'checked',
event: 'change'
},
props: ['checked'],
template: `
`
};
v-model
的本质
v-model
是 v-bind
和 v-on
的语法糖,用于实现双向数据绑定。v-model
的工作流程
value
属性。自定义组件的 v-model
model
选项自定义 v-model
的属性和事件。
在 Vue.js 中,组件通信是开发复杂应用的关键。以下是 Vue 组件通信的多种方式及其适用场景:
props
和 $emit
父组件向子组件传递数据:通过 props
。
// 父组件
<Child :message="message" />
// 子组件
export default {
props: ['message']
};
子组件向父组件传递数据:通过 $emit
触发事件。
// 子组件
this.$emit('update', newValue);
// 父组件
<Child @update="handleUpdate" />
v-model
// 子组件
export default {
model: {
prop: 'value',
event: 'input'
},
props: ['value']
};
// 父组件
<Child v-model="message" />
通过父组件中转
// 父组件
<ChildA @update="handleUpdate" />
<ChildB :message="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);
});
provide
和 inject
provide
提供数据,后代组件通过 inject
注入数据。// 祖先组件
export default {
provide() {
return {
message: this.message
};
}
};
// 后代组件
export default {
inject: ['message']
};
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;
$refs
$refs
直接访问子组件的属性和方法。// 父组件
<Child ref="child" />
this.$refs.child.methodName();
$parent
和 $children
$parent
访问父组件,通过 $children
访问子组件。this.$parent.methodName();
this.$children.methodName();
通信方式 | 适用场景 | 示例 |
---|---|---|
props 和 $emit |
父子组件通信 | |
v-model |
父子组件双向绑定 | |
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 算法的详细说明:
虚拟 DOM
Diff 算法的目标
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 操作。批量更新
虚拟 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);
}
}
Diff 算法的目标
Diff 算法的策略
key
优化列表项的对比。Diff 算法的优化
key
作为唯一标识,批量更新 DOM 操作。理解 Diff 算法的原理和实现,可以帮助开发者更好地优化 Vue 应用的性能。
Webpack 是一个模块打包工具,用于将 JavaScript、CSS、图片等资源打包成静态文件。以下是 Webpack 的基本配置及其详细说明:
入口(Entry)
输出(Output)
加载器(Loader)
插件(Plugin)
模式(Mode)
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')
}
};
CSS 加载器
npm install style-loader css-loader --save-dev
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-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']
}
}
}
]
}
};
图片加载器
npm install file-loader --save-dev
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: ['file-loader']
}
]
}
};
HTML 插件
npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html' // 指定模板文件
})
]
};
代码压缩插件
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new TerserPlugin()]
}
};
npm install webpack-dev-server --save-dev
module.exports = {
devServer: {
contentBase: './dist', // 服务器根目录
port: 8080, // 端口号
hot: true // 启用热更新
}
};
Webpack 的基本配置
常用配置
通过理解 Webpack 的配置和常用插件,可以高效地打包和管理前端项目。
当用户在浏览器中输入 URL 并按下回车键后,浏览器会执行一系列操作,最终将网页呈现给用户。以下是这一过程的详细步骤:
解析协议、域名、端口和路径
http
或 https
)、域名(如 www.example.com
)、端口(默认 80
或 443
)和路径(如 /index.html
)。检查缓存
查询 DNS 缓存
DNS 递归查询
三次握手
浏览器与服务器通过 TCP 协议建立连接,进行三次握手:
SYN
报文。SYN-ACK
报文。ACK
报文。HTTPS 加密(可选)
构造 HTTP 请求
GET /index.html HTTP/1.1
)、请求头(如 Host
、User-Agent
)和请求体(如 POST 数据)。发送请求
解析请求
处理请求
生成响应
HTTP/1.1 200 OK
)、响应头(如 Content-Type
、Content-Length
)和响应体(如 HTML 内容)。接收响应
检查状态码
200
表示成功,404
表示未找到),决定后续操作。解析 HTML
加载外部资源
构建 CSSOM 树
构建渲染树
布局和绘制
解析和执行 JavaScript
触发事件
DOMContentLoaded
和 load
事件,表示页面加载完成。步骤 | 描述 |
---|---|
URL 解析 | 解析协议、域名、端口和路径,检查缓存 |
DNS 解析 | 查询 DNS 缓存,递归查询域名对应的 IP 地址 |
建立 TCP 连接 | 通过三次握手建立连接,HTTPS 进行 TLS 握手 |
发送 HTTP 请求 | 构造并发送 HTTP 请求到服务器 |
服务器处理请求 | 解析请求,处理请求,生成 HTTP 响应 |
接收 HTTP 响应 | 接收并检查服务器返回的 HTTP 响应 |
解析和渲染页面 | 构建 DOM 树、CSSOM 树、渲染树,布局和绘制 |
执行 JavaScript | 解析和执行 JavaScript,触发页面事件 |
前端优化是提升网页性能、用户体验和 SEO 效果的关键。以下是常见的前端优化方法及其详细说明:
合并文件
使用雪碧图
background-position
显示不同图标。内联资源
浏览器缓存
Cache-Control
和 Expires
头,缓存静态资源。Cache-Control: max-age=31536000
Service Worker 缓存
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll(['/', '/styles.css', '/app.js']);
})
);
});
延迟加载
<img data-src="image.jpg" alt="Lazy Loaded Image" class="lazyload">
异步加载脚本
async
或 defer
属性异步加载 JavaScript 文件。<script src="app.js" async>script>
预加载关键资源
预加载关键资源。<link rel="preload" href="styles.css" as="style">
压缩代码
移除未使用的代码
优化 CSS
@import
,减少 CSS 嵌套层级,移除未使用的样式。使用合适的格式
压缩图片
响应式图片
和 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);
使用虚拟 DOM
语义化 HTML
、
)提升 SEO 效果。优化 meta
标签
title
、description
和 keywords
等 meta
标签。<meta name="description" content="Page description">
使用结构化数据
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Article Title"
}
script>
优化方法 | 描述 |
---|---|
减少 HTTP 请求 | 合并文件、使用雪碧图、内联资源 |
使用缓存 | 浏览器缓存、Service Worker 缓存 |
优化资源加载 | 延迟加载、异步加载脚本、预加载关键资源 |
优化代码 | 压缩代码、移除未使用的代码、优化 CSS |
优化图片 | 使用合适的格式、压缩图片、响应式图片 |
优化渲染性能 | 减少重绘和重排、使用 requestAnimationFrame 、虚拟 DOM |
优化 SEO | 语义化 HTML、优化 meta 标签、结构化数据 |
通过理解这些前端优化方法,可以显著提升网页性能、用户体验和 SEO 效果。