性能优化概述
Kotlin/JS 应用程序的性能优化涉及多个层面,从编译时优化到运行时调优。理解 JavaScript 引擎的工作原理和 Kotlin/JS 的编译机制,可以帮助我们构建高性能的 Web 应用程序。
性能优化的重要性
- 用户体验:快速响应的界面提升用户满意度
- SEO 优化:页面加载速度影响搜索引擎排名
- 资源消耗:优化后的应用占用更少的内存和 CPU
- 可访问性:性能优化使应用在低端设备上也能良好运行
代码分割与懒加载
动态导入
// 使用动态导入实现懒加载
suspend fun loadAdminModule(): AdminModule {
return js("import('./admin/AdminModule.js')")
}
class App {
private val adminModule = lazy {
GlobalScope.promise { loadAdminModule() }
}
suspend fun navigateToAdmin() {
val module = adminModule.value.await()
module.initialize()
}
}
// 模块定义
@JsModule("./admin/AdminModule.js")
external class AdminModule {
fun initialize()
}
路由级别的代码分割
import react.*
import react.router.dom.*
import kotlinx.coroutines.promise
// 懒加载路由组件
val LazyHomePage = lazy {
GlobalScope.promise {
js("import('./pages/HomePage.js')")
}
}
val LazyAboutPage = lazy {
GlobalScope.promise {
js("import('./pages/AboutPage.js')")
}
}
// 使用Suspense处理加载状态
val App = FC<Props> {
BrowserRouter {
div {
Navigation()
React.Suspense {
fallback = div { +"Loading..." }
Routes {
Route {
path = "/"
element = React.lazy(LazyHomePage)
}
Route {
path = "/about"
element = React.lazy(LazyAboutPage)
}
}
}
}
}
}
条件性模块加载
// 基于用户权限的条件性加载
class ModuleLoader {
suspend fun loadFeatureModule(featureName: String): dynamic {
return when (featureName) {
"admin" -> js("import('./features/admin/AdminModule.js')")
"dashboard" -> js("import('./features/dashboard/DashboardModule.js')")
"reports" -> js("import('./features/reports/ReportsModule.js')")
else -> throw Error("Unknown feature: $featureName")
}
}
suspend fun loadModulesForUser(user: User) {
val modules = user.permissions.map { permission ->
GlobalScope.async {
loadFeatureModule(permission.feature)
}
}
modules.awaitAll()
}
}
data class User(val id: String, val permissions: List<Permission>)
data class Permission(val feature: String, val level: String)
内存管理与垃圾回收
避免内存泄漏
// 正确的事件监听器管理
class EventManager {
private val listeners = mutableMapOf<String, (Event) -> Unit>()
fun addListener(element: HTMLElement, eventType: String, handler: (Event) -> Unit) {
val key = "${element.id}-$eventType"
listeners[key] = handler
element.addEventListener(eventType, handler)
}
fun removeListener(element: HTMLElement, eventType: String) {
val key = "${element.id}-$eventType"
listeners[key]?.let { handler ->
element.removeEventListener(eventType, handler)
listeners.remove(key)
}
}
fun cleanup() {
listeners.clear()
}
}
// 使用示例
class ComponentWithEvents {
private val eventManager = EventManager()
fun mount(element: HTMLElement) {
eventManager.addListener(element, "click") { event ->
handleClick(event)
}
}
fun unmount(element: HTMLElement) {
eventManager.removeListener(element, "click")
}
private fun handleClick(event: Event) {
println("Click handled")
}
}
协程生命周期管理
// 使用MainScope管理协程生命周期
class ComponentWithCoroutines {
private val scope = MainScope()
fun startLongRunningTask() {
scope.launch {
while (isActive) {
// 执行周期性任务
updateData()
delay(1000)
}
}
}
fun cleanup() {
scope.cancel() // 取消所有协程
}
private suspend fun updateData() {
// 更新数据逻辑
}
}
// 使用自定义CoroutineScope
class DataManager {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
fun loadData() {
scope.launch {
try {
val data = fetchDataFromApi()
updateUI(data)
} catch (e: Exception) {
handleError(e)
}
}
}
fun destroy() {
job.cancel() // 取消所有子协程
}
}
对象池模式
// 对象池减少频繁的对象创建
class ObjectPool<T>(
private val factory: () -> T,
private val reset: (T) -> Unit = {},
private val maxSize: Int = 10
) {
private val pool = mutableListOf<T>()
private var currentSize = 0
fun acquire(): T {
return if (pool.isNotEmpty()) {
pool.removeAt(pool.size - 1)
} else {
factory()
}
}
fun release(obj: T) {
if (currentSize < maxSize) {
reset(obj)
pool.add(obj)
currentSize++
}
}
fun clear() {
pool.clear()
currentSize = 0
}
}
// 使用对象池
class DataProcessor {
private val bufferPool = ObjectPool(
factory = { ByteArray(1024) },
reset = { array -> array.fill(0) },
maxSize = 5
)
fun processData(data: ByteArray): String {
val buffer = bufferPool.acquire()
try {
// 使用buffer处理数据
return processWithBuffer(data, buffer)
} finally {
bufferPool.release(buffer)
}
}
private fun processWithBuffer(data: ByteArray, buffer: ByteArray): String {
// 处理逻辑
return "Processed data"
}
}
构建优化策略
Webpack 配置优化
// 在build.gradle.kts中优化Webpack配置
kotlin {
js {
browser {
commonWebpackConfig {
cssSupport {
enabled.set(true)
}
// 代码分割配置
config {
optimization = js("""
{
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\\\/]node_modules[\\\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: 'runtime'
}
}
""".trimIndent())
}
}
webpackTask {
// 生产环境优化
mode = if (project.hasProperty("production")) "production" else "development"
// 输出配置
output.library = "MyApp"
output.libraryTarget = "umd"
// 解析配置
resolve {
extensions = listOf(".js", ".kjs", ".css")
}
}
}
}
}
Tree Shaking 优化
// 使用@JsExport选择性导出
@JsExport
class PublicAPI {
// 公共API
fun publicMethod(): String = "Public"
// 私有方法不会被导出
private fun privateMethod(): String = "Private"
companion object {
// 静态公共方法
@JsExport
fun staticMethod(): String = "Static"
// 内部静态方法不会被导出
internal fun internalMethod(): String = "Internal"
}
}
// 使用@Suppress避免不必要的导出
@Suppress("unused")
@JsExport
object Utils {
// 只导出需要的工具函数
@JsExport
fun formatCurrency(amount: Double): String = "$$amount"
@JsExport
fun formatDate(timestamp: Long): String {
return Date(timestamp).toDateString()
}
// 内部工具函数不会被导出
internal fun internalHelper(): String = "Internal"
}
压缩与混淆
// 配置生产环境压缩
tasks.withType<org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfigTask> {
doLast {
// 自定义压缩配置
val configFile = file("webpack.config.js")
if (configFile.exists()) {
// 应用自定义配置
}
}
}
运行时性能优化
虚拟化长列表
// 虚拟列表组件
val VirtualList = FC<VirtualListProps<T>> { props ->
val containerRef = useRef<HTMLDivElement>(null)
val [visibleRange, setVisibleRange] = useState(Pair(0, props.visibleCount))
useEffect(props.items) {
val container = containerRef.current
if (container != null) {
val itemHeight = props.itemHeight
const scrollTop = container.scrollTop
const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = Math.min(
startIndex + props.visibleCount,
props.items.length
)
setVisibleRange(Pair(startIndex, endIndex))
}
}
div {
ref = containerRef
style = js("""
{
height: '${props.visibleCount * props.itemHeight}px',
overflow: 'auto'
}
""")
div {
style = js("""
{
height: '${props.items.length * props.itemHeight}px',
position: 'relative'
}
""")
for (i in visibleRange.first until visibleRange.second) {
val item = props.items[i]
div {
key = props.getKey(item)
style = js("""
{
position: 'absolute',
top: '${i * props.itemHeight}px',
height: '${props.itemHeight}px',
width: '100%'
}
""")
props.renderItem(item)
}
}
}
}
}
external interface VirtualListProps<T> : Props {
var items: List<T>
var itemHeight: Int
var visibleCount: Int
var renderItem: (T) -> ReactNode
var getKey: (T) -> String
}
防抖与节流
// 防抖Hook
fun useDebounce<T>(value: T, delay: Int): T {
val [debouncedValue, setDebouncedValue] = useState(value)
useEffect(value, delay) {
val handler = window.setTimeout({
setDebouncedValue(value)
}, delay)
cleanup {
window.clearTimeout(handler)
}
}
return debouncedValue
}
// 节流Hook
fun useThrottle<T>(value: T, delay: Int): T {
val [throttledValue, setThrottledValue] = useState(value)
val lastExecuted = useRef(Date.now().toLong())
useEffect(value) {
val now = Date.now().toLong()
if (now - lastExecuted.current > delay) {
setThrottledValue(value)
lastExecuted.current = now
} else {
val handler = window.setTimeout({
setThrottledValue(value)
lastExecuted.current = Date.now().toLong()
}, delay - (now - lastExecuted.current))
cleanup {
window.clearTimeout(handler)
}
}
}
return throttledValue
}
// 使用示例
val SearchComponent = FC<Props> {
var searchQuery by useState("")
val debouncedQuery = useDebounce(searchQuery, 300)
useEffect(debouncedQuery) {
// 执行搜索
performSearch(debouncedQuery)
}
div {
input {
type = InputType.text
value = searchQuery
onChange = { event ->
searchQuery = event.target.value
}
placeholder = "Search..."
}
p {
+"Searching for: $debouncedQuery"
}
}
}
缓存策略
// 内存缓存
class MemoryCache<K, V>(
private val maxSize: Int = 100,
private val ttl: Long = 5 * 60 * 1000 // 5分钟
) {
private val cache = mutableMapOf<K, CacheEntry<V>>()
data class CacheEntry<V>(
val value: V,
val timestamp: Long = Date.now().toLong()
)
fun get(key: K): V? {
val entry = cache[key]
return if (entry != null && !isExpired(entry)) {
entry.value
} else {
cache.remove(key)
null
}
}
fun put(key: K, value: V) {
if (cache.size >= maxSize) {
evictOldest()
}
cache[key] = CacheEntry(value)
}
private fun isExpired(entry: CacheEntry<V>): Boolean {
return Date.now().toLong() - entry.timestamp > ttl
}
private fun evictOldest() {
val oldestKey = cache.minByOrNull { it.value.timestamp }?.key
oldestKey?.let { cache.remove(it) }
}
}
// 使用缓存的API服务
class CachedApiService {
private val cache = MemoryCache<String, String>()
suspend fun fetchData(url: String): String {
// 先检查缓存
cache.get(url)?.let { cachedData ->
return cachedData
}
// 缓存未命中,从网络获取
val response = window.fetch(url).await()
val data = response.text().await()
// 存入缓存
cache.put(url, data)
return data
}
}
调试技巧
源码映射配置
// 在build.gradle.kts中启用源码映射
kotlin {
js {
browser {
webpackTask {
sourceMaps = true
sourceMapsEmbedSources = true
}
runTask {
sourceMaps = true
}
}
}
}
开发工具集成
// 开发模式下的调试工具
class DebugUtils {
companion object {
fun logPerformance(name: String, operation: () -> Unit) {
if (isDevelopmentMode()) {
console.time(name)
operation()
console.timeEnd(name)
} else {
operation()
}
}
fun logMemoryUsage() {
if (isDevelopmentMode()) {
val memory = js("performance.memory")
console.log("Memory usage:", js("""
{
used: Math.round(memory.usedJSHeapSize / 1048576) + ' MB',
total: Math.round(memory.totalJSHeapSize / 1048576) + ' MB',
limit: Math.round(memory.jsHeapSizeLimit / 1048576) + ' MB'
}
"""))
}
}
fun traceFunction<T>(name: String, function: () -> T): T {
if (isDevelopmentMode()) {
console.trace("Entering $name")
val result = function()
console.trace("Exiting $name")
return result
} else {
return function()
}
}
private fun isDevelopmentMode(): Boolean {
return js("process.env.NODE_ENV") === "development"
}
}
}
// 使用调试工具
class PerformanceMonitor {
fun measureComponentRender(componentName: String) {
DebugUtils.logPerformance("Render $componentName") {
// 渲染逻辑
}
}
fun monitorMemoryUsage() {
setInterval({
DebugUtils.logMemoryUsage()
}, 10000) // 每10秒记录一次
}
}
错误边界与日志
// 错误边界组件
external interface ErrorBoundaryState : State {
var hasError: Boolean
var error: String?
var errorInfo: dynamic
}
class ErrorBoundary : Component<PropsWithChildren, ErrorBoundaryState>() {
init {
state = ErrorBoundaryState().apply {
hasError = false
error = null
errorInfo = null
}
}
override fun componentDidCatch(error: Throwable, errorInfo: dynamic) {
// 记录错误
console.error("Error caught by boundary:", error, errorInfo)
// 发送错误报告
sendErrorReport(error, errorInfo)
setState(ErrorBoundaryState().apply {
hasError = true
this.error = error.message
this.errorInfo = errorInfo
})
}
override fun render(): ReactNode {
return if (state.hasError) {
div {
h1 {
style = js("{ color: 'red' }")
+"Something went wrong"
}
if (isDevelopmentMode()) {
details {
summary { +"Error details" }
pre {
+state.error
}
pre {
+JSON.stringify(state.errorInfo, null, 2)
}
}
}
button {
onClick = {
setState(ErrorBoundaryState().apply {
hasError = false
error = null
errorInfo = null
})
}
+"Try again"
}
}
} else {
props.children
}
}
private fun sendErrorReport(error: Throwable, errorInfo: dynamic) {
// 发送错误到监控服务
GlobalScope.launch {
try {
val errorData = js("""
{
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
timestamp: new Date().toISOString()
}
""")
window.fetch("/api/errors", js("""
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errorData)
}
""")).await()
} catch (e: Exception) {
console.error("Failed to send error report:", e)
}
}
}
}
性能监控
性能指标收集
// 性能监控工具
class PerformanceMonitor {
private val metrics = mutableMapOf<String, MutableList<Double>>()
fun startTiming(name: String): () -> Unit {
val startTime = performance.now()
return {
val endTime = performance.now()
val duration = endTime - startTime
recordMetric(name, duration)
}
}
fun recordMetric(name: String, value: Double) {
metrics.getOrPut(name) { mutableListOf() }.add(value)
// 保持最近100个测量值
if (metrics[name]!!.size > 100) {
metrics[name]!!.removeAt(0)
}
}
fun getMetrics(name: String): MetricStats? {
val values = metrics[name] ?: return null
return MetricStats(
count = values.size,
average = values.average(),
min = values.minOrNull() ?: 0.0,
max = values.maxOrNull() ?: 0.0,
p95 = calculatePercentile(values, 0.95)
)
}
private fun calculatePercentile(values: List<Double>, percentile: Double): Double {
val sorted = values.sorted()
val index = (percentile * sorted.size).toInt()
return sorted[index.coerceIn(0, sorted.size - 1)]
}
fun getAllMetrics(): Map<String, MetricStats> {
return metrics.mapValues { (_, values) ->
MetricStats(
count = values.size,
average = values.average(),
min = values.minOrNull() ?: 0.0,
max = values.maxOrNull() ?: 0.0,
p95 = calculatePercentile(values, 0.95)
)
}
}
}
data class MetricStats(
val count: Int,
val average: Double,
val min: Double,
val max: Double,
val p95: Double
)
// 使用性能监控
class MonitoredComponent {
private val monitor = PerformanceMonitor()
fun performExpensiveOperation() {
val stopTiming = monitor.startTiming("expensive-operation")
try {
// 执行耗时操作
Thread.sleep(100) // 模拟耗时操作
} finally {
stopTiming()
}
}
fun getPerformanceReport(): String {
val metrics = monitor.getAllMetrics()
return metrics.entries.joinToString("\n") { (name, stats) ->
"$name: avg=${stats.average}ms, p95=${stats.p95}ms, count=${stats.count}"
}
}
}
用户体验指标
// 用户体验监控
class UXMonitor {
fun measurePageLoad() {
window.addEventListener("load") {
val navigation = js("performance.navigation")
val timing = js("performance.timing")
val metrics = mapOf(
"domContentLoaded" to timing.domContentLoadedEventEnd - timing.navigationStart,
"pageLoad" to timing.loadEventEnd - timing.navigationStart,
"firstPaint" to getFirstPaint(),
"firstContentfulPaint" to getFirstContentfulPaint()
)
reportMetrics("page-load", metrics)
}
}
fun measureInteraction(name: String) {
val startTime = performance.now()
return {
val endTime = performance.now()
val duration = endTime - startTime
reportMetric("interaction-$name", duration)
}
}
private fun getFirstPaint(): Double {
val paintEntries = js("performance.getEntriesByType('paint')")
return paintEntries.find { it.name == "first-paint" }?.startTime ?: 0.0
}
private fun getFirstContentfulPaint(): Double {
val paintEntries = js("performance.getEntriesByType('paint')")
return paintEntries.find { it.name == "first-contentful-paint" }?.startTime ?: 0.0
}
private fun reportMetrics(category: String, metrics: Map<String, Double>) {
// 发送指标到分析服务
GlobalScope.launch {
try {
window.fetch("/api/metrics", js("""
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
category: category,
metrics: metrics,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
})
}
""")).await()
} catch (e: Exception) {
console.error("Failed to report metrics:", e)
}
}
}
private fun reportMetric(name: String, value: Double) {
reportMetrics("interaction", mapOf(name to value))
}
}
最佳实践
1. 性能优化清单
// 性能优化检查清单
class PerformanceChecklist {
fun checkApplication(): List<String> {
val issues = mutableListOf<String>()
// 检查代码分割
if (!hasCodeSplitting()) {
issues.add("Consider implementing code splitting for better initial load time")
}
// 检查图片优化
if (!hasOptimizedImages()) {
issues.add("Optimize images for better performance")
}
// 检查缓存策略
if (!hasCachingStrategy()) {
issues.add("Implement caching strategy for static resources")
}
// 检查Bundle大小
if (getBundleSize() > 1024 * 1024) { // 1MB
issues.add("Bundle size is too large, consider further optimization")
}
return issues
}
private fun hasCodeSplitting(): Boolean {
// 检查是否有多个chunk文件
return true // 实际实现需要检查构建输出
}
private fun hasOptimizedImages(): Boolean {
// 检查图片是否经过优化
return true // 实际实现需要检查图片资源
}
private fun hasCachingStrategy(): Boolean {
// 检查是否有缓存配置
return true // 实际实现需要检查缓存配置
}
private fun getBundleSize(): Long {
// 获取Bundle大小
return 512 * 1024 // 示例值
}
}
2. 性能预算
// 性能预算配置
data class PerformanceBudget(
val maxBundleSize: Long = 1024 * 1024, // 1MB
val maxChunkSize: Long = 250 * 1024, // 250KB
val maxInitialLoadTime: Double = 3000.0, // 3秒
val maxTimeToInteractive: Double = 5000.0 // 5秒
)
// 性能预算检查器
class BudgetChecker(private val budget: PerformanceBudget) {
fun checkBudget(metrics: PerformanceMetrics): BudgetReport {
val violations = mutableListOf<BudgetViolation>()
if (metrics.bundleSize > budget.maxBundleSize) {
violations.add(BudgetViolation(
"bundle-size",
metrics.bundleSize,
budget.maxBundleSize,
"Bundle size exceeds budget"
))
}
if (metrics.initialLoadTime > budget.maxInitialLoadTime) {
violations.add(BudgetViolation(
"initial-load-time",
metrics.initialLoadTime,
budget.maxInitialLoadTime,
"Initial load time exceeds budget"
))
}
return BudgetReport(
withinBudget = violations.isEmpty(),
violations = violations
)
}
}
data class PerformanceMetrics(
val bundleSize: Long,
val initialLoadTime: Double,
val timeToInteractive: Double
)
data class BudgetViolation(
val metric: String,
val actual: Any,
val budget: Any,
val message: String
)
data class BudgetReport(
val withinBudget: Boolean,
val violations: List<BudgetViolation>
)
总结
Kotlin/JS 性能优化是一个系统性的工程,需要从多个维度进行考虑。通过合理的代码分割、内存管理、构建优化和性能监控,我们可以构建出高性能、用户体验优秀的 Web 应用程序。
在下一篇文章中,我们将学习 Kotlin/JS 与 Kotlin Multiplatform 的协同开发,探索如何实现跨平台代码共享。