redis

Redis 基础命令

Redis 是一个开源的、基于内存的高性能的键值存储系统,常用作数据库、缓存和消息代理。

基础命令包含:键,字符串,哈希,列表,集合,有序集合,发布订阅,事务,服务器等命令。

1. 键(Key)相关命令

1.1 DEL - 删除键

css 复制代码
DEL key [key ...]

删除一个或多个键,返回被删除键的数量。

案例

shell 复制代码
> SET name "Redis"
OK
> DEL name
(integer) 1

1.2 EXISTS - 检查键是否存在

vbnet 复制代码
EXISTS key [key ...]

检查一个或多个键是否存在,返回存在的键的数量。

案例

sql 复制代码
> SET age 10
OK
> EXISTS age
(integer) 1
> EXISTS non_existing_key
(integer) 0

1.3 EXPIRE - 设置过期时间

vbnet 复制代码
EXPIRE key seconds

为键设置过期时间(秒)。

案例

shell 复制代码
> SET session:1234 "user_data"
OK
> EXPIRE session:1234 3600  # 1小时后过期
(integer) 1
> TTL session:1234  # 查看剩余时间
(integer) 3598

1.4 KEYS - 查找键

sql 复制代码
KEYS pattern

查找所有符合给定模式的键。

案例

sql 复制代码
> MSET user:1 "Alice" user:2 "Bob" product:1 "Book"
OK
> KEYS user:*
1) "user:1"
2) "user:2"

2. 字符串(String)相关命令

场景:字符串是最基本的数据类型,可以用来存储任何形式的字符串,包括二进制数据。它可以用于缓存简单的数据,比如配置信息,字典,用户登录Token等。

2.1 SET/GET - 设置/获取值

vbnet 复制代码
SET key value [EX seconds] [PX milliseconds] [NX|XX]
GET key

设置或获取字符串值。

案例

shell 复制代码
> SET website "redis.io"
OK
> GET website
"redis.io"

# 带过期时间的设置
> SET token "abc123" EX 3600
OK

# 仅当键不存在时设置
> SET username "admin" NX
OK
> SET username "newadmin" NX
(nil)  # 设置失败

2.2 INCR/DECR - 自增/自减

vbnet 复制代码
INCR key
DECR key
INCRBY key increment
DECRBY key decrement

对数字值进行原子性的增减操作。

案例

markdown 复制代码
> SET counter 100
OK
> INCR counter
(integer) 101
> INCRBY counter 10
(integer) 111
> DECR counter
(integer) 110
> DECRBY counter 5
(integer) 105

2.3 MSET/MGET - 批量设置/获取

vbnet 复制代码
MSET key value [key value ...]
MGET key [key ...]

一次性设置或获取多个键值。

案例

arduino 复制代码
> MSET key1 "Hello" key2 "World"
OK
> MGET key1 key2
1) "Hello"
2) "World"

3. 哈希(Hash)相关命令

场景:哈希类型允许你存储键值对集合,其中每个键都是一个字符串,每个值可以是字符串或数字。它非常适合存储对象信息,如用户信息、商品信息等。

3.1 HSET/HGET - 设置/获取哈希字段

vbnet 复制代码
HSET key field value [field value ...]
HGET key field

操作哈希表中的字段。

案例

sql 复制代码
> HSET user:1000 username "antirez" birthyear 1977
(integer) 2
> HGET user:1000 username
"antirez"
> HGET user:1000 birthyear
"1977"

3.2 HGETALL - 获取所有字段和值

vbnet 复制代码
HGETALL key

获取哈希表中所有的字段和值。

案例

sql 复制代码
> HGETALL user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"

3.3 HINCRBY - 哈希字段自增

vbnet 复制代码
HINCRBY key field increment

增加哈希表中数字字段的值。

案例

bash 复制代码
> HSET product:1000 views 100
(integer) 1
> HINCRBY product:1000 views 1
(integer) 101
> HINCRBY product:1000 views 10
(integer) 111

4. 列表(List)相关命令

场景:列表类型可以用来存储一系列有序的数据项,如队列、消息列表等。它非常适合实现消息队列、任务队列等场景。

4.1 LPUSH/RPUSH - 从头部/尾部插入元素

css 复制代码
LPUSH key element [element ...]
RPUSH key element [element ...]

从列表的头部或尾部插入一个或多个元素。

案例

bash 复制代码
> LPUSH mylist "world"
(integer) 1
> LPUSH mylist "hello"
(integer) 2
> RPUSH mylist "!"
(integer) 3
> LRANGE mylist 0 -1
1) "hello"
2) "world"
3) "!"

4.2 LPOP/RPOP - 从头部/尾部弹出元素

css 复制代码
LPOP key [count]
RPOP key [count]

从列表的头部或尾部移除并返回元素。

案例

bash 复制代码
> RPUSH numbers 1 2 3 4 5
(integer) 5
> LPOP numbers
"1"
> RPOP numbers
"5"
> LRANGE numbers 0 -1
1) "2"
2) "3"
3) "4"

4.3 LLEN - 获取列表长度

vbnet 复制代码
LLEN key

获取列表的长度。

案例

bash 复制代码
> LPUSH tasks "task1" "task2" "task3"
(integer) 3
> LLEN tasks
(integer) 3

5. 集合(Set)相关命令

场景:集合类型用来存储不重复的字符串元素,它非常适合需要快速查找、删除和添加元素的应用场景,如标签系统、社交网络中的共同好友、共同群组等

5.1 SADD - 添加元素

sql 复制代码
SADD key member [member ...]

向集合添加一个或多个成员

案例

bash 复制代码
> SADD tags "redis" "database" "nosql"
(integer) 3

5.2 SMEMBERS - 获取所有成员

vbnet 复制代码
SMEMBERS key

返回集合中的所有成员。

案例

arduino 复制代码
> SMEMBERS tags
1) "nosql"
2) "database"
3) "redis"

5.3 SISMEMBER - 检查成员是否存在

sql 复制代码
SISMEMBER key member

判断成员是否在集合中。

案例

bash 复制代码
> SISMEMBER tags "redis"
(integer) 1
> SISMEMBER tags "sql"
(integer) 0

5.4 SINTER - 集合交集

vbnet 复制代码
SINTER key [key ...]

返回多个集合的交集。

案例

bash 复制代码
> SADD set1 a b c
(integer) 3
> SADD set2 b c d
(integer) 3
> SINTER set1 set2
1) "b"
2) "c"

6. 有序集合(Sorted Set)相关命令

场景:有序集合和集合类似,但它为每个成员关联了一个分数(double类型),这使得它可以按照分数来排序成员。它非常适合实现排行榜、带权重的标签系统等

6.1 ZADD - 添加元素

css 复制代码
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

向有序集合添加一个或多个成员,或更新已存在成员的分数

案例

bash 复制代码
> ZADD leaderboard 100 "player1" 200 "player2" 300 "player3"
(integer) 3

6.2 ZRANGE - 获取范围内的元素

vbnet 复制代码
ZRANGE key start stop [WITHSCORES]

通过索引区间返回有序集合指定区间内的成员。

案例

bash 复制代码
> ZRANGE leaderboard 0 -1  # 获取所有成员
1) "player1"
2) "player2"
3) "player3"

> ZRANGE leaderboard 0 -1 WITHSCORES  # 带分数返回
1) "player1"
2) "100"
3) "player2"
4) "200"
5) "player3"
6) "300"

6.3 ZREVRANGE - 逆序获取范围内的元素

vbnet 复制代码
ZREVRANGE key start stop [WITHSCORES]

返回有序集中指定区间内的成员,通过索引,分数从高到低。

案例

arduino 复制代码
> ZREVRANGE leaderboard 0 -1 WITHSCORES
1) "player3"
2) "300"
3) "player2"
4) "200"
5) "player1"
6) "100"

7. 发布/订阅相关命令

场景:实时消息通知、事件广播

7.1 PUBLISH - 发布消息

复制代码
PUBLISH channel message

向指定的频道发布消息。

7.2 SUBSCRIBE - 订阅频道

css 复制代码
SUBSCRIBE channel [channel ...]

订阅一个或多个频道。

案例: 在客户端1:

vbnet 复制代码
> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1

在客户端2:

bash 复制代码
> PUBLISH news "Hello Redis!"
(integer) 1

客户端1将收到:

arduino 复制代码
1) "message"
2) "news"
3) "Hello Redis!"

8. 事务相关命令

8.1 MULTI/EXEC - 事务执行

sql 复制代码
MULTI
命令1
命令2
...
EXEC

标记一个事务块的开始,然后执行所有在 MULTI 和 EXEC 之间的命令。

案例

shell 复制代码
> MULTI
OK
> SET name "Alice"
QUEUED
> SET age 30
QUEUED
> INCR counter
QUEUED
> EXEC
1) OK
2) OK
3) (integer) 1

8.2 WATCH - 监视键

vbnet 复制代码
WATCH key [key ...]

监视一个或多个键,如果在事务执行之前这些键被其他命令改动,那么事务将被打断。

案例

shell 复制代码
> WATCH balance
OK
> MULTI
OK
> DECRBY balance 100
QUEUED
> EXEC  # 如果在执行前balance被其他客户端修改,则返回(nil),unwach 取消监控

9. 服务器相关命令

9.1 FLUSHALL - 清空所有数据库

css 复制代码
FLUSHALL [ASYNC]

删除所有数据库的所有键。

9.2 INFO - 获取服务器信息

css 复制代码
INFO [section]

返回关于 Redis 服务器的各种信息和统计数值。

案例

makefile 复制代码
> INFO server
# Server
redis_version:6.2.4
redis_git_sha1:00000000
...

9.3 DBSIZE - 获取键数量

复制代码
DBSIZE

返回当前数据库的 key 的数量。

案例

bash 复制代码
> DBSIZE
(integer) 42

Redis 管道

一、Redis 管道技术详解

1. 管道技术原理

Redis 管道技术(Pipeline)的核心原理是通过减少网络往返时间(RTT)提升服务器吞吐量来优化批量操作的性能。

本质是将多次网络往返合并为一次,通过协议层的批量数据传输和服务器端的顺序处理实现性能飞跃。

  • 减少网络往返时间(RTT)

    • 传统模式:客户端每发送一个命令都需要等待服务器返回结果后才能发送下一个命令,网络延迟(RTT)会成为性能瓶颈。

      • 例如:执行100次GET命令需要100次RTT。
    • 管道模式:客户端将多个命令一次性批量发送到服务器,服务器按顺序处理并批量返回结果。

      • 例如:100次GET命令仅需1次RTT(或少量RTT,取决于数据包大小限制)
    makefile 复制代码
    传统模式:
    客户端: 命令1 → 等待 → 响应1 → 命令2 → 等待 → 响应2 → ...
    
    管道模式:
    客户端: 命令1 → 命令2 → 命令3 → ... → 响应1, 响应2, 响应3, ...
  • 服务端批量处理

    • 缓冲队列:Redis服务器将管道中的命令缓存在内存队列中,按顺序依次执行。
  • 适用场景

    • 批量初始化数据
    • 大批量数据导入
    • 需要执行多个无依赖关系的命令
    • 对原子性要求不高的批量操作

2. 管道 vs 普通模式

特性 普通模式 管道(Pipeline)
网络消耗 高(N次往返) 低(1次往返)
原子性
执行顺序 顺序执行 顺序执行
错误处理 立即报错 继续执行
适用场景 单命令操作 批量非关联命令

3. 性能对比

假设网络延迟为 1ms,Redis 服务器处理每个命令需要 0.1ms:

  • 普通模式 执行 100 次 SET:100 * (1 + 0.1) = 110ms
  • 管道模式 执行 100 次 SET:1 + (100 * 0.1) = 11ms

二、SpringBoot 中使用 Redis 管道

1. 基础配置

首先确保项目中已配置 Redis:

xml 复制代码
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml 复制代码
# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0

2. 完整实战案例

批量写入数据
vbnet 复制代码
@Service
public class RedisPipelineService {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 普通方式批量插入(对比用)
     */
    public long batchInsertNormal(Map<String, String> data) {
        long start = System.currentTimeMillis();
        
        data.forEach((key, value) -> {
            stringRedisTemplate.opsForValue().set(key, value);
        });
        
        long end = System.currentTimeMillis();
        return end - start;
    }
    
    /**
     * 使用管道批量插入数据
     * @param data 要插入的键值对
     * @return 执行时间(毫秒)
     */
    public long batchInsertWithPipeline(Map<String, String> data) {
        long start = System.currentTimeMillis();
        
        List<Object> results = stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            data.forEach((key, value) -> {
                connection.set(key.getBytes(), value.getBytes());
            });
            return null;
        });
        
        long end = System.currentTimeMillis();
        return end - start;
    }
    
}
性能测试对比
typescript 复制代码
@RestController
public class BenchmarkController {
    
    @Autowired
    private RedisPipelineService redisPipelineService;
    
    @GetMapping("/benchmark")
    public String benchmark() {
        // 准备测试数据(1000个键值对)
        Map<String, String> testData = new HashMap<>();
        for (int i = 0; i < 1000; i++) {
            testData.put("key:" + i, "value:" + i);
        }
        
        // 普通模式测试
        long normalTime = redisPipelineService.batchInsertNormal(testData);
        
        // 管道模式测试
        long pipelineTime = redisPipelineService.batchInsertWithPipeline(testData);
        
        // 删除测试数据
        stringRedisTemplate.delete(testData.keySet());
        
        return String.format("普通模式: %dms, 管道模式: %dms, 性能提升: %.2f%%", 
                normalTime, pipelineTime, 
                (normalTime - pipelineTime) * 100.0 / normalTime);
    }
}

典型输出结果:

makefile 复制代码
普通模式: 718ms, 管道模式: 82ms, 性能提升: 88.58%

通过合理使用 Redis 管道技术,可以显著提升 SpringBoot 应用与 Redis 交互的性能,特别是在批量操作场景下。但需要注意根据实际业务场景选择合适的技术方案,平衡性能与功能需求。

三、最佳实践与注意事项

  1. 批量大小控制

    • 建议每批命令控制在 1000-5000 个
    • 过大的批量会导致内存消耗和 Redis 阻塞
  2. 错误处理:如果管道中某条命令出错,其他命令仍会继续执行

  3. 非原子性:管道中的命令不是原子性的,中间可能会被其他客户端的命令插入

  4. 连接管理

    • 管道会占用连接直到所有命令执行完成
    • 避免在管道中执行耗时操作,防止连接池耗尽
  5. 监控与调优

    • 监控管道执行时间:redisTemplate.executePipelined(...) 的耗时
    • 根据实际网络延迟调整批量大小

Redis 事务

一、Redis 事务核心概念

1. Redis 事务特性

Redis 事务通过 MULTI/EXEC/DISCARD/WATCH 命令实现,具有以下特点:

  • 原子性:事务中的命令要么全部执行,要么全部不执行
  • 隔离性:事务执行过程中不会被其他客户端命令打断
  • 无回滚机制:Redis 事务不支持回滚(与关系型数据库不同)
  • 单线程保证:Redis 单线程特性自然保证了命令的顺序执行

2. Redis 事务核心命令

命令 作用 说明
MULTI 开启事务 标记事务开始,后续命令会加入队列
EXEC 执行事务 执行队列中的所有命令
DISCARD 取消事务 清空命令队列,放弃执行
WATCH 监视键 乐观锁机制,如果被监视的键被修改,事务失败
UNWATCH 取消监视 取消所有 WATCH 监视

3. 事务执行流程

  1. MULTI:开启事务。

  2. 命令入队:后续命令不会立即执行,而是加入队列。

  3. EXEC:执行队列中的所有命令,返回结果数组。

  4. DISCARD(可选):放弃事务,清空队列。

    scss 复制代码
    +-------------------+       +-------------------+
    |   Client          |       |   Redis Server    |
    |                   |       |                   |
    | 1. MULTI          |       |                   |
    |    ───────────────┼───────► 2. 开启事务模式    |
    |                   |       |   (标记事务开始)   |
    |                   |       |                   |
    | 3. 命令入队        |       |                   |
    |   (CMD1,CMD2,...) |       |                   |
    |    ───────────────┼───────► 4. 返回QUEUED      |
    |                   |       |   (命令存入队列)   |
    |                   |       |                   |
    | 5. EXEC           |       |                   |
    |    ───────────────┼───────► 6. 原子性执行队列  |
    |                   |       |   (顺序执行所有命令)|
    |                   |       |                   |
    | 7. 接收批量响应 ◄───┼─────── 8. 返回结果数组    |
    |                   |       |                   |
    +-------------------+       +-------------------+

4. 基本事务案例

示例 1:简单事务(MULTI + EXEC)
ruby 复制代码
127.0.0.1:6379> MULTI          # 开启事务
OK
127.0.0.1:6379> SET name "Alice"
QUEUED                         # 命令进入队列
127.0.0.1:6379> INCR counter
QUEUED
127.0.0.1:6379> GET name
QUEUED
127.0.0.1:6379> EXEC           # 执行事务
1) OK                          # SET name "Alice" 的结果
2) (integer) 1                 # INCR counter 的结果
3) "Alice"                     # GET name 的结果
示例 2:事务失败(语法错误)

如果命令在入队时就有语法错误(如 SET 写错),整个事务不会执行:

ruby 复制代码
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET name "Bob"
QUEUED
127.0.0.1:6379> INCRR counter  # 错误命令(INCRR 不存在)
(error) ERR unknown command 'INCRR'
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
示例 3:运行时错误(不影响其他命令)

如果命令在运行时出错(如对字符串 INCR),仅该命令失败,其他命令仍执行:

ruby 复制代码
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET age "twenty"
QUEUED
127.0.0.1:6379> INCR age       # 对字符串执行 INCR 会失败
QUEUED
127.0.0.1:6379> SET score 100
QUEUED
127.0.0.1:6379> EXEC
1) OK                          # SET age "twenty" 成功
2) (error) ERR value is not an integer or out of range  # INCR age 失败
3) OK                          # SET score 100 仍执行

5. WATCH 乐观锁机制

WATCH 用于实现 CAS(Check-And-Set) 操作,监视一个或多个键,如果事务执行前这些键被修改,则事务失败。

Redis 的 CAS 机制通过以下流程工作:

  1. 客户端 WATCH 要监控的键
  2. 检查当前值
  3. MULTI/EXEC 事务中修改值
  4. 如果被监控的键在 WATCHEXEC 之间被修改过,则整个事务失败
示例 4:使用 WATCH 实现余额扣减
ruby 复制代码
# 客户端1
127.0.0.1:6379> SET balance 100
OK
127.0.0.1:6379> WATCH balance  # 监视 balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY balance 20
QUEUED
127.0.0.1:6379> EXEC            # 如果 balance 未被其他客户端修改,事务执行成功
1) (integer) 80

# 客户端2(在客户端1 EXEC 前修改 balance)
127.0.0.1:6379> INCRBY balance 50
(integer) 150

# 此时客户端1的 EXEC 会返回 (nil),事务失败
127.0.0.1:6379> EXEC
(nil)
UNWATCH 取消监视
ruby 复制代码
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> UNWATCH        # 取消所有监视
OK

二、SpringBoot 实现 Redis 事务

1. 基础配置

确保已添加 Spring Data Redis 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置 Redis 连接(application.yml):

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0

2. 事务实现方式

方式1:使用 SessionCallback
csharp 复制代码
@Service
public class RedisTransactionService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 基本事务操作
     */
    public void basicTransaction() {
        List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi(); // 开启事务
                
                operations.opsForValue().set("tx_key1", "value1");
                operations.opsForValue().set("tx_key2", "value2");
                operations.opsForValue().get("tx_key1");
                
                return operations.exec(); // 执行事务
            }
        });
        
        // 第三个命令(GET)的结果
        System.out.println("事务中获取的值: " + results.get(2));
    }
}
方式2:使用 Lambda 简化
csharp 复制代码
public void lambdaTransaction() {
    List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
        @Override
        public List<Object> execute(RedisOperations operations) throws DataAccessException {
            operations.multi();
            operations.opsForValue().set("product:stock:1001", "10");
            operations.opsForValue().decrement("product:stock:1001"); // 减库存
            operations.opsForList().leftPush("order:queue", "order1001");
            return operations.exec();
        }
    });
    
    System.out.println("减库存结果: " + results.get(1));
}

