当前位置: 首页 > news >正文

JavaScript变量与数据类型详解

JavaScript变量与数据类型详解

Posted on 2026-06-15 00:00  lzhdim  阅读(0)  评论(0)    收藏  举报
一、变量声明在 JavaScript 中, 我们主要有三种声明变量的方式: varlet 和 const. 它们看起来差不多, 但实际上差别可大了!1) 老派风的varvar 是 JavaScript 最早的变量声明方式, 它有一些"独特"的行为: 
var name = "小明";console.log(name); // 输出: 小明
// var 允许重复声明var name = "小红";console.log(name); // 输出: 小红
// var 有变量提升的特性console.log(age); // 输出: undefined (不会报错!)var age = 18;
问题:
  • 可以重复声明,容易导致变量被意外覆盖

  • 存在变量提升(hoisting), 代码执行前会把声明提到作用域顶部

  • 只有函数作用域,没有块级作用域

 

2) 现代派的let

ES6 引入了 let, 解决了 var 的很多问题: 

let name = "小明";console.log(name); // 输出: 小明
// let 不允许重复声明let name = "小红"; // 报错: Identifier 'name' has already been declared
// let 也有提升, 但不允许在声明前使用(暂时性死区)console.log(age); // 报错: Cannot access 'age' before initializationlet age = 18;
// let 有块级作用域if (true) {  let food = "苹果";  console.log(food); // 输出: 苹果}console.log(food); // 报错: food is not defined
3) 常量constconst 用于声明常量, 声明后不能重新赋值:
const PI = 3.14159;console.log(PI); // 输出: 3.14159
PI = 3.14; // 报错: Assignment to constant variable
// 但注意! 对于对象和数组, const 只保证引用不变const person = { name: "小明" };person.name = "小红"; // 这是允许的!console.log(person); // 输出: { name: "小红" }
person = { name: "小刚" }; // 报错: Assignment to constant variable
4) 三者之间的对比
特性varletconst
作用域 函数作用域 块级作用域 块级作用域
重复声明 允许 不允许 不允许
变量提升 有(但不可用) 有(但不可用)
可重新赋值 可以 可以 不可以
必须初始化 不需要 不需要 需要
建议:
  1. 默认使用 const

  2. 需要重新赋值的变量用 let

  3. 避免使用 var (除非你需要兼容非常老的浏览器)


二、JavaScript的数据类型1) 基本类型

基本类型有 7 种:

  1. number - 数字

  2. string - 字符串

  3. boolean - 布尔值

  4. null - 空值

  5. undefined - 未定义

  6. symbol - 符号 (ES6新增)

  7. bigint - 大整数 (ES2020新增)


特点:
  • 存储在栈内存中

  • 按值访问

  • 不可变(immutable)

  • 比较时比较的是值


示例:
let a = 10;let b = a; // b 是 a 的值的一个副本a = 20;console.log(a); // 20console.log(b); // 10
let str = "hello";str[0] = "H"; // 字符串是不可变的,这行代码不会报错但也不起作用console.log(str); // 输出: "hello"
2) 引用类型

引用类型主要是:

Object (包括数组、函数、日期、正则表达式等)

特点:
  • 存储在堆内存中

  • 按引用访问

  • 可变(mutable)

  • 比较时比较的是引用(内存地址)


示例:
let obj1 = { name: "小明" };let obj2 = obj1; // obj2和 obj1指向同一个对象
obj1.name = "小红";console.log(obj2.name); // 输出:"小红" (因为两者引用同一个对象)
let arr1 = [1, 2, 3];let arr2 = arr1;arr1.push(4);console.log(arr2); // 输出:[1, 2, 3, 4]
3) 基本类型与引用类型的比较
// 基本类型比较let num1 = 5;let num2 = 5;console.log(num1 === num2); // true (值相同)
// 引用类型比较let obj1 = { name: "小明" };let obj2 = { name: "小明" };console.log(obj1 === obj2); // false (虽然内容相同, 但引用不同)
let obj3 = obj1;console.log(obj1 === obj3); // true (引用相同)
4) 特殊的null和undefined

null 和 undefined 都表示"无", 但有细微差别: 

  • undefined 表示变量已声明但未赋值

  • null 表示变量有值, 但这个值是"空"

let a;console.log(a); //undefined (声明但未赋值)
let b = null;console.log(b); //null (明确赋值为空)
// 有趣的现象console.log(typeof null); //"object" (这是历史遗留bug)console.log(typeof undefined); //"undefined"

三、类型检测

1) typeof运算符

typeof 是最常用的类型检测方法:

typeof 42; // "number"typeof "hello"; // "string"typeof true; // "boolean"typeof undefined; // "undefined"typeof Symbol(); // "symbol"typeof 123n; // "bigint"
// 两个特殊情况typeof null; // "object" (历史遗留问题)typeof function() {}; // "function"
问题:
  • 不能区分数组和普通对象(都返回 "object")

  • 对 null 返回 "object"

 

2) instanceof运算符

instanceof 用于检测对象是否是某个构造函数的实例:

let arr = [1, 2, 3];arr instanceof Array; //truearr instanceof Object; //true (因为数组也是对象)
let date = new Date();date instanceof Date; //truedate instanceof Object; //true
// 基本类型不能用 instanceof检测42 instanceof Number; //false

3) Object.prototype.toString.call()

这是最准确的类型检测方法:

Object.prototype.toString.call(42); //"[object Number]"Object.prototype.toString.call("hello"); //"[object String]"Object.prototype.toString.call(true); //"[object Boolean]"Object.prototype.toString.call(null); //"[object Null]"Object.prototype.toString.call(undefined); //"[object Undefined]"Object.prototype.toString.call([]); //"[object Array]"Object.prototype.toString.call({}); //"[object Object]"Object.prototype.toString.call(new Date()); //"[object Date]"

4) Array.isArray()

专门用于检测数组:

Array.isArray([]); //trueArray.isArray({}); //false

5) 最佳实践

  1. 检测基本类型(除了null)用 typeof

  2. 检测数组用 Array.isArray()

  3. 检测null用 === null

  4. 需要精确检测对象类型时用 Object.prototype.toString.call()

  5. 知道对象的具体构造函数时可以用 instanceof

 

四、类型转换JavaScript 是弱类型语言, 会自动进行类型转换. 这既是便利也是坑!1) 显示类型转换字符串转换:
String(123); //"123"String(true); //"true"String(null); //"null"String(undefined); //"undefined"
//等价于123.toString(); //"123"true.toString(); //"true"
//注意:null和 undefined没有toString方法
数字转换:
Number("123"); //123Number("123abc"); //NaN(Not a Number)Number(true); //1Number(false); //0Number(null); //0Number(undefined); //NaN
//其他方法parseInt("123px"); //123parseFloat("3.14.15"); //3.14+"123"; //123 (一元加号运算符)
布尔值转换:
Boolean(1); // trueBoolean(0); //falseBoolean(""); //falseBoolean("hello"); //trueBoolean(null); //falseBoolean(undefined); //falseBoolean({}); //trueBoolean([]); //true
//等价于!!1; //true(双感叹号技巧)!!0; //false
2) 隐式类型转换JavaScript 在某些情况下会自动进行类型转换:字符串拼接
"今年是" + 2023; //"今年是2023" (数字转为字符串)1 + "2"; //"12"
数学运算
"10" - 5; // 5 (字符串转为数字)"10" * "2"; // 20"10" / "2"; // 5"abc" - 1; // NaN
//注意加法的特殊性1 + 2 + "3"; // "33" (从左到右计算)"1" + 2 + 3; // "123"
布尔值
if ("hello") { //"hello"被转为 true  console.log("执行");}
//常见的假值(falsy values)://false, 0, "", null, undefined, NaN//其他所有值都是真值(true)
==和===的区别
1 == "1"; //true(类型转换后比较)1 === "1"; //false(严格比较,不转换类型)
0 == false; //true0 === false; //false
null == undefined; //truenull === undefined; //false
总是应该使用 === 和 !== 避免隐式转换带来的意外行为.五、深拷贝与浅拷贝1) 浅拷贝 (只拷贝第一层属性)
let person = { name: "小明", hobbies: ["篮球", "音乐"] };
//方法1:Object.assignlet copy1 = Object.assign({}, person);
//方法2:展开运算符let copy2 = { ...person };
//修改拷贝后的对象copy1.name = "小红";copy1.hobbies.push("游泳");
console.log(person.name); //"小明"(未受影响)console.log(person.hobbies); //["篮球", "音乐", "游泳"](受影响!)
2) 深拷贝 (完全拷贝所有层级)
//方法1: JSON.parse(JSON.stringify()) (最简单但有局限)let deepCopy1 = JSON.parse(JSON.stringify(person));
//方法2: 使用工具库如lodash的_.cloneDeep//let deepCopy2 = _.cloneDeep(person);
//方法3: 手动实现递归拷贝function deepClone(obj) {  if (obj === null || typeof obj !== "object") return obj;  let clone = Array.isArray(obj) ? [] : {};  for (let key in obj) {    if (obj.hasOwnProperty(key)) {      clone[key] = deepClone(obj[key]);    }  }  return clone;}
let deepCopy3 = deepClone(person);
六、ES6+新特性1) 模板字符串
const name = "小明";const age = 18;console.log(`我叫${name}, 今年${age}岁`); //我叫小明, 今年18岁
2) 解构赋值
//对象解构const person = { name: "小明", age: 18 };const { name, age } = person;
//数组解构const arr = [1, 2, 3];const [first, second] = arr;
3) 新的原始类型Symbol:
const sym1 = Symbol("唯一标识");const sym2 = Symbol("唯一标识");console.log(sym1 === sym2); //false(每个Symbol都是唯一的)
//常用作对象的唯一键const obj = {  [sym1]: "这是Symbol作为键的值"};
BigInt:
const bigNum = 1234567890123456789012345678901234567890n;console.log(bigNum + 1n); //可以表示和操作超大整数