搞懂 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 数字(含整数、浮点、NaN、Infinity) 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():保证拷出来的还是原类型------[].constructor 是 Array,({}).constructor 是 Object,不会把数组拷成对象。
  • 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]'

原理 :toString 是 Object.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 堆存引用」那张图,再决定用浅拷贝还是深拷贝。

相关推荐
deli00729 分钟前
手机拍灯为什么有一条条黑纹?PWM 240Hz 实测
前端
wangruofeng5 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng5 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈8 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技8 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569159 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马11 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng12 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding13 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天13 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron