0%

格式选择

  • JPEG: 照片
  • PNG: 透明图
  • WebP: 现代格式
  • SVG: 矢量图

压缩

  • 使用工具压缩
  • 响应式图片
1
2
3
4
<picture>
<source media="(min-width: 800px)" srcset="large.jpg">
<img src="small.jpg" alt="Responsive Image">
</picture>

懒加载

见 【648】懒加载实现

CDN

使用图片 CDN 加速。

总结

图片优化显著提升性能。选择合适格式和大小。

响应式图片

使用 srcsetsizes 为不同屏幕提供不同分辨率:

1
2
3
4
<img src="small.jpg" 
srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 480px, 800px"
alt="示例图片">

自动化转换工具

  • imagemin
  • image-webpack-loader
  • svgo
  • sharp for Node.js

JPEG/PNG/WebP 比较

格式 优点 缺点
JPEG 良好压缩 不支持透明
PNG 支持透明 文件大
WebP 现代压缩 浏览器支持需注意

CSS sprites 和图标字体

减少请求次数,通过雪碧图将多个小图合并为一张大图,或使用 iconfont。

CDN + 镜像

在 CDN 中启用图片压缩和自动格式转换(如 Cloudflare Image Resizing)。

性能衡量

使用 Lighthouse 查看“减少未使用的 CSS”和“延迟图片加载”提示。

实践建议

  1. 保持图片尺寸恰当,避免在浏览器�1. 保持图片尺寸恰当,避免在浏览器�1.d-image`,并结合媒�1. 保持图片尺寸恰当,避免在浏览器�1. 保持图片�结1. 保��1. 保持图片尺寧�1. 保持图片尺寸恰当,避免在浏览器�1. 保持�以显著提升页面性能。

图片优化补充 1
图片优化补充 2
图片优化补充 3
图片优化补充 4
图片优化补充 5
图片优化补充 6
图片优化补充 7
图片优化补充 8
图片优化补充 9
图片优化补充 10
图片优化补充 11
图片优化补充 12
图片优化补充 13
图片优化补充 14
图片优化补充 15
图片优化补充 16
图片优化补充 17
图片优化补充 18
图片优化补充 19
图片优化补充 20
图片优化补充 21
图片优化补充 22
图片优化补充 23
图片优化补充 24
图片优化补充 25
图片优化补充 26
图片优化补充 27
图片优化补充 28
图片优化补充 29
图片优化补充 30
图片优化补充 31
图片优化补充 32
图片优化补充 33
图片优化补充 34
图片优化补充 35
图片优化补充 36
图片优化补充 37
图片优化补充 38
图片优化补充 39
图片优化补充 40
图片优化补充 41
图片优化补充 42
图片优化补充 43
图片优化补充 44
图片优化补充 45
图片优化补充 46
图片优化补充 47
图片优化补充 48
图片优化补充 49
图片优化补充 50
图片优化补充 51
图片优化补充 52
图片优化补充 53
图片优化补充 54
图片优化补充 55
图片优化补充 56
图片优化补充 57
图片优化补充 58
图片优化补充 59
图片优化补充 60
图片优化补充 61
图片优化补充 62
图片优化补充 63
图片优化补充 64
图片优化补充 65
图片优化补充 66
图片优化补充 67
图片优化补充 68
图片优化补充 69
图片优化补充 70
图片优化补充 71
图片优化补充 72
图片优化补充 73
图片优化补充 74
图片优化补充 75
图片优化补充 76
图片优化补充 77
图片优化补充 78
图片优化补充 79
图片优化补充 80
图片优化补充 81
图片优化补充 82
图片优化补充 83
图片优化补充 84
图片优化补充 85
图片优化补充 86
图片优化补充 87
图片优化补充 88
图片优化补充 89
图片优化补充 90

为什么代码分割?

减少初始包大小,提升加载速度。

动态导入

1
2
3
import('./module.js').then(module => {
// 使用模块
});

React 路由分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));

function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}

Webpack 配置

1
2
3
4
5
optimization: {
splitChunks: {
chunks: 'all'
}
}

总结

代码分割按需加载。提升用户体验。

打包工具示例

Webpack

1
2
3
4
5
6
7
8
9
module.exports = {
entry: './src/index.js',
output: { filename: '[name].bundle.js' },
optimization: {
splitChunks: {
chunks: 'all',
},
},
};

Rollup

1
2
3
4
5
6
7
8
9
10
export default {
input: 'src/main.js',
output: {
dir: 'output',
format: 'es',
},
plugins: [
dynamicImport(),
],
};

分割策略

  • 入口点分割
  • 路由级分割
  • 库/第三方依赖分割
  • 运行时分割

SSR 与代码分割

服务端渲染时需要对动态导入进行处理,常用 @loadable/componentsreact-loadable

动态加载的优点

  • 初始页面轻量
  • 按需加载,提高首屏渲染速度

监控

使用网络面板查看 chunk 大小,部署时对比基线。

常见问题

  • Chunk 名称冲突
  • 动态导入失败时的回退方案

总结

代码分割是前端优化过程中不可缺少的一环,可灵活配合懒加载和�代码分割是前端优化过程中不可缺少的��。

代码分割补充行 1
代码分割补充行 2
代码分割补充行 3
代码分割补充行 4
代码分割补充行 5
代码分割补充行 6
代码分割补充行 7
代码分割补充行 8
代码分割补充行 9
代码分割补充行 10
代码分割补充行 11
代码分割补充行 12
代码分割补充行 13
代码分割补充行 14
代码分割补充行 15
代码分割补充行 16
代码分割补充行 17
代码分割补充行 18
代码分割补充行 19
代码分割补充行 20
代码分割补充行 21
代码分割补充行 22
代码分割补充行 23
代码分割补充行 24
代码分割补充行 25
代码分割补充行 26
代码分割补充行 27
代码分割补充行 28
代码分割补充行 29
代码分割补充行 30
代码分割补充行 31
代码分割补充行 32
代码分割补充行 33
代码分割补充行 34
代码分割补充行 35
代码分割补充行 36
代码分割补充行 37
代码分割补充行 38
代码分割补充行 39
代码分割补充行 40
代码分割补充行 41
代码分割补充行 42
代码分割补充行 43
代码分割补充行 44
代码分割补充行 45

图片懒加载

1
<img data-src="image.jpg" alt="Lazy Image">
1
2
3
4
5
6
7
8
9
10
11
12
13
const images = document.querySelectorAll('img[data-src]');

const imageObserver = 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 => imageObserver.observe(img));

组件懒加载

1
2
3
4
5
6
7
8
9
const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}

总结

懒加载提升首屏性能。Intersection Observer 简单有效。

交互型懒加载

  • 滚动加载(infinite scroll)
  • 按需加载组件
  • 使用 React.lazySuspense 实现 React 组件懒加载
1
2
3
4
5
6
7
8
9
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}

兼容性与 Polyfill

IntersectionObserver 在旧浏览器中需引入 polyfill:

1
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>

图片懒加载的注意点

  • 保留 width/height 属性以避免 CLS
  • SEO 影响:使用 noscript 提供备选内容
  • 使用 loading="lazy" 原生属性(支持现代浏览器)

代码示例:CSS 懒加载

1
2
3
4
5
6
7
.lazy {
opacity: 0;
transition: opacity 0.3s;
}
.lazy.loaded {
opacity: 1;
}

性能分析

  • 减少首屏流量
  • 降低内存占用
  • 提高滚动流畅度- 提高滚动流畅度- 提高滚动流畅度- 提高rsection-observer

实践建议

  1. 只对可见区域外的资源使用懒加�1. 只对可见区域外的资源使用懒加�1. 只对加载。
    333333�333333�333333�333333�333333�333333�333333�333333�333333�3�单有效的优化手段,但使用不当会导致用户感知性能下降。需根据�333333�333333唨户行为进行权衡。

更深内容

延续上文,详细讨论懒加载中常见的问题和高级应用。

SEO 与用户体验

使用 noscript 标签为搜索引擎提供备选内容:

1
<noscript><img src="image.jpg" alt="fallback"></noscript>

性能指标

记录懒加载元素加载时间,分析其对 LCP 的影响。

自动化工具

  • webpack 的 import() 自动分割
  • 使用 Babel 插件

安全性

确认懒加载内容不会触发脚本注入

结语

懒加载是一种可扩展的策略,可与预加载、缓存一起使用构建渐进式体验。

懒加载补充行 1
懒加载补充行 2
懒加载补充行 3
懒加载补充行 4
懒加载补充行 5
懒加载补充行 6
懒加载补充行 7
懒加载补充行 8
懒加载补充行 9
懒加载补充行 10
懒加载补充行 11
懒加载补充行 12
懒加载补充行 13
懒加载补充行 14
懒加载补充行 15
懒加载补充行 16
懒加载补充行 17
懒加载补充行 18
懒加载补充行 19
懒加载补充行 20
懒加载补充行 21
懒加载补充行 22
懒加载补充行 23
懒加载补充行 24
懒加载补充行 25

优化策略

  • 减少 HTTP 请求
  • 压缩资源
  • 缓存
  • 懒加载
  • CDN

代码层面

  • 减少重绘重排
  • 使用 CSS3 动画
  • 避免内存泄漏

工具

  • Webpack Bundle Analyzer
  • Chrome DevTools

总结

性能优化持续过程。监控和改进。

网络优化

  • 合并资源、使用 HTTP/2 多路复用
  • 使用 CDN 分发静态资源
  • 减少 DNS 查询和连接数
  • 使用 preconnectprefetchpreload 提示浏览器提前加载
1
2
<link rel="preload" href="main.js" as="script">
<link rel="preconnect" href="https://fonts.googleapis.com">

渲染优化

  • 虚拟 DOM 更新批处理
  • 使用 will-change 进行渲染提示
  • 减少重排(reflow)与重绘(repaint)
  • 使用 GPU 加速动画

内存管理

  • 清理不必要的 DOM 节点
  • 使用 WeakMap 存储元素状态
  • 监控内存使用,避免泄漏

性能指标

指标 说明
FCP 首次内容绘制
LCP 最大内容绘制
TTI 可交互时间
CLS 视觉稳定性

使用 Lighthouse、WebPageTest 和 Chrome DevTools 进行分析。

性能预算

设置 JS、CSS、图片等大小限制,持续监控并在构建时阻止超标。

案例研究

  • �- �- �- �- �- �- �- �- �- �- �- �- �- �- �- �- �- �- �- 䜬�- �- �- �- 佽的�- �- �- �- �- �- �- �- �- �- �- �- �- �- �ac- �- �- �- �- �- �- �- �- �-
    �- �- �- �- �- �- �- �- �- �- �e- �- �- �- �- �- �- �- �- �- �e- �- �- �- �- �- �- �- �- �- �e`- �- �- �求浪费
  • 误用 async/defer 引起依赖错误

