JavaScript 变量与数据类型详解

小飞兽 JavaScript 487 次阅读 2026-06-12

JavaScript 变量与数据类型详解

Introduction(引言)

JavaScript 是 Web 开发的核心语言,而变量与数据类型则是 JavaScript 编程的基石。无论你是刚刚入门的前端新人,还是希望夯实基础的开发者,理解变量声明、数据类型转换以及它们在内存中的行为,都是写出高质量代码的第一步。本篇文章将系统讲解 JavaScript 中的变量声明方式(var、let、const)、七大原始类型与对象类型、类型检测方法、隐式转换规则,以及实战中常见的问题与解决方案,帮助你建立清晰、完整的 JavaScript 类型系统认知。

一、变量声明:var、let、const 的选择

1.1 三种声明方式对比

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升声明提升,初始化不提升(undefined)声明提升,但存在暂时性死区(TDZ)声明提升,但存在暂时性死区(TDZ)
重复声明允许不允许(SyntaxError)不允许(SyntaxError)
修改值可修改可修改常量不可重新赋值(但引用类型内部可改)

1.2 代码示例

// var 的函数作用域问题
function exampleVar() {
  if (true) {
    var fruit = '苹果'; // 函数作用域,不是块级作用域
  }
  console.log(fruit); // 输出:苹果,变量泄漏到函数体
}
exampleVar();

// let 的块级作用域
function exampleLet() {
if (true) {
let dessert = '蛋糕';
const PI = 3.14159;
console.log(dessert); // 输出:蛋糕
}
// console.log(dessert); // ReferenceError: dessert is not defined
// console.log(PI); // ReferenceError: PI is not defined
}
exampleLet();

// const 的本质
const person = { name: '小明', age: 25 };
person.age = 26; // ✅ 允许修改对象属性
person.gender = '男'; // ✅ 允许添加新属性
// person = {}; // ❌ Assignment to constant variable

console.log(person); // { name: '小明', age: 26, gender: '男' }

1.3 变量提升与暂时性死区

// var 的变量提升
console.log(hoistedVar); // 输出:undefined(声明提升,值为undefined)
var hoistedVar = '我被提升了';

// let 的暂时性死区(TDZ)
// console.log(tdzVar); // ReferenceError: Cannot access 'tdzVar' before initialization
let tdzVar = '我不会在TDZ期间被访问';
console.log(tdzVar); // 正常输出

// 函数声明 vs 函数表达式
console.log(declared()); // 输出:我是一个函数声明
function declared() { return '我是一个函数声明'; }

console.log(expressed()); // TypeError: expressed is not a function
var expressed = function() { return '我是一个函数表达式'; };

二、JavaScript 七大数据类型

2.1 原始类型(Primitive Types)

JavaScript 共有 7 种原始类型,它们存储在调用栈中,赋值时传递的是值的副本。

// 七大原始类型
const str   = '你好,世界';        // String
const num   = 42;                  // Number
const big   = 9007199254740993n;   // BigInt
const bool  = true;                // Boolean
const nul   = null;                // Null(历史上null是对象类型的bug)
const und   = undefined;           // Undefined
const sym   = Symbol('唯一标识');  // Symbol

console.log(typeof str); // "string"
console.log(typeof num); // "number"
console.log(typeof big); // "bigint"
console.log(typeof bool); // "boolean"
console.log(typeof nul); // "object" (历史bug,null的实际类型就是null)
console.log(typeof und); // "undefined"
console.log(typeof sym); // "symbol"

2.2 对象类型(Object Types)

对象类型存储在堆内存中,变量保存的是引用(内存地址)。

// Object(普通对象)
const user = { name: '小红', score: 98 };

// Array(数组,本质是特殊对象)
const languages = ['JavaScript', 'Python', 'Go'];

// Function(函数,本质是可调用对象)
const add = function(a, b) { return a + b; };

// Date、RegExp、Map、Set...
const now = new Date();
const map = new Map();

console.log(typeof user); // "object"
console.log(typeof languages); // "object"
console.log(typeof add); // "function"(function是对象的子类型)
console.log(typeof now); // "object"
console.log(typeof map); // "object"

三、类型检测:typeof 与 instanceof

// typeof:适合检测原始类型(除了null和array)
console.log(typeof 'hello');   // "string"
console.log(typeof 123);       // "number"
console.log(typeof true);      // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol());  // "symbol"
console.log(typeof null);      // "object" (JS历史bug)

// typeof 对数组和对象都返回"object",无法区分
console.log(typeof []); // "object"
console.log(typeof {}); // "object"

// instanceof:检测原型链,适合检测对象类型
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log('hello' instanceof String); // false(原始类型)
console.log(new String('hello') instanceof String); // true

// 最佳实践:Array.isArray()
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray('array')); // false

// 严格判断null
function isNull(val) {
return val === null;
}
console.log(isNull(null)); // true
console.log(isNull(undefined)); // false

四、类型转换:隐式转换与显式转换

4.1 隐式转换(自动类型转换)

// 字符串拼接
console.log('年龄:' + 25);       // "年龄:25"(数字→字符串)
console.log('100' + 50);          // "10050"(字符串拼接)

// 算术运算中的隐式转换
console.log('6' - 2); // 4(字符串→数字)
console.log('6' * '2'); // 12
console.log('hello' - 1); // NaN(无法转为数字)
console.log(true + 1); // 2(true→1)
console.log(false + 1); // 1(false→0)
console.log(null + 1); // 1(null→0)
console.log(undefined + 1); // NaN(undefined→NaN)

