huaji
发布于 2026-07-24 / 12 阅读
0

JS基础-类型及检测方式

一、JS 8大数据类型与内存存储机制

1. 完整类型划分(ES标准8种)

分类清单包含细分内存存放位置
7种原始(基本)类型Number / String / Boolean / Undefined / Null / Symbol / BigInt无细分,值本身不可修改栈内存 Stack
1种引用类型Object普通Object、Array、Function、Date、RegExp、Math等堆内存 Heap

2. 栈、堆内存核心区别

  1. 原始类型
    栈直接存真实值;赋值、拷贝为完全复制一份值,新旧变量互不干扰;占用空间小、长度固定,栈读写速度快。
  2. 引用类型
    栈只存堆内存地址指针,真实数据存在堆;多个变量共用同一个堆地址,一处修改全部同步;堆空间大、大小不固定,分配回收开销更高。
  3. JS运行三大内存区域
  • 代码区:存放JS可执行代码;
  • 栈区:执行上下文、原始值、引用地址;
  • 堆区:对象、数组、闭包变量。

3. 闭包内存特殊规则

闭包变量不会随函数执行完毕销毁,JS引擎在堆创建内部closure(fn)对象存放闭包变量,因此闭包变量常驻堆。

经典例题1:引用共享

let a = { name: 'lee', age: 18 }
let b = a;
console.log(a.name); // lee
b.name = 'son';
console.log(a.name); // son
console.log(b.name); // son

原因:ab栈中存同一个堆地址,修改堆内属性,所有指向该地址的变量全部变化。

经典例题2:函数传参引用细节(高频面试)

let a = { name: 'Julia', age: 20 }
function change(o) {
  o.age = 24; // 通过地址修改堆内原对象,影响外部a
  o = { name: 'Kath', age: 30 }; // 让局部o指向全新堆地址,和外部a彻底断开
  return o;
}
let b = change(a);
console.log(b.age); // 30(新对象)
console.log(a.age); // 24(原对象被改)

关键:函数传参是地址拷贝。改属性影响原值;给形参整体重新赋值,仅改变局部变量指向,外部不受影响。


二、四种数据类型检测方案(优缺点+手写源码)

1. typeof(适合基础类型+函数)

执行结果一览

typeof 2            // number
typeof true         // boolean
typeof 'str'        // string
typeof undefined    // undefined
typeof Symbol()     // symbol
typeof 100n         // bigint
typeof []           // object
typeof {}           // object
typeof function(){} // function
typeof null         // object(历史bug)

优缺点

✅ 优点:快速判断除null外的所有原始类型、精准区分function;
❌ 缺点:

  1. null 判定为object(早期二进制000标签导致);
  2. 数组、正则、日期等所有引用类型(除function)全部返回object,无法细分。

2. instanceof(判断引用类型原型链)

原理

顺着实例__proto__向上查找原型链,是否能匹配构造函数prototype只能判断引用类型,原始类型全部false

console.log(2 instanceof Number); // false 原始值无效
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true

手写简易instanceof

function _instanceof(left, right) {
  // 原始类型直接返回false
  if (typeof left !== 'object' || left === null) return false;
  let proto = right.prototype;
  left = left.__proto__;
  while (true) {
    if (left === null) return false;
    if (left === proto) return true;
    left = left.__proto__;
  }
}

❌ 缺陷:原型可被手动篡改;跨iframe失效;不支持基础类型。

3. constructor(通过构造器判断)

(2).constructor === Number // true
[].constructor === Array // true

致命坑:可随意修改原型,constructor会错乱:

function Fn(){}
Fn.prototype = new Array();
const f = new Fn();
console.log(f.constructor === Fn); // false
console.log(f.constructor === Array); // true

不可靠,生产少用

4. Object.prototype.toString.call()(最精准万能方案)

规则

调用Object原型原生toString,固定返回格式 [object 类型名],可精准区分所有内置类型。

Object.prototype.toString.call(1)        // "[object Number]"
Object.prototype.toString.call('1')      // "[object String]"
Object.prototype.toString.call(null)     // "[object Null]"
Object.prototype.toString.call(undefined)// "[object Undefined]"
Object.prototype.toString.call([])       // "[object Array]"
Object.prototype.toString.call(/\d/)      // "[object RegExp]"
Object.prototype.toString.call(new Date())// "[object Date]"
Object.prototype.toString.call(window)   // "[object Window]"
Object.prototype.toString.call(document)  // "[object HTMLDocument]"

封装通用精准类型判断函数

