跳到主要内容

clientWidth、offsetWidth、scrollWidth 三者的区别

· 阅读需 2 分钟
1adybug
子虚伊人

clientWidth

  • 表示元素的内部宽度
  • 包括内容区(content)和内边距(padding)
  • 不包括边框(border)、外边距(margin)和滚动条
  • 对于内联元素,clientWidth 总是返回 0

例如一个元素样式如下:

.element {
width: 100px;
padding: 10px;
border: 5px solid black;
}

clientWidth = 120px (content 100px + padding 20px)

offsetWidth

  • 表示元素的布局宽度
  • 包括内容区(content)、内边距(padding)和边框(border)
  • 不包括外边距(margin)
  • 包括滚动条宽度(如果有)

继续上面的例子:

offsetWidth = 130px (content 100px + padding 20px + border 10px)

scrollWidth

  • 表示元素内容的完整宽度,包括因超出元素宽度而不可见的部分
  • 如果元素内容没有超出可视区域,则等于 clientWidth
  • 如果内容超出可视区域,则等于实际内容宽度加上内边距

举个例子:

// 如果一个容器宽度为 200px,但内容实际宽度为 300px
const container = document.querySelector(".container")

console.log(container.clientWidth) // 200px

console.log(container.scrollWidth) // 300px

这些属性的主要应用场景:

  • clientWidth:计算元素的可视内容区域
  • offsetWidth:获取元素实际占用的布局空间
  • scrollWidth:检测内容是否溢出,实现横向滚动功能

总结

在实际开发中如何选择:

  1. 需要判断元素是否需要滚动时,比较 scrollWidthclientWidth
  2. 需要获取元素实际占用空间时,使用 offsetWidth
  3. 需要获取元素可视内容区域时,使用 clientWidth

需要注意的是,这些值都是只读的,如果需要修改元素尺寸,应该使用 CSS 的 widthpadding 等属性。

修改 nvm 源为淘宝镜像

· 阅读需 1 分钟
1adybug
子虚伊人

仅限 windows 版,一共两种方法:

修改文件

nvm 的安装路径下,找到 settings.txt 文件,设置 node_mirrornpm_mirror 为国内镜像地址,在文件末尾加入:

阿里云镜像

node_mirror: https://npmmirror.com/mirrors/node/
npm_mirror: https://npmmirror.com/mirrors/npm/

腾讯云镜像

node_mirror: http://mirrors.cloud.tencent.com/npm/
npm_mirror: http://mirrors.cloud.tencent.com/nodejs-release/

命令行

阿里云镜像

nvm npm_mirror https://npmmirror.com/mirrors/npm/
nvm node_mirror https://npmmirror.com/mirrors/node/

腾讯云镜像

nvm npm_mirror http://mirrors.cloud.tencent.com/npm/
nvm node_mirror http://mirrors.cloud.tencent.com/nodejs-release/

在右键菜单中添加“在终端中打开”

· 阅读需 1 分钟
1adybug
子虚伊人

添加

新建文本文档,将以下内容复制到文本文档中:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\wt]
@="在此处打开 Windows Terminal"
"Icon"="C:\\Users\\lenovo\\Pictures\\terminal.ico"

[HKEY_CLASSES_ROOT\Directory\Background\shell\wt\command]
@="wt.exe -d \"%V\""

[HKEY_CLASSES_ROOT\Directory\shell\wt]
@="在此处打开 Windows Terminal"
"Icon"="C:\\Users\\lenovo\\Pictures\\terminal.ico"

[HKEY_CLASSES_ROOT\Directory\shell\wt\command]
@="wt.exe -d \"%V\""

C:\\Users\\lenovo\\Pictures\\terminal.ico 替换为你的 Windows Terminal 程序或者 .ico 文件图标路径。

注意

由于 Windows Terminal 经常会升级,所以如果使用 Windows Terminal 的绝对路径作为图标,可能会导致图标失效。

使用 UTF-16 LE 编码格式保存文件,将文件后缀名改为 .reg,双击运行即可

删除

新建文本文档,将以下内容复制到文本文档中:

Windows Registry Editor Version 5.00

[-HKEY_CLASSES_ROOT\Directory\Background\shell\wt]
[-HKEY_CLASSES_ROOT\Directory\shell\wt]

同样使用 UTF-16 LE 编码格式保存文件,将文件后缀名改为 .reg,双击运行即可

HTTP 状态码

· 阅读需 2 分钟
1adybug
子虚伊人
状态码类别状态码英文说明中文说明
1xx 信息性100Continue继续。客户端应继续请求
101Switching Protocols切换协议。服务器同意切换协议
2xx 成功200OK请求成功。一般用于 GET 与 POST 请求
201Created已创建。成功请求并创建了新的资源
204No Content无内容。服务器处理成功,但未返回内容
3xx 重定向301Moved Permanently永久移动。请求的资源已永久移动到新 URI
302Found临时移动。请求的资源临时移动到新 URI
304Not Modified未修改。资源未改变,可使用缓存
4xx 客户端错误400Bad Request错误请求。请求语法错误
401Unauthorized未授权。需要身份验证
403Forbidden禁止。服务器拒绝请求
404Not Found未找到。服务器找不到请求的资源
405Method Not Allowed方法禁用。不允许使用该请求方法
429Too Many Requests请求过多。用户在给定时间内发送了太多请求
5xx 服务器错误500Internal Server Error服务器内部错误
502Bad Gateway错误网关。服务器作为网关收到无效响应
503Service Unavailable服务不可用。服务器暂时过载或维护
504Gateway Timeout网关超时。服务器作为网关未及时响应

在 Express 中实现断点续传

· 阅读需 1 分钟
1adybug
子虚伊人
import { createReadStream } from "fs"
import { stat } from "fs/promises"

import express from "express"

const app = express()

app.get("/video", async (request, response) => {
const filename = "demo.mp4"
const { size } = await stat(filename)
response.setHeader("Content-Type", "video/mp4")
const range = request.headers.range

if (!range) {
response.setHeader("Content-Length", size)
response.status(200)
createReadStream(filename).pipe(response)
return
}

const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0])
const end = parts[1] ? parseInt(parts[1]) : size - 1
const chunksize = end - start + 1
response.setHeader("Content-Range", `bytes ${start}-${end}/${size}`)
response.setHeader("Accept-Ranges", "bytes")
response.setHeader("Content-Length", chunksize)
response.status(206)
createReadStream(filename, { start, end }).pipe(response)
})

app.listen(4567)

在 hono 中实现断点续传

· 阅读需 1 分钟
1adybug
子虚伊人
import { createReadStream } from "fs"
import { stat } from "fs/promises"
import { Readable } from "stream"

import { Hono } from "hono"

const app = new Hono()

function nodeToWebStream(nodeStream: Readable) {
return new ReadableStream({
start(controller) {
// 处理数据块
nodeStream.on("data", chunk => controller.enqueue(chunk))

// 处理流结束
nodeStream.on("end", () => controller.close())

// 处理错误
nodeStream.on("error", error => controller.error(error))
},
cancel() {
nodeStream.destroy()
},
})
}

app.get("/video", async c => {
const filename = "demo.mp4"
const { size } = await stat(filename)
const headers = new Headers()
headers.set("Content-Type", "video/mp4")
const range = c.req.header("Range")

if (!range) {
headers.set("Content-Length", String(size))
return c.newResponse(nodeToWebStream(createReadStream("demo.mp4")), {
status: 200,
headers,
})
}

const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0])
const end = parts[1] ? parseInt(parts[1]) : size - 1
const chunksize = end - start + 1
headers.set("Content-Range", `bytes ${start}-${end}/${size}`)
headers.set("Accept-Ranges", "bytes")
headers.set("Content-Length", String(chunksize))
return c.newResponse(nodeToWebStream(createReadStream(filename, { start, end })), { status: 206, headers })
})

export default {
port: 4567,
fetch: app.fetch,
}

使用 Tailwind CSS 选择元素

· 阅读需 1 分钟
1adybug
子虚伊人

在 Tailwind 中经常会有一些奇怪的类名,比如 w-[calc(100vw_-_64px)],这种类名是无法直接使用 document.querySelector 来选择的,需要进行一些处理,将类名的中 [](): 替换为 \\[\\]\\(\\)\\:,然后使用 document.querySelector 来选择。

className.replace(/([\[\]\(\):])/g, "\\$1")

AbortSignal 的用法

· 阅读需 1 分钟
1adybug
子虚伊人

对于 AbortSignal,我们常见的用法应该是用于取消 fetch 请求:

const controller = new AbortController()
const signal = controller.signal

fetch(url, { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))

// 取消请求
controller.abort()

但是 AbortSignal 还有其他的用法,比如用于取消事件监听:

useEffect(() => {
const controller = new AbortController()
const signal = controller.signal
window.addEventListener("click", () => console.log("click"), { signal })
return () => {
controller.abort()
}
}, [])

当然,更高效的用法是取消多个事件监听

AbortSignal 类还有两个静态方法 AbortSignal.timeoutAbortSignal.any

AbortSignal.timeout 用于设置超时时间,在指定时间后会自动取消:

const signal = AbortSignal.timeout(1000)

类似于:

const controller = new AbortController()
const signal = controller.signal
setTimeout(() => controller.abort(), 1000)

AbortSignal.any 用于多个 AbortSignal 任意一个触发时取消:

const controller = new AbortController()
const controller2 = new AbortController()
const signal = AbortSignal.any([controller.signal, controller2.signal])

Nebula Graph 中的连续匹配

· 阅读需 2 分钟
1adybug
子虚伊人

先看两组 Cypher 语句:

MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
RETURN count(p)
MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
RETURN count(p)

通过运行结果可知两组语句的结果是一样的,可知连续匹配的作用域就是上一个匹配的结果。 uwm 是上一个匹配的结果,所以第二组语句中的 count(p) 与第一组语句中的 count(p) 是一样的。

如果我们把第二组语句改为:

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
MATCH p = (u:`user`)-[w2:watched]->(m:`movie`)
RETURN count(p)

那么此时,只有 um 是继承了上一次匹配的结果,w2 是重新匹配的结果,所以 count(p) 的结果就不一样了,也就是对于每一组 um 重新匹配了 w2 这条边,那么 count(p) 的结果必然是大于或者等于第一组语句的结果。

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
WITH u, m
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
RETURN count(p)

此时如果我们使用 WITH,那么 w 依然被丢失了,在第二次匹配中的 w 也相当于重新匹配的结果,所以 count(p) 的结果也是大于或者等于第一组语句的结果。

如果我们做以下改动也是错误的:

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
WITH u, m
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
RETURN count(p)

因为第一次匹配的结果并没有保证 um 之间的组合是独一无二的,也就是结果的列表中可能会存在系统的 um 组合,所以我们应该加一个语句:

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
WITH u, m, count(w) as c
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
RETURN count(p)

通过 count(w) 就可以让 um 之间的组合是独一无二的

在 react 中手动触发 change 事件

· 阅读需 2 分钟
1adybug
子虚伊人

react 中存在很多受控组件,比如 inputtextarea 等,一般来说,受控组件将会是如下写法:

import { FC } from "react"

const App: FC = () => {
const [value, setValue] = useState("")

return <input value={value} onChange={e => setValue(e.target.value)} />
}

export default App

这种写法在 react 内部使用没有任何问题,但是对于外部使用者来说却存在很多问题,比如:

import { FC, useState } from "react"

const App: FC = () => {
const [value, setValue] = useState("")

return (
<div>
<div>
<label htmlFor="input">input:</label>
<input id="input" value={value} onChange={e => setValue(e.target.value)} />
</div>
<div>value:{value}</div>
</div>
)
}

export default App

当我们使用如下代码来更改 input 的值:

input.value = "123456"

却看到输入框的值发生了更改,下方的文本却没有任何变化,说明我们更改 input 的值的行为并没有触发 react 内部的 change 事件,那我们来手动触发 changeinput 事件:

const changeEvent = new Event("change", { bubbles: true })
const inputEvent = new Event("input", { bubbles: true })

input.dispatchEvent(changeEvent)
input.dispatchEvent(inputEvent)

可以看到,下方文本任然没有任何变化,Google 一番终于找到了正确答案 Simulate React On-Change On Controlled Components

const set = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set

set?.call(input, "123456")

const inputEvent = new Event("input", { bubbles: true })

input.dispatchEvent(inputEvent)

下方的文本成功同步!