### ### ### ### ### ### ### ### ### ### ### ### ### ### ##�、代### ### �染到运### ### ### ### ### ### ###。持续监测并结合业务需求制定策略。

性能优化补充 1
性能优化补充 2
性能优化补充 3
性能优化补充 4
性能优化补充 5
性能优化补充 6
性能优化补充 7
性能优化补充 8
性能优化补充 9
性能优化补充 10
性能优化补充 11
性能优化补充 12
性能优化补充 13
性能优化补充 14
性能优化补充 15
性能优化补充 16
性能优化补充 17
性能优化补充 18
性能优化补充 19
性能优化补充 20
性能优化补充 21
性能优化补充 22
性能优化补充 23
性能优化补充 24
性能优化补充 25
性能优化补充 26
性能优化补充 27
性能优化补充 28
性能优化补充 29
性能优化补充 30
性能优化补充 31
性能优化补充 32
性能优化补充 33
性能优化补充 34
性能优化补充 35
性能优化补充 36
性能优化补充 37
性能优化补充 38
性能优化补充 39
性能优化补充 40
性能优化补充 41
性能优化补充 42
性能优化补充 43
性能优化补充 44
性能优化补充 45
性能优化补充 46
性能优化补充 47
性能优化补充 48
性能优化补充 49
性能优化补充 50
性能优化补充 51
性能优化补充 52
性能优化补充 53
性能优化补充 54
性能优化补充 55
性能优化补充 56
性能优化补充 57
性能优化补充 58
性能优化补充 59
性能优化补充 60
性能优化补充 61
性能优化补充 62
性能优化补充 63
性能优化补充 64
性能优化补充 65
性能优化补充 66
性能优化补充 67
性能优化补充 68
性能优化补充 69
性能优化补充 70
性能优化补充 71
性能优化补充 72
性能优化补充 73
性能优化补充 74
性能优化补充 75
性能优化补充 76
性能优化补充 77
性能优化补充 78
性能优化补充 79
性能优化补充 80

什么是内存泄漏?

程序不再使用的内存没有释放。

常见原因

意外的全局变量

1
2
3
function func() {
leak = 'I am global' // 忘记 var/let
}

被遗忘的定时器

1
2
3
4
5
6
const interval = setInterval(() => {
// ...
}, 1000)

// 忘记清除
// clearInterval(interval)

闭包

1
2
3
4
5
6
7
function createLeak() {
const largeData = new Array(1000000)

return function() {
console.log(largeData.length)
}
}

DOM 引用

1
2
3
4
const element = document.getElementById('element')
const data = { element }

// 即使 DOM 移除,data.element 仍引用

检测工具

  • Chrome DevTools Memory 面板
  • heap snapshot

预防

  • 使用 let/const
  • 及时清除定时器
  • 移除事件监听器
  • 避免 DOM 引用

总结

内存泄漏是前端性能常见问题,主要由未释放引用导致。了解泄漏类型并使用工具检测是关键。

泄漏类型

  1. 意外的全局变量
    x = 1; 会创建全局变量,难以回收。

  2. 闭包保留
    回调函数持有外层变量,导致原本应该释放的对象继续存在。

  3. 被遗忘的定时器
    setIntervalsetTimeout 未清除。

  4. DOM 节点保留
    除 DOM 外的引用阻止其回收,如数组缓存引用到 DOM 元素。

  5. 循环引用
    普通对象之间循环引用不会直接导致泄漏,但在某些非标内存情况下会。

检测和诊断

  • Heap 快照:比较不同时间点的快照,找增长的对象。
  • Timeline/Performance:观察内存曲线。
  • Chrome DevTools 的 Allocation instrumentation:追踪内存分配。

避免策略

  • 使用 let/const,避免隐式全局
  • 定时器使用后 clearInterval / clearTimeout
  • 在组件销毁时解除事件监听 removeEventListener
  • 使用 WeakMap 保存 DOM 关联数据
  • 及时 null 化不再使用的变量

实战示例

1
2
3
4
5
function createLeak() {
const hugeArray = new Array(1000000).fill('*');
window.leak = hugeArray; // 全局引用造成泄漏
}
createLeak();

释放技巧

1
2
3
function clean() {
window.leak = null;
}

后端和 Node.js

在 Node 中也可能发生泄漏,常见于全局缓存、闭包和未关闭的流。

性能影响

泄漏会增加 GC 次数、拖慢响应并最终导致浏览器崩溃。定期监控对保持稳定运行至关重要。

工具推荐

  • MemLab(Facebook 的内存分析工具)
  • LeakCanary(移动端)
  • Chrome Treemap 可视化对象关系

进阶内容

  • 利用 WeakRef 和 FinalizationRegistry 管理弱引用
  • 使用 ESLint 插件 no-leaked-jquery-handlesno-closure-leak 检测潜在问题

结语

排查内存泄漏是开发中的重要环节,养成写干净代码的习惯并定期使用工具检查,可以大幅提升应用稳定性和性能。

内存泄漏影响性能。开发时注意,定期检查内存使用。内存泄漏补充 1
内存泄漏补充 2
内存泄漏补充 3
内存泄漏补充 4
内存泄漏补充 5
内存泄漏补充 6
内存泄漏补充 7
内存泄漏补充 8
内存泄漏补充 9
内存泄漏补充 10
内存泄漏补充 11
内存泄漏补充 12

内存管理

JavaScript 自动垃圾回收。

引用计数

对象引用计数为0时回收。

1
2
let obj = { name: 'Alice' }
obj = null // 回收

循环引用问题

1
2
3
4
5
6
7
let obj1 = {}
let obj2 = {}
obj1.ref = obj2
obj2.ref = obj1
obj1 = null
obj2 = null
// 现代引擎能处理

标记清除

主流算法。

  1. 标记根可达对象
  2. 清除未标记对象

内存泄漏

  • 全局变量
  • 闭包
  • DOM 引用
  • 定时器

优化

  • 避免全局变量
  • 及时清除引用
  • 使用 WeakMap/WeakSet

总结

JavaScript 的垃圾回收机制自动管理内存,最常见的是标记-清除算法。深入理解 GC 有助于写出内存高效的程序,并避免性能陷阱。

垃圾回收算法

标记-清除 (Mark-and-Sweep)

  1. 从根对象开始标记可达对象
  2. 清除未被标记的对象

引用计数 (Reference Counting)

为每个对象维护一个引用计数;计数为0时回收。存在循环引用问题。

分代回收 (Generational GC)

对象根据存活时间分代,新生代回收频繁,老生代回收较少。

增量回收与并行回收

现代浏览器支持将 GC 工作拆分成小块并在后台线程执行,减少停顿时间。

内存管理技巧

  • 避免建立大规模长生命周期对象
  • 使用 const 避免意外重新赋值
  • 对 DOM 对象使用 WeakMap 保存元数据

WeakMap/WeakSet 的意义

WeakMap 的键是弱引用,垃圾回收不会因为键存在而阻止其回收:

1
2
3
4
let wm = new WeakMap();
let obj = {};
wm.set(obj, 'meta');
obj = null; // 键对象可以被回收

诊断工具

  • Chrome DevTools:Memory 面板、Heap snapshot、Allocation instrumentation
  • Firefox:Memory 工具
  • Node.js--inspect 模式、heapdump 模块

手动触发垃圾回收

在 Chrome 的开发者控制台中可以调用 window.gc()(需要开启 --js-flags="--expose-gc")。

常见问题

  • 大量对象未被清除:检查是否存在闭包、全局变量或 DOM 事件监听未释放。
  • 内存增长缓慢:可能是分代回收暂时未触发。
  • 性能抖动:GC 停顿影响 UI,可通过减少分配和简化对象关系来优化。

专业建议

  • 在性能敏感的循环中尽量避免创建临时对象
  • 使用 requestAnimationFrame 合理安排 UI 更新
  • 在大型项目中定期分析 heap snapshot,查找泄漏或不必要的保留

相关资源

  • V8 的垃圾回收原理
  • SpiderMonkey GC 文档
  • Google Chrome 开发者博客关于性能优化的系列文章

了解垃圾回收有助于写高效代码。现代引擎很智能,但仍需注意内存使用。

跨平台差异

不同引擎(V8、SpiderMonkey、JavaScriptCore)在 GC 实现上略有差异,但基本原理相似。调优时应在目标平台上进行测试。

内存碎片

频繁分配和释放大对象可能导致内存碎片,影响性能。可以使用对象池(object pool)复用对象。

GC 可视化

使用 Chrome about://tracing 或 DevTools 中的 Timeline 记录 GC 活动,观察停顿时间。

垃圾回收补充行 1
垃圾回收补充行 2
垃圾回收补充行 3
垃圾回收补充行 4
垃圾回收补充行 5
垃圾回收补充行 6
垃圾回收补充行 7
垃圾回收补充行 8
垃圾回收补充行 9
垃圾回收补充行 10
垃圾回收补充行 11
垃圾回收补充行 12
垃圾回收补充行 13
垃圾回收补充行 14
垃圾回收补充行 15
垃圾回收补充行 16
垃圾回收补充行 17
垃圾回收补充行 18
垃圾回收补充行 19
垃圾回收补充行 20