三、事务使用场景及案例

秒杀系统实现

typescript 复制代码
@Service
public class SeckillService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String SECKILL_STOCK_PREFIX = "seckill:stock:";
    private static final String SECKILL_SUCCESS_USERS = "seckill:success:users:";
    
    public boolean seckillProduct(String productId, String userId) {
        // 1. 验证用户是否已经秒杀过
        if (Boolean.TRUE.equals(
            redisTemplate.opsForSet().isMember(SECKILL_SUCCESS_USERS + productId, userId))) {
            return false;
        }
        
        // 2. 使用WATCH监控库存
        redisTemplate.watch(SECKILL_STOCK_PREFIX + productId);
        
        try {
            // 3. 获取当前库存
            String stockStr = redisTemplate.opsForValue()
                .get(SECKILL_STOCK_PREFIX + productId);
            if (stockStr == null || Integer.parseInt(stockStr) <= 0) {
                redisTemplate.unwatch();
                return false;
            }
            
            // 4. 开始事务
            List<Object> results = redisTemplate.execute(new SessionCallback<List<Object>>() {
                @Override
                public List<Object> execute(RedisOperations operations) throws DataAccessException {
                    operations.multi();
                    operations.opsForValue().decrement(SECKILL_STOCK_PREFIX + productId);
                    operations.opsForSet().add(SECKILL_SUCCESS_USERS + productId, userId);
                    return operations.exec();
                }
            });
            
            // 5. 判断事务是否执行成功
            return results != null && !results.isEmpty();
        } catch (Exception e) {
            redisTemplate.unwatch();
            throw e;
        }
    }
}

五、注意事项与最佳实践

  1. Redis事务与关系型数据库事务的区别

    • Redis事务没有隔离级别概念
    • Redis事务执行期间不会被其他客户端中断
    • Redis事务出错后不会回滚已执行的命令
  2. 性能考虑

    • 事务会阻塞Redis服务器,避免在事务中包含大量命令
    • 对于纯写入场景,考虑使用管道(pipeline)代替事务
  3. WATCH使用建议

    • 监视的key不宜过多
    • 被WATCH的key如果频繁被修改,会导致事务频繁失败
    • 考虑结合重试机制
  4. 事务超时问题

    • Redis默认不限制事务执行时间
    • 长时间运行的事务会导致连接占用
    • 建议设置合理的超时时间

通过合理使用Redis事务,可以在SpringBoot应用中实现简单的原子性操作。对于复杂的业务场景,可以结合管道、Lua脚本等技术实现更高效可靠的Redis操作。

事务 vs 管道(Pipeline)

特性 事务(MULTI/EXEC) 管道(Pipeline)
原子性 ✅ 全部成功或失败 ❌ 单条失败不影响其他
执行方式 命令在 EXEC 时执行 命令立即发送,批量返回
用途 需要原子性操作(如转账) 批量操作优化(减少RTT)
错误处理 运行时错误不影响后续命令 单条失败不影响其他
相关推荐
Sun_light1 分钟前
深入理解JavaScript中的「this」:从概念到实战
前端·javascript
小桥风满袖2 分钟前
Three.js-硬要自学系列33之专项学习基础材质
前端·css·three.js
聪明的水跃鱼7 分钟前
Nextjs15 构建API端点
前端·next.js
小明爱吃瓜23 分钟前
AI IDE(Copilot/Cursor/Trae)图生代码能力测评
前端·ai编程·trae
不爱说话郭德纲29 分钟前
🔥Vue组件的data是一个对象还是函数?为什么?
前端·vue.js·面试
绅士玖31 分钟前
JavaScript 中的 arguments、柯里化和展开运算符详解
前端·javascript·ecmascript 6
GIS之路34 分钟前
OpenLayers 图层控制
前端
断竿散人34 分钟前
专题一、HTML5基础教程-http-equiv属性深度解析:网页的隐形控制中心
前端·html
星河丶34 分钟前
介绍下navigator.sendBeacon方法
前端
curdcv_po35 分钟前
🤸🏼🤸🏼🤸🏼兄弟们开源了,用ThreeJS还原小米SU7超跑!
前端