阅读视图

发现新文章,点击刷新页面。

前端登录token到底应该存在哪?LocalStorage、SessionStorage还是Cookie?一篇说透!

大家好,我是大华! 前几天有个小伙伴问我:“我登录之后拿到了token,到底该往哪儿存?LocalStorageSessionStorage还是Cookie?为啥不同网站做法不一样?” 说实话,这个问题我也曾经纠结过好久!每次看到不同项目用不同的存储方式,我就想问:到底哪个是对的?

前言

为什么token存储这么重要?想象一下,你家的钥匙你会放哪儿?随身携带?藏在门垫下面?还是交给保安?放错了地方,小偷就可能进你家门!

token就是用户进入系统的钥匙,存错了地方,黑客就能冒充用户登录账号,后果不堪设想啊!


一、区别

1. LocalStorage

  • 永久存储(除非手动删)
  • 同源就能读(JS随便拿)
  • 刷新不丢,关浏览器也不丢
  • XSS攻击下,Token直接暴露
  • 需要手动添加到请求头

2. SessionStorage

  • 只在当前会话有效
  • 同源可读
  • 适合临时操作
  • 关了浏览器标签就没了
  • 同样有XSS风险
  • 需要手动管理

3. Cookie

  • 可设置过期时间
  • 可设置HttpOnly(JS拿不到!)
  • 可设置Secure(只走HTTPS)
  • 可设置SameSite(防CSRF)
  • 自动随请求发送(比如发API时自动带Token)
  • 容量限制(4KB)
  • 每次请求都携带(可能浪费流量)

4. 内存存储(Memory)

  • 页面刷新就丢失
  • 完全前端控制,不持久化
  • 最快最安全,但生命周期最短
  • 页面刷新就丢失
  • 不适合持久化需求
  • 标签页关闭就没了

二、为什么大家都用LocalStorage?

我懂,很多前端的朋友第一反应:“用LocalStorage最方便啊!”

两行代码搞定:

// 存
localStorage.setItem('token', res.token);

// 发请求时
axios.defaults.headers.common['Authorization'] = localStorage.getItem('token');

方便、简单是真的,但同时也会伴随着安全隐患。

案例1:XSS攻击,Token被偷

假设你网站有个评论区,用户输入没做转义:

<script>
  fetch('/steal?token=' + localStorage.getItem('token'))
</script>

用户打开页面,这条JS一执行,你的Token就会被发送到黑客服务器上。 而如果 Token 在HttpOnly Cookie里,JS 读不到,XSS 攻击直接失效。


三、用Cookie就完美了吗?

不,Cookie也有坑。黑客诱导你访问一个恶意页面:

<img src="https://yourbank.com/transfer?to=hacker&amount=100000" />

如果你的登录态在 Cookie 里,浏览器会自动带上 Cookie,请求就成功了!

用户没点确认,钱就没了。

解决方案

方案一:前后端分离 + JWT + LocalStorage(最常见)

这是目前绝大多数新项目采用的方式。

技术栈:

  • 前端:Vue3 + Vue CLI / Vite,部署在 Nginx / CDN
  • 后端:SpringBoot,提供 RESTful API
  • 通信:Axios + JWT(JSON Web Token)
  • Token存储localStorage或内存

部署方式:

用户浏览器
    ↓
