每日一题-交替位二进制数🟢
给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。
示例 1:
输入:n = 5 输出:true 解释:5 的二进制表示是:101
示例 2:
输入:n = 7 输出:false 解释:7 的二进制表示是:111.
示例 3:
输入:n = 11 输出:false 解释:11 的二进制表示是:1011.
提示:
1 <= n <= 231 - 1
给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。
示例 1:
输入:n = 5 输出:true 解释:5 的二进制表示是:101
示例 2:
输入:n = 7 输出:false 解释:7 的二进制表示是:111.
示例 3:
输入:n = 11 输出:false 解释:11 的二进制表示是:1011.
提示:
1 <= n <= 231 - 1在现代 Web 应用开发中,首屏加载速度(FCP)和最大内容绘制(LCP)是衡量用户体验的核心指标。随着富媒体内容的普及,图片资源往往占据了页面带宽的大部分。如果一次性加载页面上的所有图片,不仅会阻塞关键渲染路径,导致页面长时间处于“白屏”或不可交互状态,还会浪费用户的流量带宽。
图片懒加载(Lazy Loading)作为一种经典的性能优化策略,其核心思想是“按需加载”:即只有当图片出现在浏览器可视区域(Viewport)或即将进入可视区域时,才触发网络请求进行加载。这一策略能显著减少首屏 HTTP 请求数量,降低服务器压力,并提升页面的交互响应速度。
本文将基于 React 生态,从底层原理出发,深入探讨图片懒加载的多种实现方案,并重点分析如何解决布局偏移(CLS)等用户体验问题。
图片懒加载的本质是一个“可见性检测”问题。我们需要实时判断目标图片元素是否与浏览器的可视区域发生了交叉。在技术实现上,主要依赖以下两种依据:
HTML5 标准为 标签引入了 loading 属性,这是实现懒加载最简单、成本最低的方式。
Jsx
const NativeLazyLoad = ({ src, alt }) => {
return (
<img
src={src}
alt={alt}
loading="lazy"
width="300"
height="200"
/>
);
};
分析:
优点:零 JavaScript 代码,完全依赖浏览器原生行为,不会阻塞主线程。
缺点:
在 IntersectionObserver 普及之前,监听 scroll 事件是主流做法。其原理是在 React 组件挂载后绑定滚动监听器,在回调中计算图片位置。
React 实现示例:
Jsx
import React, { useState, useEffect, useRef } from 'react';
import placeholder from './assets/placeholder.png';
// 简单的节流函数,生产环境建议使用 lodash.throttle
const throttle = (func, limit) => {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
};
const ScrollLazyImage = ({ src, alt }) => {
const [imageSrc, setImageSrc] = useState(placeholder);
const imgRef = useRef(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const checkVisibility = () => {
if (isLoaded || !imgRef.current) return;
const rect = imgRef.current.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
// 设置 100px 的缓冲区,提前加载
if (rect.top <= windowHeight + 100) {
setImageSrc(src);
setIsLoaded(true);
}
};
// 必须使用节流,否则滚动时会频繁触发重排和重绘,导致性能灾难
const throttledCheck = throttle(checkVisibility, 200);
window.addEventListener('scroll', throttledCheck);
window.addEventListener('resize', throttledCheck);
// 初始化检查,防止首屏图片不加载
checkVisibility();
return () => {
window.removeEventListener('scroll', throttledCheck);
window.removeEventListener('resize', throttledCheck);
};
}, [src, isLoaded]);
return <img ref={imgRef} src={imageSrc} alt={alt} />;
};
关键点分析:
这是目前最推荐的方案。IntersectionObserver 运行在独立线程中,不会阻塞主线程,且浏览器对其进行了内部优化。
React 实现示例:
我们可以将其封装为一个通用的组件 LazyImage。
Jsx
import React, { useState, useEffect, useRef } from 'react';
import './LazyImage.css'; // 假设包含样式
const LazyImage = ({ src, alt, placeholderSrc, width, height }) => {
const [imageSrc, setImageSrc] = useState(placeholderSrc || '');
const [isVisible, setIsVisible] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
let observer;
if (imgRef.current) {
observer = new IntersectionObserver((entries) => {
const entry = entries[0];
// 当元素进入视口
if (entry.isIntersecting) {
setImageSrc(src);
setIsVisible(true);
// 关键:图片加载触发后,立即停止观察,释放资源
observer.unobserve(imgRef.current);
observer.disconnect();
}
}, {
rootMargin: '100px', // 提前 100px 加载
threshold: 0.01
});
observer.observe(imgRef.current);
}
// 组件卸载时的清理逻辑
return () => {
if (observer) {
observer.disconnect();
}
};
}, [src]);
return (
<img
ref={imgRef}
src={imageSrc}
alt={alt}
width={width}
height={height}
className={`lazy-image ${isVisible ? 'loaded' : ''}`}
/>
);
};
export default LazyImage;
优势分析:
仅仅实现“懒加载”是不够的。在工程实践中,如果处理不当,懒加载会导致严重的累积布局偏移(CLS, Cumulative Layout Shift) 。即图片加载前高度为 0,加载后撑开高度,导致页面内容跳动。这不仅体验极差,也是 Google Core Web Vitals 的扣分项。
必须在图片加载前确立其占据的空间。现代 CSS 提供了 aspect-ratio 属性,配合宽度即可自动计算高度。
CSS
/* LazyImage.css */
.img-wrapper {
width: 100%;
/* 假设图片比例为 16:9,或者由后端返回具体宽高计算 */
aspect-ratio: 16 / 9;
background-color: #f0f0f0; /* 骨架屏背景色 */
overflow: hidden;
position: relative;
}
.lazy-image {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.lazy-image.loaded {
opacity: 1;
}
结合后端返回的元数据(如宽高、主色调),我们可以构建一个体验极佳的懒加载组件。
Jsx
const AdvancedLazyImage = ({ data }) => {
// data 结构示例: { url: '...', width: 800, height: 600, basicColor: '#a44a00' }
const imgRef = useRef(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
const img = entry.target;
// 使用 dataset 获取真实地址,或者直接操作 state
img.src = img.dataset.src;
img.onload = () => setIsLoaded(true);
observer.unobserve(img);
}
});
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<div
className="img-container"
style={{
// 核心:使用 aspect-ratio 防止 CLS
aspectRatio: `${data.width} / ${data.height}`,
// 核心:使用图片主色调作为占位背景,提供渐进式体验
backgroundColor: data.basicColor
}}
>
<img
ref={imgRef}
data-src={data.url} // 暂存真实地址
alt="Lazy load content"
style={{
opacity: isLoaded ? 1 : 0,
transition: 'opacity 0.5s ease'
}}
/>
</div>
);
};
| 方案 | 实现难度 | 性能 | 兼容性 | 适用场景 |
|---|---|---|---|---|
| 原生属性 (loading="lazy") | 低 | 高 | 中 (现代浏览器) | 简单的 CMS 内容页、对交互要求不高的场景。 |
| Scroll 监听 | 中 | 低 (需节流) | 高 (全兼容) | 必须兼容 IE 等老旧浏览器,或有特殊的滚动容器逻辑。 |
| IntersectionObserver | 中 | 极高 | 高 (需 Polyfill) | 现代 Web 应用、无限滚动列表、对性能和体验有高要求的场景。 |
图片懒加载是前端性能优化的基石之一。从早期的 Scroll 事件监听,到如今标准化的 IntersectionObserver API,再到原生 HTML 属性的支持,技术在不断演进。
在 React 项目中落地懒加载时,我们不能仅满足于“功能实现”。作为架构师,更应关注性能损耗(如避免主线程阻塞)、资源管理(及时销毁 Observer)以及用户体验(防止 CLS、优雅的过渡动画)。通过合理利用 aspect-ratio 和占位策略,我们可以让懒加载不仅“快”,而且“稳”且“美”。
前端性能优化专栏 - 第九篇
在网页设计中,字体往往被视为“灵魂”。它不仅关乎品牌识别和视觉统一,更能直接影响界面的质感与专业感。好的字体能降低用户的认知成本,传递出产品想要营造的独特氛围。
然而,字体作为一种静态资源,必须经过下载和管理。如果处理不当,它就会变成性能的“累赘”,甚至引发一些让用户抓狂的“怪现象”。
你是否遇到过这样的场景:页面打开了,但文字部分是一片空白,过了好几秒才突然蹦出来?或者文字先是以一种普通的系统字体显示,然后突然“闪”一下,变成了精美的设计字体?
这可不是浏览器的 Bug,而是浏览器默认的字体加载策略在作祟。
这是 Chrome 等现代浏览器的典型行为。当自定义字体还没下载完时,浏览器会选择完全不渲染文本。
这是 IE 等浏览器的传统行为。当自定义字体未加载完时,浏览器先用后备字体(系统自带字体)渲染。
字体加载本质上是一个异步网络请求。在等待期间,浏览器必须决定:
为了不让浏览器“瞎猜”,我们需要一种方式显式地告诉它:“在这个项目里,你应该如何平衡内容、样式和性能。”
在 @font-face 中使用 font-display 属性,可以精准控制字体在不同加载阶段的渲染策略。
@font-face {
font-family: 'My Custom Font';
src: url(/fonts/my-font.woff2) format('woff2');
font-display: swap; /* 关键控制位 */
}
这是目前最主流、最被推荐的策略。
字体优化不是简单的“全都要”,而是一场关于内容可见性与视觉一致性的博弈。
swap。block。fallback 或 optional 是你的好伙伴。合理利用 font-display,让你的网页在保持美感的同时,也能拥有丝滑的加载体验!
下一篇预告: 页面加载完了,但一滚动就发现元素在“乱跳”?这种让人头大的现象叫布局抖动(Layout Thrashing) 。下一篇我们将深入探讨如何识别并优化布局抖动,让你的页面稳如泰山!敬请期待!
根据题意,对 $n$ 的每一位进行遍历检查。
代码:
###Java
class Solution {
public boolean hasAlternatingBits(int n) {
int cur = -1;
while (n != 0) {
int u = n & 1;
if ((cur ^ u) == 0) return false;
cur = u; n >>= 1;
}
return true;
}
}
另外一种更为巧妙的方式是利用交替位二进制数性质。
当给定值 $n$ 为交替位二进制数时,将 $n$ 右移一位得到的值 $m$ 仍为交替位二进制数,且与原数 $n$ 错开一位,两者异或能够得到形如 $0000...1111$ 的结果 $x$,此时对 $x$ 执行加法(进位操作)能够得到形如 $0000...10000$ 的结果,将该结果与 $x$ 执行按位与后能够得到全 $0$ 结果。
代码:
###Java
class Solution {
public boolean hasAlternatingBits(int n) {
int x = n ^ (n >> 1);
return (x & (x + 1)) == 0;
}
}
今日份加餐:经典「状态压缩 + 位运算」入门题 🎉🎉
或是考虑加练如下「位运算」题目 🍭🍭🍭
| 题目 | 题解 | 难度 | 推荐指数 |
|---|---|---|---|
| 137. 只出现一次的数字 II | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
| 190. 颠倒二进制位 | LeetCode 题解链接 | 简单 | 🤩🤩🤩 |
| 191. 位1的个数 | LeetCode 题解链接 | 简单 | 🤩🤩🤩 |
| 260. 只出现一次的数字 III | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩 |
| 405. 数字转换为十六进制数 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩 |
| 461. 汉明距离 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩 |
| 477. 汉明距离总和 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩 |
| 526. 优美的排列 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/
也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
所有题解已经加入 刷题指南,欢迎 star 哦 ~
思路
从最低位至最高位,我们用对 $2$ 取模再除以 $2$ 的方法,依次求出输入的二进制表示的每一位,并与前一位进行比较。如果相同,则不符合条件;如果每次比较都不相同,则符合条件。
代码
###Python
class Solution:
def hasAlternatingBits(self, n: int) -> bool:
prev = 2
while n:
cur = n % 2
if cur == prev:
return False
prev = cur
n //= 2
return True
###Java
class Solution {
public boolean hasAlternatingBits(int n) {
int prev = 2;
while (n != 0) {
int cur = n % 2;
if (cur == prev) {
return false;
}
prev = cur;
n /= 2;
}
return true;
}
}
###C#
public class Solution {
public bool HasAlternatingBits(int n) {
int prev = 2;
while (n != 0) {
int cur = n % 2;
if (cur == prev) {
return false;
}
prev = cur;
n /= 2;
}
return true;
}
}
###C++
class Solution {
public:
bool hasAlternatingBits(int n) {
int prev = 2;
while (n != 0) {
int cur = n % 2;
if (cur == prev) {
return false;
}
prev = cur;
n /= 2;
}
return true;
}
};
###C
bool hasAlternatingBits(int n) {
int prev = 2;
while (n != 0) {
int cur = n % 2;
if (cur == prev) {
return false;
}
prev = cur;
n /= 2;
}
return true;
}
###go
func hasAlternatingBits(n int) bool {
for pre := 2; n != 0; n /= 2 {
cur := n % 2
if cur == pre {
return false
}
pre = cur
}
return true
}
###JavaScript
var hasAlternatingBits = function(n) {
let prev = 2;
while (n !== 0) {
const cur = n % 2;
if (cur === prev) {
return false;
}
prev = cur;
n = Math.floor(n / 2);
}
return true;
};
复杂度分析
时间复杂度:$O(\log n)$。输入 $n$ 的二进制表示最多有 $O(\log n)$ 位。
空间复杂度:$O(1)$。使用了常数空间来存储中间变量。
思路
对输入 $n$ 的二进制表示右移一位后,得到的数字再与 $n$ 按位异或得到 $a$。当且仅当输入 $n$ 为交替位二进制数时,$a$ 的二进制表示全为 $1$(不包括前导 $0$)。这里进行简单证明:当 $a$ 的某一位为 $1$ 时,当且仅当 $n$ 的对应位和其前一位相异。当 $a$ 的每一位为 $1$ 时,当且仅当 $n$ 的所有相邻位相异,即 $n$ 为交替位二进制数。
将 $a$ 与 $a + 1$ 按位与,当且仅当 $a$ 的二进制表示全为 $1$ 时,结果为 $0$。这里进行简单证明:当且仅当 $a$ 的二进制表示全为 $1$ 时,$a + 1$ 可以进位,并将原最高位置为 $0$,按位与的结果为 $0$。否则,不会产生进位,两个最高位都为 $1$,相与结果不为 $0$。
结合上述两步,可以判断输入是否为交替位二进制数。
代码
###Python
class Solution:
def hasAlternatingBits(self, n: int) -> bool:
a = n ^ (n >> 1)
return a & (a + 1) == 0
###Java
class Solution {
public boolean hasAlternatingBits(int n) {
int a = n ^ (n >> 1);
return (a & (a + 1)) == 0;
}
}
###C#
public class Solution {
public bool HasAlternatingBits(int n) {
int a = n ^ (n >> 1);
return (a & (a + 1)) == 0;
}
}
###C++
class Solution {
public:
bool hasAlternatingBits(int n) {
long a = n ^ (n >> 1);
return (a & (a + 1)) == 0;
}
};
###C
bool hasAlternatingBits(int n) {
long a = n ^ (n >> 1);
return (a & (a + 1)) == 0;
}
###go
func hasAlternatingBits(n int) bool {
a := n ^ n>>1
return a&(a+1) == 0
}
###JavaScript
var hasAlternatingBits = function(n) {
const a = n ^ (n >> 1);
return (a & (a + 1)) === 0;
};
复杂度分析
时间复杂度:$O(1)$。仅使用了常数时间来计算。
空间复杂度:$O(1)$。使用了常数空间来存储中间变量。
将n右移一位异或n,检查结果是否全为1。
class Solution:
def hasAlternatingBits(self, n: int) -> bool:
tmp = n^(n>>1)
return tmp&(tmp+1) == 0
在现代 Web 应用的开发流程中,前后端分离已成为行业标准。然而,在实际协作中,前端工程师常常面临“后端接口未就绪、联调环境不稳定、异常场景难以复现”等痛点。这些问题导致前端开发进度被迫依赖后端,严重制约了交付效率。
Mock.js 作为一种数据模拟解决方案,不仅能解除这种依赖,还能通过工程化的方式提升代码的健壮性。本文将从架构视角出发,深入剖析 Mock.js 的核心价值、技术原理,并结合 Vite 生态展示如何在现代项目中落地最佳实践,同时客观分析其局限性与应对策略。
在工程化体系中,Mock.js 不仅仅是一个生成随机数据的库,它是实现“并行开发”的关键基础设施。
Mock.js 的核心原理是重写浏览器原生的 XMLHttpRequest 对象。当代码发起请求时,Mock.js 会在浏览器端拦截该请求,判断 URL 是否匹配预定义的规则。如果匹配,则阻止网络请求的发出,并直接返回本地生成的模拟数据;如果不匹配,则放行请求。
直接在业务代码(如 main.js)中引入 Mock.js 是一种侵入性较强的做法,且原生 Mock.js 拦截请求后,浏览器的 Network 面板无法抓取到请求记录,给调试带来不便。
在 Vite 生态中,推荐使用 vite-plugin-mock。该插件在开发环境(serve)下,通过 Node.js 中间件的形式拦截请求。这意味着请求真正从浏览器发出并到达了本地开发服务器,因此可以在 Network 面板清晰查看请求详情,体验与真实接口完全一致。
以下将展示如何在 Vite + TypeScript 项目中集成 Mock.js,并实现一个包含逻辑处理(分页、切片)的模拟接口。
建议将 Mock 数据与业务代码分离,保持目录结构清晰:
Text
project-root/
├── src/
├── mock/
│ ├── index.ts # Mock 服务配置
│ └── user.ts # 用户模块接口
│ └── list.ts # 列表模块接口(本例重点)
├── vite.config.ts # Vite 配置
└── package.json
通过配置插件,确保 Mock 服务仅在开发模式下启动,生产构建时自动剔除。
TypeScript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { viteMockServe } from 'vite-plugin-mock';
export default defineConfig(({ command }) => {
return {
plugins: [
vue(),
viteMockServe({
// mock 文件存放目录
mockPath: 'mock',
// 仅在开发环境开启 mock
localEnabled: command === 'serve',
// 生产环境关闭,避免 mock 代码打包到生产包中
prodEnabled: false,
}),
],
};
});
模拟接口不仅仅是返回死数据,更需要具备一定的逻辑处理能力。以下代码演示了如何利用 Mock.js 生成海量数据,并根据前端传入的 page 和 pageSize 参数进行数组切片,模拟真实的数据库查询行为。
TypeScript
import { MockMethod } from 'vite-plugin-mock';
import Mock from 'mockjs';
// 1. 生成模拟数据池
// 使用 Mock.js 模板语法生成 100 条具有语义的列表数据
const dataPool = Mock.mock({
'list|100': [
{
'id|+1': 1, // ID 自增
author: '@cname', // 随机中文名
title: '@ctitle(10, 20)', // 10-20字的中文标题
summary: '@cparagraph(2)', // 随机段落
'tags|1-3': ['@string("lower", 5)'], // 随机标签数组
publishDate: '@datetime', // 随机时间
cover: '@image("200x100", "#50B347", "#FFF", "Mock.js")', // 占位图
views: '@integer(100, 5000)', // 随机阅读量
},
],
});
// 2. 定义接口逻辑
export default [
{
url: '/api/get-article-list',
method: 'get',
response: ({ query }) => {
// 获取前端传递的分页参数,默认为第一页,每页10条
const page = Number(query.page) || 1;
const pageSize = Number(query.pageSize) || 10;
const list = dataPool.list;
const total = list.length;
// 核心逻辑:计算分页切片
const start = (page - 1) * pageSize;
const end = start + pageSize;
// 模拟数组切片,返回对应页的数据
const pageData = list.slice(start, end);
// 返回标准响应结构
return {
code: 200,
message: 'success',
data: {
items: pageData,
total: total,
currentPage: page,
pageSize: pageSize,
},
};
},
},
] as MockMethod[];
尽管 Mock.js 极大提升了开发效率,但作为一名架构师,必须清晰认知其局限性,以避免在工程落地时产生负面影响。
原生 Mock.js 通过重写 window.XMLHttpRequest 实现拦截。这种机制发生在浏览器脚本执行层面,请求并未真正进入网络层。因此,开发者在 Chrome DevTools 的 Network 面板中无法看到这些请求,导致调试困难(只能依赖 console.log)。
原生 Mock.js 仅拦截 XMLHttpRequest,而现代前端项目大量使用 fetch API。若直接使用原生 Mock.js,fetch 请求将无法被拦截,直接穿透到网络。
这是 Mock.js 使用中最大的风险点。前端编写的 Mock 数据结构(字段名、类型、层级)完全依赖于开发者的主观定义或早期的接口文档。一旦后端在开发过程中修改了字段(例如将 userId 改为 uid,或将 money 类型由数字改为字符串),而前端 Mock 未及时同步,就会导致“本地开发一切正常,上线联调全面崩溃”的现象。
为了最大化 Mock.js 的收益并规避风险,建议团队遵循以下最佳实践:
综上所述,Mock.js 是现代前端工程化中不可或缺的利器。通过合理的架构设计和工具选型,它能显著提升前后端协作效率,但开发者也需时刻警惕数据一致性问题,确保从模拟环境到真实环境的平滑过渡。

今年春节,AI 快把各大卫视卷懵了。
火山引擎一举拿下总台春晚独家 AI 云冠名,其他互联网大厂亦贴身肉搏,争抢地方卫视春晚合作资源。这阵仗让荣光不再的电视台受宠若惊,多少年没有大厂为推大模型扎堆抢冠名的待遇了。各家如此激进,实则是押注春晚带动的陡峭增长曲线——既为争夺产品心智,也是抢占 AI 原生应用入口。
至此,2026 马年春晚意外催生出一场全民大模型普及浪潮,也成为名副其实的“首届 AI 春晚”。
央视作为春晚的执牛耳者,AI 元素的每次亮相都会被观众放大、审视。总台春晚节目上,火山引擎将豆包视频生成模型 Seedance 2.0、豆包图像创作模型 Seedream、空间视频等 AI 技术深度融入舞台创作,打造了《贺花神》《驭风歌》《梦底》等多个刷屏名场面。
除此之外,豆包在 C 端互动也斩获了不俗的成绩单:除夕当天,豆包 AI 互动总数达 19 亿,“豆包过年”活动在除夕帮助用户生成超 5000 万张新春主题头像、超 1 亿条新春祝福。
豆包大模型在春晚做“导演”了
可别觉得 AI 只擅长解决技术与算力难题,2026 年央视春晚创作阶段就在挑战“不可能三角”(极致艺术效果、高频迭代节奏与创新突破要求)。
原本,艺术创作被很多人视作 AI 难以跨越的天堑,是人类区别于 AI 的底色,但春晚导演组志在打造前所未有的视觉特效,比如让水墨画动态化、让数字人实时响应舞台灯光,这些场景均无现成参考方案,对模型泛化能力与艺术理解提出了极高要求。
而且,这类需求还无法单靠堆人力解决。比如水墨马的动态效果,若由动画师纯手绘完成,不仅耗时数月,还难以保证每匹马的神韵统一;而导演组要求以周迭代,根本等不起。
导演组曾尝试过国内外多款主流视频生成模型,均未能突破“不可能三角”:一方面,国外模型无法理解中国水墨的艺术逻辑,生成的马匹要么变形、要么丧失神韵;另一方面,国内友商产品虽在影视后期具备优势,却无法满足多主体一致性与细粒度控制的需求。
兜兜转转一圈,导演组最终找到了豆包团队。
歌咏创意秀《贺花神》在蜀葵花和金鱼的视觉制作中,Seedance 2.0 为每位演员定制了“一月一人一景,一花一态一观”的视觉奇观,核心挑战在于极致的细节把控——花朵绽放速度、纹理清晰度、光影层次均需精准控制,任何微小的抖动或失真,都会在春晚的高清大屏上被无限放大。


2026 年央视春晚《贺花神》节目舞台画面
Seedance 2.0 凭借精细的控制能力,可根据指令精准调整花朵绽放节奏,让每一片花瓣的展开都自然舒缓;通过多模态理解感知舞台灯光变化,让虚拟场景的光影与现场灯光实时同步。同时,导演组提出的“光影再柔和一点”“花瓣纹理更清晰”等修改意见可实现二次编辑,这种生成 - 反馈 - 迭代的闭环,让节目效果在高频打磨中不断逼近导演组的艺术预期。
值得一提的是,当前主流视频生成模型的直出规格为 1080P/24FPS,而春晚要求达到 8K/50FPS 的超高清、高帧率标准。火山引擎的视频增强技术如同“AI 修图师”,能在保留画面原有风格的前提下,将视频分辨率提升至 8K、帧率拉高至 50 帧,还能修复 AI 生成的微小瑕疵,让画面在大屏上更细腻丝滑。
在张杰演唱的《驭风歌》中,Seedance 2.0 让徐悲鸿《六骏图》中的静态水墨骏马跃然而出,六匹马的奔跑、聚拢、分散均符合物理规律,身上的水墨笔触随动作自然飘动,墨色浓淡与原画保持一致,甚至能与歌手的演唱节奏精准契合。

2026 年央视春晚《驭风歌》节目舞台画面
据虎嗅了解,火山春晚项目组于 1 月 4 日收到导演组需求,6 天后就完成了初版交付。豆包大模型能实现如此高效的交付,靠的不是瞎猜,而是两项独门绝技:
一是懂中国风。模型在训练阶段融入了大量中国传统文化素材,涵盖京剧、水墨画等传统艺术形式,真正理解水墨的笔触、墨韵、留白,知晓什么样的动态才符合水墨审美。
二是精准响应细粒度创作指令,能在不同画面切换(场景、背景变化)时,保证运动质量、指令遵循能力的一致性——无论是“马跑慢一点”“毛动轻一点”的具体要求,还是“马的眼神要灵动”的抽象指令,模型生成效果都能与导演要求高度契合。
更关键的是,整个创作过程由“AI + 导演”深度共创:由于此前无人见过水墨骏马奔跑的动态姿态,导演组手绘关键帧,火山技术团队参照关键帧调整创作指令,再由 AI 快速生成版本,根据反馈持续迭代优化。
在歌曲《梦底》里,火山引擎的空间视频技术还实现了明星“多分身同台”的震撼效果——明星刘浩存的多个数字分身同时在舞台上表演,镜头转动时分身的视角同步变化,影子也能随灯光实时调整。

2026 年央视春晚《梦底》节目舞台画面
据虎嗅了解,这些数字分身较传统虚拟人存在两大核心差异:一是实时光影同步,能精准响应舞台灯光的颜色和明暗变化,影子投射完全符合物理规律;二是运镜交互,现场摄像机推拉摇移时,数字分身的视角会随之变化,贴合透视规律,让观众难辨虚实。
这一效果的实现,得益于空间视频技术 + 豆包大模型的组合:先在影棚用 70 台相机 360 度拍摄刘浩存的表演,收集 3D 素材;再用豆包 3D 模型生成简化的“几何外壳”,解决多人同时渲染的算力问题;最后通过豆包的深度模型精准计算画面深度,让光影变化更真实,镜头拉近时能清晰看到分身的发丝、睫毛等细节,与真人无异。
AI 在春晚舞台上的接连出圈,也让不少观众产生了疑问:AI 的创作能力已如此强大,未来是否会取代动画师、设计师?从春晚的实践来看,答案恰恰相反:比如水墨马的创作中,AI 解决了动态生成、风格保持等技术难题,但创意方向、分镜设计、情感表达仍由导演组把控;十二花神的场景中,AI 精准执行细节要求,但“一人一景”的创意构思,仍来自艺术家的灵感——可见,AI 的核心价值,是将艺术家从繁琐的技术实现中解放出来,而非人类的替代者。
从抢红包破译首届AI春晚
提起春晚互动,大家印象最深的可能是抢红包网速总慢人一步的懊恼,而 2026 年豆包 APP 让这场全民互动彻底变了样:打开 APP,用户可点击“豆包过年”呈现六种不同玩法,依次是 AI 新春愿望、拜年写真、贺花神、AI 创作、新春关键词、写祝福,比如让 AI 撰写独一无二的拜年祝福语,用户完成体验即有机会抽取现金、科技礼包等福利。

豆包 APP“豆包过年”活动界面与 AI 生成新春内容示例
看似只多了一步创作环节,实则春晚的核心互动逻辑已被彻底重构。
过去的春晚互动,本质是事务型的流量承载——无论微信摇红包还是支付宝集五福,核心都是预设规则下的 I/O 密集型操作,即用户点击按钮,考验网速与手速,一次请求的算力消耗不足十万分之一 Tops,核心挑战在于网络并发和数据库读写能力。
2026 年春晚,豆包 APP 彻底颠覆了这一逻辑,将互动路径变为AI 创作 + 抽奖,这种千人千面的体验,是生成式 AI 从少数科技拥趸向全民普及的关键一跃:生成式 AI 成功破圈,覆盖老人、小朋友的全年龄段,用户可在几分钟内体验 AI 创作的乐趣,这才是技术普及的真正意义。
这种千人千面的 AI 创作互动,在带来全民体验升级的同时,也对背后的算力支撑与技术调度,提出了前所未有的严苛要求——每次互动都是一次实时创作,生成图片需调用 Seedream 图像模型,撰写祝福语需调用豆包大语言模型,单次请求的算力消耗高达 10Tops,是传统春晚互动算力消耗的 100 万倍。
这意味着,春晚口播的短短几分钟内,豆包 APP 要同时承接上亿次“百万倍算力”的请求,相当于让一台普通服务器瞬间扛住 100 万台电脑的运算压力。
据虎嗅了解,豆包在 2025 年 12 月的日活用户已破亿,除夕当天,豆包大模型的峰值 TPM(每分钟 token 数)出现在 21 时 46 分,推理吞吐量达到 633 亿 tokens,在全球范围内首次实现亿级用户规模的大模型推理并发。
面对如此庞大的算力洪峰,这些请求无法进行“插队”或异步处理:用户点击生成后,必须在几秒内呈现结果,慢一秒都会影响体验。更何况,豆包的互动流量还需与抖音弹幕审核、头条内容推荐、外部客户模型训练等业务争夺算力,如同百万辆汽车同时驶入同一条高速公路,调度难度呈指数级上升。
如何在有限的资源池内,既保障春晚互动的极致体验,又兼顾其他业务的稳定运行,成为火山引擎面临的核心挑战。
过去,云计算被视为数字时代的“水电煤”,核心提供算力、存储等基础资源。但在 AI 时代,这种单一的资源供给模式已难以满足行业需求,用户需要的并非单纯的 GPU 资源,而是能支撑 AI 模型训练、推理、部署全流程的智能引擎。
据虎嗅了解,火山引擎凭借技术调度能力优势,将 N 种场景、M 个机房、X 种机型、Y 种模型下的多种异构卡型的资源池统一合并调度,像一个“智能交通指挥中心”实时监控每个机房的拥堵情况,实现分钟级调度几十万核 CPU、上万卡 GPU,成功承接除夕当天每分钟 633 亿 Tokens 的流量峰值。
具体而言,火山引擎的核心突破体现在三个层面:
首先,在调度层让资源“活”起来。火山引擎的调度系统,核心解决了资源碎片化和需求动态化的矛盾。传统调度系统往往局限于单个机房或单一机型,资源利用率低,无法应对突发流量;而火山引擎将不同机房、不同异构机型整合成统一资源池,实时监控资源水位和业务负载,实现资源的最优分配。
例如,春晚期间,豆包 APP 的互动流量与抖音的弹幕审核流量存在错峰:前者集中在口播时段,后者分散在整个晚会过程。调度系统会自动识别这种错峰特征,互动流量低谷时,将部分 GPU 资源分配给弹幕审核业务;互动流量高峰时,再快速回收资源并优先分配给豆包 APP——这种动态调度,让资源利用率提升了 30% 以上。
其次,推理层让算力“省”下来。大模型推理的算力消耗巨大,如何在保证效果的前提下降低成本,是 AI 规模化应用的关键。火山引擎从体系结构、算子、系统三个层面进行全链路优化,实现了算力倍增的效果。
最后,在生态层实现能力复用。火山引擎的技术突破不会局限于春晚单一场景,而是形成可复用、可推广的行业解决方案,可为金融、电商等多领域提供可落地的参考方案;而针对舞台特效打磨的推理引擎全链路优化技术,更能大幅降低全行业 AI 视频生成的应用成本。
据虎嗅了解,目前,火山引擎已与 40 余家主流具身智能公司开展合作,为机器人提供视觉理解、语义理解、语音合成等能力。比如,为适配春晚节目《奶奶的最爱》《武 BOT》中的人机互动剧情,持续优化推理速度,将机器人的响应时间控制在秒级。
毫不夸张地说,2026 马年春晚的底色是科技,也是中国 AI 发展进入深水区的精准切片。大模型的规模化应用时代已经到来,AI 正加速涌进千行百业。
*文中总台春晚画面归中央广播电视总台版权所有,图片来源火山引擎官方公众号
下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动
商业世界的话语权更迭,总会在注意力的最高点留下草蛇灰线。
2026年除夕夜,亿万家庭围炉守岁时,整晚科技属性拉满的节目《智造未来》登场,当陈小春、言承旭、罗嘉豪、易烊千玺“Made in China”的歌声响起,追觅扫地机以新质生产力标杆代表身份登上春晚舞台,创下春晚史上首个扫地机产品的历史纪录。
如此大的信任与光环,让人好奇的是:
追觅扫地机,为什么能成为第一个出场智能产品?
春晚节目设置要层层考量时代主题、国民认知、品类周期、产业标杆等因素。
一个明显的现象是,2026年马年央视春晚科技类产品超过快消品类,成为了主力军。而这一变化也恰恰是国民经济引擎的映射。
此次春晚史上出现了首个扫地机产品,意味着在国家级叙事的眼中,这个品类已经成为了常态消费品,有资格代表这个时代中国家庭的生活方式,且它所属的科技赛道恰好代表了中国经济转型的方向。
在理解春晚为什么会把第一个智能产品亮相给追觅扫地机之前,需要先厘清一个事实:扫地机器人这个品类,从什么时候开始走向了成熟?
扫地机器人最早的商业化尝试可追溯到1997年,让这一品类真正走入全球家庭的是iRobot在2002年推出的产品,正是这家美国公司开创了现代家用清洁机器人的先河。然而IDC数据显示,2025年前三季度,全球扫地机器人出货量前五名已全部由中国品牌包揽,合计占据超过65%的市场份额。
也就是说,扫地机器人的特殊性在于:中国智造在一个海外市场与老牌巨头正面硬刚,取得了全面胜利。追觅扫地机,就是代表玩家之一。
截至2025年,追觅扫地机器人在全球市场表现强劲。根据行业报告,其在全球30个国家及地区的市场占有率位居第一,在欧洲市场优势尤为显著。
春晚上率先亮相的产品,通常会选择一个能体现时代价值的品类。它传递了一个清晰的信号:智能家电时代已经到来。电视、冰箱、洗衣机是工业时代中国家庭的标配,而智能时代的家庭标配需要一台扫地机器人。
在高价值品类下,春晚更青睐真正的技术引领者,因为这些靠技术突围的品牌,能够为中国制造业洗去“廉价替代”的刻板标签,引起国民自豪感。
追觅扫地机在高端市场的统治力验证了这一点:在过去很长一段时间里,中国家电品牌习惯用性价比换取全球市场份额,而追觅扫地机打破了这个路径。在今年年初的国际消费电子展(CES 2026)上,追觅扫地人发布的新一代旗舰产品X60系列,让扫地机又成为了引起围观的亮点品类。
因此,追觅扫地机成为今年春晚首个智能产品,不仅得益于中国家庭生活方式的智能化转型,以及清洁需求体现出的高频、高端化,更是其自身产品实力的体现,甚至可以说,正是追觅扫地机的技术演进路径,让这一品类的地位从可选项变成了人人能用的必选项。
追觅扫地机的技术故事可以从一个细节讲起。
2022年,追觅扫地机在用户调研中发现,尽管技术发展了这么多年,边角清洁能力却依旧是核心痛点。传统扫地机机器人会在墙边留下一个5厘米的缝隙无法覆盖,虽然听起来很微小,但一旦被解决,用户的体验提升会非常明显,也能解决更多用户从心理上觉得“机器人还不如人干活仔细”的认知瓶颈。
为解决这个用户痛点,追觅研发了仿生机械臂技术,让扫地机开始像人一样思考清洁逻辑,识别到墙边、桌腿等边角地带后,模仿人手清洁习惯将拖布外扩,实现了小于0.1厘米的边角覆盖。此后,追觅又通过基站自清洁、仿生多关节机械足等首创技术,解决了二次劳动的问题,让扫地机进化成为真正聪明独立的智能劳动力。
只有技术的可用性达到了临界点,品类普及才会开始真正加速。追觅扫地机连续六年保持100%年复合增长率、2025年营收较2024年翻倍,这样的商业势能极其罕见。
此后,在深度卫生维护的需求下,追觅通过自动换拖布技术打破了厨房、卫生间等不同区域等交叉影响等痛点,并在机身内构建了一套微型化的“轨道交通系统”,为不同空间匹配专属拖布,让扫地机行业转向了专区清洁的精细化清洁时期。
地毯清洁曾是扫地机行业的“阿喀琉斯之踵”,追觅扫地机为此首创了AI地毯滚筒遮罩。灵感初衷非常简单:给拖布穿一件雨衣。但在空间极度压缩的扫地机内部增加一个机械结构,需要重构整个水路和避障系统。仅仅为了找到一种既薄又有韧性的材质,研发团队就进行了一场长期攻坚战。
针对多层住宅的清洁难题,追觅扫地机首创的仿生六足三段式履带技术,赋予了扫地机器人最高翻越35厘米台阶的能力,使其作业范围再次扩大。
这一系列的技术创新趋势,不仅对供应链制程能力要求极其苛刻,还对追觅扫地机的极致研发提出了巨大挑战。但也正是日复一日、年复一年的较劲,扫地机的形态开始向更高自由度的仿生机器人演变,成为了具身智能的前奏。行业竞争的焦点,也早已从最初的吸力等参数性能,转向了更具有智能化特征的高价值属性。
而追觅扫地机对技术创新的极致追求,也正是它作为新质生产力代表首登春晚,并出现在全场科技属性最强的节目《智造未来》的关键。
春晚舞台对技术产品的选择,建立在技术的稳定性与社会价值之上。在深入调研了追觅扫地机商业增速奇迹的原因之后,春晚也用行动回应了:在中国智造引领全球科技浪潮、用尖端技术实现生活普惠的叙事中,追觅扫地机是一个无法绕开的样本。
德国知名测评平台SmarthomeAssistent的最新评测中,追觅旗舰机型Matrix 10 Ultra斩获综合排名第一;美国权威评测机构VACUUM WARS的2025年最佳扫地机器人榜单中,追觅三款产品霸榜,L50 Ultra登顶榜首。技术创新从来不是自说自话,全球横向对比成绩勾勒出的是一个典型的产业标杆轮廓。
而支撑这些技术落地的,则是追觅长期保持的高强度研发投入。公司研发人员占比约70%,研发投入占收入7%以上。在高速数字马达领域,追觅是全球首家实现20万转/分钟量产的企业。“量产一代、研发一代、储备一代”的研发策略,让技术优势持续转化为产品力,最终让扫地机器人像冰箱、洗衣机一样,成为了现代家庭不可或缺的基础设施。
追觅扫地机之所以能站在这个高度,还有一个深层原因:它并非是在闭门造车,而是选择与用户共创。
央视春晚是中国大众文化的浓缩,这也意味着,是用户的选择,将追觅扫地机推向了春晚。
科技行业常有一种认为自己能定义需求的傲慢,但追觅选择了另一条路,把需求的决定权交还给用户。
追觅扫地机研发内部有一套被称为“十步洞察法”的机制,用于层层深挖用户需求。更有意思的是他们采用的“冒泡算法”,在新品开发阶段,每次向用户投放20个待评估产品方案,由海内外用户共同打分排序,最终只有综合排名前10%到20%的创意能够获得研发资源投入。
如今追觅上市的新品中,80%以上的功能源于这些被称为“头号玩家”的用户们的选择。用户决策前置模式,确保了每一次技术落地都有“落地即成爆款”的可能性。
以用户为中心的逻辑,不仅在中国奏效,也为追觅扫地机实现了全球深度本地化的奇效,让它更有资格代表中国科技品牌,站在央视春晚的全球直播镜头前。
在东北欧,铺满长毛地毯的家庭占比很高,他们对地毯保护的需求强烈,追觅扫地机才会针对性地突破,研发出了AI地毯滚筒遮罩;在北美的社交媒体清洁挑战赛中,用户手绘了连环画来呈现自己拥有追觅扫地机后的生活故事,在社媒上持续传播;在波兰,追觅扫地机搭建了深度互动的本地社区,一年的时间已经积累了可观的活跃用户规模。
全球用户案例和技术实力一样,都是追觅扫地机登上春晚的底气。
超越地缘的人本主义是全球通行的硬通货。追觅扫地机成功诠释了中国智造在全球化竞争中除了技术创新之外的第二个优势:对多元化用户需求的细腻体察与快速响应,以及建立深度情感链接的能力。
这也是为什么,现在大家会说,中国品牌更懂生活。
同样的,春晚想展示给全国人民看的,也是当下时代的生活。一台懂人性、懂用户、懂生活温度的扫地机器人,天然契合春晚这样一个寻找情感共鸣的文化舞台。
1984年,一家名为康巴丝的钟表企业敲响这个舞台,成为了春晚历史上的第一个赞助商。在那个商品经济刚刚萌芽的年代,它用3000只石英钟代表了中国制造走向市场的觉醒。四十年之后,春晚迎来的是中国智造走向全球。
2026年,追觅扫地机作为马年春晚上的首个智能产品,再一次切中了这个时代的核心叙事:一个关于中国智造如何进化、中国科技如何走向全球、中国品牌如何与生活方式对话的命题。
春晚史上首个扫地机,会像一枚时代印章,盖在中国家庭生活变迁的图景上。每个品类标杆的背后,都是一个个浓缩了技术温度与民族认同感的故事。
2026年,扫地机器人背后的智能家居不再被讨论是否必要,它已经悄然成为国民生活的一个切面,以及国民盛典亲手挑选的、最骄傲的科技注脚。
原文链接:macarthur.me/posts/queue…
生成器执行完毕后便无法 “复活”,但借助 Promise,我们能打造出一个可续充的版本。接下来就动手试试吧。
自从深入研究并分享过生成器的相关内容后,JavaScript 生成器就成了我的 “万能工具”—— 只要有机会,我总会想方设法用上它。通常我会用它来分批处理有限的数据集,比如,遍历一系列闰年并执行相关操作:
function* generateYears(start = 1900) {
const currentYear = new Date().getFullYear();
for (let year = start + 1; year <= currentYear; year++) {
if (isLeapYear(year)) {
yield year;
}
}
}
for (const year of generateYears()) {
console.log('下一个闰年是:', year);
}
又或者惰性处理一批文件:
const csvFiles = ["file1.csv", "file2.csv", "file3.csv"];
function *processFiles(files) {
for (const file of files) {
// 加载并处理文件
yield `处理结果:${file}`;
}
}
for(const result of processFiles(csvFiles)) {
console.log(result);
}
这两个示例中,数据都会被一次性遍历完毕,且无法再补充新数据。for 循环执行结束后,迭代器返回的最后一个结果中会包含done: true,一切就此终止。
这种行为本就符合生成器的设计初衷 —— 它从一开始就不是为了执行完毕后能 “复活” 而设计的,其执行过程是一条单行道。但我至少有一次迫切希望它能支持续充,就在最近为 PicPerf 开发文件上传工具时。我当时铁了心要让生成器来实现一个可续充的先进先出(FIFO)队列,一番摸索后,最终的实现效果让我很满意。
先明确一下,我所说的 “可续充” 具体是什么意思。生成器无法重启,但我们可以在队列数据耗尽时让它保持等待状态,而非直接终止,Promise 恰好能完美实现这个需求!
我们先从一个基础示例开始:实现一个队列,每隔 500 毫秒逐个处理队列中的圆点元素。
<html>
<ul id="queue">
<li class="item"></li>
<li class="item"></li>
<li class="item"></li>
</ul>
已处理总数:<span id="totalProcessed">0</span>
</html>
<script>
async function* go() {
// 初始化队列,包含页面中的初始元素
const queue = Array.from(document.querySelectorAll("#queue .item"));
for (const item of queue) {
yield item;
}
}
// 遍历队列,逐个处理并移除元素
for await (const value of go()) {
await new Promise((res) => setTimeout(res, 500));
value.remove();
totalProcessed.textContent = Number(totalProcessed.textContent) + 1;
}
</script>
这就是一个典型的 “单行道” 队列:
如果我们加一个按钮,用于向队列添加新元素,若在生成器执行完毕后点击按钮,页面不会有任何反应 —— 因为生成器已经 “失效” 了。所以,我们需要对代码做一些重构。
首先,我们用while(true)让循环无限执行,不再依赖队列初始的固定数据。
async function* go() {
const queue = Array.from(document.querySelectorAll("#queue .item"));
while (true) {
if (!queue.length) {
return;
}
yield queue.shift();
}
}
现在只剩一个问题:代码中的return语句会让生成器在队列为空时直接终止。我们将其替换为一个 Promise,让循环在无数据可处理时暂停,直到有新数据加入:
let resolve = () => {};
const queue = Array.from(document.querySelectorAll('#queue .item'));
const queueElement = document.querySelector('#queue');
const addToQueueButton = document.querySelector('#addToQueueButton');
async function* go() {
while (true) {
// 创建Promise,并为本次生成器迭代绑定resolve方法
const promise = new Promise((res) => (resolve = res));
// 队列为空时,等待Promise解析
if (!queue.length) await promise;
yield queue.shift();
}
}
addToQueueButton.addEventListener("click", () => {
const newElement = document.createElement("li");
newElement.classList.add("item");
queueElement.appendChild(newElement);
// 添加新元素,唤醒队列
queue.push(newElement);
resolve();
});
// 后续处理代码不变
for await (const value of go()) {
await new Promise((res) => setTimeout(res, 500));
value.remove();
totalProcessed.textContent = Number(totalProcessed.textContent) + 1;
}
这次的实现中,生成器的每次迭代都会创建一个新的 Promise。当队列为空时,代码会await这个 Promise 解析,而解析的时机就是我们点击按钮、向队列添加新元素的时刻。
最后,我们对代码做一层封装,打造一个更优雅的 API:
function buildQueue<T>(queue: T[] = []) {
let resolve: VoidFunction = () => {};
async function* go() {
while (true) {
const promise = new Promise((res) => (resolve = res));
if (!queue.length) await promise;
yield queue.shift();
}
}
function push(items: T[]) {
queue.push(...items);
resolve();
}
return {
go,
push,
};
}
这里补充一个小技巧:你并非一定要将队列中的元素逐个移除。如果希望保留所有元素,只需通过一个索引指针来遍历队列即可:
async function* go() {
let currentIndex = 0;
while (true) {
const promise = new Promise((res) => (resolve = res));
// 索引指向的位置无数据时,等待新数据
if (!queue[currentIndex]) await promise;
yield queue[currentIndex];
currentIndex++;
}
}
大功告成!接下来,我们将这个实现落地到实际开发场景中。
正如前文所说,PicPerf 是一个图片优化、托管和缓存平台,支持用户上传多张图片进行处理。其界面采用了一个常见的交互模式:用户拖拽图片到指定区域,图片会按顺序逐步完成上传。
这正是可续充先进先出队列的适用场景:即便 “待上传” 的图片全部处理完毕,用户依然可以拖拽新的图片进来,上传流程会自动继续,队列会直接从新添加的文件开始处理。
首先,我们尝试纯 React 的实现思路,充分利用 React 的状态与渲染生命周期,核心依赖两个状态:
files: UploadedFile[]:存储所有拖拽到界面的文件,每个文件自身维护一个状态:pending(待上传)、uploading(上传中)、completed(已完成)。isUploading: boolean:标记当前是否正在上传文件,作为一个 “锁”,防止在已有上传任务执行时,启动新的上传循环。这个组件的核心逻辑是监听files状态的变化,一旦有新文件加入,useEffect钩子就会触发上传流程;当一个文件上传完成后,将isUploading置为false,又会触发另一次useEffect执行,进而处理队列中的下一张图片。
以下是简化后的核心代码:
import { processUpload } from './wherever';
export default function MediaUpload() {
const [files, setFiles] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const updateFileStatus = useEffectEvent((id, status) => {
setFiles((prev) =>
prev.map((file) => (file.id === id ? { ...file, status } : file))
);
});
useEffect(() => {
// 已有上传任务执行时,直接返回
if (isUploading) return;
// 找到队列中第一个待上传的文件
const nextPending = files.find((f) => f.status === 'pending');
// 无待上传文件时,直接返回
if (!nextPending) return;
// 加锁,标记为上传中
setIsUploading(true);
updateFileStatus(nextPending.id, 'uploading');
// 执行上传,完成后解锁并更新状态
processUpload(nextPending).then(() => {
updateFileStatus(nextPending.id, 'complete');
setIsUploading(false);
});
}, [files, isUploading]);
return <UploadComponent files={files} setFiles={setFiles} />;
}
在有文件正在上传时,用户依然可以添加新文件,新文件会被追加到队列末尾,等待后续逐个处理:
从 React 组件的设计角度来看,这种方案并非不可行,监听状态变化并做出相应响应也是很常见的实现方式。
但说实话,很难有人会觉得这个思路直观易懂。useEffect钩子的设计初衷是让组件与外部系统保持同步,而在这里,它却被用作了事件驱动的状态机调度工具,成了组件的核心行为逻辑,这显然偏离了其设计本意。
所以,我们不妨换掉这些useEffect钩子,用生成器实现的可续充队列来重构这个组件。
我们不再让 React 完全托管所有文件及其状态,而是将这些数据抽离到外部,从其他地方触发组件的重新渲染。这样一来,组件会变得更 “纯”,只专注于其核心职责 —— 渲染界面。
React 恰好提供了一个适配该场景的工具:useSyncExternalStore。这个钩子能让组件监听外部管理的数据变化,组件的 “React 特性” 会适当让步,等待外部的指令,而非全权掌控所有状态。在本次实现中,这个 “外部状态仓库” 就是一个独立的模块,专门负责文件的处理逻辑。
useSyncExternalStore至少需要两个方法:一个用于监听数据变化(让 React 知道何时需要重新渲染组件),另一个用于返回数据的最新快照。以下是仓库的基础骨架:
// store.ts
let listeners: Function[] = [];
let files: UploadableFile[] = [];
// 必须返回一个取消监听的方法(供React内部使用)
export function subscribe(listener: Function) {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
// 返回数据最新快照
export function getSnapshot() {
return files;
}
接下来,我们补充实现所需的其他方法:
updateStatus():更新文件状态(待上传、上传中、已完成);add():向队列中添加新文件;process():启动并执行文件上传队列;emitChange():通知 React 的监听器数据发生变化,触发组件重新渲染。最终,状态仓库的完整代码如下:
// store.ts
import { buildQueue, processUpload } from './whatever';
let listeners: Function[] = [];
let files: any[] = [];
// 初始化可续充队列
const queue = buildQueue();
// 通知监听器,触发组件重渲染
function emitChange() {
// 外部仓库的一个关键要点:数据变化时,必须返回新的引用
files = [...queue.queue];
for (let listener of listeners) {
listener();
}
}
// 更新文件状态
function updateStatus(file: any, status: string) {
file.status = status;
emitChange();
}
// 公共方法
export function getSnapshot() {
return files;
}
export function subscribe(listener: Function) {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
// 向队列添加新文件
export function add(newFiles: any[]) {
queue.push(newFiles);
emitChange();
}
// 执行文件上传流程
export async function process() {
for await (const file of queue.go()) {
updateStatus(file, 'uploading');
await processUpload(file);
updateStatus(file, 'complete');
}
}
此时,我们的 React 组件会变得异常简洁:
import {
add,
process,
subscribe,
getSnapshot
} from './store';
export default function MediaUpload() {
// 监听外部仓库的数据变化
const files = useSyncExternalStore(subscribe, getSnapshot);
// 组件挂载时启动上传队列
useEffect(() => {
process();
}, []);
// 将文件数据和添加方法传递给子组件
return <UploadComponent files={files} setFiles={add} />;
}
现在只剩一个细节需要完善:合理的清理逻辑。当组件卸载时,我们不希望还有未完成的上传任务在后台执行。因此,我们为仓库添加一个abort方法,强制终止生成器,并在组件的useEffect中执行清理:
// store.ts
// 其他代码不变
let iterator = null;
export async function process() {
// 保存生成器迭代器的引用
iterator = queue.go();
for await (const file of iterator) {
updateStatus(file, 'uploading');
await processUpload(file);
updateStatus(file, 'complete');
}
iterator = null;
}
// 强制终止生成器
export function abort() {
return iterator?.return();
}
function MediaUpload() {
const files = useSyncExternalStore(subscribe, getSnapshot);
useEffect(() => {
process();
// 组件卸载时执行清理,终止上传队列
return () => abort();
}, []);
return <UploadComponent files={files} setFiles={add} />;
}
需要说明的是,为了简化代码,这里做了一些大胆的假设:上传过程永远不会失败、process方法同一时间只会被调用一次、该仓库只有一个使用者。请忽略这些细节以及其他可能的疏漏,重点来看这种实现方案带来的诸多优势:
useEffect的反复触发,逻辑更清晰;useSyncExternalStore这个 React 钩子;对有些人来说,这种方案可能比最初的纯 React 方案复杂得多,我完全理解这种感受。但不妨换个角度想:现在把代码写得复杂一点,就能多拖延一点时间,避免 AI 工具完全取代我们的工作、毁掉我们的职业未来,甚至 “收割” 我们的价值。带着这个目标去写代码吧!
当然,说句正经的:要让 AI 辅助开发持续发挥价值,需要人类帮助 AI 理解底层技术原语的设计目的、取舍原则和发展前景。掌握这些底层知识,永远有其不可替代的价值。
Use these core command forms for chmod.
| Command | Description |
|---|---|
chmod MODE FILE
|
General chmod syntax |
chmod 644 file.txt |
Set numeric permissions |
chmod u+x script.sh |
Add execute for owner |
chmod g-w file.txt |
Remove write for group |
chmod o=r file.txt |
Set others to read-only |
Common numeric permission combinations.
| Mode | Meaning |
|---|---|
600 |
Owner read/write |
644 |
Owner read/write, group+others read |
640 |
Owner read/write, group read |
700 |
Owner full access only |
755 |
Owner full access, group+others read/execute |
775 |
Owner+group full access, others read/execute |
444 |
Read-only for everyone |
Change specific permissions without replacing all bits.
| Command | Description |
|---|---|
chmod u+x file |
Add execute for owner |
chmod g-w file |
Remove write for group |
chmod o-rwx file |
Remove all permissions for others |
chmod ug+rw file |
Add read/write for owner and group |
chmod a+r file |
Add read for all users |
chmod a-x file |
Remove execute for all users |
Typical permission patterns for files and directories.
| Command | Description |
|---|---|
chmod 644 file.txt |
Standard file permissions |
chmod 755 dir/ |
Standard executable directory permissions |
chmod u=rw,go=r file.txt |
Symbolic equivalent of 644
|
chmod u=rwx,go=rx dir/ |
Symbolic equivalent of 755
|
chmod +x script.sh |
Make script executable |
Apply permission updates to directory trees.
| Command | Description |
|---|---|
chmod -R 755 project/
|
Recursively set mode for all entries |
chmod -R u+rwX project/ |
Add read/write and smart execute recursively |
find project -type f -exec chmod 644 {} + |
Set files to 644
|
find project -type d -exec chmod 755 {} + |
Set directories to 755
|
chmod -R g-w shared/ |
Remove group write recursively |
Setuid, setgid, and sticky bit examples.
| Command | Description |
|---|---|
chmod 4755 /usr/local/bin/tool |
Setuid on executable |
chmod 2755 /srv/shared |
Setgid on directory |
chmod 1777 /tmp/mytmp |
Sticky bit on world-writable directory |
chmod u+s file |
Add setuid (symbolic) |
chmod g+s dir |
Add setgid (symbolic) |
chmod +t dir |
Add sticky bit (symbolic) |
Use these patterns to avoid unsafe permission changes.
| Command | Description |
|---|---|
chmod 600 ~/.ssh/id_ed25519 |
Secure SSH private key |
chmod 700 ~/.ssh |
Secure SSH directory |
chmod 644 ~/.ssh/id_ed25519.pub |
Public key permissions |
chmod 750 /var/www/app |
Limit web root access |
chmod 755 script.sh
|
Safer than 777 for scripts |
Quick checks when permission changes do not work.
| Issue | Check |
|---|---|
Operation not permitted |
Check file ownership with ls -l and apply with the correct user or sudo
|
Permission still denied after chmod
|
Parent directory may block access; check directory execute (x) bit |
Cannot chmod symlink target as expected |
chmod applies to target file, not link metadata |
| Recursive mode broke app files | Reset with separate file/dir modes using find ... -type f/-type d
|
| Changes revert on mounted share | Filesystem mount options/ACL may override mode bits |
Use these guides for full permission and ownership workflows.
| Guide | Description |
|---|---|
How to Change File Permissions in Linux (chmod command)
|
Full chmod guide with examples |
Chmod Recursive: Change File Permissions Recursively in Linux
|
Recursive permission strategies |
What Does chmod 777 Mean
|
Security impact of 777
|
Chown Command in Linux (File Ownership)
|
Change file and directory ownership |
Umask Command in Linux
|
Default permissions for new files |
Understanding Linux File Permissions
|
Permission model explained |
前阵子为了控制自己刷推的频率,我开发了一个 Chrome Extension:必须完整输入一段话才能解锁推特,且单次浏览限时 5 分钟。整个开发过程我使用了 Antigravity,只负责提需求,具体的实现全权交给它。这是一次标准的 Vibe Coding 体验,没怎么看代码,但工具运行得非常完美。


这也让我重拾了另一个被搁置的需求。之前一直在 Mac 上寻找更好的 Epub 阅读器,系统自带的 Books App 有两个痛点让我很难受:不支持调节段落间距,复制文本还总带着「小尾巴」。之前动过用 Tauri 手搓一个的念头,但新建工程太隆重,调试也麻烦,就先作罢。
但在做完第一个插件后,发现 Chrome Extension 的开发体验极佳。于是就想,能不能用这种轻量级的方式来解决「读书 App」的需求?这次的功能虽然复杂了不少,但在 Antigravity 的加持下,一路 Vibe Coding 下来依然非常顺畅。



更棒的是它的迭代速度。比如后来我想加一个 Highlight 高亮功能,只需要跟 Antigravity 描述一下,功能很快就实现了,也能立刻用上。
有了这两次成功经验,我又开始琢磨能不能做一个针对语言学习的播放器插件。理论上没有技术壁垒,于是又花了一天时间,把它做出来了,效果依然很满意。



后来我忽然想到,Chrome Extension 简直是 Vibe Coding 的绝佳载体。因为它的反馈回路短,工程负担轻,能力还很强(读写本地文件、存储、网络请求、自定义网页内容等等),相比于开发 Native App,Chrome Extension 不需要配置繁琐的编译环境,也不涉及复杂的系统级 API。它本质上就是网页,使用着 AI 最擅长的 HTML、CSS 和 JavaScript。即使是一个没有编程经验的人,只要花一些时间熟悉工具,也能快速上手。
我曾有一个坏习惯:常常会忍不住打开推特,看看有什么新闻(尤其是 AI 技术日新月异的当下,容易 FOMO)、关注的人又发布了什么动态、自己的推文有没有被点赞或评论。一开始倒没觉得有什么问题,但当这个行为的发生频率变高之后,我意识到它带来了明显的负面影响:注意力难以集中,思维变得碎片化。于是我想改掉这个坏习惯。
改掉坏习惯不是一件容易的事,所以我把它当作一个课题来研究。首先做的是认知校正:坏习惯不是自身的某个缺点,而是一种被错误引导的能量,是可以被调整的行为,它有对应的习惯回路:触发器(Trigger) -> 行为(Action) -> 奖励(Reward)。
在「刷推」这个 case 里,Trigger 往往是潜意识里的某种不适感(任务太难、无聊、焦虑),Action 是打开推特,Reward 则是短暂的愉悦感和多巴胺分泌。要解开这道题,需要在回路的每一个节点设置观察点和阻断机制。
虽然控制外界的 Trigger(如工作压力)很难,但我们可以识别它。当那个「想刷推」的念头升起时,不急着行动,而是采用「冲动冲浪」(Urge Surfing) 策略:面对冲动(海浪),不筑墙(不压抑),也不随波逐流(不执行)。你踩在滑板上,看着那个浪头升起,感受它的最高点,然后随着它自然落下。
如果这个动作过于自然,以至于来不及冲浪,就需要引入「异物」来唤醒显意识。比如给手机绑一根橡皮筋,这个突兀的触觉会把你从潜意识的自动导航中强行拉出来。
如果冲浪失败,没有熬过浪尖的峰值体验,我们还可以从 Action 入手,增加执行难度。
最有效的方式是环境构建:把手机放到够不着的地方、调至灰度模式,或者去图书馆,让学习的氛围带动自己去学习,同时增加刷手机的心理负担。
如果还是突破了防线,那么就借助坏习惯的动量,绑定一个新的微习惯。我做了一个 Chrome 插件,在每次打开推特页面时,都会弹出一个对话框,要求输入一段预设的文字。这个绑定的微习惯必须满足低脑力消耗、中低执行难度的特点,否则很容易因为成本过高而被跳过。
如果还是开始了这个习惯行为,那就在 Reward 环节做文章。
首先是定时,避免越陷越深。其次是关键的一步:「不带评判的观察」。当带着这种心态去进行那个坏习惯时,会发现它并没有想象中那么有吸引力。如果大脑的预判奖励是 10 分,实际体验下来可能只有 3 分。一旦大脑更新了这个奖励价值(Reward Value),意识到「原来这件事也就那样」,它对这个行为的渴望就会自然下降。
做到以上几步,坏习惯可能暂时被压制了,但解题还没有结束。大脑极其厌恶真空。如果你拿走了一个坏习惯,却没在原地填入一个新东西,那个功能空洞(比如原本用来缓解无聊或焦虑的时间段)会产生巨大的吸力,把你拽回原来的轨道。
此时需要寻找替代行为,重构生态位。这个替代品需要具备两个特征:
比如,当无聊袭来时,可以是用 Kindle 读一页书、整理昨天的笔记、玩一局 Wordle,或者仅仅是站起来做 3 个深呼吸。用这些良性的「微行动」,去占领原本属于坏习惯的生态位。
当这道「题目」被拆解到这个程度,你会发现,改掉坏习惯不再是一场痛苦的意志力拉锯战,而是一次有趣的、关于自我认知的系统重构。
坏习惯是道好题目,因为这个过程可以强化自己的「觉察」能力、认知重构能力(坏习惯不等于缺点,坏习惯可以被用来绑定好习惯等)和产品设计能力(如何设计一套行之有效的解决方案),这些能力很基础又有很强的通用性,可以用来应对其他的坏习惯,帮助构建自己的「心智操作系统」。
我们都知道应该去做那些「难而正确」的事。我们熟读各类方法论:建立系统而非仅盯着目标;先确立身份认同再行动;利用福格行为模型微调习惯……但在现实的引力面前,这些道理往往显得苍白。
因为正确的事通常伴随着当下的痛感(或者枯燥),且反馈周期漫长。相比之下,大脑更原始的本能总是倾向于那些即时满足的选项。
当注意力即将滑向短期快感时,我们需要的不是宏大的意志力,而是一个微小的「阻断器」。深呼吸,就是这个阻断器。
这并非玄学,而是有着明确的生理机制。当我们焦虑或冲动时,身体由处于「战斗或逃跑」的交感神经主导。而深呼吸——特别是呼气长于吸气的呼吸——能激活迷走神经,强制启动副交感神经系统。这就像是给高速运转的大脑物理降温,将我们从应激状态强行拉回「休息与消化」的理智状态。
给自己设一个微小的「绊脚石」:在想要下意识点开那个红色 App 之前,或者伸手拿烟之前,深呼吸 3 次。
这里的难点在于「记得」。在多巴胺渴望飙升的瞬间,理智往往是缺席的。所以,不要指望意志力,要依靠环境暗示。比如给手机套一根橡皮筋,利用这个物理触感的停顿,给自己 3 秒钟的窗口期。如果不做这个动作,惯性会带走你;做了这个动作,选择权才更容易回到手中。
如果说深呼吸是急救包,那么冥想就是长期的肌肉训练。
冥想的核心功效,是培养一种「旁观者」的视角。它能帮我们对抗「注意力经济」的掠夺,打断「刺激-反应」的自动化回路。经过冥想训练的大脑,具备更敏锐的「觉察力」。当你下意识地刷手机时,大脑会突然亮起一盏灯:看,我产生了一个想寻求刺激的念头。
在这「被看见」的一瞬间,你就不再是情绪和欲望的奴隶,而是它们的主人。
现代的注意力经济通过高密度的感官轰炸(短视频、爆款标题),不仅拉高了多巴胺阈值,还让多巴胺受体变得极度迟钝。我们像耐药性极强的病人,对低刺激的事物(读书、深度思考、发呆)感到无法忍受的枯燥。
我们丧失了「无聊」的能力,而无聊恰恰是创造力的温床。冥想本质上是一件主动拥抱无聊的事。通过坚持冥想,你在为大脑进行「多巴胺排毒」,恢复受体的敏感度,重新学会如何与「无刺激」的状态相处,并从中获得平静的喜悦。
除了有点无聊,践行冥想最大的阻力,往往来自于我们把它看得太重,试图寻找一段「不被打扰」的完美时间,更高效的策略可能是「缝隙冥想」。
比如通勤的地铁上、排队等咖啡的间隙、或者是午休结束准备开始工作的瞬间。这些时刻原本也是「垃圾时间」,通常会被用来刷手机填补空白。如果能用这 3-5 分钟来关注呼吸,不需要特意调整坐姿,也不必追求绝对的安静,只要把注意力从纷乱的思绪中收回来。
这种「微冥想」虽然短暂,但因为它发生在高频的、甚至有些嘈杂的日常场景中,反而更能训练我们在混乱中随时「调用」平静的能力。
归根结底,我们无法控制外界的信息洪流,也难以完全屏蔽生活的噪音,但我们始终拥有控制自己呼吸的自由。
在刺激和反应之间,有一个空间。在那个空间里,藏着我们要选择的反应。在我们的反应里,藏着我们的成长和自由。
呼吸,就是撑开这个空间的支柱。它不是为了让你变成一台更高效的执行机器,而是为了让你在这个加速的世界里,依然能保有停顿的权利。在这个微小的停顿里,你不再是算法的猎物,而是自己的主人。
今年夏天我大概尝试了 3 个月的冷水澡,感觉还不错,一开始不太适应,但慢慢也习惯了,后来天气一冷,就又换回了热水澡。
前几天在看 Andrew Huberman 的视频,他提到了洗冷水澡的诸多好处,尤其是对多巴胺的正面影响,就又开始了冷水澡的尝试,不过这次的挑战明显比夏天的大。
为了避免步子迈太大,我采用了热冷交替法:3 分钟热水(扩张血管),1 分钟冷水(收缩血管),然后做 2-3 个循环,最后以冷水结束。
有了 3 分钟的热水打底,让 1 分钟的冷水变得更能接受些,但当 10 来度的冷水浇在身上时,还是会忍不住地喊出来,甚至跳起来。第一轮的冷水冲击力最大,之后的会稍微好一些,但依旧在舒适区之外。
这看似有点受虐的几分钟,如果从投资的角度看,回报还是挺丰厚的。
人类的身体不是为了恒温环境而设计的。在漫长的进化史上,舒适是异常,寒冷才是常态。当你拧开冷水龙头,实际上是在激活一段古老的代码。
更重要的是,它改变了你的化学环境。冷水能让多巴胺水平显著提升,这种提升不像糖或咖啡因那样会有随后的崩溃(crash),它是一种平稳的、持续的清醒,可以持续 2-3 个小时。
在注意力成为稀缺资源的今天,能够通过一种物理手段,在不服用任何药物的情况下获得这种精神上的敏锐度,这本身就是一种巨大的优势。
洗冷水澡的理念跟斯多葛学派推崇的「自愿不适」(Voluntary Discomfort)也非常 match。但为什么要自找苦吃呢?
因为过度的舒适会让我们变得脆弱。当习惯了恒温、软床和热食,「基准线」会被抬高。一旦环境稍微变得恶劣,就会感到痛苦。冷水澡是一种重置基准线的方式。当你能从容面对冷水时,生活中的其他麻烦——交通堵塞、难缠的邮件、糟糕的天气——似乎就没那么难以忍受了。
这其实是在训练一种极其核心的能力:切断刺激与反应之间的自动连接。
当冷水击中你时,身体的自动反应是恐慌和急促呼吸。这和你在面对工作危机或社交压力时的反应是一样的。通过在冷水中强迫自己冷静下来、控制呼吸,实际上是在重写你的神经回路。你在告诉大脑:「虽然这很不舒服,但我依然掌舵。」
早晨通常是一场意志力的博弈。大脑倾向于选择阻力最小的路径。而洗冷水澡是一个反向操作。
这不仅仅是洗澡,可能也是当天的第一个决定。你站在那里,理智告诉你应该拧开冷水,但本能告诉你不要。当你最终执行了理智的命令,你就赢得了一场微小的胜利。
这一胜虽然微不足道,但它至关重要。因为它设定了当天的基调:你不是一个顺从冲动的人,你是一个能够为了长远利益而克服短期不适的人。这种自我认同会像雪球一样滚动,影响你接下来在工作和生活中的每一个选择。
这可能就是为什么这种简单的习惯能带来惊人回报的原因。在这个充满捷径和舒适陷阱的世界里,愿意主动选择不适的人,拥有了一种隐形的竞争优势。
洗冷水澡对场地、器材的要求很低,只需要一个淋浴头,就可以开始,而它带来的回报,又非常丰厚。这就是塔勒布在《反脆弱》中提到的「非对称收益」(低风险,高收益)。这么好的投资机会,不想试试吗?
我们通常会觉得 Routine 是「自由」的反义词,它让人联想到枯燥的重复、僵化的日程表,或者那种一眼就能望到头的生活,这是一个巨大的误解。
在谈论 Routine 之前,我们先来看看复利公式:
把它放到人生这个大背景下,天赋、机遇和单次努力的强度,构成了那个基础的增长率 。很多雄心勃勃的人都盯着 看。他们试图通过一次通宵工作、一个绝妙的点子或者一次爆发式的冲刺来获得巨大的 。
但这很难持久。
Routine 的作用,是掌控指数 。
在很多方面,Routine 就像是定投。如果只有在「感觉来了」的时候才去写作,或者在「状态很好」的时候才去锻炼,你的 是断断续续的,增长曲线是锯齿状的。
而一个好的 Routine 是一种承诺:无论我今天感觉如何,无论 是大是小,那个 都会自动加一。
当 足够大时,它就不再是线性的累加,而是指数级的爆发。那些伟大的小说家每天早起写几千字,不是因为他们每天都有几千字的灵感,而是因为他们把写作变成了一种像刷牙一样的生理节律。他们不对抗它,只是执行它。
这就是 Routine 的本质:它把困难的事情自动化,从而让时间站在你这一边。
很多人认为「我不设计 Routine,是因为我不想被束缚」。但真相是:根本不存在「没有 Routine」这回事。
如果你不主动设计你的生活流程,你的身体和环境会为你设计一套。这套「默认 Routine」通常由你的生物本能(懒惰)和外部世界(诱惑)共同编写。
当你拿起手机无意识地刷了两个小时,这本身就是一个极其高效和稳固的 Routine。
既然「默认 Routine」也是自动化的,为什么我们还会感到疲惫?
因为你并没有完全放弃。内心依然有一个想要变好的声音,它在不断地试图把这辆正滑向舒适区的车拉回来。这就是决策疲劳的来源:一场无休止的内战。
这种讨价还价非常消耗能量,宝贵的精力被浪费在了「决定要做什么」上,而不是「做这件事」本身。
一个好的 Routine(比如「穿上跑鞋 = 出门」)是一条不可协商的规则。它绕过了「谈判」环节,直接进入行动。在这个过程中,你宝贵的意志力没有被内耗掉,而是被完整地保留给了真正重要的问题:如何解决这个难题?如何把作品打磨得更好?
如果把人生看作是一项长期的投资,建立 Routine,意味着你从一个短视的投机者,变成了一个长期的价值投资者。
投机者依赖心情、运气、状态、灵感(),而投资者诉诸系统和时间()。
当你开始设计 Routine 时,实际上是在重新分配资产。你不再说「没办法,我就是管不住自己」,而是开始像分析一张糟糕的资产负债表一样分析你的生活:「哦,看来我在‘压力大’这个触发条件下,会自动买入‘逃避’这项资产。我需要调整策略,把它换成‘运动’。」
这并不容易。改变习惯总是痛苦的,但如果坚持下去,哪怕只是每天微调一点点,复利的力量就会显现。
最开始,你只是改变了起床后的 30 分钟。一年后,你会发现你变了一个人。
你不仅改变了你做的事,你改变了你是谁。
所以,不要再把 Routine 看作是一张枯燥的时间表。它是你为了结束内耗、夺回控制权而制定的投资策略。它是你对抗熵增、对抗平庸、对抗混乱的武器。
Routine 就是人生的复利。