什么是事件循环?

JavaScript 单线程,通过事件循环处理异步任务。

执行栈

函数调用形成栈。

任务队列

异步任务放入队列。

宏任务 vs 微任务

  • 宏任务:setTimeout, setInterval, I/O
  • 微任务:Promise, MutationObserver

执行顺序

  1. 执行同步代码
  2. 执行所有微任务
  3. 执行一个宏任务
  4. 重复

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('start')

setTimeout(() => {
console.log('timeout')
}, 0)

Promise.resolve().then(() => {
console.log('promise')
})

console.log('end')

// 输出: start, end, promise, timeout

总结

理解事件循环对写异步代码很重要。微任务优先于宏任务。

Node 事件循环分阶段

Node 事件循环有多个阶段,利用 libuv 实现:

  1. timers 阶段:执行计时器回调(setTimeoutsetInterval
  2. pending callbacks:处理上一轮循环遗留的 I/O 回调
  3. idle, prepare:内部
  4. poll 阶段:等待新的 I/O 事件
  5. check 阶段:执行 setImmediate 回调
  6. close callbacks:执行关闭回调

示例:

1
2
3
4
5
6
fs.readFile('file', () => {
console.log('read');
});
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
// 输出顺序:timeout -> immediate -> read (可能因 I/O 速度不同)

高级话题

为什么 Promise.then 是微任务?

标准指定所有 .then 回调加入微任务队列,以确保在当前宏任务结束后立即执行,这样可以保证程序逻辑的一致性。

MutationObserver 与微任务

MutationObserver 也会插入微任务,可用来观察 DOMMutationObserver 也会插入微任务,可用来观察 DevTooMutationObserver 也会插的 MutationObserver 也会插入微任务,可用来观�或MutationObserver 也会插入微任务,可用来观察 DOMMutationO长MutationObserver也会插入微任务,可用来观察setImMutationObserver 也�out(…,0) 在 Node 中避免在 timers �MutationObserver 也会插入微任务,� 提高异步代码可读性

常见错误

  • 在微任务中�- 在微任务中�- 在
    �-------------------extTick -------------------extTick ---------------的-------------------extTick` -------------| No—|
    |------|--------|------|
    | 微任务 | Promise、MutationObserver | Promise|��pr| 微任务 | P|
    |||||||||||||||||||||||||||||||||||||||||||| t|||||||||||||||||||||||||||||||||||||||||||| t|||||||||||||||||||||||||||||||||||||||||||| t|||||||||||||||||||||||||||||||||||||||||||| t|||||||||||- 参�|||||||||||||||||||||||||||||||�

附加事件循环行 1
附加事件循环行 2
附加事件循环行 3
附加事件循环行 4
附加事件循环行 5
附加事件循环行 6
附加事件循环行 7
附加事件循环行 8
附加事件循环行 9
附加事件循环行 10
附加事件循环行 11
附加事件循环行 12
附加事件循环行 13
附加事件循环行 14
附加事件循环行 15
附加事件循环行 16
附加事件循环行 17
附加事件循环行 18
附加事件循环行 19
附加事件循环行 20
附加事件循环行 21
附加事件循环行 22
附加事件循环行 23
附加事件循环行 24
附加事件循环行 25
附加事件循环行 26
附加事件循环行 27
附加事件循环行 28
附加事件循环行 29
附加事件循环行 30
附加事件循环行 31
附加事件循环行 32
附加事件循环行 33
附加事件循环行 34
附加事件循环行 35
附加事件循环行 36
附加事件循环行 37
附加事件循环行 38
附加事件循环行 39
附加事件循环行 40
附加事件循环行 41
附加事件循环行 42
附加事件循环行 43
附加事件循环行 44
附加事件循环行 45
附加事件循环行 46
附加事件循环行 47
附加事件循环行 48
附加事件循环行 49
附加事件循环行 50
附加事件循环行 51
附加事件循环行 52
附加事件循环行 53
附加事件循环行 54
附加事件循环行 55
附加事件循环行 56
附加事件循环行 57
附加事件循环行 58
附加事件循环行 59
附加事件循环行 60

this 指向

this 指向函数执行时的上下文。

全局上下文

1
console.log(this) // window (浏览器)

函数上下文

普通函数

1
2
3
4
5
function func() {
console.log(this)
}

func() // window

对象方法

1
2
3
4
5
6
7
const obj = {
func() {
console.log(this)
}
}

obj.func() // obj

构造函数

1
2
3
4
5
6
function Person(name) {
this.name = name
}

const person = new Person('Alice')
console.log(person.name) // 'Alice'

箭头函数

箭头函数没有自己的 this,继承外层。

1
2
3
4
5
const obj = {
func: () => console.log(this)
}

obj.func() // window

改变 this

call()

1
func.call(obj, arg1, arg2)

apply()

1
func.apply(obj, [arg1, arg2])

bind()

1
2
const boundFunc = func.bind(obj)
boundFunc()

常见坑

  • 回调函数丢失 this
  • 箭头函数在对象方法中

总结

this 的指向是 JavaScript 中经常让人迷惑的概念,掌握 callapplybind、箭头函数以及严格模式规则,可以在编写回调、事件处理和类方法时避免错误。建议在开发时保持一致性,明确组件/模块之间的边界。

箭头函数与 this

箭头函数没有自己的 this,它绑定创建时的上下文:

1
2
3
4
5
const obj = {
value: 10,
getValue: () => this.value
};
console.log(obj.getValue()); // undefined

类方法中的 this

在 ES6 类中,方法的 this 通常指向实例,可通过 bind 固定:

1
2
3
4
5
6
7
8
9
class Timer {
constructor() {
this.seconds = 0;
setInterval(this.tick.bind(this), 1000);
}
tick() {
this.seconds++;
}
}

DOM 事件中的 this

事件处理函数中的 this 默认指向绑定的 DOM 元素:

1
2
3
element.addEventListener('click', function() {
console.log(this); // element
});

使用箭头函数则 this 指向外层环境。

坑与避雷

  • 在回调中传递方法时忘记绑定
  • 与 React/Angular 等框架中的 this 差异
  • 在严格模式下 thisundefined,非法调用可报错

调试技巧

  • 在 Chrome 控制台使用 this 快捷键
  • 通过 console.log(this) 检查当前指向
  • 使用 IDE 的“跳转到定义”功能查看上下文

总览

理解 this 后,编写面向对象的 JavaScript、处理事件和使用框架时会更加自如。大量练习和阅读他人代码有助于巩固概念。

this 灵活但复杂。多练习,理解不同场景下的指向。

什么是闭包?

闭包是函数和其词法环境的组合。

1
2
3
4
5
6
7
8
9
10
11
12
function outer() {
const value = 'closure'

function inner() {
console.log(value)
}

return inner
}

const func = outer()
func() // 'closure'

作用

  • 封装私有变量
  • 创建工厂函数
  • 实现模块模式

经典例子

计数器

1
2
3
4
5
6
7
8
9
10
11
12
function createCounter() {
let count = 0

return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
}
}

const counter = createCounter()
counter.increment() // 1

私有变量

1
2
3
4
5
6
7
8
function Person(name) {
let _name = name

return {
getName: () => _name,
setName: (newName) => _name = newName
}
}

注意事项

  • 内存泄漏:闭包可能导致变量无法释放
  • 性能:过度使用影响性能

总结

闭包是 JavaScript 最强大的特性之一,它赋予函数维护私有状态的能力,也是模块化、迭代器、回调和许多高级模式的基础。合理使用闭包可以写出简洁优雅的代码,但滥用则可能导致内存泄漏和调试困难。

闭包在模块化中的应用

1
2
3
4
5
6
const counter = (function() {
let count = 0;
return function() {
return ++count;
};
})();

性能和优化

  • 避免在循环中创建不必要的闭包
  • 使用 let/const 代替 var 防止意外共享
  • 释放引用:将不再使用的闭包赋值为 null

闭包与事件处理

1
2
3
4
button.addEventListener('click', (function() {
let clicks = 0;
return () => { console.log(++clicks); }
})());

常见误区

  • 认为闭包永远存在:只要有引用就会保持
  • 认为闭包创造全局变量:闭包本身是局部的

调试闭包

  • 使用 Chrome DevTools 的 Scope 窗口查看闭包变量
  • heap snapshot 分析函数闭包

小结

掌握闭包需要理解作用域链与垃圾回收的交互。不断练习示例,并阅读开源库源码(如 jQuery、Lodash)以体会闭包的实际用法。

闭包是 JavaScript 强大特性。理解它能写出更好代码,但要小心内存使用。

练习题

  1. 写出一个闭包实现的缓存函数。
  2. 使用闭包创建一个计时器。

扩展阅读

  • JavaScript 权威指南中的闭包章节
  • Paul Irish 关于闭包的演讲

代码审查建议

在审查代码时留意函数内部是否创建了不必要的闭包,以及是否安全释放了外部引用。

练习答案示例

1
2
3
4
5
6
7
function memoize(fn) {
const cache = {};
return function(arg) {
if (cache[arg]) return cache[arg];
return cache[arg] = fn(arg);
};
}

补充闭包行 1
补充闭包行 2
补充闭包行 3
补充闭包行 4
补充闭包行 5
补充闭包行 6
补充闭包行 7
补充闭包行 8
补充闭包行 9
补充闭包行 10

原型和原型链

JavaScript 通过原型链实现继承。

构造函数

1
2
3
4
5
function Person(name) {
this.name = name
}

const person = new Person('Alice')

原型对象

1
2
3
4
5
Person.prototype.sayHello = function() {
console.log(`Hello, ${this.name}`)
}

person.sayHello() // Hello, Alice

proto

实例的 __proto__ 指向构造函数的 prototype。

1
person.__proto__ === Person.prototype // true

原型链

1
2
3
person.__proto__ === Person.prototype
Person.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

继承

原型继承

1
2
3
4
5
6
7
function Student(name, grade) {
Person.call(this, name)
this.grade = grade
}

Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student

ES6 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
constructor(name) {
this.name = name
}

sayHello() {
console.log(`Hello, ${this.name}`)
}
}

class Student extends Person {
constructor(name, grade) {
super(name)
this.grade = grade
}
}

总结

理解原型链对掌握 JavaScript 很重要。ES6 class 简化了继承,但底层仍是原型。

示例:手动实现继承函数

1
2
3
4
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
sub.prototype.constructor = sub;
}

原型链深度与性能

浏览器查找属性沿着原型链向上,如果链条过长会增加查找时间。尽量避免在关键路径上频繁访问深层属性。

ES6 class 实际编译

Babel 将 class 转换为如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

var Person = function Person(name) {
_classCallCheck(this, Person);
this.name = name;
};

Person.prototype.sayHello = function sayHello() {
console.log("Hello, " + this.name);
};

这些代码仍然使用原型机制。

原型链的用途

  • 模拟继承
  • 实现多态
  • 提供共享方法

面试题

如何判断一个对象是某个构�> 如何判断一个对象是某个构�> 如何判断一个对象�链:

1
2
3
4
5
function isInstanceOfunction nstrufunction isInstanceOfunctectfunction isInstanceOfunction nstrufunction isI (function isInstanceOfunction ns) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}

注意事项

  • 修改原型会影响所有实例
  • 不要在运行时频繁修改 prototype,�- 不要在运行时频�## - 不要在运行时频繁修改 proto��底层设计,- 不要在运行时频繁修改 prototype`,�也有助于理解框架如 Vue、React 的对象模型。

附加内容补充1
附加内容补充2
附加内容补充3
附加内容补充4
附加内容补充5
附加内容补充6