Vue 前端(http://fe.yourcompany.com) ←→ SpringBoot 后端(http://api.yourcompany.com)

前端和后端完全独立部署,通过CORS跨域通信。

认证流程:

  1. 用户登录
  2. SpringBoot验证用户名密码,生成JWT
  3. 返回给前端:{ token: "xxxxxx" }
  4. 前端存入 localStorage
  5. 后续请求,前端手动加Header:
    axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
    
  6. SpringBoot在拦截器中解析JWT,验证身份

优点:

  • 前后端完全解耦,各自独立开发、部署、扩展
  • 适合微服务、云原生架构
  • 开发简单,调试方便
  • 可配合Nginx做负载均衡、缓存

缺点:

  • XSS 风险高:一旦有富文本漏洞,localStorage中的token可能被盗
  • 需要手动管理token(过期、刷新)
  • 跨域配置麻烦(CORS)

📌 适用场景:

  • 中后台管理系统
  • ToC产品(官网、商城)
  • 快速上线的MVP项目

这是目前最主流的方案,90% 的新项目都这么干。


方案二:前后端合并部署(传统做法,逐渐减少)

把Vue打包后的dist文件放到SpringBoot的 resources/static 目录下,由SpringBoot统一提供页面和 API。

目录结构:

src/
 └── main/
     ├── java/        ← SpringBoot 代码
     └── resources/
         ├── static/  ← Vue 打包后的 css/js
         └── templates/ ← index.html(可选)

访问方式:

  • 页面:http://localhost:8080/
  • API:http://localhost:8080/api/xxx

优点:

  • 部署简单,一个 jar 包搞定
  • 没有跨域问题
  • 适合小型项目、内部系统

缺点:

  • 前后端耦合,不利于独立迭代
  • 静态资源由 Java 服务提供,性能不如 Nginx
  • 不适合高并发场景

📌 适用场景:

  • 内部工具、小项目
  • 学习 demo
  • 对性能要求不高的系统

这种方案在企业级项目中逐渐被淘汰,但在教学和小项目中依然常见。


方案三:双 Token 机制(高安全要求项目)

这是金融、银行、高权限系统中越来越流行的“专业做法”。

方案核心:

  • access_token:短期JWT,存前端内存,用于API认证
  • refresh_token:长期token,存HttpOnly Cookie,用于刷新access_token

流程:

  1. 登录成功
    • 后端:Set-Cookie: refresh_token=xxx; HttpOnly; Secure
    • 响应体:{ access_token: "yyy" }
  2. 前端:
    • access_token到内存
    • 请求时加 Authorization: Bearer yyy
  3. access_token过期后:
    • /refresh 接口
    • 浏览器自动带refresh_token Cookie
    • 拿到新access_token

优点:

  • XSS 攻不破(refresh_tokenJS 拿不到)
  • 即使access_token泄露,有效期短(5-15分钟)
  • 安全性极高

缺点:

  • 实现复杂
  • 需要后端配合
  • 刷新机制要处理好并发

📌 适用场景:

  • 银行、支付、高权限后台
  • 对安全要求极高的系统

方案四:使用 Cookie + Session(传统安全做法)

SpringBoot使用Spring Security + Session,登录后Set-Cookie: JSESSIONID=xxx; HttpOnly

Vue 前端不需要管 token,浏览器自动带 Cookie。

优点:

  • 安全性高(防 XSS)
  • 后端可管理 session(如强制下线)
  • 适合内网系统

缺点:

  • 需要处理 CSRF
  • 不适合无状态、微服务架构
  • 跨域配置复杂

📌 适用场景:

  • 传统企业系统
  • 内网管理系统
  • 已有 Spring Security 架构的项目

总结:四种方案对比

方案 安全性 易用性 推荐度 适用场景
前后端分离 + JWT + LocalStorage ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐☆ 90% 的新项目
前后端合并部署 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 小项目、学习
双 Token 机制 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ 高安全系统
Cookie + Session ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ 传统企业系统

最终建议

  1. 如果你是新手或者做小项目:用前后端分离 + JWT + localStorage,简单直接。

  2. 如果你做中后台或者ToC产品:同上,但必须做好 XSS 防护(输入过滤、CSP、DOMPurify)。

  3. 如果你做金融或者高权限系统:上双 Token 机制,安全第一。

  4. 如果你是传统企业或者内网系统:可以考虑Cookie + Session,但要配好 CSRF。


目前 Vue + SpringBoot 的标准就是:前后端分离 + JWT + LocalStorage。

虽然它有 XSS 风险,但凭借开发效率高、架构清晰、适合云原生等优势,已经成为主流。

安全问题不是靠“不用 localStorage”解决的,而是靠:

  • 严格的输入验证
  • CSP 策略
  • 定期安全审计
  • 使用Content-Security-Policy

技术选型,永远是安全、效率、成本的权衡。

公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》

《别再被 Stream.toMap() 劝退了!3 个真实避坑案例,建议收藏》

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》

Vue3 的 ref 和 reactive 到底用哪个?90% 的开发者都选错了

前言

refreactiveVue3Composition API里最常用的两个核心成员,专门用来创建响应式数据的。 啥叫响应式呢?就是数据一变,页面自动跟着更新,不用你手动刷新。 其实它们干的是一件事就是:让Vue能监听到你的数据变化

但它们用起来不一样,适合的场景也不一样。很多人在用的时候都是凭感觉选,到底该用哪个?一直都觉得很模糊。

先简单认识一下:

ref:可以包装任何类型的值,包括基本类型(数字、字符串等)和对象类型。使用时需要通过.value来访问和修改值。 reactive:只能包装对象类型(包括数组)。使用时直接访问属性即可,不需要.value

b0ca79428871de1434b721295ba9daef.jpg

下面咱们通过实际例子来看一下。

一、区别

// ref 啥都能包
const count = ref(0)          // 数字
const name = ref('小明')       // 字符串  
const isActive = ref(false)   // 布尔值
const user = ref({ age: 18 }) // 对象
const list = ref([1, 2, 3])   // 数组

// reactive 只接对象/数组
const user = reactive({ age: 18 }) // 对象
const list = reactive([1, 2, 3])   // 数组
const count = reactive(0)         // 报错

二、优缺点

ref

优点:
  1. 什么都能包:基本类型、对象、数组,来者不拒
  2. 赋值简单:直接 xxx.value = newValue
  3. 解构安全:不用怕响应式丢失
  4. 类型推导好TypeScript支持完美
缺点:
  1. 总要写 .value:有点烦人
  2. 模板中也要 .value:不过 Vue 会自动解包

reactive

优点:
  1. 不用写 .value:直接访问属性
  2. 相关联数据组织性好:类似Vue2data
缺点:
  1. 解构大坑:直接解构容易丢失响应式
  2. 赋值限制:不能整个重新赋值
  3. 类型推导有时抽风:TS环境下偶尔会出现问题

三、watch监听中的差异

这里是最容易踩坑的地方!

监听 ref

const count = ref(0)
const user = ref({ name: '小明', age: 18 })

// 监听基本类型 ref
watch(count, (newVal, oldVal) => {
  console.log('count变化:', newVal, oldVal)
})

// 监听对象 ref - 需要深度监听
watch(user, (newVal, oldVal) => {
  console.log('user变化:', newVal, oldVal)
}, { deep: true }) // 必须加 deep!

// 监听对象 ref 的特定属性
watch(() => user.value.name, (newVal, oldVal) => {
  console.log('name变化:', newVal, oldVal)
})

监听 reactive

const state = reactive({
  count: 0,
  user: { name: '小明', age: 18 }
})

// 自动深度监听,不需要 deep: true
watch(state, (newVal, oldVal) => {
  console.log('state变化:', newVal, oldVal)
}) 

// 推荐:监听特定属性
watch(() => state.count, (newVal) => {
  console.log('count变了', newVal)
})

// 监听嵌套属性
watch(() => state.user.name, (newVal) => {
  console.log('名字变了', newVal)
})

// 监听多个属性
watch([() => state.count, () => state.user.name], ([newCount, newName]) => {
  console.log('count或name变了', newCount, newName)
})

watch 的重要区别

1.深度监听:

  • ref对象需要手动{ deep: true }
  • reactive自动深度监听

2.旧值获取:

  • reactive的旧值和新值相同(Proxy特性)
  • ref的旧值正常

3.性能影响:

  • reactive自动深度监听,可能影响性能
  • ref可以精确控制监听深度

四、案例

案例1:表单处理 - 推荐 reactive

// 相关联的表单数据,用 reactive 更合适
const form = reactive({
  username: '',
  password: '',
  remember: false,
  errors: {}
})

// 验证函数
const validateForm = () => {
  form.errors = {}
  if (!form.username) {
    form.errors.username = '用户名不能为空'
  }
  // ...其他验证
}

案例2:API 数据加载 - 推荐 ref

// API 返回的数据,经常需要重新赋值,用 ref
const data = ref(null)
const loading = ref(false)
const error = ref(null)

const fetchData = async () => {
  loading.value = true
  try {
    const response = await fetch('/api/data')
    data.value = await response.json() // 直接赋值,美滋滋
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

案例3:组件状态管理 - 看情况

// 方案1:用 reactive(状态相关联)
const modal = reactive({
  isOpen: false,
  title: '',
  content: '',
  loading: false
})

// 方案2:用多个 ref(状态相对独立)
const isModalOpen = ref(false)
const modalTitle = ref('')
const modalContent = ref('')
const modalLoading = ref(false)

案例4:列表操作 - 强烈推荐 ref

const list = ref([])

// 添加项目
const addItem = (item) => {
  list.value = [...list.value, item] // 重新赋值,安全
}

// 删除项目
const removeItem = (id) => {
  list.value = list.value.filter(item => item.id !== id)
}

// 清空列表
const clearList = () => {
  list.value = [] // 直接赋值,不会丢失响应式
}

如果用reactive来做列表:

const list = reactive([])

// 添加项目 - 只能用 push
const addItem = (item) => {
  list.push(item) // 可以,但不够直观
}

// 删除项目 - 需要找到索引
const removeItem = (id) => {
  const index = list.findIndex(item => item.id === id)
  if (index !== -1) {
    list.splice(index, 1) // 有点麻烦
  }
}

// 清空列表 - 只能修改长度
const clearList = () => {
  list.length = 0 // 能工作,但有点 hack
}

五、组合式函数中的选择

这是决定用哪个的关键场景!

返回多个 ref:灵活好用

function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)
  const increment = () => count.value++

  return { count, double, increment }
}

// 使用时:
const { count, double, increment } = useCounter()

返回 reactive:结构固定

function useUser() {
  const state = reactive({
    user: null,
    loading: false,
    error: null
  })

  const fetchUser = async (id) => {
    state.loading = true
    try {
      state.user = await fetchUserById(id)
    } catch (err) {
      state.error = err.message
    } finally {
      state.loading = false
    }
  }

  return { ...toRefs(state), fetchUser } // 必须用 toRefs!
}

// 使用时:
const { user, loading, error, fetchUser } = useUser()

明显看到,返回多个ref更简单直接。

六、混合使用模式(推荐)

// 基本类型和需要重新赋值的用 ref
const loading = ref(false)
const error = ref(null)
const data = ref(null)

// 相关联的数据用 reactive
const form = reactive({
  username: '',
  password: '',
  remember: false
})

// 这样写,既清晰又安全

七、性能

其实在大多数情况下,性能差异可以忽略不计,推荐如下:

  1. 大量基本类型:用ref,因为reactive需要创建Proxy对象
  2. 大对象但只关心部分属性:用多个ref,避免不必要的深度监听
  3. 频繁重新赋值:用refreactive不能直接重新赋值

总结

优先使用ref的场景: -基本类型(数字、字符串、布尔值) -DOM 引用和组件引用 -需要频繁重新赋值的变量 -API 返回的数据 -组合式函数的返回值 -列表数据

考虑使用reactive的场景: -相关联的表单数据 -复杂的组件状态 -配置对象 -不需要重新赋值的对象

希望这篇分析对你有帮助。如果有不同意见,欢迎在评论区理性讨论!

公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》

《Java 订单超时未支付,如何自动关闭?掌握这 3 种方案,轻松拿 offer!》

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》

Vue 的 DOM 更新竟然是异步的?90%的人没有搞懂 nextTick

刚接触Vue那会儿,以为修改数据后DOM会立刻更新,结果踩了不少坑。相信不少小伙伴也有过这样的经历,今天来分享一下这个让人困惑的问题。

场景

假设我们有这样一个简单的组件:

<template>
  <div>
    <button @click="updateData">点击我</button>
    <p ref="content">{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '初始消息'
    }
  },
  methods: {
    updateData() {
      this.message = '更新后的消息'
      console.log('DOM内容:', this.$refs.content.textContent)
    }
  }
}
</script>

猜猜点击按钮后,控制台会输出什么? 是"更新后的消息"吗?不是!实际输出的是"初始消息"!

为什么会这样?

原来,VueDOM更新不是同步的,而是异步的!当我们修改数据时,Vue并不会立即去更新DOM,而是将这些更新操作放入一个队列中,在下一个事件循环中才统一执行。

这就好比你去餐厅点餐,服务员不会每点一道菜就跑去厨房一次,而是等你点完所有菜后,才把整个菜单交给厨房。

实际开发中的问题

我曾经在做项目时遇到过这样的问题:

updateData() {
  this.message = '新消息'
  this.isShow = true
  // 想要操作更新后的DOM
  this.$nextTick(() => {
    // 这样才能拿到更新后的DOM
    this.$refs.content.style.color = 'red'
  })
}

如果不使用nextTick,直接操作DOM,很可能操作的是更新前的状态,导致效果不符合预期。

nextTick的使用

nextTick是Vue提供的一个方法,它会在DOM更新完成后执行回调函数。常见使用场景:

1. 操作更新后的DOM

this.message = '更新了'
this.$nextTick(() => {
  // 这里可以安全地操作更新后的DOM
  console.log(this.$refs.element.textContent) // 输出:更新了
})

2. 等待视图更新后再执行操作

this.list.push(newItem)
this.$nextTick(() => {
  // 滚动到最新添加的项目
  window.scrollTo(0, this.$refs.list.scrollHeight)
})

3. 在组件更新后执行操作

this.visible = true
this.$nextTick(() => {
  // 此时组件已经渲染完成,可以获取DOM节点
  this.$refs.input.focus()
})

Vue内部使用了一种巧妙的机制来批量处理DOM更新:

  1. 数据变化时,Vue开启一个队列
  2. 同一个事件循环内的数据变化会被批量处理
  3. 在下一个事件循环中,Vue刷新队列并执行实际DOM更新

这样做的好处是避免不必要的重复渲染,提高性能。

Vue3的nextTick用法

上面讲了Vue2的DOM异步更新,那Vue3呢?DOM更新也是异步吗?nextTick用法有变化吗?

没错,Vue3继承了Vue2的异步更新机制,而且原理基本相同。但是用法上有一些小变化。

同样的场景,Vue3中试试

<template>
  <div>
    <button @click="updateData">点击我</button>
    <p ref="content">{{ message }}</p>
  </div>
</template>

<script>
import { ref, nextTick } from 'vue'

export default {
  setup() {
    const message = ref('初始消息')
    const content = ref(null)

    const updateData = async () => {
      message.value = '更新后的消息'
      console.log('DOM内容:', content.value?.textContent) // 还是"初始消息"!
      
      // Vue3的用法
      nextTick(() => {
        console.log('nextTick中的DOM内容:', content.value.textContent) // "更新后的消息"
      })
    }

    return {
      message,
      content,
      updateData
    }
  }
}
</script>

结果和Vue2一模一样!DOM更新还是异步的。

Vue3中nextTick的变化

1. 引入方式变了

// Vue2
this.$nextTick(() => {
  // 操作DOM
})

// Vue3
import { nextTick } from 'vue'

nextTick(() => {
  // 操作DOM
})

2. 支持async/await

这是Vue3的一个很酷的新特性:

import { ref, nextTick } from 'vue'

const message = ref('初始消息')
const content = ref(null)

const updateData = async () => {
  message.value = '更新后的消息'
  
  // 等待DOM更新完成
  await nextTick()
  
  // 这里可以安全操作DOM了
  console.log(content.value.textContent) // "更新后的消息"
  content.value.style.color = 'red'
}

await nextTick()让代码看起来更简洁。

Vue3实际案例

例1:自动聚焦输入框

import { ref, nextTick } from 'vue'

const showInput = ref(false)
const inputRef = ref(null)

const openInput = async () => {
  showInput.value = true
  await nextTick()
  inputRef.value.focus() // 确保input已经渲染出来
}

例2:列表更新后滚动到底部

import { ref, nextTick } from 'vue'

const messages = ref([])
const listRef = ref(null)

const addMessage = async (text) => {
  messages.value.push(text)
  await nextTick()
  // 滚动到最新消息
  listRef.value.scrollTop = listRef.value.scrollHeight
}

