头像

My Learning Journal

This is where I will post about my journey learning tech.

2026
Git基础

本文将按照提交变更的工作流程,说明Git部分底层原理,适合Git初学者,建议跟随本文实际操作,可以更容易理解文章内容。

初始化仓库

让我们从这条命令开始探索Git的世界。

git init git-learning

这条命令会在当前目录新建一个gitlearning文件夹,并在其中构建一个.git目录

cd git-learning/.git

ls
config description HEAD hooks/ info/ objects/ refs/

object/:用于存储对象文件,项目数据都存储在这里

Git有3种对象文件:Blob、Tree、Commit,分别存放文件内容、层次结构、版本历史记录。这是本文的关键内容,下面将逐个进行解释。

info/、pack/:这两个文件夹在Git优化时会使用,一般来说不用管

refs/:指针仓库,包含heads/remotes/tags/ ,分别存放本地分支、远程分支、tag。

info/:Git辅助信息目录,其中的exclude文件的作用类似.gitignore

hooks:存放hook脚本,如pre-commit等,可以在特定的Git事件之前自动执行自定义脚本。

HEAD:当前工作分支的引用。

config:Git配置文件,包含用户信息,远程仓库地址等数据

git add <文件名>

新建一个文件,然后使用这条命令将文件加入暂存区

也可以使用.,表示将所有文件加入暂存区。

git add .

.git文件夹的影响:

  1. 在objects中生成Blob对象

  2. 修改index文件,记录文件信息。

index:这个文件包含了整个目录树,也可以称之为暂存区,只有在首次暂存文件后被创建。

解释一下下面这条命令的输出:文件权限、哈希值、阶段号、文件路径

git ls-files --stage
100644 30d74d258442c7c65512eafab474568dd706c430 0       test.txt

生成Blob:对于add的每一个文件,Git使用SHA-1和文件内容生成哈希值,在objects目录下,使用哈希值的前两位命名子目录,在子目录中生成一个Blob对象,使用剩下38位命名。

不过有的时候,Git不会为一个文件生成Blob,为什么会这样?让我们先了解一下Blob是什么。

Blob

全称是binary large object,是文件内容的完整复制,每个文件都对应一个Blob,是对文件内容进行压缩后的二进制文件,不包含任何元数据,甚至是文件名

这样设计的一个好处就是两个内容完全相同的文件可以复用同一个Blob,无论文件的路径和名称如何变化,这是一个非常高效的去重设计,这也是为什么有时候Git可以不为暂存的文件生成Blob。

既然Blob不存储文件名,你觉得文件名会存储在哪里?

git cat-file <哈希值>:这条命令可以查看git对象的数据,-p参数输出文件内容,-t参数输出对象类型

git cat-file -t 30d74d258442c7c65512eafab474568dd706c430
blob

git cat-file -p 30d74d258442c7c65512eafab474568dd706c430
test

使用命令时,不需要输入完整的哈希值,最少只需使用4位哈希值。

git commit

之前使用了git add将文件暂存,现在可以提交变更,并附加提交说明。

git commit -m "first commit"

[master cdff5ec] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 test2.txt
  1. 提交变更后,Git会在objects文件夹中生成一个Commit对象,为每个目录层级生成一个Tree对象。

  2. refs\heads目录下的分支文件更新,内容变成新Commit对象的哈希值

这个文件作为指针,可以直接查看,本质上是文本文件,内容是当前Commit的哈希值。

  1. index文件中的一些信息会被更新

Tree

Tree对象可以存储Blob和其他Tree对象,用来构建文件和子目录的完整层次结构。

git cat-file e5e92b97d6c9add7460cf898ca57afbdb2c8815c -p
100644 Blob f73693a16cdf594532ee4c423a46d32ce3430c4e    first.txt

树对象中有文件权限,对象类型、哈希值和文件名

之前我们有一个疑问,文件名称存储在哪里?答案就是Tree对象。

Commit

Commit对象将一个个版本历史节点串在了一起,可以使用之前提交时返回的哈希值,也可以使用git log列出所有的Commit对象。

git log
commit 8b6185ac0d3d8a743521ed08c1227fd881324481 (HEAD -> master)
Author: Jack-S-H <58410600+Jack-S-H@users.noreply.Github.com>
Date:   Wed Jul 8 16:03:05 2026 +0800

    first commit
    
git cat-file 8b6185ac0d3d8a743521ed08c1227fd881324481 -p
tree e5e92b97d6c9add7460cf898ca57afbdb2c8815c
author Jack-S-H <58410600+Jack-S-H@users.noreply.Github.com> 1783497785 +0800
committer Jack-S-H <58410600+Jack-S-H@users.noreply.Github.com> 1783497785 +0800

first commit

Commit对象中有作者、提交者、提交日期和提交消息

还有一个Tree对象

第二次及以后的Commit对象还会多一个上次提交的哈希值

parent 8b6185ac0d3d8a743521ed08c1227fd881324481

这些对象类型以哈希值命名,所以非常难记,但是我们可以为Commit对象创建别名——他们本质是指向Commit对象的引用(References)。

分支(Branch)

分支是指向Commit对象的指针,是Commit的引用,每个分支都是一个文件,存储在refs/中,而每个分支的文件内容,就是对应Commit对象的哈希值,代表这个分支的最新一次提交。

cat master
c751429b4da18797552c5b86cebad84a71c23422

分支也有引用——HEAD,这个文件在文章开头也有提到过,保存当前工作分支的路径,意思是指向当前工作的分支的指针。

cat HEAD
ref: refs/heads/master

标签

和分支类似,文件存储在refs/tags/中,不同的是Tag文件在创建后无法再修改,也就是说Tag是一个分支的不可变引用

使用以下命令可以创建tag或显示所有tag

git tag first

git tag
first

总结

Blob存储文件内容,Tree存储目录结构,Commit存储版本记录,连接所有内容,非常优秀的功能解耦,非常优雅

Git每次提交都会生成Blob、Commit、Tree,所以Git管理的历史版本记录了非常完整的数据

对象只增不减,只有在gc时才会处理悬空提交,之后对象文件夹中的info和pack文件夹中会出现新文件

运行gc后观察到的文件变化:refs/:heads清空了,logs/:heads被修改了,info/:refs、packed-refs、index、orig_head被修改了

Git是一个很有趣的东西,你不需要Gitee,Gitlab,甚至不要Git,只需要ssh就可以实现一个Git服务器(我之后可能会发布一篇关于这个的文章)

参考:

How Git Internally Works

Book

这是一篇日志。

1.0.3

  • 上一个更新小小的翻车了。。。
  • 修复在某些特殊情况下不显示卡片的问题
  • 修复和某些插件冲突导致不显示卡片的问题
  • 新增鼠标点击非卡片区域关闭卡片的功能
  • 尝试删除z-index设置
  • 在使用指南页面页面增加意见反馈渠道

1.0.2:

  • 首次更新!
  • 重要!原先使用S键获取单词信息,为了防止按键冲突,新版本将快捷键修改为CTRL+ALT+S
  • 增加了翻译英文句子的功能,选中文字后使用快捷键CTRL+ALT+T,将会在左上角显示译文卡片
  • 在单词卡片以及搜索结果中增加删除功能
  • 修复bug、调整样式

这是一篇指南。

安装方式:谷歌商店

简介:在任意网站上标记单词,加入单词本,通过AI大模型获取翻译信息

  • 标记单词
    • 双击 英文单词
    • 选中 单词按CTRL+ALT+S

标记的单词会添加 红色波浪下划线,鼠标移动到划线范围会自动弹 出单词卡片

  • 翻译句子:选中一段英文按CTRL+ALT+T键,会在左上角显示译文卡片
  • 扩展设置:这里做成了单词本页面,在这里可以删除单词,需要使用浏览器自带的搜索
  • popup页面:点击扩展图标或按CTRL+M组合键,可以在搜索框中快速查找已标记的单词或者手动录入单词。
  • AI 翻译:首次使用需要输入 DeepSeek API key,获取中文释义、词性、音标以及记忆建议。

反馈意见

使用three实现一个简单的场景

目录

  1. 前言
  2. 搭建基础环境
  3. 准备画布
  4. 增加几何体
  5. 可选优化
  6. 完整代码

前言

Three.js是一个基于 WebGL 的 3D 库,让开发者轻松在浏览器中创建三维内容。本文将展示 Three.js 的核心组件(场景、相机、渲染器、几何体、材质、网格),并实现一个非常简单的场景,包含一个彩色的平面几何体。

搭建基础环境

  1. 创建HTML文件

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <canvas id="c"></canvas>
</body>
</html>

这里包含了一个<canvas></canvas>元素,之后会用到

  1. 引入 Three.js

有多种方法,这里使用 CDN,还可以使用npm安装。

<script type="importmap">
{
	"imports":
    {
"three":"https://cdn.jsdelivr.net/npm/three@v0.181.0/build/three.module.js",
"three/addons/":"https://cdn.jsdelivr.net/npm/three@v0.181.0/examples/jsm/"
    }
}
</script>

three/addons/是three的扩展包,可以提供额外的材质、几何体、后期处理工具等

  1. 设置css属性

设置CSS宽高(比如 width: 100%; height: 100vh;)并隐藏滚动条,这样可以让画面占满视口。

html,
body {
    margin: 0;
    overflow: hidden;
    height: 100%;
}

准备画布

  1. 导入three包

import * as THREE from 'three';
  1. 设置宽高参数

后面设置大小时需要,提前定义

为了保证渲染与显示一致,这里的宽高从canvas元素获取

