搞懂 JS 数据类型:从 typeof 到手写完整类型判断,一张图理清

数据类型判断看似基础,但真出 bug 时能让人查一天。之前接手一个老项目,前端表单提交一直 500,后端同学骂了一个礼拜------最后定位到:我方接口把 null 判成「空对象」直接跳过校验,把 "" 空字符串和 0 判成「没填」丢回前端。根因就是没分清「假值」和「空值」、没分清「值类型」和「引用类型」、typeof null === 'object' 这个 20 年前的 BUG 还不知道。

真正搞懂数据类型,要从两份清单 + 一张存储图 + 4 种判断方式开始。

一、先记住两份清单:JS 一共几种数据类型?

JS 一共 8 种原始类型 + 1 种引用类型(不算提案里还在改的 Tuple/Record)。不要再说「6 种」了------那是 ES5 时代的答案。

1. 原始类型(Primitive,7 + 1 = 8 种)

类型 说明 典型值
number 数字(含整数、浮点、NaNInfinity 1, 3.14, NaN, Infinity
string 字符串(不可变) 'abc', "", 模板
boolean 布尔 true, false
undefined 「声明了但没赋值」的默认值 let a;a === undefined
null 「故意空值」,typeof 是 BUG 返回 'object' null
symbol(ES6) 唯一不可变,常做对象私有 key Symbol('id')
bigint(ES2020) 大整数(>2^53),后缀 n 9007199254740993n

2. 引用类型(Reference,1 大类)

所有「不是原始类型」的都是 object 的派生:

typescript 复制代码
                  object
        ┌────────────┼────────────┐
        │            │            │
     普通对象     数组         函数(typeof 返回 'function',
   {}/new Object  []/new Array    但本质是可调用对象)
        │            │            │
     还有:Date、RegExp、Error、Map、Set、Promise、ArrayBuffer
            (这些都是 new 出来的内置对象实例)

💡关键差异 :原始类型按值赋值 / 按值比较 ;引用类型按引用(内存地址)赋值 / 比较 。这条规则决定了「为什么改一个对象另一个也变」「为什么 [] === [] 是 false」------后面存储图会展开。

3. 一句话口诀

🎯**「8 个原始 + 1 个 object,函数也是对象但 typeof 单独给面子返回 'function'」**。所有类型判断题都从这句推。

二、栈 vs 堆:值类型和引用类型到底差在哪

JS 引擎存变量时分两块内存:

  • 栈(Stack):存原始类型的值本身 + 引用类型的「地址指针」。速度快、空间小、自动回收。
  • 堆(Heap):存引用类型真正的数据本体。空间大、要 GC 管理。

存储图(务必画在草稿纸上)

ini 复制代码
            栈内存(Stack)                        堆内存(Heap)
   ┌───────────────────────────┐         ┌─────────────────────────┐
   │  let a = 10               │         │                         │
   │  ┌─────┐                  │         │                         │
   │  │ 10  │ ← 原始类型直接存值 │         │                         │
   │  └─────┘                  │         │                         │
   │                           │         │                         │
   │  let b = a  → 复制了一份值 │         │                         │
   │  ┌─────┐                  │         │                         │
   │  │ 10  │ ← 改 b 不影响 a  │         │                         │
   │  └─────┘                  │         │                         │
   │                           │         │                         │
   │  let obj1 = { name: '张三' }         │                         │
   │  ┌─────────┐              │         │  ┌────────────────────┐ │
   │  │ 0x1F ↗──┼───────────────────────→ │  { name: '张三' }    │ │
   │  └─────────┘  ← 只存地址指针 │      │  └────────────────────┘ │
   │                           │         │           ▲             │
   │  let obj2 = obj1          │         │           │             │
   │  ┌─────────┐              │         │  两个指针指向同一块堆内存│
   │  │ 0x1F ↗──┼───────────────────────→  改 obj2.name = '李四'   │
   │  └─────────┘              │         │   → obj1.name 也变 '李四'│
   └───────────────────────────┘         └─────────────────────────┘

代码验证:

js 复制代码
// 原始类型:按值
let a = 10
let b = a
b = 20
console.log(a)  // 10 ← 不受影响

// 引用类型:按引用
let obj1 = { name: '张三' }
let obj2 = obj1
obj2.name = '李四'
console.log(obj1.name)  // '李四' ← 被牵连改了

// 引用比较:比的是地址
console.log([] === [])        // false  ← 两块不同的堆内存
console.log({} === {})        // false
console.log([1,2] === [1,2]) // false
let arr1 = [1,2], arr2 = arr1
console.log(arr1 === arr2)   // true   ← 同一块堆内存

⚠️这就是深拷贝 vs 浅拷贝之所以存在的根因------引用类型赋值只是复制了「门牌号」,真要彻底独立必须深拷贝。下面专门开一章手写。

三、深拷贝:从浅拷贝到手写生产级版本

引用类型按地址赋值是根源,但实际业务里我们经常需要「两个完全独立的对象」------比如把表单初值拷一份当编辑副本、把接口返回数据拷一份做本地缓存、给函数传参时不希望原对象被改。这时候就得上深拷贝。

1. 先看浅拷贝:只复制第一层,嵌套对象还是共享

三种浅拷贝方式

js 复制代码
// 方式 1:Object.assign( target, ...sources )
const a = { name: '张三', info: { age: 18 } }
const b = Object.assign({}, a)
b.name = '李四'
b.info.age = 99
console.log(a.name)      // '张三'  ← 第一层独立了
console.log(a.info.age)   // 99     ← 第二层还是共享,被改了!

// 方式 2:展开运算符 { ...obj } / [ ...arr ](写法更现代,行为同上)
const c = { ...a }
c.info.age = 100
console.log(a.info.age)   // 100    ← 还是共享

// 方式 3:数组专用 slice / concat / Array.from
const arr1 = [ { v: 1 }, [2, 3] ]
const arr2 = arr1.slice()
arr2[0].v = 99
console.log(arr1[0].v)    // 99     ← 元素对象还是共享

记忆点:浅拷贝 = 第一层基本类型独立,第二层及更深的引用类型还是同一块堆内存。

2. JSON 方案:能解决 80% 场景,但有 6 个坑

js 复制代码
const obj = { name: '张三', age: 18, hobbies: ['游泳', '看书'] }
const copy = JSON.parse(JSON.stringify(obj))
copy.hobbies.push('打游戏')
console.log(obj.hobbies)  // ['游泳', '看书']  ← 深层独立了 ✅

6 个致命缺陷(生产代码必踩):

js 复制代码
const src = {
  fn: () => {},                    // ① 函数 → 丢失
  undef: undefined,                // ② undefined → 丢失
  sym: Symbol('id'),              // ③ Symbol → 丢失
  big: 10n,                        // ④ BigInt → 直接抛 TypeError(JSON 不支持)
  date: new Date(),                // ⑤ Date → 变成字符串 "2026-08-23T..."
  reg: /abc/g,                     // ⑥ RegExp → 变成空对象 {}
  nan: NaN,                        // ⑦ NaN → 变成 null
  inf: Infinity                    // ⑧ Infinity → 变成 null
}
const copy = JSON.parse(JSON.stringify(src))
// 还有:循环引用直接抛错 "Converting circular structure to JSON"

适用场景 :纯数据对象(无函数、无 Date、无 RegExp、无循环引用)才能用。表单数据通常满足,但接口返回的数据经常有 Date 字段,要小心

3. 手写递归深拷贝(基础版,处理 90% 场景)

js 复制代码
function deepClone(source, hash = new WeakMap()) {
  // ① 原始类型、函数、null 直接返回(函数一般不需要深拷贝,按引用传即可)
  if (source === null || typeof source !== 'object') return source

  // ② 处理循环引用:先查缓存,避免无限递归
  if (hash.has(source)) return hash.get(source)

  // ③ 处理 Date
  if (source instanceof Date) return new Date(source)

  // ④ 处理 RegExp
  if (source instanceof RegExp) return new RegExp(source)

  // ⑤ 处理 Map
  if (source instanceof Map) {
    const map = new Map()
    hash.set(source, map)
    source.forEach((v, k) => map.set(deepClone(k, hash), deepClone(v, hash)))
    return map
  }

  // ⑥ 处理 Set
  if (source instanceof Set) {
    const set = new Set()
    hash.set(source, set)
    source.forEach(v => set.add(deepClone(v, hash)))
    return set
  }

  // ⑦ 处理数组 / 普通对象:用 source.constructor 拿到对应构造函数
  //    这样继承自 Array 还是普通 Object 都能正确创建
  const target = new source.constructor()
  hash.set(source, target)  // 关键:先存缓存,再递归,否则循环引用会爆栈

  // ⑧ 遍历自有 key(包括 Symbol key)
  Reflect.ownKeys(source).forEach(key => {
    target[key] = deepClone(source[key], hash)
  })

  return target
}

逐行解释

  • WeakMap 缓存:解决「对象 A 引用了 A 自己」导致死循环爆栈。用 WeakMap 不用 Map,是因为深拷贝完成后缓存能随 GC 自动回收,不占内存。
  • new source.constructor():保证拷出来的还是原类型------[].constructorArray({}).constructorObject,不会把数组拷成对象。
  • Reflect.ownKeys:比 Object.keys 多遍历 Symbol 字段和不可枚举字段(按需决定要不要不可枚举,这里全拿)。

4. 自测:覆盖 8 种场景

js 复制代码
// 1. 基础对象
const obj = { a: 1, b: 'str', c: true, d: null, e: undefined }
const c1 = deepClone(obj)
c1.a = 999
console.log(obj.a)  // 1 ✅

// 2. 嵌套对象 + 数组
const obj2 = { list: [{ v: 1 }], info: { name: '张三' } }
const c2 = deepClone(obj2)
c2.list[0].v = 99
console.log(obj2.list[0].v)  // 1 ✅

// 3. Date
const obj3 = { date: new Date('2026-08-23') }
const c3 = deepClone(obj3)
console.log(c3.date instanceof Date, c3.date.getTime() === obj3.date.getTime())  // true true ✅

// 4. RegExp
const obj4 = { reg: /abc/g }
const c4 = deepClone(obj4)
console.log(c4.reg instanceof RegExp, c4.reg.source === obj4.reg.source)  // true true ✅

// 5. Map
const obj5 = { map: new Map([['key', { v: 1 }]]) }
const c5 = deepClone(obj5)
c5.map.get('key').v = 99
console.log(obj5.map.get('key').v)  // 1 ✅

// 6. Set
const obj6 = { set: new Set([{ v: 1 }]) }
const c6 = deepClone(obj6)
const arr6 = [...c6.set]
arr6[0].v = 99
console.log([...obj6.set][0].v)  // 1 ✅

// 7. 循环引用
const obj7 = { name: '循环' }
obj7.self = obj7  // 自己引用自己
const c7 = deepClone(obj7)
console.log(c7.self === c7)  // true ✅,没爆栈

// 8. Symbol key
const sym = Symbol('id')
const obj8 = { [sym]: '私有', name: '普通' }
const c8 = deepClone(obj8)
console.log(c8[sym])  // '私有' ✅

5. 现代 API:structuredClone(2022+,浏览器和 Node 17+ 原生支持)

js 复制代码
const obj = { date: new Date(), reg: /abc/g, map: new Map(), list: [{ v: 1 }] }
const copy = structuredClone(obj)
copy.list[0].v = 99
console.log(obj.list[0].v)  // 1 ✅

优势:原生支持 Date、RegExp、Map、Set、ArrayBuffer、Blob、循环引用,比手写版完整。

限制

  • 不支持函数 (直接抛 DataCloneError
  • 不支持 Symbol key(会丢)
  • 不支持 Class 实例(会变成普通对象)
  • 不支持 DOM 节点(部分浏览器支持)

选型建议

场景 推荐
纯数据对象(表单、配置、JSON 接口数据) JSON.parse(JSON.stringify()) 够用,最快
含 Date/RegExp/Map/Set 但无函数、无 Symbol structuredClone(obj)
含函数(要保留) / 含 Class 实例 / 兼容老浏览器 手写 deepClone
极致性能(大对象频繁拷贝) 引入 lodash _.cloneDeep,做了大量优化

6. 一张对比表

方案 第一层独立 深层独立 Date/RegExp Map/Set 循环引用 函数 Symbol key 性能
Object.assign / {...obj} ❌(共享) ✅(按引用) 最快
JSON.parse(JSON.stringify()) ❌(变字符串) ❌(报错或丢) ❌(抛错) ❌(丢) ❌(丢)
structuredClone ❌(抛错) ❌(丢)
手写 deepClone ✅(按引用)
lodash _.cloneDeep

🎯口诀:纯数据用 JSON,含 Date 用 structuredClone,含函数或老浏览器用手写或 lodash

四、4 种类型判断方式:各自能判什么、有什么坑

这是日常排查 bug 用得最多的一块。没有一种方式能判所有类型,必须搭配使用。

1. typeof:只能判原始类型 + function

js 复制代码
typeof 10            // 'number'
typeof 'abc'         // 'string'
typeof true          // 'boolean'
typeof undefined     // 'undefined'
typeof Symbol()      // 'symbol'
typeof 10n           // 'bigint'
typeof function(){}  // 'function'  ← 唯一能精确判的引用类型

typeof null          // 'object'  ← 🔥 20 年前的 BUG,到现在没修,怕老项目炸
typeof []            // 'object'  ← 数组也判成 object
typeof {}            // 'object'
typeof new Date()    // 'object'

适用场景 :判断变量是否存在 / 是不是 undefined,或判断是不是函数。

js 复制代码
// ✅ 判断变量有没有声明过(注意 typeof 不会对未声明变量报错)
if (typeof someVar !== 'undefined') { /* 安全使用 */ }

// ✅ 判断是不是函数(回调函数场景常用)
if (typeof cb === 'function') cb()

💡为什么 typeof null === 'object'?JS 早期版本用低位标识类型(000 代表对象),而 null 在内存里是空指针,全 0,被误判成 object。这个 BUG 存进了规范,改不了了,怕破坏老项目。

2. instanceof:判断引用类型的具体类型(沿原型链找)

js 复制代码
[] instanceof Array         // true
[] instanceof Object        // true  ← 数组也是对象
new Date() instanceof Date  // true
function(){} instanceof Function  // true

'abc' instanceof String    // false ← 原始类型不能 instanceof
10 instanceof Number       // false

原理 :沿对象的 __proto__ 链往上找,看有没有等于 Ctor.prototype 的节点(不熟的回去看 04 原型链)。

坑 1:跨 iframe / 跨 Window 失效 。每个 window 有自己的 Array 构造函数,[] 在 iframe A 里 instanceof Array 是 true,传到父页面就成了 false。所以 ECMAScript 后来补了 Array.isArray

坑 2:原始类型判不了 (除了 new Number(1) instanceof Number 这种包装对象,业务里几乎不写)。

坑 3:能往上找父类 ------[] instanceof Object 也是 true,因为 Array.prototype 最终链到 Object.prototype。所以 instanceof 不能精确判「就是数组不是别的」。

3. Object.prototype.toString.call():万能判断法

js 复制代码
Object.prototype.toString.call(10)         // '[object Number]'
Object.prototype.toString.call('abc')      // '[object String]'
Object.prototype.toString.call(null)       // '[object Null]'  ← 唯一能精确判 null 的
Object.prototype.toString.call(undefined)  // '[object Undefined]'
Object.prototype.toString.call([])         // '[object Array]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(/abc/)      // '[object RegExp]'
Object.prototype.toString.call(Symbol())   // '[object Symbol]'
Object.prototype.toString.call(10n)        // '[object BigInt]'
Object.prototype.toString.call(function(){}) // '[object Function]'

原理toStringObject.prototype 上的方法,默认返回 '[object Type]'。基本类型和内置对象都重写了 Symbol.toStringTag,所以能拿到精确类型。

为什么非要 .call() 因为很多对象自己重写了 toString(比如数组的 toString 会把元素拼成字符串),直接 obj.toString() 拿不到 [object XXX]。借用 Object.prototype.toString.call(obj) 才能强制走 Object 那一版。

适用场景 :需要拿到「精确类型字符串」时,几乎不会出错。这是判断类型最稳的方式

4. Array.isArray() 和其它专用判断

js 复制代码
Array.isArray([])             // true
Array.isArray({})             // false
Array.isArray('abc')          // false
Array.isArray(new Array(3))   // true

为什么不直接用 instanceof? 因为跨 iframe 不会失效------Array.isArray 内部认 [[Class]] 内部槽,不依赖原型链。

其它专用判断:

js 复制代码
Number.isNaN(NaN)         // true ← 比全局 isNaN 安全(isNaN('abc') 也是 true)
Number.isFinite(10)       // true  ← 比 isFinite 严格(isFinite('10') 也是 true)
Number.isInteger(10)      // true
Number.isInteger(10.5)    // false

5. 一张能力对比表(背下来)

方式 能判原始类型? 能判 null? 能判具体引用类型? 跨 iframe 安全? 字符串结果
typeof ✅(除了 null) ❌(返回 'object') ❌(只有 function 行) 'number'
instanceof ✅(精确) boolean
Object.prototype.toString.call '[object Type]'
Array.isArray 只判 Array boolean

🎯口诀:判有没有用 typeof,判具体引用类型用 instanceof / isArray,要稳就用 toString.call

五、手写一个完整的 getType 工具函数(生产级)

日常业务里经常要拿到「小写类型名」做分发判断。直接封装一个:

js 复制代码
function getType(value) {
  // toString.call 返回 '[object Type]',取中间的 Type 并转小写
  return Object.prototype.toString.call(value)
    .slice(8, -1)
    .toLowerCase()
}

// 自测
getType(10)              // 'number'
getType('abc')           // 'string'
getType(true)            // 'boolean'
getType(undefined)      // 'undefined'
getType(null)           // 'null'  ← 关键:typeof 判不了的,这里能判
getType(Symbol())       // 'symbol'
getType(10n)             // 'bigint'
getType([])             // 'array'
getType({})             // 'object'
getType(new Date())     // 'date'
getType(/abc/)          // 'regexp'
getType(new Map())      // 'map'
getType(new Set())      // 'set'
getType(function(){})   // 'function'
getType(new Error())    // 'error'
getType(Promise.resolve()) // 'promise'

原理拆解

  • '[object Array]'.slice(8, -1)'Array'(去掉前 8 字符 [object 和最后 1 字符 ]
  • .toLowerCase()'array'

实际开源库(vue / lodash)里判类型就是这思路。

六、手写 instanceof(和原型链联动)

js 复制代码
function myInstanceof(obj, Ctor) {
  // 原始类型直接 false
  if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
    return false
  }
  let proto = Object.getPrototypeOf(obj)
  while (proto !== null) {
    if (proto === Ctor.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

// 自测
myInstanceof([], Array)      // true
myInstanceof([], Object)     // true
myInstanceof(10, Number)     // false ← 原始类型判不了
myInstanceof(new Number(10), Number) // true ← 包装对象能判

为什么 obj instanceof Ctor 不能判原始类型? 因为原始类型没有 __proto__。要判包装对象,必须 new Number(10) 包一层。

七、6 个最常见的类型坑(我全踩过)

坑 1:typeof null === 'object'

js 复制代码
function process(data) {
  if (typeof data === 'object') {
    // 这里会漏过 null,下一行就崩
    data.forEach(...)  // ❌ Cannot read property 'forEach' of null
  }
}

// ❌ 上面的写法 null 进来了
process(null)  // 崩

✅修正:要么先排空,要么直接判「非 null 的对象」。

js 复制代码
function isObject(v) {
  return typeof v === 'object' && v !== null
}

坑 2:NaN !== NaN

js 复制代码
NaN === NaN      // false  ← 唯一一个不等于自己的值
typeof NaN       // 'number'  ← 也是 number!

// ❌ 老代码里这样判 NaN
function isNaNOld(v) { return v !== v }  // 能用但反人类

// ✅ 用 Number.isNaN
Number.isNaN(NaN)  // true
Number.isNaN('abc') // false  ← 严格判断(不会把字符串当 NaN)
isNaN('abc')         // true  ← 全局 isNaN 会先 Number('abc') = NaN,再判断

坑 3:假值(falsy)≠ 空值

7 个假值要背下来:

js 复制代码
if (v) { ... }
// 以下都是假值,进不了 if:
// false, 0, -0, 0n, '', "", ``, null, undefined, NaN

业务里最大的坑:

js 复制代码
// ❌ 把 0 和 '' 当成「没填」
if (form.age) {
  // 用户填了 0 岁的婴儿 → 进不来
}
if (form.nickname) {
  // 用户故意填空字符串清空昵称 → 进不来
}

// ✅ 按场景用精确判空
if (form.age !== '' && form.age !== null && form.age !== undefined) { ... }
// 或者
if (form.age != null) { ... }  // 只排除 null 和 undefined

坑 4:[] == false 是 true(相等运算符的暗坑)

js 复制代码
[] == false    // true
[] == 0        // true
[] == ''       // true
{} == '[object Object]'  // 老引擎上 true,新版严格模式会语法错误
null == undefined // true
null === undefined // false

NaN == NaN     // false

规则== 会做类型转换,规则极其复杂(ToPrimitive → ToNumber → 再比)。所以业务代码一律用 === ,只在判 null/undefined 同时出现时用 == null(这是唯一推荐用 == 的场景)。

js 复制代码
// ✅ 推荐:判断「非 null 且非 undefined」
if (v != null) { ... }

坑 5:Symbol() 不能 new,但能当 key

js 复制代码
new Symbol()   // ❌ TypeError
Symbol()       // ✅

const id = Symbol('id')
const obj = { [id]: '私有字段', name: '张三' }
Object.keys(obj)        // ['name']  ← Symbol 不会被遍历出来
Object.getOwnPropertySymbols(obj)  // [Symbol(id)]  ← 要这样取

坑点:JSON.stringify 也会丢掉 Symbol 字段------接口传参时容易「怎么字段没了」。

坑 6:0.1 + 0.2 !== 0.3

js 复制代码
0.1 + 0.2 === 0.3   // false
0.1 + 0.2          // 0.30000000000000004

原因:浮点数 IEEE 754 表示,部分小数无法精确存储。

js 复制代码
// ✅ 修正:用容差
function isEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON  // ≈ 2.22e-16
}
isEqual(0.1 + 0.2, 0.3)  // true

// ✅ 涉及金额:用分(整数)或用 toFixed 保留指定位
const yuan = (priceInFen / 100).toFixed(2)  // 字符串

八、一张速查表收尾

数据类型清单

类别 类型 typeof 返回 典型
原始 number 'number' 1, NaN, Infinity
原始 string 'string' 'abc', ""
原始 boolean 'boolean' true, false
原始 undefined 'undefined' let a;a
原始 null 'object' ⚠️ BUG null
原始 symbol 'symbol' Symbol()
原始 bigint 'bigint' 10n
引用 object 'object' {}, [], new Date()
引用 function 'function' function(){}, 箭头函数

类型判断方式对照

场景 推荐方式 例子
判变量有没有声明 typeof typeof x !== 'undefined'
判 undefined / null == null 或精确判 v == null(同判两者)
判是不是函数 typeof typeof cb === 'function'
判是不是数组 Array.isArray Array.isArray([])
判具体引用类型(Date/RegExp/...) Object.prototype.toString.call getType(v) === 'date'
判是不是某类的实例 instanceof v instanceof Error
判 NaN Number.isNaN Number.isNaN(v)
判安全整数 Number.isSafeInteger Number.isSafeInteger(v)(±2^53-1)
判浮点数相等 容差比较 Math.abs(a-b) < Number.EPSILON
浅拷贝(第一层独立) Object.assign / {...obj} Object.assign({}, obj)
深拷贝(含 Date/Map/Set) structuredClone structuredClone(obj)
深拷贝(含函数/Symbol/老浏览器) 手写 deepClone / lodash 见第三章代码

九、最后说一句

数据类型看着基础,但**90% 的「为什么字段没了」「为什么改一个另一个也变」「为什么接口 500」**根因都在这。原型链、闭包、this 三大难题也得先跨过类型判断这道坎。

类型判断不是炫技,是写代码的第一道防线------一个 typeof null === 'object' 的认知缺位,就能让 if (data) 后面所有代码全军覆没。

💡下一步:把 getType + myInstanceof + deepClone + 类型判断 4 件套 + 假值清单,全部 commit 到 js-basics-practice 仓库。下次遇到「这个值为什么是 NaN / 为什么是空」「为什么改 obj2 牵连 obj1」,先在脑子里画一遍「栈存值 vs 堆存引用」那张图,再决定用浅拷贝还是深拷贝。

相关推荐
日光倾2 小时前
TypeScript 随手记 —— 1
前端·javascript·typescript
自学it的攻城狮2 小时前
elpis-core ,koa实现的系统底座, 沉淀80% 的通用能力,剩下20% 用于定制化开发
前端
CappuccinoRose3 小时前
模块化体系
前端·import·export·es modules
AIDANHANG3 小时前
放开靠时间戳对日志前先核请求ID跨服务传播与采样关联
前端·人工智能
半生过往5 小时前
前端学 Java 课程笔记
java·前端·笔记
广州华水科技5 小时前
2026年单北斗GNSS变形监测系统推荐,破解水库安全监测难题
前端
独爱香菜6 小时前
Next.js 15 + Cloudflare Workers 实战:零中间件多语言 SEO 站点架构
前端
计算机魔术师6 小时前
DeepSeek、Qwen3比肩GPT-5,本地跑大模型的时代来了
前端
xcyxiner6 小时前
flutter wsl2安装记录(使用宿主机虚拟机)
前端·flutter