例3:动画效果

import { ref, nextTick } from 'vue'

const isVisible = ref(false)
const elementRef = ref(null)

const showWithAnimation = async () => {
  isVisible.value = true
  await nextTick()
  // 确保元素已经渲染,然后添加动画
  elementRef.value.classList.add('fade-in')
}

Vue3setup函数中,如果你喜欢用Options API的风格,也可以这样用:

import { getCurrentInstance } from 'vue'

const { proxy } = getCurrentInstance()
proxy.$nextTick(() => {
  // 和Vue2一样的用法
})

不过还是推荐直接使用import { nextTick } from 'vue'的方式,更符合Vue3的风格。

为什么Vue3还要保持异步更新?

其实原因和Vue2一样:

  1. 性能优化 - 批量处理更新,避免重复渲染
  2. 避免不必要的计算 - 多次数据变化只进行一次DOM更新
  3. 保证数据一致性 - 在同一事件循环中的所有变化一起处理

需要注意的点

  1. Composition API中nextTick需要手动引入
  2. Options API中,仍然可以使用this.$nextTick()(为了兼容性)
  3. SSR场景nextTick在服务端渲染中不会做任何事情

总结

  • DOM更新是异步的,数据变化后不能立即获取更新后的DOM
  • 使用nextTick()来确保在DOM更新后再执行操作
  • 这在处理动画、计算尺寸或位置等场景时特别重要

你知道为什么Vue要选择异步更新DOM吗?欢迎在评论区留言讨论!

我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》

《别再手写判空了!SpringBoot 自带的 20 个高效工具类》

《别学23种了!Java项目中最常用的6个设计模式,附案例》

《Vue3+TS设计模式:5个真实场景让你代码更优雅》

摸鱼神器!前端大佬私藏的 11 个 JS 神级 API,复制粘贴就能用,效率翻倍

大家好,我是大华! 今天给大家分享一些我私藏的JS神级API。 每一个都很简单,复制粘贴就能用,真的能让你效率翻倍,摸鱼时间更多。嘿嘿!

1. 一键复制文本

场景:调试时,想复制接口返回的一大串JSON,点半天复制不全。 以前console.log→手动选中→复制 现在:一行代码,直接复制到剪贴板。

// 复制任意文本
function copyText(text) {
  navigator.clipboard.writeText(text);
}

// 用法
copyText("这串数据太长了,直接复制走!");

案例:封装一个按钮,点一下,接口返回的data自动复制。

<button onclick="copyText(JSON.stringify(apiData))">复制数据</button>

省下30秒,够刷半条抖音了。

2. 检测用户是否在打字

场景:搜索框输入,不想每敲一个字就发请求,想等用户停手再查。 APIinput事件 + setTimeout组合。

let typingTimer;
const input = document.getElementById('search');

input.addEventListener('input', () => {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(() => {
    console.log('用户打完了,去请求吧');
    // 调搜索接口
  }, 500); // 500毫秒内没再输入,就认为打完了
});

公司内部的用户搜索,加了这个,接口请求量直接降了80%。

3. 监听页面是否在后台

