类型和值✅
JavaScript 中有几种数据类型 ?
答案
- 原始类型
- Undefined
- Null
- Boolean
- String
- Symbol
- Number 采用 IEEE 754 标准
- BigInt 属于新增类型
- 对象类型(引用类型)
- 内建对象 EMACScript 定义 一些常用对象类型如下
- Object
- Function
- Date
- Error
- Set
- WeakSet
- Map
- WeakMap
- Array
- RegExp
- Proxy
- Reflect
- 宿主对象 取决于运行环境,如 DOM、BOM..
- 自定义对象 用户创建的对象
- 内建对象 EMACScript 定义 一些常用对象类型如下
示例说明
面试官视角
该题一般作为面试热身题,帮助面试者快速进入状态,通过此题又可以引出很多知识点,来对面试者能力作进一步下探。
延伸阅读
- ecmascript language types 规范中定义的类型
- MDN 数据类型
什么是原始类型和引用类型,有什么区别?
答案
- 原始类型(Primitive Types) 也称值类型,直接存储数据信息,属于只读不可改的数据片段
包括以下几种类型:
undefined
:表示未定义的值。null
:表示空值或无值。boolean
:表示布尔值,只有两个可能的值:true
和false
。number
:表示数字,包括整数和浮点数。string
:表示字符串,是一系列字符的集合。symbol
:ES6 引入的一种新的原始数据类型,用于创建唯一的标识符。bigint
:ES11 引入的一种新的原始数据类型,用于表示任意精度的整数。
- 引用类型 所有对象都是引用类型,以键值对方式存储,其中值可以为原始类型或者引用类型。
原始类型和引用类型的核心区别在于,值类型是只读类型,不可修改,赋值操作会原样拷贝, 引用类型修改会直接对原始对象产生影响。 此外 JS 的赋值操作都是值拷贝。拷贝的值可以是原始类型也可以是引用类型。
举例说明
下面是一个简单的例子,来说明值类型和引用类型在操作时的区别:
// 值类型
let x = 1
const y = x
x = 2
console.log(x) // 输出2
console.log(y) // 输出1
// 值类型无法修改
const str = 'hello'
str[0] = 'b'
console.log(str) // 输出'hello',因为字符串是不可变的
// 引用类型
const a = { name: 'Tom', age: 20 }
const b = a
a.age = 30
console.log(a.age) // 输出30
console.log(b.age) // 输出30,原因是 a 和 b 指向同一个对象
答案解析
理解值和引用的概念才能避免在 js 中一些非预期的副作用。比如错误对引用对象的变更导致影响了所有消费引用对象的变量。 从数据的存储层面,可以理解为值就是一个内存区块直接存放的是数据对应的存储编码,比如 boolean 可能对应就是一个位存储 0 表示 false,1 表示 true。数值采用 IEEE 754 编码存储。字符串采用 UTF-16 编码存储等,而引用则存储的是一个地址指针,指向实际存储数据的内存位置。学习者可以结合 c 语言中的指针、值传递和引用传递的概念来进一步深入理解原始类型和引用类型
延伸阅读
- JavaScript高级程序设计(第5版) 第四章 原始类型和引用类型讲解
- javascript-references 图例讲解 JavaScript 引用
- ECMAScript 规范对赋值操作的描述 重点是看右值的获取逻辑,GetValue 说明了 JS 规范定义的赋值为值拷贝,也讲解了内部值提取的逻辑。
有哪些方法判断变量的类型
答案
方法 | 描述 | 优点 | 缺点/注意事项 |
---|---|---|---|
typeof 运算符 | 判断表达式返回的值的类型 | 用于快速判断是基础类型还是 | 对于 null 返回 "object" ,对于数组等对象类型均返回 "object" ,无法细分对象类型。 |
instanceof 运算符 | 判断一个对象是否是某个构造函数的实例。 | 可以准确判断自定义对象类型和内置对象类型(如 Array , Date )。 | 不能用于判断原始类型(会返回 false )。 |
Array.isArray() | 判断一个值是否为数组。 | 专门用于判断数组,简洁明了。 | 只能判断数组类型。 |
constructor 属性 | 返回创建实例对象的构造函数的引用。 | 可以获取对象的构造函数。 | constructor 属性可以被修改,因此判断结果可能不准确;对 null 和 undefined 无效。 |
Object.prototype.toString() | 方法通过读取对象内部的 [[Class]] 属性(一个内部属性,在 ES5 之后规范中被称为 [[NativeBrand]] 或类似的内部槽位,但概念相似)来获取其类型。当使用 .call() 或 .apply() 将上下文 this 绑定到要检查的变量时,它就能返回该变量的准确类型信息。 | 可以准确判断所有类型,包括原始类型和对象类型。 |
延伸阅读
null undefined 区别?
答案
- 语言层面
- null 表示值为空
- undefined 表示缺省值或默认值
- 功能层面:
- 数值转换
- null 会变为 0
- undefined 变为 NaN
- 数值转换
详细区别参考 阮一峰 undefined 与 null 的区别
有用过 Symbol 么,说一下?
答案
Symbol
是 ES6 引入的一种原始数据类型,用于生成唯一的标识符。每个 Symbol 都是唯一的,常用于对象属性名,避免命名冲突。
用途与说明:
-
唯一属性名
Symbol 可作为对象属性名,确保不会与其他属性冲突,适合用于框架、库等场景下扩展对象。const id = Symbol('id')
const obj = { [id]: 123 } -
模拟私有属性
由于 Symbol 属性不会被常规遍历(如for...in
、Object.keys
)枚举,可用来模拟类的私有属性。const _secret = Symbol('secret')
class Demo {
constructor () {
this[_secret] = 'hidden'
}
getSecret () {
return this[_secret]
}
} -
内置 Symbol 用于自定义行为
如Symbol.iterator
、Symbol.toStringTag
等,可自定义对象的迭代、类型描述等行为。const coll = {
* [Symbol.iterator] () {
yield 1
yield 2
}
}
for (const v of coll) console.log(v) -
全局 Symbol 注册表
使用Symbol.for(key)
可获取全局唯一的 Symbol,实现跨模块共享。const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
注意事项:
- Symbol 不能与字符串自动转换,需显式调用
String(symbol)
。 - Symbol 属性不会被 JSON 序列化。
- Symbol 主要用于避免属性名冲突、实现私有属性和自定义对象协议。
总结:
Symbol 提供了创建唯一属性名的能力,是实现对象扩展、私有属性和协议定制的重要工具。
0.1 + 02 !== 0.3
// 说出下面语句的执行结果,并解释原因
console.log(0.1 + 0.2)
console.log(0.1 + 0.2 === 0.3)
答案
返回 0.30000000000000004、 false
, 因为 js 浮点采用的是 IEEE-754 编码,浮点计算误差是由于编码方式导致的进度丢失,和引擎无关。
这里还有一个细节,采用 js 输出数值的时候最多输出的精度就是科学计数法能表示的精度 17 位。(注意不是输出的字符长度,而是输出的精度最多只能是科学计数法表示的 17 位数字),这也是 IEEE-754 规范中定义的,而非引擎实现。它的理论证明可以参考 Correctly Rounded Binary-Decimal and Decimal-Binary Conversions
要解决上述的精度误差可以使用如下策略
- 使用
Number.EPSILON
来比较两个浮点数是否接近:
function numbersAreCloseEnough (num1, num2) {
return Math.abs(num1 - num2) < Number.EPSILON
}
const result = 0.1 + 0.2
console.log(numbersAreCloseEnough(result, 0.3))
- 将浮点数乘以一个适当的倍数转换为整数进行计算,计算完成后再除以这个倍数转换回浮点数:
const num1 = 0.110
const num2 = 0.210
const sum = (num1 + num2) / 10
console.log(sum === 0.3)
- 使用第三方库,如
decimal.js
,它提供了更精确的十进制运算:
const Decimal = require('decimal.js')
const num1 = new Decimal('0.1')
const num2 = new Decimal('0.2')
const sum = num1.plus(num2)
console.log(sum.eq(0.3))
这些方法可以帮助您在处理浮点数运算时更准确地得到预期的结果。
答案解析
IEEE 754 浮点数 (64位双精度)
结构 (64位)
[S (1位)] [阶码 E (11位)] [尾数 M (52位)]
组件释义
- 符号 (S): 0 = 正 (+),1 = 负 (-)。
- 阶码 (E): 存储偏移后的指数。实际指数 = E存储值 - 1023。
- 尾数 (M): 存储规格化后的小数点后部分。实际有效数字为 1.M (隐藏位 1)。
计算公式
数值 = (-1)S × 2(E存储值 - 1023) × (1.M)
示例:十进制数 32.375
- 符号 (S): 0
- 实际指数: 5
- 阶码 (E存储值): 1028 (二进制: 10000000100)
- 尾数 (M分数部分): .00000011 (二进制)
- 尾数域 (M存储值, 52位):
0000001100000000000000000000000000000000000000000000 - IEEE 754 二进制表示 (64位):
0100000001000000001100000000000000000000000000000000000000000000 - 十六进制表示: 4040300000000000
延伸阅读
- IEEE 754 详细讲解 js IEEE 754 编码
- JavaScript 悟道 2-5 章讲解了 Number 一些细节
- iEEE 754 js 实现
- iEEE 754 online 在线工具查看转换
如何判断一个数值是整数?
答案
方法 | 优点 | 缺点 |
---|---|---|
Number.isInteger(value) | 语义清晰,专为判断整数设计,支持所有数值类型(包括负数、0、正数、NaN、Infinity) | 仅 ES6+ 支持,旧环境需 polyfill |
Math.floor(value) === value | 简单直观,兼容性好 | 对于非数值类型(如字符串、NaN、Infinity)判断不准确 |
/^-?\d+$/.test(value) | 可用于字符串类型的整数判断 | 仅适用于字符串,不能判断数值类型,不能识别超大数或科学计数法 |
推荐:优先使用 Number.isInteger
,如需兼容性可结合其他方法。
什么是 NaN ,它的类型,如何判断 NaN?
答案
NaN(Not-a-Number) 是 JavaScript 中的一个特殊数值,表示“不是一个数字”。它属于 number
类型,用于表示数学运算失败或无法返回有效数字的情况。
NaN 主要用于标识那些本应返回数值但因非法操作(如 0/0、parseInt('abc'))而无法得到有效结果的场景。它让开发者可以检测到数值计算中的异常。
NaN 的类型是 number
,这是 JS 语言设计的历史遗留问题。不能用 ==
或 ===
判断 NaN,因为 NaN 与任何值都不相等,包括它自身:
console.log(NaN === NaN) // false
判断 NaN 推荐使用:
Number.isNaN(value)
:严格判断,只有值为 NaN 时才返回 true。isNaN(value)
:会先将值转换为数字再判断,容易产生误判(如 isNaN('abc') 为 true)。
示例:
Number.isNaN(NaN) // true
Number.isNaN('abc') // false
isNaN(NaN) // true
isNaN('abc') // true (因为 Number('abc') 是 NaN)
- NaN 是唯一一个不等于自身的值,可用
value !== value
判断是否为 NaN。 - NaN 参与任何数学运算结果仍为 NaN,会在数值计算链中“传染”。
- 推荐优先使用
Number.isNaN
判断,避免类型转换带来的误判。
总结:
NaN 用于标识无效的数值结果,类型为 number,判断时应使用 Number.isNaN
,避免直接比较或使用 isNaN
带来的误判。
延伸阅读:
BigInt 是如何实现的?
答案
BigInt 是 ES2020 引入的一种原生数据类型,用于表示任意精度的整数。它的实现原理如下:
- 存储方式:BigInt 在底层不是用普通的 64 位二进制存储,而是用一组有符号整数数组(如 V8 引擎中用
std::vector<uint64_t>
)来存储大数的每一部分,实现任意长度。 - 运算实现:BigInt 的加减乘除等运算通过模拟手工大数运算(如逐位相加、进位、分段乘法等)实现,保证精度不会丢失。
- 与 Number 区别:BigInt 不会丢失精度,适合处理超大整数。BigInt 与 Number 不能直接混用运算,否则会抛出 TypeError。
- 语法:通过在整数后加
n
或BigInt()
构造器创建,如123n
或BigInt("12345678901234567890")
。
示例:
const a = 1234567890123456789012345678901234567890n
const b = BigInt('9876543210987654321098765432109876543210')
const sum = a + b
console.log(sum) // 输出大整数结果
延伸阅读
js 中类型转换规则?
答案
类型转换(Type Conversion)是指将一种数据类型的值转换为另一种数据类型。JS 在表达式求值、运算符操作、函数调用等场景下会自动或手动进行类型转换。类型转换分为两大类:隐式类型转换 和 显式类型转换。
-
隐式类型转换(自动转换)
- 由 JS 引擎自动完成,常见于运算符操作和比较操作。
- 典型场景:
==
操作符会自动进行类型转换以比较不同类型的值。- 算术运算符(如
+
,-
,*
,/
)会根据参与运算的值自动转换类型。+
运算符:只要有一个操作数是字符串,则进行字符串拼接。- 其他算术运算符会将操作数转换为数字。
- 对象参与运算时,会依次调用
Symbol.toPrimitive
、valueOf
、toString
方法决定转换结果。
-
显式类型转换(强制转换)
- 由开发者主动调用转换函数实现。
- 常用方法:
String(value)
:转为字符串Number(value)
:转为数字Boolean(value)
:转为布尔值parseInt(value)
、parseFloat(value)
:将字符串解析为整数或浮点数value.toString()
:大多数对象和原始类型都支持
类型转换的注意事项
- 对象的隐式转换顺序为:
Symbol.toPrimitive
→valueOf
→toString
,且这些方法可被自定义覆盖。 ==
运算符会触发复杂的类型转换规则,容易导致意外结果,推荐使用===
避免隐式转换。- 隐式转换可能导致难以发现的 bug,建议优先使用显式转换。
- 特殊值如
null
、undefined
、NaN
、空字符串等在转换时有特殊规则,需注意边界情况。 - 模板字符串
${}
会自动调用toString
方法。
// 显式转换
String(123) // "123"
Number('42') // 42
Boolean('') // false
parseInt('10px') // 10
// 隐式转换
1 + '2' // "12"(数字转字符串拼接)
'5' * '2' // 10 (字符串转数字相乘)
true + 1 // 2 (布尔转数字)
[1, 2] + [3, 4] // "1,23,4"(数组转字符串拼接)
undefined == null // true(隐式转换规则)
参考与延伸