面试官:说说JavaScript中的数据类型?存储上的差别?

76 阅读12分钟

前言

JavaScript中,我们可以分成两种类型:

  • 基本类型
  • 复杂类型

两种类型的区别是:存储位置不同

一、基本类型

number
null
string
undefined
boolean
symbol

1. number(数字类型)

number 是 JavaScript 中用于表示「整数」和「浮点数」的基本类型,所有数值均以 64 位双精度浮点数 格式存储(即不区分整数和小数,统一按浮点数处理)。

核心特性

  • 可表示整数、小数、科学计数法数值;
  • 包含特殊值:NaNInfinity-Infinity

示例代码

javascript

运行

// 1. 整数
let intNum = 10;
// 2. 浮点数
let floatNum = 3.14;
// 3. 科学计数法(等同于 12300)
let sciNum = 1.23e4;
// 4. 特殊值:NaN(Not a Number,非数字)
let nanResult = "abc" * 2; // 字符串与数字运算,结果为 NaN
// 5. 特殊值:Infinity(正无穷)、-Infinity(负无穷)
let infinityNum = 1e1000; // 超出最大可表示数值,结果为 Infinity

注意点

  • NaN 与任何值(包括自身)比较都不相等,需用 isNaN() 函数判断是否为非数字;
  • 64 位浮点数无法精确表示某些小数(如 0.1 + 0.2 = 0.30000000000000004),需通过 toFixed() 或第三方库处理精度问题。

2. null(空值类型)

null 是一个特殊的基本类型,表示「空的对象引用」,语义上用于明确表示 “变量期望指向一个对象,但目前没有值”(即 “无对象” 状态)。

核心特性

  • 唯一值为 null
  • typeof 检测结果为 object(JavaScript 早期设计遗留问题,本质仍是基本类型)。

示例代码

javascript

运行

// 声明一个变量,明确其后续可能指向对象,初始值设为 null
let user = null;
// 后续可赋值为对象
user = { name: "Alice", age: 25 };

// 检测类型(注意:typeof null 返回 "object",需特殊判断)
console.log(typeof user); // 赋值对象后,输出 "object"
console.log(user === null); // 判断是否为空值,输出 false(此时 user 已指向对象)

注意点

  • 不要与 undefined 混淆:null 是 “主动赋值的空”,undefined 是 “未赋值的未定义”;
  • 常用于函数参数,表示 “未传入有效对象”,或作为函数返回值,表示 “无结果”。

3. string(字符串类型)

string 用于表示「文本数据」,是由零个或多个 Unicode 字符组成的不可变序列(一旦创建,字符串内容无法修改,修改时会创建新字符串)。

核心特性

  • 可用单引号 '、双引号 "、模板字面量 ` 创建;
  • 模板字面量支持多行文本和表达式嵌入(${表达式});
  • 不可变性:任何字符串操作(如 slicereplace)都会返回新字符串,原字符串不变。

示例代码

javascript

运行

// 1. 单引号创建
let str1 = 'Hello';
// 2. 双引号创建
let str2 = "World";
// 3. 模板字面量(支持多行和表达式)
let name = "Bob";
let greeting = `Hello, ${name}! 
Today is a nice day.`; // 多行文本无需拼接
console.log(greeting); 
// 输出:
// Hello, Bob! 
// Today is a nice day.

// 4. 字符串不可变(原字符串 str1 未改变,返回新字符串)
let newStr = str1.slice(0, 3);
console.log(str1); // 输出 "Hello"
console.log(newStr); // 输出 "Hel"

常用 API

  • length:获取字符串长度("abc".length → 3);
  • includes(substr):判断是否包含子串("abc".includes("ab") → true);
  • split(separator):按分隔符分割为数组("a,b,c".split(",") → ["a","b","c"])。

4. undefined(未定义类型)

undefined 表示「变量已声明但未赋值」的状态,或 “函数无显式返回值” 时的默认返回值,语义上是 “值不存在”。

核心特性

  • 唯一值为 undefined
  • 无需主动赋值,变量声明后未赋值即默认是 undefined