场景:用户切到其它软件,页面不可见。这时候别疯狂刷新数据。

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    console.log('用户去摸鱼了,切后台了');
    // 可以暂停轮询、视频、动画
  } else {
    console.log('用户回来了,继续干活');
    // 恢复轮询
  }
});

后台管理系统的实时订单刷新,切后台就暂停,回来再重新拉取。省电又省服务器。

4. 获取用户在线状态

场景:用户地铁进隧道,网络断了。你得知道,不然他点了提交,没反应,以为你代码有bug。

window.addEventListener('online', () => {
  alert('网络恢复啦,可以继续操作了!');
});

window.addEventListener('offline', () => {
  alert('大哥,没网了!先别点了!');
});

我们有个表单提交功能,断网时按钮变灰,提示请检查网络,用户体验直接拉满。

5. 检测用户是否滚动到底部

场景:做列表页,想实现上拉加载更多。

window.addEventListener('scroll', () => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  
  if (scrollTop + clientHeight >= scrollHeight - 5) {
    console.log('用户到底了,加载下一页');
    // 调接口,加载更多数据
  }
});

公司商品列表页,50行代码搞定无限滚动,产品经理直呼专业。

6. 获取用户地理位置

场景:打卡、签到、本地服务推荐。

function getLocation() {
  navigator.geolocation.getCurrentPosition(
    (position) => {
      const { latitude, longitude } = position.coords;
      console.log(`纬度: ${latitude}, 经度: ${longitude}`);
      // 传给后端,判断是否在公司范围内
    },
    (error) => {
      console.log('获取位置失败:', error.message);
    }
  );
}

// 调用
getLocation();

注意:浏览器会弹授权框,用户可能会点拒绝。

7. 全屏显示

场景:给老板演示系统,想全屏展示,逼格拉满。

function openFullscreen() {
  const elem = document.documentElement;
  if (elem.requestFullscreen) {
    elem.requestFullscreen();
  }
}

// 退出全屏
function closeFullscreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  }
}

汇报PPT嵌在页面里,点一下全屏,领导:“这小伙子可以啊。”

8. 检测设备是手机还是电脑

场景:手机端隐藏某些复杂功能,PC端才显示。

function isMobile() {
  return window.innerWidth <= 768;
}

// 用法
if (isMobile()) {
  document.getElementById('complex-feature').style.display = 'none';
}

看屏幕宽度。比解析UA字符串靠谱多了。

9. 页面加载完成后执行

场景:DOM还没加载完,JS就执行了,报错“找不到元素”。

document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM加载完了,可以操作元素了');
  // 初始化你的组件、绑定事件
});

window.onload快:它等所有资源(图片、CSS)加载完。DOMContentLoaded只等HTML结构。

10. 本地存储,记住用户偏好

场景:用户调了字体大小、主题颜色,关了页面又打开,还是原来的设置。

// 存
localStorage.setItem('theme', 'dark');
localStorage.setItem('fontSize', '16px');

// 取
const theme = localStorage.getItem('theme'); // 'dark'
const size = localStorage.getItem('fontSize'); // '16px'

// 页面加载时应用
document.body.className = theme;
document.body.style.fontSize = size;

公司后台系统,用户换肤后,下次登录自动记住,省得再调。

11. 结构化数据控制台打印:console.table()

场景:还在用console.log看数组数据?眼睛都快看花了吧?

const users = [
  { name: '张三', age: 25, job: '前端' },
  { name: '李四', age: 30, job: '后端' },
  { name: '王五', age: 28, job: '设计' }
];

console.table(users);

一行代码,表格形式展示数据,清晰多了!

总结

以上就是我今天要分享的11个JS神级API,每一个都是我日常开发中经常用到的。 简单、实用、能偷懒(划掉)提高效率。

我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》

《Elasticsearch 太重?来看看这个轻量级的替代品 Manticore Search》

《别再手写判空了!SpringBoot 自带的 20 个高效工具类》

《Vue3+TS设计模式:5个真实场景让你代码更优雅》

❌