普通视图
会通股份:股东拟减持公司不超1%股份
ES6+ 新特性解析:让 JavaScript 开发更优雅高效
ES6(ECMAScript 2015)是 JavaScript 语言发展的里程碑,引入了大量让代码更简洁、更易维护的新特性。本文将深入解析这些特性,并通过实际代码示例展示它们的强大之处。
解构赋值:优雅的数据提取
解构赋值让我们能够从数组或对象中快速提取值,赋给变量。
数组解构
// 基本数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 嵌套数组解构
const [a, [b, c, [d], e]] = [1, [2, 3, [4], 5]];
console.log(a, b, c, d, e); // 1 2 3 4 5
// 剩余参数解构
const arr = [1, 2, 3, 4, 5];
const [first, ...rest] = arr;
console.log(first, rest); // 1 [2, 3, 4, 5]
// 实际应用:提取教练和球员
const users = ['Darvin Ham', 'James', 'Luka', 'Davis', 'Ayton', 'Kyle'];
const [captain, ...players] = users;
console.log(captain, players);
// Darvin Ham ['James', 'Luka', 'Davis', 'Ayton', 'Kyle']
对象解构
const sex = 'boy';
const obj = {
name: 'Darvin Ham',
age: 25,
sex, // 对象属性简写
like: {
n: '唱跳'
}
};
// 对象解构
let { name, age, like: { n } } = obj;
console.log(name, age, n); // Darvin Ham 25 唱跳
// 字符串也可以解构
const [e, r, ...u] = 'hello';
console.log(e, r, u); // h e ['l', 'l', 'o']
// 获取字符串长度
const { length } = 'hello';
console.log(length); // 5
解构的实用技巧
// 交换变量(无需临时变量)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
// 函数参数解构
function greet({ name, greeting = 'Hello' }) {
console.log(`${greeting}, ${name}!`);
}
greet({ name: 'Bob' }); // Hello, Bob!
// 设置默认值
const { name, gender = 'unknown' } = { name: 'Alice' };
console.log(gender); // unknown(因为 user 中没有 gender 属性)
模板字符串:更优雅的字符串处理
模板字符串使用反引号(``)定义,支持多行字符串和插值表达式。
let myName = 'zhangsan';
// 传统字符串拼接
console.log('hello, i am ' + myName);
// 模板字符串
console.log(`hello, i am ${myName}`);
console.log(`hello, i am ${myName.toUpperCase()}`);
// 多行字符串
const message = `
亲爱的 ${myName}:
欢迎使用我们的服务!
祝您使用愉快!
`;
console.log(message);
更现代的循环:for...of
for...of 循环提供了更好的可读性和性能。
let myName = 'zhangsan';
// for...of 遍历字符串
for(let x of myName) {
console.log(x); // 依次输出: z h a n g s a n
}
// 与 for 循环对比
const arr = [1, 2, 3, 4, 5];
// 传统的 for 循环
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// 更简洁的 for...of
for(let item of arr) {
console.log(item);
}
性能建议:
for...of语义更好,可读性更强,性能也不会比计数循环差太多。而for...in性能较差,应尽量避免使用。
BigInt:处理大整数的新数据类型
JavaScript 的数字类型有精度限制,最大安全整数是 2^53-1。
// JavaScript 的数字精度问题
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
let num = 1234567890987654321;
console.log(num); // 1234567890987654400(精度丢失!)
// 使用 BigInt
let num2 = 1234567890987654321n;
console.log(num2, typeof num2); // 1234567890987654321n 'bigint'
// BigInt 运算
const big1 = 9007199254740991n;
const big2 = 1n;
console.log(big1 + big2); // 9007199254740992n
// 注意事项
console.log(1n + 2); // ❌ TypeError: Cannot mix BigInt and other types
console.log(Math.sqrt(16n)); // ❌ TypeError
const x = 3.14n; // ❌ SyntaxError: Invalid or unexpected token
BigInt 使用要点:
- 使用
n后缀表示 BigInt 字面量 - 不能与 Number 类型直接混合运算
- 不能使用 Math 对象的方法
- 只能表示整数,不支持小数
函数参数的增强
默认参数
function foo(x = 1, y = 1) {
return x + y;
}
console.log(foo(3)); // 4 (使用默认值 y=1)
console.log(foo(3, 5)); // 8
console.log(foo()); // 2 (使用默认值 x=1, y=1)
剩余参数 vs arguments
function foo(...args) {
console.log('剩余参数:', args, typeof args);
// [1, 2, 3, 4] 'object'
console.log('是数组吗:', Array.isArray(args)); // true
console.log('arguments:', arguments, typeof arguments);
// [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 } 'object'
console.log('arguments是数组吗:', Array.isArray(arguments)); // false
// 剩余参数支持数组方法
console.log('参数个数:', args.length);
console.log('参数总和:', args.reduce((a, b) => a + b, 0));
}
foo(1, 2, 3, 4);
剩余参数的优势:
- 是真正的数组,可以使用所有数组方法
- 更清晰的语法
- 更好的类型推断(TypeScript)
其他实用特性
指数运算符
// ES7 (2016) 引入的指数运算符
console.log(2 ** 10); // 1024
console.log(3 ** 3); // 27
// 替代 Math.pow()
console.log(Math.pow(2, 10)); // 1024 (传统方式)
对象属性简写
const name = 'Alice';
const age = 25;
// 传统写法
const obj1 = {
name: name,
age: age
};
// ES6 简写写法
const obj2 = {
name,
age
};
console.log(obj2); // { name: 'Alice', age: 25 }
总结
ES6+ 的新特性让 JavaScript 开发变得更加优雅和高效:
- 解构赋值 让数据提取更直观
- 模板字符串 让字符串处理更简洁
- for...of 提供更好的循环体验
- BigInt 解决大整数计算问题
- 函数参数增强 提供更灵活的函数设计
这些特性不仅提高了开发效率,也让代码更易读、易维护。建议在实际项目中积极采用这些现代 JavaScript 特性,提升代码质量。
学习建议:从解构赋值和模板字符串开始,逐步掌握其他特性,让 ES6+ 成为你的开发利器!
消息人士称京东工业计划下周启动IPO,拟募资至多5亿美元
阿里AI猛补课,蚂蚁的“灵光”灵不灵?
文|邓咏仪
编辑|苏建勋
阿里系公司,正在To C市场全力出击。
11月19日,蚂蚁正式宣布推出全新应用“灵光”,首批上线了三大功能模块,分别是“灵光对话”、“灵光闪应用”和“灵光开眼”。
“灵光”的定位是“全代码生成多模态内容”AI助手。用一句话来解释,AI助手的回复不再是单纯的文本,每一次对话,交付物都是一个可以交互的网页,能生成图文、3D模型、动画、地图、表格、音视频等全模态内容。
![]()
△来源:灵光
在“闪应用”中,用户只需输入一句自然语言,灵光就能在30秒内生成可编辑、可交互的小应用,支持多种信息输出方式。
“灵光开眼”则更像一个视觉助手,用户可以通过上传图片或实时拍照,让AI识别并理解图片内容,进而提供相关信息或执行后续操作。
11月18日上线后截至发稿,“灵光”下载量已超100万,排在App Store总榜第6。这一增速也已超越多款现象级AI应用。据移动应用分析机构Appfigures数据,近期通用引起热议的Sora2,破百万则用了5天。
另一个巧合在于,就在11月18日,阿里将旗下的AI To C应用进行了翻新和整合,以“千问”App的形式重新推出;蚂蚁推出的“灵光”仅仅相隔一天就推出。
为何相隔一天推出两款对话式的AI助手?蚂蚁集团CTO何征宇表示,在产品发布双方并没有提前约好时间,只是巧合。
一个大背景是,前几年,阿里并没有投入太多到To C应用上。2025年,AI领域重回模型竞赛之后,阿里在To C应用上明显开始快速“补课”。“千问”上线时,阿里就曾高调宣布全力打响AI To C之战,大有抢占新入口的架势。
对阿里而言,鸡蛋也不能放在一个篮子里。“马老师也鼓励我们,让我们冲到App Store榜前列。”蚂蚁CTO何征宇表示。
当前模型能力依旧在快速变换中,充满不确定性,押注多个方向才是比较合适的策略,“我经常打一个比方,如果在沙漠中要找水,我一定不会把所有人都派到一个方向上,一定是好几路一起去找。”
两款产品在定位上也有明显区隔。
从定位上看,“千问”,基于阿里旗舰模型Qwen来构建,更像是阿里展现模型实力的出口,适合处理通用知识问答、长文本写作和复杂的逻辑推理任务。
相较之下,“灵光”更侧重移动端交互的交互创新。“灵光”不仅能回答问题,还能通过生成代码来渲染多模态内容(图表、界面等),直接交付一个信息容量丰富的网页;二是押注代码能力,让AI直接帮用户生成小程序。
不过何征宇也补充,灵光的目标并不是通用助手入口,也不像豆包等应用主打陪伴功能,还是定位效率工具。
不做AI陪伴,做信息密度更高的AI助手
和传统的通用AI助手相比,灵光的最明显差异,在于信息展现形式的创新。
这相当于在原来的AI助手对话形态上前进一步——AI的回答不再只有文字,而是会画图、做动画、生成3D模型、制作图表,就像一个边说边画的老师,让人看得见摸得着。
举个例子,当用户问“怎么做糖醋排骨”,传统AI助手给用户的回答大概率是一段长长文字食谱。但并不是所有人,都习惯这种长篇大论的交互,“灵光”则希望用更多种模态的形式来做展示。
《智能涌现》试着用菜谱场景来测试,比如搜索“怎么做糖醋排骨”。
第一体验是非常丝滑——灵光在数秒内,就生成一个色泽鲜亮的糖醋排骨长图,不仅会给出详细步骤,还用不同字体、小标题、图表,甚至表情包,进行图文并茂的排版,让用户能更快地看懂。
![]()
△来源:智能涌现制图
灵光产品负责人蔡伟用了一个类比来解释:“这就像是从email进化到web时代。以前写邮件只能用文字,现在打开网页,有图片、视频、交互按钮。我们希望AI的回答也能达到这种信息密度。”
这种“所见即所得”的交互,天然更适合人类认知世界的方式,也可以适用在更多场景中——比如,在写论文时随口问个问题,AI直接给你生成了一张图表;讨论装修时它给你画出3D户型图;聊到太阳系,一个行星运转的动画就出现在对话框里。
“我们每天都会接触各种各样的信息,很多时候都淹没在信息的海洋里。”蔡伟表示,“但这些信息里哪些是重要的?以前我们去搜索引擎搜,它给你一堆链接,需要一个个点进去看。我们更希望用更高效的方式,让信息传递效率最大化。”
优化信息的表达方式是第一步。灵光的另一个拳头功能“闪应用”,能根据用户需求,自动生成交互式“小应用”。
比如,用自然语言说“帮我做一个计时器”,30秒后,一个可以直接使用的计时器应用就生成了,这些小应用可以直接使用、编辑、保存和分享,就像真正的App一样。
![]()
△来源:智能涌现制图
这种技术路径,并不是难在概念——目前所有的顶尖模型厂商都会用这种方式展示自己的AI能力。但无论是AI生成的网页、App,是否真正达到可用状态,才是更关键的。
这种难度更多体现在模型架构、工程实现上。蚂蚁集团CTO何征宇在采访时,就透露了一个数据:相比纯文本输出,代码的膨胀率大概是5-6倍。
简单理解,对于一个15个字的指令“生成一个居中的蓝色‘提交’按钮”,模型需要生成一个可以实际交互的组件,需要的代码量,很容易就达到几十甚至上百个字符。
![]()
△来源:智能涌现制图
难度在于,用简单的指令,让模型生成大量内容(高膨胀率),本身就需要更多的计算资源和时间——如何保证模型的高性能和稳定,“灵光”就做了大量的工程实现优化。
这不仅需要代码生成能力,还需要推理能力足够精准(判断用什么形式呈现信息)、工具调用能力(动态生成地图、图表)、数学能力(处理数据可视化),以及对用户意图的深度理解。
也正因为如此,在蚂蚁内部,灵光被定义为“在技术前沿上,在最不稳定的边界上建立产品”。
和其他AI生成web应用的产品相比,灵光的特色是直接可以在移动端上做应用,而是可以直接开始用的成品,工程难度更高。
![]()
△一句话用灵光制作的“吃什么”应用 来源:蚂蚁
DeepSeek解放了大厂做应用的包袱
今年1月DeepSeek R1的发布,是蚂蚁决定all in AGI的转折点。何征宇回忆起那个时刻的心情,用了三个词来概括:兴奋、紧迫感、羞愧。
“AGI原来是几百亿、上千亿美金的传说,DeepSeek用很小的资源投入就把它做出来了,这给了我们极大的信心,”何征宇说,“我们也在反思,以往我们很多的技术积累,没有把它很好地表达出来,整合到一起,让用户直接能感受到价值。”
春节后,蚂蚁就集结资源,成立了独立的AGI组织“Inclusion AI”,一个集Research(研究)、Engineering(工程)、Producting(产品)三位一体的组织架构。
战略上,蚂蚁也做了选择:并不意在争夺AI通用助手的入口——比如主打AI陪伴的豆包,希望做让用户消耗时间越,而是先聚焦在更细分的方向:coding能力 + 全模态,定位效率工具。
围绕这个细分目标,蚂蚁做了不少取舍。比如,今年模型厂商纷纷在竞争推理能力,但灵光并没有选择嵌入到产品中。“DeepSeek已经做得足够好了,能帮大家解决很多问题,我们没有必要再去复刻。”蔡伟表示。
蚂蚁赌的是基础模型不断上涨的代码能力。
在“灵光”立项的2025年3月,基础模型的coding能力仍在非常早期的阶段,一句话生成应用的效果非常不理想。
“我们当时确定的是Coding是很重要的能力,并且会不断向前发展。但到底能发展到什么程度,是在3个月、6个月还是9个月达到用户预期?其实有非常多不确定性。”蔡伟表示。
模型能力决定了产品的上限。蚂蚁选择从模型和产品两个方向同时去做——基模团队在这一阶段提升底层代码能力,应用团队负责做后训练和产品打磨。
而蚂蚁选择做的功能迭代,都是可以长期积累的,可复用的模块。当基础模型升级时,所有的后训练优化都能叠加上去,而不是推倒重来。
在DeepSeek时刻之后,各家的AI应用在产品主张上已经有明显的分化。在当下,品味可能和比短期竞争更重要。
差异化是如今AI市场上的最关键问题——通用AI助手依托强大的基座模型迭代,场上的位置已然拥挤。比如,字节的豆包走更亲民、偏重语音交互等多模态路线;而DeepSeek、Kimi则侧重更专业化的工作场景。
如果用一句话概括蚂蚁的AGI策略,也许是要做AGI时代的二维码。
这意味着要用最小的成本,来找新技术的PMF(Product Market Fit),切口要小,交付的价值足够集中。“二维码不是我们发明的,但我们是推广最广泛的,把它应用于支付场景,今天的AI应用也面临这样的难题。”何征宇说。
未来,灵光还在在规划闪应用生态,包括应用市场、托管平台和分享机制。”我们还是希望降低所有人创作和消费闪应用的门槛。”蔡伟说。短期内,灵光平台计划上线创作收益页面,探索应用从创作到消费的闭环。
封面来源|AI生成
![]()
👇🏻 扫码加入「智涌AI交流群」👇🏻
![]()
欢迎交流
中触媒:控股股东拟4200万元—8000万元增持公司股份
安泰科技:斩获近7000万元可控核聚变关键大单
耀皮玻璃:中国复材拟减持公司不超过2.26%股份
深入理解 JavaScript 异步编程:从 Ajax 到 Promise
在现代 Web 开发中,异步编程是不可或缺的核心概念。本文将通过几个实际的代码示例,带你深入理解 JavaScript 中的异步操作,从传统的 Ajax 到现代的 Promise 和 Fetch API。
1. 传统 Ajax 与现代 Fetch API
XMLHttpRequest:经典的异步请求方式
<script>
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/orgs/lemoncode/members', true);
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
}
</script>
XMLHttpRequest 是传统的异步请求方式,基于回调函数实现。需要手动处理 readyState 和 status 状态码,代码相对复杂。
Fetch API:现代化的替代方案
<script>
fetch('https://api.github.com/orgs/lemoncode/members')
.then(res => res.json())
.then(data => {
console.log(data);
})
</script>
Fetch API 的优势:
- 基于 Promise 实现,支持链式调用
- 语法简洁,无需手动处理状态码
- 更符合现代 JavaScript 编程风格
2. 封装基于 Promise 的 getJSON 函数
为了将传统的 Ajax 改造成 Promise 风格,我们可以封装一个 getJSON 函数:
const getJSON = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
resolve(data);
} else {
reject(`HTTP错误: ${xhr.status}`);
}
}
}
xhr.onerror = function() {
reject('网络错误');
}
});
}
// 使用示例
getJSON('https://api.github.com/orgs/lemoncode/members')
.then(data => {
console.log(data);
})
.catch(err => {
console.log(err);
})
关键点说明:
- Promise 构造函数接收一个执行器函数,同步执行
- resolve() 将 Promise 状态改为 fulfilled,触发 then 回调
- reject() 将 Promise 状态改为 rejected,触发 catch 回调
- onerror 只能捕获网络错误,不能捕获 HTTP 错误状态码
3. Promise 状态管理
| 状态 (State) | 含义 | 说明 |
|---|---|---|
| pending (等待中) | 初始状态 | Promise 被创建后,尚未被兑现或拒绝时的状态 |
| fulfilled (已成功) | 操作成功完成 | 异步任务成功结束,调用了 resolve(value) |
| rejected (已失败) | 操作失败 | 异步任务出错,调用了 reject(reason) |
4. 实现 Sleep 函数:控制异步流程
在同步语言中,我们可以使用 sleep 函数暂停程序执行,但在 JavaScript 中需要借助 Promise 模拟这一行为:
// 基础版本
function sleep(n) {
let p;
p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(p); // pending 状态
resolve(); // 或 reject()
console.log(p); // fulfilled/rejected 状态
}, n);
});
return p;
}
// 简化版本
const sleep = n => new Promise(resolve => setTimeout(resolve, n));
// 使用示例
sleep(3000)
.then(() => {
console.log('3秒后执行');
})
.catch(() => {
console.log('error');
})
.finally(() => {
console.log('finally'); // 无论成功失败都会执行
});
setTimeout vs Sleep:本质区别
setTimeout - 事件调度
console.log("第1步");
setTimeout(() => {
console.log("第3步 - 在2秒后执行");
}, 2000);
console.log("第2步 - 不会等待setTimeout");
// 输出顺序:
// 第1步
// 第2步 - 不会等待setTimeout
// 第3步 - 在2秒后执行
Sleep - 流程控制
async function demo() {
console.log("第1步");
await sleep(2000); // 真正暂停函数的执行
console.log("第2步 - 在2秒后执行");
}
demo();
// 输出顺序:
// 第1步
// (等待2秒)
// 第2步 - 在2秒后执行
这么来说吧:setTimeout是告诉 JavaScript 引擎:“等 X 毫秒后,帮我执行这个函数。” 它会去做别的同步操作,不会阻塞其他代码的执行。sleep则是在一个连续的异步操作流中,插入一段等待时间。sleep 让你写出“顺序等待”的逻辑,而 setTimeout 只是“未来某个时间点做某事”。
5. JavaScript 内存管理与数据拷贝
理解 JavaScript 的内存管理对于编写高效代码至关重要:
const arr = [1, 2, 3, 4, 5, 6];
const arr2 = [].concat(arr);
arr2[0] = 0;
console.log(arr, arr2); // arr 不变,arr2 改变
// 深拷贝 - 开销大
const arr3 = JSON.parse(JSON.stringify(arr));
arr3[0] = 100;
console.log(arr, arr3); // arr 不变,arr3 改变
在数组上进行存储的时候我们应该尽量规避
JSON序列化的深拷贝,开销太大,我们可以选择用[].cancat(arr)这种巧妙的方法实现拷贝,虽然创建了一个新数组,但开销仍然小于序列化。
内存管理要点:
- JS 变量在编译阶段分配内存空间
- 简单数据类型存储在栈内存中
- 复杂数据类型存储在堆内存中,栈内存存储引用地址
- 浅拷贝只复制引用,深拷贝创建完全独立的新对象
总结
通过本文的示例和解析,我们深入探讨了:
- Ajax 与 Fetch 的对比:Fetch API 提供了更简洁的 Promise-based 接口
- 手写getJson函数:将传统的 Ajax 改造成 现代的 Promise 风格
- 手写sleep函数:使用 Promise 实现类似同步的编程体验
- 内存管理:理解变量存储方式对编写高效代码的重要性
掌握这些概念将帮助你编写更清晰、更易维护的异步 JavaScript 代码,为学习更高级的 async/await 语法打下坚实基础。
尚纬股份:拟5.2亿元增资控股四川中氟泰华,进入电子化学品及相关基础化学品领域
React 日历组件完全指南:从网格生成到农历转换
本文详细介绍如何从零实现一个功能完整的 React 日历组件,包括日历网格生成、农历显示和月份切换功能。
前言
在开发排班管理系统时,我们需要实现一个功能完整的日历组件。这个组件不仅要显示标准的月历网格,还要支持农历显示和流畅的月份切换。经过实践,我总结了一套完整的实现方案,适用于任何 React 项目。
一、日历网格生成
1.1 核心需求
一个标准的月历网格需要满足以下要求:
- 显示当前月份的所有日期
- 补齐上月末尾的日期(填充第一周)
- 补齐下月开头的日期(填充最后一周)
- 总是显示完整的 6 周(42 天)
- 周日为每周的第一天
1.2 实现思路
我们使用 date-fns 库来处理日期计算,整个算法分为三个步骤:
// DateService.ts
getMonthCalendarGrid(date: Date): Date[] {
// Step 1: 获取月份的起止日期
const monthStart = startOfMonth(date);
const monthEnd = endOfMonth(date);
// Step 2: 扩展到完整的周
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 0 });
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 0 });
// Step 3: 生成日期数组
return eachDayOfInterval({
start: calendarStart,
end: calendarEnd
});
}
关键点解析:
-
获取月份边界:使用
startOfMonth和endOfMonth获取当月的第一天和最后一天 -
扩展到完整周:使用
startOfWeek和endOfWeek确保日历从周日开始,到周六结束 -
生成连续日期:使用
eachDayOfInterval生成两个日期之间的所有日期
1.3 实际案例
以 2024 年 11 月为例:
输入:new Date(2024, 10, 15) // 2024-11-15
Step 1: 月份边界
monthStart = 2024-11-01 (周五)
monthEnd = 2024-11-30 (周六)
Step 2: 扩展到周
calendarStart = 2024-10-27 (周日)
calendarEnd = 2024-11-30 (周六)
Step 3: 生成日期
共 35 天 (5周)
渲染结果:
日 一 二 三 四 五 六
27 28 29 30 31 1 2 ← 10月27-31 + 11月1-2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
1.4 为什么是 6 周?
大多数月份只需要 5 周(35 天)就能显示完整,但某些特殊情况需要 6 周(42 天)。
需要 6 周的条件:
- 月份有 31 天
- 月初是周六(需要补充前面 6 天)
为了保持布局一致性,我们统一使用 6 周布局,这样月份切换时高度不变,动画过渡更流畅。
二、农历(阴历)显示
2.1 实现原理
农历转换使用预定义数据表 + 算法计算的方式,无需外部依赖,支持 1900-2100 年。
2.2 数据结构
农历信息表
private static lunarInfo = [
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, // 1900-1904
// ... 共 201 个元素(1900-2100年)
];
每个十六进制数编码了一年的农历信息:
0x04bd8 的二进制表示:
0000 0100 1011 1101 1000
解析:
├─ 后 4 位 (1000 = 8):闰月位置(8月)
├─ 第 5 位 (1):闰月天数(1=30天,0=29天)
└─ 第 6-17 位:每月天数(1=30天,0=29天)
农历日期文本
private static lunarDays = [
'初一', '初二', '初三', ..., '廿九', '三十'
];
2.3 转换算法
公历转农历分为四个步骤:
Step 1: 计算与基准日期的天数差
基准:1900-01-31(农历1900年正月初一)
Step 2: 从1900年开始,逐年累减天数,确定农历年份
Step 3: 逐月累减天数,确定农历月份(处理闰月)
Step 4: 剩余天数 + 1 = 农历日期
2.4 实际案例
以 2024-11-24 为例:
Step 1: 天数差
(2024-11-24 - 1900-01-31) = 45590 天
Step 2: 确定农历年
1900年:354天,剩余 45236天
1901年:354天,剩余 44882天
...
2023年:384天,剩余 324天
→ 农历2024年
Step 3: 确定农历月
正月:30天,剩余 294天
二月:29天,剩余 265天
...
十月:30天,剩余 29天
→ 农历十月
Step 4: 确定农历日
29 + 1 = 30
→ 三十
结果:2024-11-24 = 农历2024年十月三十
2.5 使用方法
// 获取农历日期文本
const lunarText = LunarUtil.getLunarDateText(new Date(2024, 10, 24));
console.log(lunarText); // 输出:三十
// 在日历中应用
<div className="day-cell">
<div className="day-text">{date.getDate()}</div>
<div className="lunar-text">
{LunarUtil.getLunarDateText(date)}
</div>
</div>
渲染效果:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ 24 │ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │
│廿四 │廿五 │廿六 │廿七 │廿八 │廿九 │三十 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┘
三、月份切换功能
3.1 核心思路
月份切换的本质是改变当前显示的月份,然后重新生成日历网格。
核心要素:
- 维护一个
currentDate状态 - 提供切换方法(上一月/下一月)
- 根据
currentDate重新生成日历网格
3.2 状态管理
const [currentDate, setCurrentDate] = useState(new Date());
currentDate 的作用:
- 决定显示哪个月份
- 作为生成日历网格的输入
3.3 切换方法
使用 date-fns 实现月份切换:
import { addMonths, subMonths } from 'date-fns';
// 下一个月
const goToNextMonth = () => {
setCurrentDate(prevDate => addMonths(prevDate, 1));
};
// 上一个月
const goToPrevMonth = () => {
setCurrentDate(prevDate => subMonths(prevDate, 1));
};
// 通用方法
const handleMonthChange = (direction: 'next' | 'prev') => {
setCurrentDate(prevDate => {
return direction === 'next'
? addMonths(prevDate, 1)
: subMonths(prevDate, 1);
});
};
3.4 自动处理边界
JavaScript Date 构造函数会自动处理月份溢出:
// 12月 → 1月(跨年)
new Date(2024, 12, 1) // 自动变为 2025-01-01
// 1月 → 12月(跨年)
new Date(2024, -1, 1) // 自动变为 2023-12-01
3.5 响应式更新
使用 useMemo 实现响应式更新:
const MonthView: React.FC<MonthViewProps> = ({ currentDate }) => {
const currentMonthDates = useMemo(() => {
return DateService.getMonthCalendarGrid(currentDate);
}, [currentDate]); // 依赖 currentDate
// currentDate 变化 → useMemo 重新计算 → 生成新的日历网格
};
3.6 完整数据流
用户点击"下一月"
↓
setCurrentDate(新月份)
↓
useMemo 重新计算
↓
生成新的日历网格
↓
渲染新月份
四、完整实现
4.1 日历组件
import React, { useState, useMemo } from 'react';
import { addMonths, subMonths, format } from 'date-fns';
function Calendar() {
const [currentDate, setCurrentDate] = useState(new Date());
// 切换月份
const handleMonthChange = (direction: 'next' | 'prev') => {
setCurrentDate(prev => {
return direction === 'next'
? addMonths(prev, 1)
: subMonths(prev, 1);
});
};
// 生成日历网格
const dates = useMemo(() => {
return generateCalendarGrid(currentDate);
}, [currentDate]);
return (
<div className="calendar">
{/* 标题 */}
<h2>{format(currentDate, 'yyyy年MM月')}</h2>
{/* 切换按钮 */}
<button onClick={() => handleMonthChange('prev')}>上一月</button>
<button onClick={() => handleMonthChange('next')}>下一月</button>
{/* 日历网格 */}
<CalendarGrid dates={dates} />
</div>
);
}
4.2 渲染网格
function CalendarGrid({ dates }) {
// 分组为周
const weeks = [];
for (let i = 0; i < dates.length; i += 7) {
weeks.push(dates.slice(i, i + 7));
}
return (
<div className="calendar-grid">
{/* 星期头部 */}
<div className="week-header">
{['日', '一', '二', '三', '四', '五', '六'].map(day => (
<div key={day} className="week-day">{day}</div>
))}
</div>
{/* 日历网格 */}
{weeks.map((week, weekIndex) => (
<div key={weekIndex} className="week-row">
{week.map((date, dayIndex) => (
<div key={dayIndex} className="day-cell">
{/* 公历日期 */}
<div className="day-text">{date.getDate()}</div>
{/* 农历日期 */}
<div className="lunar-text">
{LunarUtil.getLunarDateText(date)}
</div>
</div>
))}
</div>
))}
</div>
);
}
五、性能优化
5.1 使用 useMemo 缓存计算
// 缓存日历网格
const dates = useMemo(() => {
return generateCalendarGrid(currentDate);
}, [currentDate]);
5.2 使用 useCallback 缓存回调
const handleDatePress = useCallback((date: Date) => {
onDatePress(date);
}, [onDatePress]);
5.3 使用 React.memo 避免无效渲染
export default React.memo(MonthView);
六、关键要点总结
6.1 日历网格生成
核心 API:
-
startOfMonth/endOfMonth- 获取月份边界 -
startOfWeek/endOfWeek- 扩展到完整周 -
eachDayOfInterval- 生成连续日期
数据结构:
Date[] (35-42个元素)
↓ 分组
Date[][] (5-6个数组,每个7个元素)
↓ 渲染
6行 × 7列的网格
6.2 农历转换
核心算法:
- 计算天数差 - 与基准日期(1900-01-31)的差值
- 确定农历年 - 逐年累减天数
- 确定农历月 - 逐月累减天数,处理闰月
- 确定农历日 - 剩余天数 + 1
数据结构:
- 预定义表 - 201个十六进制数(1900-2100年)
- 位运算 - 高效解析农历信息
- 文本数组 - 30个农历日期名称
6.3 月份切换
核心流程:
状态变化 → 网格重新生成 → 数据重新加载 → 组件重新渲染
关键技术:
-
useState- 状态管理 -
useMemo- 缓存计算结果 -
useEffect- 监听变化,自动加载数据 - JavaScript Date - 自动处理月份边界
七、总结
通过本文,我们实现了一个功能完整的 React 日历组件,包括:
✅ 标准的月历网格生成(支持 5-6 周布局) ✅ 农历显示(支持 1900-2100 年) ✅ 流畅的月份切换(自动处理跨年) ✅ 响应式数据更新(状态驱动) ✅ 性能优化(useMemo、useCallback、React.memo)
核心思想是利用 date-fns 处理日期计算,使用 React Hooks 实现响应式更新,通过预定义数据表实现农历转换。整个实现简洁高效,易于维护和扩展。
这套方案不仅适用于 Web 应用,也可以轻松移植到 React Native 等其他 React 生态项目中。
希望这篇文章能帮助你理解日历组件的实现原理,并应用到自己的项目中。
相关资源:
简单聊聊webpack摇树的原理
Webpack 的 Tree Shaking(摇树)是一项用于消除 JavaScript 上下文中未引用代码的优化手段,它能有效减小打包体积。
核心原理
Tree Shaking 的本质是 死代码消除,它依赖 ES6 模块(ESM)的静态语法结构。
-
静态分析:ESM 的
import/export语句必须位于模块顶层(注意:模块顶层不是模块文件顶部的意思,模块顶层可以认为是模块文件中最外层的代码区,不在任何函数、类或代码块内部),且模块路径必须是字符串常量。这样, Webpack 在编译阶段就能构建出完整的模块依赖图,无需运行代码即可分析出哪些导出值未被其他模块使用 。这时有同学就会问了,那么动态 import 怎么判断呢?
其实,还是那个关键点,是否可以被“静态分析”。
// ❌ 难以静态分析,无法使用摇树优化 const componentMap = { basic: () => import('./BasicComponent'), advanced: () => import('./AdvancedComponent') }; const getComponent = componentMap[userInput]; // 运行时才能确定 // ✅ 条件明确,可以被静态分析 if (import.meta.env.VITE_APP_MODE === 'basic') { const BasicComponent = await import('./BasicComponent'); } -
标记与清除:Webpack 的 Tree Shaking 过程大致分为两步。首先,在编译阶段,Webpack 会遍历所有模块,标记(Mark) 出未被使用的导出(通常会在注释中生成类似
unused harmony export的提示)。随后,在代码压缩阶段,Terser 等压缩工具会真正将标记过的"死代码"清除(Shake) 掉 。
这些配置你是否清楚?
要让 Tree Shaking 生效,需要同时满足以下条件:
-
使用 ES6 模块语法:必须使用
import和export语句。CommonJS 的require和module.exports是动态的,无法在编译时进行静态分析,因此不支持 Tree Shaking 。 -
启用生产模式或明确配置:在 Webpack 配置中,将
mode设置为'production'生产模式下会自动开启相关的优化功能。当然也可以在开发模式下手动配置optimization.usedExports和optimization.minimize。
// webpack.config.js
module.exports = {
mode: 'production', // 生产模式自动开启优化
optimization: {
usedExports: true, // 启用使用导出分析
minimize: true // 启用代码压缩(清除死代码)
}
};
- **正确声明副作用 (
sideEffects)**:在项目的package.json中,通过sideEffects属性告知 Webpack 哪些文件是"纯净"的(无副作用),可以安全移除。这能防止具有副作用的文件(如全局样式表、polyfill)被误删 。
// package.json
{
"sideEffects": false, // 表示整个项目都没有副作用
// 或明确指定有副作用的文件
"sideEffects": [
"**/*.css",
"./src/polyfill.js"
]
}
有同学又会问了,摇树摇的不是 js 吗,样式表 css 怎么会被摇掉呢?
其实,这里指的是导入的但是没有明确导出的 css 样式表,导入导出是明确的 js 语句,css 是“副作用”,比如:
- 仅导入但未使用任何导出(如
import './style.css'),属于是无形的“使用”,可能被误删- 使用 CSS Modules(如
import styles from './Component.module.css'),被视为有被使用的对象(如styles.className),通常不会被误删
这些问题你遇到过吗?
开发过程中,以下情况仍可能导致 Tree Shaking 失效,看看你有没有遇到过:
-
Babel 配置不当:Babel 预设
@babel/preset-env可能会将 ESM 转换为 CommonJS。务必确保其modules选项设置为false,只有 ESM 可以摇树。
// .babelrc
{
"presets": [["@babel/preset-env", { "modules": false }]]
}
-
第三方库的模块版本:优先选择提供 ES6 模块版本的库(如使用
lodash-es而非lodash),并采用按需导入的方式 。
// 推荐:按需导入
import { debounce } from 'lodash-es';
// 不推荐:整体导入
import _ from 'lodash';
- 导出粒度太粗:尽量使用具名导出而非默认导出对象,有助于进行更精细的分析 。
// 推荐:细粒度导出
export function func1() {}
export function func2() {}
// 谨慎使用:粗粒度导出(不利于分析内部未使用属性)
export default { func1, func2 };
万胜智能:预中标约4298.66万元国家电网项目
第二批科创债风险分担工具支持的民营企业债券即将发行
神舟二十二号飞船计划于11月25日发射
理想 VLA 再更新,不但自主充电还能躲「鱼雷」
![]()
每当苹果在 WWDC 发布新版 iOS,相关热搜总是会出现类似这样的话题:究竟哪款老 iPhone 首次被排除在升级名单之外,不幸「掉队」?
新能源汽车也是如此——引入智能化后,「常用常新」总是厂商会打出的卖点之一。
如今,用户不只看马力和续航,更在意智能化更新的可持续性:OTA 的频率与内容、核心功能的长期可用性(导航/智驾/座舱)、硬件冗余是否支撑未来升级,以及付费订阅与保值率的平衡。
换句话说,一台优秀的智能车,不止是当下的配置清单,更是持续进化的能力与承诺。
![]()
![]()
在 2025 年广州车展上,理想汽车公布了辅助驾驶板块最新的进展与规划,带来了创新的 VLA 充电和防御性 AES 自动紧急转向功能,并且计划向 AD Max 车型全量推送。
我看了看已经陪伴了我 975 天的 2023 款理想 L7 Max,具备 AD Max 的它,仍在更新名单之列。
VLA 充电,打通充电的最后 100 米
基于 9 月份理想为所有 AD Max 车型推送的「VLA司机大模型」,进一步带来了「VLA 充电」这项全新能力。
从体验上来说,它看起来也很符合用户直觉。
![]()
行进途中,用户只要对「理想同学」说出「直接帮我导航到附近的超充站」,系统就会自动搜寻附近的「理想超充站」位置并添加到途径点。
开启 NOA 智能领航辅助后,车辆将按照导航自主驶向超充站;临近站区时,系统会主动推荐空闲充电桩车位,并由 NOA 自动驶入站内并漫游泊至对应车位,全程几乎无需手动介入。
![]()
目前看起来唯一还需要「手动」操作的,就只有车主下车,手动拔插充电枪这一步骤了。
不过此前理想已经公布了「龙门架」形式的自动充电机器人,单个机器手就能覆盖站点的多个充电桩位,能自动识别车型充电口与枪头,实现「人不下车,自动插枪」。
![]()
理论上理想可以在现有的这些超充站内,直接进行改造,这也就意味着现在发布会上唯一没有被点亮的「手动充电」,很可能在后续被打通为「自动充电」。
要实现体验上的无感且自然,「VLA 充电」这项能力,在技术上也做出了相当多的努力和协同。
按照理想的介绍,这是 VLA 司机大模型与车终端、超充站、充电桩之间完成端云协同的显著成果,在超充站充电这个关键的常用场景,验证了 VLA 具备「理解空间、自动完成任务」的能力。让 VLA 司机大模型,自主地与超充站实现通讯协同。
![]()
在车端,由 VLA 司机大模型完成空间理解,实现自主驶入场站并泊入充电车位的同时,还会向云端请求空闲充电桩情况,并协同降下地锁以便完成自主泊入车位;在云端,涉及了辅助驾驶云、理想超充调度云、场站云的高效协同,实时下发空闲充电桩信息,并按 VLA 规划降下地锁。
车辆充电完成之后,车辆可以自主驶离超充站,并通过免密支付完成充电缴费。
![]()
可见,理想的超充站与 AD Max 硬件,是提前预埋的「硬底盘」;VLA 模型,是可以周更精进的「软引擎」;充电补能,则是用户高频且刚性的体验场景。
VLA 充电正是立足这套软硬一体的底座,而给出的系统级解法——把「空间理解+行为策略」从道路延伸到自营超充站区,把「找桩充电」升级为「自主充电」。
这本质上是 VLA 路线的厚积薄发:先把基础设施、传感器与算力打牢,再用模型把整段体验打通。
于是,VLA 能力自然而然地,就从「会开」外溢到「会充」,从路网延伸到站内。随着超充站点覆盖继续扩大、VLA 强化学习迭代,以及自动充电等配套落地,补能体验将持续从可用走向好用,并在实战中自我进化。
防御性 AES:自动「躲鱼雷」
今年 8 月份发布的 VLA 司机大模型也在不停地迭代,理想宣布将在下一个版本中,优化 NOA 领航辅助驾驶在行车中的结合复杂场景。
![]()
面对相邻的红绿灯,VLA 会更加精准地完成识别;面对极窄路段的会车场景,也会向右借极窄空间绕行。在面对施工路段、临时改道的场景,VLA 也能像真实司机一样从容应对。
![]()
与此同时,无论城市通勤还是高速巡航,VLA 在轨迹平滑与制动/转向的柔和度上都在持续进化,行车更稳更舒适,行为操作也更加类人。
当然,理想汽车的辅助驾驶不仅在行车、泊车等场景持续进化,也在主动安全上不断拓展能力边界、抬升上限。
![]()
此前在理想 i8 发布会上,理想防御性 AES 就已经覆盖了三大场景,包括式截停、慢车加塞、恶意别车等高风险场景,车辆可以主动地识别此类风险并自主地完成相应的主动防御避让策略。
![]()
![]()
现在,理想还新增了两个防御性 AES 能力,在后车逼近或存在二次碰撞风险时,车辆自主就能联动「加速+转向」策略,通过「前向加速」或「安全变道」主动规避后方「鱼雷」,避免被追尾的碰撞风险。
至此,理想的防御性 AES 可以有效避免来自车辆前方、后方、侧前、侧后的碰撞风险,升级至全方位的保护能力。
按照规划,理想将在后续的 OTA 中,把防御性 AES 能力推送给所有 AD Max 车型。
理想 VLA:六维体验的长期主义
自年初提出 VLA 大模型之后,理想在取得进展的同时,也在面临着外部质疑,有同行认为它「取巧走不远」,也有业内专家认为「难以落地」。
面对这些质疑,理想汽车自动驾驶研发高级副总裁郎咸朋在接受媒体采访时回应:
他们反对 VLA,恰恰说明 VLA 是正确的。
![]()
▲理想汽车自动驾驶研发高级副总裁 郎咸朋
在理想汽车看来,上一代技术能力的上限,是下一代技术能力的起点。这也是理想从「端到端+VLM」切换到「VLA 司机大模型」的原因。
VLA 的主张不是「更像人开车」的表演,而是用多模态大模型 + 强化学习,把「看懂场景—做出决策—用数据闭环持续变好」的链路真正跑通,这与曾经由「端到端 + 数据闭环」来替代「规则拼装」的技术演进,是一脉相承的。
从技术角度看,VLA 让辅助驾驶从「猴子时期」迈进了「人类时期」,拥有了「能思考、能沟通、能记忆、能自我提升」的能力。
![]()
较早之前,理想 VLA 司机大模型就定下过六维指标:「选对路、速度对、舒适度、安心感、可沟通、高效率」。
从「自主会开」到「自主会充」,从道路到站区,从能刹住到会躲开,VLA 持续让「智驾」的价值从单点功能推到整段体验。
![]()
全新的 VLA 充电能力,让「补能」这个常用场景首次实现了「理解空间、自动完成任务」的空间理解,也验证了理想坚持 VLA 路线的正确性——可迁移、可学习、可持续进化,这正是理想汽车日益坚实的护城河之一。
同时,最新的「防御性 AES 」首次将 AES 功能扩展到「正前/正后/侧前/侧后」的全域风险规避。
就目前而言,VLA 还未曾到达「技术能力上限」的位置,仍然在不断地迭代进化,它仍然有着很多需要去落地的探索点,这也就说明 VLA 接下来还会有更多的能力,可以被更新释放。
![]()
基业长青的公司往往特别看重厚积薄发与长期复利,所以真正的护城河从来都不在 PPT 的算法名词当中,而是在每一公里的稳定行驶、每一次自主进站的充电补能、每一次被避免的事故当中。
理想 VLA 的长期价值,正在这些可被持续验证的细节中不断放大。
#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。