function getType(obj) {
  // 基础类型直接用typeof返回
  const type = typeof obj;
  if (type !== "object") return type;
  // object/null走toString解析
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
// 测试
getType([])         // Array
getType(null)       // Null
getType(window)     // Window
getType(function(){}) // function

四种检测横向对比总结

方式基础类型引用细分可篡改推荐场景
typeof绝大部分ok(null除外)❌ 除function都不行不可改快速判断原始、函数
instanceof✅ 原型链可改判断自定义类实例
constructor极易篡改几乎不推荐
toString.call✅全覆盖✅全覆盖不可篡改通用万能判断

三、JS三大类型转换:转布尔、转数字、转字符串

1. 强制转Boolean(条件判断核心)

6个假值:其余全为true

undefined、null、false、NaN、''空字符串、0、-0

Boolean(0) // false
Boolean('') // false
Boolean(null) // false
Boolean([]) // true 空数组是真值
Boolean({}) // true 空对象是真值

2. 对象转原始值通用规则(ToPrimitive)

所有对象参与运算、比较时,按固定优先级获取原始值:

  1. 优先调用 [Symbol.toPrimitive](hint)(优先级最高,hint:number/string/default)
  2. 无上面方法:调用valueOf(),返回原始值则终止
  3. 无上面:调用toString(),返回原始值则终止
  4. 全不返回原始值:报错

示例重写优先级:

let obj = {
  [Symbol.toPrimitive](){ return 200 },
  valueOf(){ return 300 },
  toString(){ return 'Hello' }
}
console.log(obj + 200); // 400,toPrimitive优先级最高

3. 强制转数字:Number()完整规则

  1. Boolean:true→1,false→0
  2. null → 0;undefined → NaN
  3. 字符串:纯数字/十六进制正常转;空串''→0;含非数字字符→NaN
  4. Symbol:直接报错
  5. 对象:先走ToPrimitive拿到原始值,再套用上面规则
Number('')      // 0
Number(null)    // 0
Number('1a')    // NaN
Number(0X11)    // 17
Number(-0X11)   // -17
Number(true)    // 1

补充:parseInt只解析开头数字,空字符串返回NaN,和Number不同。

4. 加号+隐式转换两条铁律

  1. 任意一边为字符串:两边全部转字符串,做拼接
1 + '1'        // '11'
4 + [1,2,3]    // '41,2,3' 数组toString为"1,2,3"
'a' + +'b'     // aNaN  +'b'先转NaN
  1. 两边无字符串:全部转为数字做加法
true + true    // 1+1=2
null + 1       // 0+1=1
  1. +外(* / > < -):一律把所有操作数转数字运算
4 * []     // 4*0=0
4 * [1,2]  // 4*NaN=NaN

四、== 抽象相等完整隐式转换规则(高频面试)

完整执行步骤(按顺序匹配)

  1. 两边类型完全一致:直接用===严格对比,不转换;
  2. 一方null、一方undefinedtruenull/undefined和任何其他值对比全为false
  3. 一方Symbol → 直接false;
  4. 一边字符串、一边数字:字符串转Number再对比;
  5. 一边布尔值:布尔转数字(true=1,false=0)再对比;
  6. 一边对象、一边原始值:对象走ToPrimitive转原始,再回头重走规则。

经典必背案例逐行解析

'123' == 123    // true 字符串转数字
'' == null      // false 规则2,null只认undefined
'' == 0         // true ''转数字0
[] == 0         // true []toString='' → ''==0
[] == ''        // true 数组转空串
[] == ![]       // true  ![]是false → []==false → []转'',false转0 → 0==0
null == undefined // true

经典骚题:a==1 && a==2 && a==3

利用==每次对比都会调用valueOf:

let a = {
  value: 0,
  valueOf() {
    this.value++;
    return this.value;
  }
}
console.log(a == 1 && a == 2 && a == 3); // true

每次比较触发一次valueOf自增,连续三次刚好命中。


五、null与undefined深度区别

  1. 含义不同
  • undefined:变量已声明、未赋值;函数无返回值;对象不存在属性;代表未定义
  • null:手动赋值空对象占位;预期要返回对象但无结果,主动赋值空;代表空值
  1. typeof历史bug
    typeof null === 'object',源于早期32位系统二进制类型标签:对象以000开头,null全二进制为0,被误判;规范无法修改,保留至今。

  2. 相等判断
    null == undefined → truenull === undefined → false

  3. 安全获取undefined
    undefined不是JS保留字,可以被变量覆盖;推荐用void 0获取绝对安全的undefined。


六、高频热身小题全套答案

'123' == 123        // true
'' == null          // false
'' == 0             // true
[] == 0             // true
[] == ''            // true
[] == ![]           // true
null == undefined   // true
Number(null)        // 0
Number('')          // 0
parseInt('')        // NaN
{}+10               // "[object Object]10"

七、开发落地建议

  1. 判断类型:优先Object.prototype.toString.call;简单场景用typeof;尽量不用constructor;
  2. 相等判断:业务一律用===严格相等,规避==隐式转换坑;
  3. 对象传参:牢记传地址、改属性生效,重新赋值不影响外部
  4. 空值:未知对象用null初始化;未赋值变量自然为undefined。