const width = canvas.clientWidth;
const height = canvas.clientHeight;
  1. 场景(Scene)

场景是所有物体的容器。

const scene = new THREE.Scene();
  1. 相机(Camera)

const fov = 75;
const aspect = width / height;
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

fov:(视线角度)

aspect(渲染的宽高比)

near / far(渲染的远近范围)

  1. 设置相机位置

position决定了看物体的视角,接收的3个参数分别为z轴(上下),Y轴(远近),X轴(左右)

camera.position.set(0, -5, 30);

决定图像大小的3要素:几何体大小设置、像素设置、相机远近

  1. 绑定canvas元素

这里使用元素id绑定

const canvas = document.querySelector('#c');

还有一种添加canvas的方法,不在html文件中设定元素,使用js创建一个canvas画布,然后系统会自动插入

document.body.appendChild( renderer.domElement )

注:系统自动插入元素会有不确定性,所以推荐手动创建并绑定元素。

  1. 渲染器(Renderer)

three有webglwebgpu两种renderer

const renderer = new THREE.WebGLRenderer({ antialias: true, canvas});

antialias:抗锯齿

element:添加canvas画布

  1. 调用 setSize 设置渲染尺寸

目的:确保渲染尺寸和显示尺寸一致。

renderer.setSize(width,height)

为了图像清晰,要将几何体大小和渲染的像素大小保持一致

一般不使用canvas.height=heightcanvas.width=width

  1. 启动动画

setAnimationLoop不断调用渲染函数。

function animate() {
    renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
  1. 第一次运行

你已经建立了一个场景,你现在可以启动本地服务器,然后在浏览器中访问index.html,你将会看到一个全黑的画布画面

three只需要scenecamerarenderer即可运行!

设置几何体

接下来,我们将在场景中添加一个几何体。

  1. 几何体(Geometry)

不同的图形需要不同的参数,PlaneGeometry是一个平面几何体,接收4个参数。

width:宽度

height:高度

widthSegments:横向顶点数

heightSegments:纵向顶点数

顶点数会影响变形时的画面

const g_width = 30;
const g_height = 30;
const widthSegments = 128;
const heightSegments = 128;

const geometry = new THREE.PlaneGeometry(
    g_width, g_height,
    widthSegments, heightSegments);
  1. 材质(Material)

使用 MeshBasicMaterial,设置颜色 0x8b91b8。

const material = new THREE.MeshBasicMaterial({ color: 0x8b91b8, side: THREE.DoubleSide });

可选设置side: THREE.DoubleSide 使平面双面可见(否则旋转后背面不可见)。

  1. 网格(Mesh)

使用mesh将图形和材质结合,生成最终的图像

需要两个参数:geometrymaterial

const Plane = new THREE.Mesh(geometry, material);
  1. 添加到场景中

scene.add(Plane);
  1. 光源(Light)

增加PointLight光源,参数为颜色和强度(亮度),可以使用position.set设置位置,可以产生阴影,增加立体性,还可以通过增加多种光源改善画面

const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(50, 50, 100);
scene.add(pointLight);
  1. 旋转几何体

这边给一点角度可以使几何体看的更清除一点

geometry.rotateX(-Math.PI / 6);

可选优化

  1. 窗口自适应

现在画布大小是固定的,也就是说在调整浏览器窗口时,里面显示的物体会被拉伸。

我们可以通过监听 resize 事件,实时更新渲染器尺寸和相机宽高比,防止画面被拉伸.

function onWindowResize(){
    //获取最新的宽高
    const width = window.innerWidth;
    const height = window.innerHeight;
	//设置渲染大小和相机宽高比
    renderer.setSize(width,height);
    camera.aspect = width / height;
    //让相机生效
    camera.updateProjectionMatrix();
}
//监听浏览器的 resize 事件
window.addEventListener('resize', onWindowResize);

perspective 相机需要更新宽高比,因为相机的视野是固定的,如果不改,画面会被压扁

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>Hallo Three.js</title>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style>
        html,
        body {
            margin: 0;
            /* 一直占满屏幕,没有滚动条 */
            overflow: hidden;
            height: 100%;
        }
        #c {
            /* 如果父容器没有占满屏幕
            就需要增加绝对定位absolute position */
            width: 100%;
            height: 100%;
            display: block;
        }
    </style>
    <script type="importmap">
    {
    "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@v0.181.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@v0.181.0/examples/jsm/"

    }
    }
    </script>
</head>
<body>
    <script type="module" src="/main.js"></script>
    <canvas id="c">
    </canvas>
</body>
</html>
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const canvas = document.querySelector('#c');
const width = canvas.clientWidth;
const height = canvas.clientHeight;

const scene = new THREE.Scene();

const fov = 75;
const aspect = width / height; 
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

camera.position.set(0, -5, 30);

const renderer = new THREE.WebGLRenderer({ antialias: true, canvas});

renderer.setSize(width,height)

function onWindowResize(){
    const width = window.innerWidth;
    const height = window.innerHeight;

    renderer.setSize(width,height);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();

}
window.addEventListener('resize', onWindowResize);

const g_width = 30;
const g_height = 30;
const widthSegments = 128;
const heightSegments = 128;
const geometry = new THREE.PlaneGeometry(
    g_width, g_height,
    widthSegments, heightSegments);

const material = new THREE.MeshBasicMaterial({ color: 0x8b91b8, side: THREE.DoubleSide });
const Plane = new THREE.Mesh(geometry, material);

geometry.rotateX(-Math.PI / 6);

scene.add(Plane);

function animate() {
    renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

这里提供两种识别方法

工作量证明方法——要求用户绘制画布并验证某些已知像素的数值.

生成一个特定颜色的画布,并验证特定像素的数值是否符合预期的。下面的代码片段展示了一个简单的示例,我们将画布填充为 rgba(0, 127, 255, 1)。然后,我们对每个像素进行迭代,验证 r、g、b、a 的分量是否具有期望值(因此代码中使用%4)。

var canvas = document.createElement("canvas");
canvas.height = size;
canvas.width = size;
var context = canvas.getContext("2d");
context.fillStyle = "rgba(0, 127, 255, 1)";
var pixelValues = [0, 127, 255, 255];
// 应用颜色
context.fillRect(0, 0, canvas.width, canvas.height);
var pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
for (var i = 0; i < pixels.length; i += 1) {
    if (pixels[i] !== pixelValues[i % 4]) {
        // 执行测试
        // 如果不匹配,则代表指纹被篡改
        console.log('Canvas has been overridden!')
    }
}

函数一致性检查方法

通过查看函数的原型或错误堆栈痕迹等副作用检测函数是否被覆盖

检测由js代理调用的反指纹脚本
let isOverridden = true;
try {
    Object.setPrototypeOf(HTMLCanvasElement.prototype.toDataURL, HTMLCanvasElement.prototype.toDataURL)
} catch (e) {
    if (e.message.indexOf('Cyclic') > -1) {
        isOverridden = false;
    }
}
正常情况会报错

通过监控错误堆栈,来检测特定扩展
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
try {
    context.getImageData(canvas);   
} catch (e) {
    hasAntiCanvasExtension = e.stack.indexOf('chrome-extension') > -1;
    hasCanvasBlocker = e.stack.indexOf('nomnklagbgmgghhjidfhnoelnjfndfpd') > -1;
}
2025

使用基本的html、css来实现一个简洁的时间轴

样式参考:Long Luo’s Life Notes

​实现方法有很多种,这里只给出此博客所用的方法。

以下图为例,时间轴由大圆、小圆和淡淡的竖线组成

时间轴例图

HTML结构

timeline
::before
  year
    marker //圆点
//此处输入自定义文字
  posts
    post-link
      marker //圆点
      content
//此处输入自定义文字

圆形

圆形由svg实现:

<svg
    class="marker"
    //定义css类名
    xmlns="http://www.w3.org/2000/svg"
    //声明命名空间
    width="12"
    height="12"
    //图形长宽
>
    <circle cx="6" cy="6" r="6"></circle>
    //circle是一个svg的圆形元素
    //cx,cy:圆心的x,y坐标
    //r:半径
</svg>

数轴

数轴使用了伪元素,伪元素属于css,可以在一个元素中创建拥有特殊性质的元素,伪元素的一种典型的用法就是生成线条

.timeline::before {
    content: "";
    //必须设置content,否则元素不会显示
    position: absolute;
    //定位上下文
    bottom: -20px;
    //底面移动-20px,向下微调20px
    height: 100%;
    //长度为整个元素块
    width: 2px;
    //宽度
    background-color: rgb(240, 240, 240);
    //设置线条颜色
}

至此,一个简单的时间轴的骨架就完成了。

时间轴骨架

目前这个时间轴有以下几点问题

  1. 竖线横跨了整个窗口
  2. 小圆点没有和文字对齐
  3. 小圆点被竖线覆盖了

以下是示例代码

//1.
.timeline{
    //这是因为将伪元素的position设置了absolute,默认会向上查找最近的static定位的元素
    //我们可以将timeline元素作为定位参考
    position:relative;
}
//2.
.post-link {
    display: flex;
    //可以让圆点和文字在同一行
    align-items: baseline;
	//这种对齐方式可以稍微让圆点回正一点
}
.post-link .marker {
    position: relative;
    //建立一个新的层叠上下文,让圆点覆盖竖线
    left: -2px;
    bottom: -2px;
    //对圆点位置进行微调
}

至此,这个时间轴就建好了,剩下的就是对其他元素进行样式优化就好了