跳转到正文
YGJoon
返回

Kotlin/JS 性能优化与调试技巧

编辑文章

性能优化概述

Kotlin/JS 应用程序的性能优化涉及多个层面,从编译时优化到运行时调优。理解 JavaScript 引擎的工作原理和 Kotlin/JS 的编译机制,可以帮助我们构建高性能的 Web 应用程序。

性能优化的重要性

  1. 用户体验:快速响应的界面提升用户满意度
  2. SEO 优化:页面加载速度影响搜索引擎排名
  3. 资源消耗:优化后的应用占用更少的内存和 CPU
  4. 可访问性:性能优化使应用在低端设备上也能良好运行

代码分割与懒加载

动态导入

// 使用动态导入实现懒加载
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 的协同开发,探索如何实现跨平台代码共享。


编辑文章
分享这篇文章:

上一篇
Kotlin/JS 模块化与依赖管理
下一篇
Kotlin/JS 异步编程与协程