示例代码

javascript

运行

// 1. 变量声明未赋值,默认是 undefined
let unassignedVar;
console.log(unassignedVar); // 输出 undefined

// 2. 函数无显式返回值,默认返回 undefined
function noReturnFunc() {
  console.log("我没有 return 语句");
}
let funcResult = noReturnFunc();
console.log(funcResult); // 输出 undefined

// 3. 对象未定义的属性,值为 undefined
let obj = { name: "Charlie" };
console.log(obj.age); // 输出 undefined

注意点

  • 避免主动赋值 undefined(如 let a = undefined),应使用 null 表示 “主动空值”;
  • typeof undefined 返回 "undefined",可用于判断变量是否未赋值(但需注意 “变量未声明” 时用 typeof 也返回 "undefined",需结合上下文判断)。

5. boolean(布尔类型)

boolean 是用于「逻辑判断」的基本类型,只有两个值:true(真)和 false(假),常用于条件语句(ifwhile)、逻辑运算(&&||!)。

核心特性

  • 唯一值为 true 和 false
  • 其他类型可通过「强制类型转换」转为布尔值(Boolean(值) 或 !!值)。

示例代码

javascript

运行

// 1. 直接声明布尔值
let isLogin = true;
let hasPermission = false;

// 2. 条件语句中使用
if (isLogin) {
  console.log("用户已登录");
}

// 3. 强制类型转换(哪些值会转为 false?)
// 「假值(Falsy)」:false、0、""(空字符串)、null、undefined、NaN
// 「真值(Truthy)」:除假值外的所有值(包括 1、"abc"、{}、[] 等)
console.log(Boolean(0)); // 输出 false
console.log(Boolean("hello")); // 输出 true
console.log(!!null); // 双重非运算,等同于 Boolean(null),输出 false

注意点

  • 逻辑运算 && 和 || 是 “短路运算”:a && b 若 a 为假则直接返回 aa || b 若 a 为真则直接返回 a
  • 避免直接用 == 比较布尔值(如 if (isLogin == true)),直接写 if (isLogin) 更简洁。

6. symbol(符号类型)

symbol 是 ES6 新增的基本类型,用于创建「唯一的标识符」,每个 symbol 实例都是唯一的(即使描述相同,也不相等),常用于对象属性名以避免冲突。

核心特性

  • 通过 Symbol(描述) 创建,描述仅用于调试,不影响唯一性;
  • 不能与其他类型进行运算(如 symbol + 1 会报错);
  • 作为对象属性名时,需用方括号 [] 包裹,且不会被 for...inObject.keys() 遍历到(可通过 Object.getOwnPropertySymbols() 获取)。

示例代码

javascript

运行

// 1. 创建 symbol(描述相同,实例不同)
let sym1 = Symbol("id");
let sym2 = Symbol("id");
console.log(sym1 === sym2); // 输出 false(唯一特性)

// 2. 作为对象属性名(避免属性冲突)
const user = {
  name: "Dave",
  [sym1]: 1001, // 用 symbol 作为属性名,需用 [] 包裹
  [sym2]: 1002
};
console.log(user[sym1]); // 输出 1001(需通过 symbol 访问)

// 3. 遍历 symbol 属性(普通遍历无法获取)
console.log(Object.keys(user)); // 输出 ["name"](不包含 symbol 属性)
console.log(Object.getOwnPropertySymbols(user)); // 输出 [Symbol(id), Symbol(id)]

注意点

  • Symbol.for(描述) 可创建 “全局共享的 symbol”(相同描述会返回同一个实例),区别于 Symbol(描述) 的 “局部唯一”;
  • 常用于框架 / 库的内部属性标记,或定义对象的 “私有属性”(非严格私有,但可避免外部误操作)。

基本类型总结表

类型取值范围核心特点typeof 检测结果
number整数、浮点数、NaNInfinity64 位双精度浮点数,可表示正负无穷和非数字"number"
null仅 null空对象引用,typeof 误判为 object"object"
stringUnicode 字符序列不可变,支持模板字面量和表达式嵌入"string"
undefined仅 undefined未赋值状态,函数无返回值时默认返回"undefined"
booleantruefalse逻辑判断,可通过强制转换获取真假值"boolean"
symbol唯一标识符(Symbol(描述) 创建)实例唯一,常用于对象属性名避免冲突"symbol"

二、引用类型

复杂类型统称为Object,我们这里主要讲述下面三种:

  • Object
  • Array
  • Function

一、Object(普通对象)

Object 是引用类型的 “基类”,用于存储键值对(key-value)  形式的数据,是构建其他复杂类型的基础。

1. 常用创建方式:对象字面量(最简洁)

对象字面量用 {} 包裹键值对,语法灵活,是实际开发中最常用的创建方式。

javascript

运行

let person = {
    name: "Nicholas",  // 键名可省略引号(符合标识符规则时,如字母/数字/下划线开头)
    "age": 29,         // 键名含特殊字符(如空格、连字符)或需保留关键字时,必须用字符串引号
    5: true            // 键名为数值时,会自动转为字符串(访问时可用 person[5] 或 person["5"])
};

2. 核心特性

  • 键的类型:默认是字符串(数值键会自动转字符串,Symbol 键可用于避免属性冲突);

  • 值的类型:支持任意类型(基本类型如 string/number,引用类型如 Array/Function);

  • 动态操作:创建后可随时添加、修改、删除属性:

    javascript

    运行

    // 新增属性
    person.gender = "male";
    // 修改属性
    person.age = 30;
    // 删除属性(用 delete 关键字)
    delete person[5];
    // 访问属性(点语法或方括号语法)
    console.log(person.name);    // 点语法:输出 "Nicholas"
    console.log(person["age"]);  // 方括号语法:输出 30
    

二、Array(数组)

Array 是专门用于存储有序数据集合的引用类型,与其他语言的数组相比,JavaScript 数组更灵活(支持混合类型、动态扩容)。

1. 核心特性(你的示例已体现)

  • 混合类型存储:每个槽位可存任意类型数据,无需统一类型;

  • 动态大小:无需预先指定长度,添加数据时会自动扩容。

javascript

运行

// 混合类型数组(字符串、数值、对象)
let colors = ["red", 2, { age: 20 }];
// 动态添加数据(push() 方法在数组末尾添加元素)
colors.push(2); 
console.log(colors); // 输出:["red", 2, {age: 20}, 2]

2. 常用操作补充

除 push() 外,数组还有大量实用方法,覆盖 “增删改查、遍历、转换” 等场景:

方法功能描述示例
pop()删除末尾元素,返回删除值colors.pop(); // 移除最后一个 2
shift()删除开头元素,返回删除值colors.shift(); // 移除 "red"
map()遍历并返回新数组(数据转换)const nums = [1,2]; nums.map(n => n*2); // [2,4]
filter()筛选符合条件的元素,返回新数组const even = [1,2,3].filter(n => n%2===0); // [2]

3. 注意点

  • 数组的 “索引” 本质是字符串键(如 arr[0] 等同于 arr["0"]),但 length 属性会自动根据最大数字索引更新;
  • 避免手动修改 length(如 colors.length = 2 会截断数组,丢失后续元素)。

三、Function(函数)

函数是封装可执行代码块的引用类型,本质是 Function 类型的实例,因此也有属性和方法(如 name 属性、call() 方法)。函数是 JavaScript 的 “一等公民”,可作为变量、参数、返回值传递。

1. 三种常见声明方式(你的示例已覆盖)

(1)函数声明(函数提升,可提前调用)

javascript

运行

// 函数声明:function 关键字 + 函数名 + 参数 + 代码块
function sum(num1, num2) {
    return num1 + num2;
}
// 可在声明前调用(函数提升特性)
console.log(sum(1, 2)); // 输出 3
(2)函数表达式(无提升,需先声明后调用)

javascript

运行

// 函数表达式:将匿名函数赋值给变量
let sum = function(num1, num2) {
    return num1 + num2;
};
// 必须在赋值后调用(否则报错)
console.log(sum(3, 4)); // 输出 7
(3)箭头函数(ES6 新增,简化语法,无独立 this)

javascript

运行

// 箭头函数:(参数) => 代码块(单条返回语句可省略 {} 和 return)
let sum = (num1, num2) => num1 + num2; // 简化写法,等同于 return num1 + num2
console.log(sum(5, 6)); // 输出 11

// 多条语句需用 {} 包裹并显式 return
let multiply = (num1, num2) => {
    const result = num1 * num2;
    return result;
};

2. 核心特性

  • 可调用性:通过 函数名(参数) 执行代码(如 sum(1,2));
  • this 绑定:普通函数的 this 指向调用者(如 obj.fn() 中 this 指向 obj),箭头函数无独立 this,继承自父级作用域;
  • 作为引用类型:可添加属性(如 sum.version = "1.0"),也可作为参数传递(如数组 map 方法的回调)。

三.存储区别

一、先搞懂两个 “仓库” 的区别

JS 存数据需要两个 “仓库”,分工不同:

  • 小仓库(对应 “栈”) :空间小、拿东西快,但只能放 “固定大小、简单的东西”(比如数字 10、字符串 “abc”),用完自动清理。
  • 大仓库(对应 “堆”) :空间大、能放 “复杂、大小会变的东西”(比如对象、数组),但拿东西慢,需要 “门牌号” 才能找到。

二、基本类型:小仓库里 “直接存东西”

基本类型(数字、字符串、布尔这些)都很 “简单”,直接存在小仓库里,变量和值是 “绑定死” 的。

1. 存数据:变量和值 “贴一起”

比如写 let a = 10

  • 相当于在小仓库里找个格子,格子上贴个标签 “a”,格子里直接放 “10”—— 以后找 “a”,直接从这个格子里拿 10 就行,不用绕弯。

2. 赋值:复制一份 “值”,各玩各的

再写 let b = a

  • 小仓库会新找一个格子,贴标签 “b”,然后把 “a” 格子里的 10 “复印” 一份,放进 “b” 的格子里。
  • 现在 “a” 和 “b” 是两个独立的格子,各存各的 10—— 哪怕后来把 “a” 改成 20(a = 20),“b” 的格子里还是 10,互不影响。

三、引用类型:大仓库存 “东西”,小仓库存 “门牌号”

引用类型(对象、数组、函数这些)都很 “复杂”(比如对象能加属性、数组能加元素),得存在大仓库里,小仓库只存 “大仓库的门牌号”。

1. 存数据:变量只记 “门牌号”

比如写 let obj1 = {}(空对象):

  • 先在大仓库里找个房间,把空对象 {} 放进这个房间,给房间编个门牌号(比如 “1001”);
  • 然后在小仓库找个格子,贴标签 “obj1”,格子里不存对象,只存门牌号 “1001”—— 以后用 “obj1”,得先看小仓库的门牌号,再去大仓库 “1001” 房间拿对象。

2. 赋值:复制 “门牌号”,共享一个 “房间”

再写 let obj2 = obj1

  • 小仓库新找个格子贴 “obj2”,然后把 “obj1” 格子里的门牌号 “1001” 复印一份,放进 “obj2” 的格子里;
  • 现在 “obj1” 和 “obj2” 的小仓库格子里,都是 “1001” 这个门牌号 —— 它们指向大仓库同一个房间!
  • 所以你给 obj1 加属性(obj1.name = "张三"),再看 obj2.name 也是 “张三”—— 因为改的是大仓库 “1001” 房间里的对象,两个变量共用这个对象。

四、一句话总结:区别就在 “赋值后改数据会不会影响对方”

  • 基本类型:赋值是 “复制值”,改一个不影响另一个(比如 a 和 b 各存 10,a 改 20,b 还是 10);
  • 引用类型:赋值是 “复制门牌号”,改一个会影响另一个(比如 obj1 和 obj2 都指着 “1001” 房间,改房间里的东西,两个都能看到)。