// 逻辑运算中的隐式转换(Truthy/Falsy)
console.log(Boolean('')); // false(空字符串)
console.log(Boolean(0)); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(' ')); // true(非空字符串)
console.log(Boolean([])); // true(空数组也是truthy)
console.log(Boolean({})); // true(空对象也是truthy)

// 宽松相等(==)的隐式转换
console.log(0 == ''); // true(''→0)
console.log(0 == false); // true(false→0)
console.log('' == false); // true
console.log(null == undefined); // true
console.log('100' == 100); // true
console.log('100' === 100); // false(严格相等,不转换)

// 特殊比较
console.log(NaN == NaN); // false(NaN不等于自身)
console.log(NaN === NaN); // false

4.2 显式转换(手动类型转换)

// 转字符串
String(123);              // "123"
String(true);             // "true"
(123).toString();         // "123"
Number.toString(16);      // 报错,数字无此方法

// 转数字
Number('42'); // 42
Number(' 42 '); // 42(自动trim)
Number('3.14'); // 3.14
Number('123abc'); // NaN
parseInt('42px'); // 42(解析到非数字停止)
parseFloat('3.14m/s'); // 3.14
parseInt('100', 2); // 4(二进制转十进制)
parseInt('FF', 16); // 255(十六进制转十进制)

// 转布尔
Boolean(1); // true
Boolean(0); // false
Boolean('false'); // true(字符串'false'非空)
!!'hello'; // true(双重取反简写)

// 转对象(装箱)
Object(42); // Number {42}
Object('hello'); // String {"hello"}
Object(true); // Boolean {true}

// BigInt 转换
BigInt(42); // 42n
BigInt('123456789012345'); // 123456789012345n
// BigInt(3.14); // 报错,小数不能转BigInt

五、运行效果截图说明

将上述所有代码复制到浏览器控制台(按 F12 → Console)或 Node.js 环境中运行,应得到以下类型的结果输出:

// 部分运行结果示例:
'你好,世界'          // string
42                    // number
true                  // boolean
null                  // object(历史bug)
undefined             // undefined
Symbol('id')          // symbol
9007199254740993n     // bigint

// 表达式结果
'年龄:' + 25 // "年龄:25"
'6' - 2 // 4
true + 1 // 2
Boolean([]) // true
0 == '' // true
0 === '' // false

六、常见问题与解决方案

Q1:为什么 typeof null 返回 "object"?

这是 JavaScript 1995年创立时的历史遗留 bug。Brendan Eich 设计的早期版本中,null 的值是 0x00(对象指针),typeof 检测到 0x00 就返回了 "object"。这个 bug 被网站广泛依赖,至今无法修复。

Q2:0.1 + 0.2 !== 0.3 是什么原因?

console.log(0.1 + 0.2);           // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);   // false

// 解决方案:使用整数运算或 toFixed
function equals(a, b, precision = 10) {
return Math.abs(a - b) < 1 / Math.pow(10, precision);
}
console.log(equals(0.1 + 0.2, 0.3)); // true

// 或者使用 toFixed(返回字符串)
console.log((0.1 + 0.2).toFixed(2)); // "0.30"

Q3:如何安全地比较浮点数?

// 使用 Number.EPSILON(最小的可表示差值)
function epsEquals(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}
console.log(epsEquals(0.1 + 0.2, 0.3)); // true

Q4:BigInt 有什么使用限制?

const bigNum = 9007199254740993n;

// BigInt 不能与普通 Number 混合运算
// console.log(bigNum + 1); // TypeError
console.log(bigNum + 1n); // 9007199254740994n

// BigInt 不能用于 Math. 系列函数
// Math.sqrt(bigNum); // TypeError

// JSON序列化时需要自定义处理
const json = JSON.stringify({ value: bigNum });
// const parsed = JSON.parse(json); // value变成字符串
// 正确做法:
const obj = { value: bigNum };
obj.toJSON = function() { return this.value.toString(); };
console.log(JSON.stringify(obj)); // {"value":"9007199254740993"}

Q5:const 对象属性可以修改吗?

const config = { theme: 'dark' };
config.theme = 'light'; // ✅ 完全合法
// config = {};         // ❌ 重新赋值整个对象不行

// 如需冻结(浅冻结)
const frozen = Object.freeze({ x: 1 });
// frozen.x = 2; // 在严格模式下报错,非严格模式静默失败
console.log(frozen.x); // 1

// 深冻结需要递归
function deepFreeze(obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return Object.freeze(obj);
}

七、延伸阅读

    • JavaScript 变量提升详解:深入理解 var、function 声明的提升机制以及 let/const 的 TDZ 原理
    • 《You Don't Know JS Yet》- Types & Grammar:Kyle Simpson 撰写的深入系列,专门剖析 JavaScript 类型系统的细节与陷阱
    • ECMAScript 规范tc39.es/ecma262 — JavaScript 的官方语言规范,了解 typeof null === 'object' 的历史背景
    • BigInt 提案:ES2020引入的 BigInt 类型用于处理超过 Number 安全整数范围的大整数运算
  • Symbol 的应用场景:唯一属性键、Symbol.for() 全局注册表、Symbol.iterator 迭代器协议等高级用法
  • WeakMap/WeakSet:使用弱引用的键值集合,适合存储对象私有数据或缓存场景

总结

JavaScript 的变量与数据类型体系看似简单,实则暗藏诸多细节:var 的函数作用域与提升陷阱、let/const 的块级作用域与 TDZ、原始类型与引用类型的根本区别、类型检测的正确姿势、隐式转换的种种规则,以及 BigInt 和 Symbol 等现代特性。掌握这些核心概念,能够让你在调试代码时快速定位问题,在设计数据结构时做出更合理的决策,为后续深入学习 JavaScript 高级特性(如原型链、闭包、异步编程)打下坚实的基础。