Skip to content

调试

webStorm
  1. 启动
  2. 在 idea 代码行标记断点
  3. control + shift 点击 dev url 弹出新浏览器则进入调试界面

资源加载

images
  • cdn
  • 压缩(oss 支持)
  • 先使用低质量图片展位,再加载原图

bugs

resize 跟 debounce 时机问题

背景:检测页面是否在前台以及 resize 进行 video 元素的播放跟暂停切换

原因:正常用户操作(切换tab)的时间间隔大于防抖间隔,因此逻辑是生效的;但是在某些情况下,比如直接使用代码进行页面的切换,导致这个间隔小于防抖间隔,原先某些 flag 作为判断是否成功的变量反而成为阻塞二次逻辑进行的阻碍(电脑性能导致的逻辑执行快慢,典型的它人电脑才能复现)

总结:因为防抖等操作创建的 flag 判断应该更加精准,细化,防止因为某些条件没有达成 flag 条件,但是却将 flag = true,导致下次的逻辑处理出现阻塞

html_element

input

[CSS]去掉 high light
scss
input:-webkit-autofill {
    -webkit-text-fill-color: var(--text-fill-color, white);
    -webkit-background-clip: text;
}
react useRef 跟 BroadcastChannel

背景:封装一个窗体通信功能,使用 useRef 来保存引用,避免多次创建,其中一个功能需要保存一个引用对象,用于储存已读用户的信息

bug:每次给该引用对象设置新的值,但是在 BroadcastChannel.onmessage 的回调中该引用对象的值始终为空

typescript
const CHANNEL_MAP = new Map<string, BroadcastChannel>()

function createChannel<T = unknown>(
    channelName: string,
    onMessage: (data: T) => void
): ChannelRes<T> {
    let channel: BroadcastChannel | null = null
    if (CHANNEL_MAP.has(channelName)) {
        channel = CHANNEL_MAP.get(channelName)!
    } else {
        channel = new BroadcastChannel(channelName)
        CHANNEL_MAP.set(channelName, channel)
    }
    const promiseMap: Record<string, Receivers> = {} // 引用对象(放在 createChannel 内部)
    const user = Math.random().toString().slice(-5)
    channel.onmessage = () => {
        console.log(promiseMap)
    }

    return {
        broadcast(message) {
            const uid = getUid()
            Reflect.set(promiseMap, uid, []) // 更新对象
        },
    }
}
const channel = useRef(createChannel('alpha'))

原因:useRef 接收值为函数执行返回结果,那么每次 react 更新这个函数都会执行,按常理说里面的引用对象都会更新,但是由于 channel 是固定的(被提升到 es 模块顶层,没有重新创建),而 channel.onmessage 只能注册一次, 即后续代码重新执行的时候,后续的 channel.onmessage 并没有生效,因此 channel.onmessage 内部使用引用的是第一次创建时候的 promiseMap,而其余地方的 promiseMap 早已随之更新,总结就是引用了不同的对象

解决:

  • 方法1: 将 promiseMap 移动到外部,即跟 CHANNEL_MAP 同级
  • 方法2:
typescript
// 修改 createChannel 创建方式,避免多次创建,其引用对象也就不会改变了
const channel = useRef()
if (!channel.current) {
    channel.current = createChannel('alpha')
}

video

autoplay

<video src="" muted autoplay>

ts
// in weChat
elVideo.play().catch(() => {
    typeof WeixinJSBridge !== 'undefined' &&
        WeixinJSBridge.invoke('getNetworkType', {}, () => {
            el.currentTime = 0
            el.play()
        })
})

http

Streamable HTTP

引用

前段时间火了个SSE,如今MCP为何全都弃了???

Streamablesse
断线重连自动断线重连与数据恢复客户端需要重新建立连接并手动恢复数据流
长连接传输结束后服务器立即关闭连接依赖持久化长连接
格式text/event-stream
​​资源消耗高(SSE需为每个客户端维持TCP长连接)
  • 服务端
ts
import { NextResponse } from 'next/server'

export async function GET() {
    const encoder = new TextEncoder()
    const stream = new ReadableStream({
        async start(controller) {
            const messages = ['Hello', 'World', 'Streaming', 'Example']
            for (const message of messages) {
                controller.enqueue(encoder.encode(`${message}\n`))
                await new Promise((resolve) => setTimeout(resolve, 1000)) // 模拟延迟
            }
            controller.close()
        },
    })

    return new NextResponse(stream, {
        headers: {
            'Content-Type': 'text/plain; charset=utf-8',
            'Transfer-Encoding': 'chunked',
        },
    })
}

export async function POST() {
    return NextResponse.json({ name: 'rick' })
}
  • 客户端
tsx
'use client'

export default function Test() {
    function handleClick() {
        const decoder = new TextDecoder()

        // body.getReader
        fetch('/api/note', { method: 'GET' })
            .then((r) => r.body!.getReader())
            .then((reader) =>
                reader.read().then(function process({ done, value }): Promise<string> {
                    if (done) return Promise.resolve('')
                    console.log(decoder.decode(value))
                    return reader.read().then(process)
                })
            )

        // 遍历方式进行读取,使用 total 进行统计
        fetch('/api/note', { method: 'GET' }).then(async (response) => {
            const total: number[] = []
            for await (const _chunk of response.body! as unknown as NodeJS.ReadableStream) {
                const chunk = _chunk as Uint8Array
                total.push(...chunk)
                // console.log(decoder.decode(chunk))
            }
            console.log(decoder.decode(new Uint16Array(total))) // total 统计时候,已经变成了 number[] 类型,decoder.decode 需要接受 Arraybuffer 类型, 然后浏览器默认使用 utf-8 格式,对应 Uint8Array
        })
    }

    function anotherHttp() {
        // 将流又重新转成 Response,整体流程实际上就相当于 response => response.text()
        fetch('/api/note')
            .then((response) => response.body)
            .then((stream) =>
                // Respond with our stream
                new Response(stream, { headers: { 'Content-Type': 'text/html' } }).text()
            )
            .then((result) => {
                // Do things with result
                console.log(result)
            })
    }

    return (
        <div className="flex gap-2">
            <button
                className="cursor-pointer bg-amber-200 p-2"
                onClick={handleClick}
            >
                发送请求
            </button>
            <button
                className="cursor-pointer bg-amber-200 p-2"
                onClick={anotherHttp}
            >
                Response 流转
            </button>
        </div>
    )
}
SSE 流式传输

一个基于 http 协议的通信技术

  • 代码实现

  • 服务端

    javascript
    const express = require('express')
    const app = express()
    
    app.get('/events', (req, res) => {
        res.setHeader('Content-Type', 'text/event-stream')
        res.setHeader('Cache-Control', 'no-cache')
        res.setHeader('Connection', 'keep-alive')
    
        const startTime = Date.now()
    
        const sendEvent = () => {
            // 检查是否已经发送了10秒
            if (Date.now() - startTime >= 10000) {
                res.write('event: close\ndata: {}\n\n') // 发送一个特殊事件通知客户端关闭
                res.end() // 关闭连接
                return
            }
    
            const data = { message: 'Hello World', timestamp: new Date() }
            res.write(`data: ${JSON.stringify(data)}\n\n`)
    
            // 每隔2秒发送一次消息
            setTimeout(sendEvent, 2000)
        }
    
        sendEvent()
    })
    
    app.listen(3000)
  • 客户端

    html
    <!--客户端-->
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <title>SSE Example</title>
        </head>
    
        <body>
            <h1>Server-Sent Events Example</h1>
            <div id="messages"></div>
    
            <script>
                const evtSource = new EventSource('/events')
                const messages = document.getElementById('messages')
    
                evtSource.onmessage = function (event) {
                    const newElement = document.createElement('p')
                    const eventObject = JSON.parse(event.data)
                    newElement.textContent =
                        'Message: ' + eventObject.message + ' at ' + eventObject.timestamp
                    messages.appendChild(newElement)
                }
            </script>
        </body>
    </html>
  • 应用场景

    倒计时同步、实时天气、实时股票、库存更新

  • 技术对比

    特性/因素SSEWebSocket
    协议基于HTTP,使用标准HTTP连接单独的协议(ws:// 或 wss://),需要握手升级
    通信方式单向通信(服务器到客户端)全双工通信
    数据格式文本(UTF-8编码)文本或二进制
    重连机制浏览器自动重连需要手动实现重连机制
    实时性高(适合频繁更新的场景)非常高(适合高度交互的实时应用)
    浏览器支持良好(大多数现代浏览器支持)非常好(几乎所有现代浏览器支持)
    适用场景实时通知、新闻feed、股票价格等需要从服务器推送到客户端的场景在线游戏、聊天应用、实时交互应用
    复杂性较低,易于实现和维护较高,需要处理连接的建立、维护和断开
    兼容性和可用性基于HTTP,更容易通过各种中间件和防火墙可能需要配置服务器和网络设备以支持WebSocket
    服务器负载适合较低频率的数据更新适合高频率消息和高度交互的场景

mobile

300ms 延迟

说明

html
<meta
    name="viewport"
    content="width=device-width"
/>

Last updated: