前端知识图谱 - JavaScript基础(变量和类型)
变量和类型
1. JavaScript 规定了几种语言类型?
JavaScript 语言的每一个值都属于某一种数据类型,它规定了 7 种语言类型。语言类型广泛用于变量、函数参数、表达式、函数返回值等场合,根据最新的语言标准,这 7 种语言类型如下:
Undefined
:
Undefined 类型表示未定义,任何一个变量未定义之前都是 Undefined类型,值为 undefined。但需要我们注意的是,undefined 不是一个关键字,它可以作为一个变量被定义,所以当我们为一个变量直接赋值 undefined 的时候,有可能和预期效果出现偏差。
const undefined: number = 123;
const udf: number = undefined; // echo 123
严谨一点的话,我们可以使用 void
运算,来把任意表达式转换为 undefined。例如:
const udf: undefined = void 0; // echo undefined
但在实际编程中,我们一般不会给 Undefined 类型的值赋值,这样可以保证它是一个未赋值的自然状态,从而让它的值为 undefined。
-
Null
:
Null 类型表示定义了但为空,它和 undefined 还是有一定区别的。 -
String
:
String 类型用来表示文本数据,只读。 -
Boolean
:
Boolean 类型有两个值,用关键字 true 和 false 来表示逻辑意义上的真和假。 -
Number
:
Number 类型表示通常意义上的数字,大致对应数学中的有理数。
JavaScript 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,但它为特定的几个语言场景规定了几种特殊情况:- Infinity:无穷大。当一个数字除以 0 时,得到的结果就是 Infinity。
- NaN:非数字。当一个数字和一个非数字(例如字符串)作加法之外的运算时,得到的结果就是 NaN。
根据双精度浮点数的定义,Number 类型中有效的整数范围是 -0x1fffffffffffff 至 0x1fffffffffffff,所以 Number 无法精确表示此范围外的整数。
根据双精度浮点数的定义,非整数的 Number 无法用 == 或 === 来比较。它也造成了一个精度丢失
的问题:0.1 + 0.2 == 0.3 // echo false
这里我们需要换一种比较方法:使用 JS 提供的最小精度值。
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // echo true
最大数字:Number.MAX_VALUE = 1.7976931348623157e+308 最大安全数字:Number.MAX_SAFE_INTEGER = 2 ^53 - 1 = 9007199254740991
-
Symbol
:
自ECMAScript 2015起,Symbol 成为了一种新的原生类型,就像 Number 和 String 一样,表示独一无二的值。 -
Object
:
Object 类型是 JS 中最复杂的类型,也是 JS 的核心机制之一,是一种无序的键值对集合。
2. 如何避免 JavaScript 浮点数运算精度丢失?
我们先来看一个经典问题:0.1 + 0.2 != 0.3
0.1 + 0.2 == 0.3 // echo false
0.1 + 0.2 // 0.30000000000000004
(0.1 + 0.2) * 10e16 // echo 30000000000000004
可以看到,相加的结果和预期差了一个微小值,我们后期计算的话就会出现误差。
为什么会这样呢?我们先来看看运算过程:小数转换二进制用的是 乘2取整法
,就是不断乘2,小数为0为止,顺序排列。
通过计算,可得:
0.1 = 0.0001100(1100) = 2^-4 * 1.1001100(1100) // 1100循环
0.2 = 0.001100(1100) = 2^-3 * 1.1001100(1100)
JS 中数字存储使用的是 ieee 754 64位双精度浮点数,64 位中第 1 位为符号位,0 正 1 负;之后 11 位为指数位,用来确定范围;其余 52 位都是尾数位,用来确定精度。
由于 0.1 和 0.2 都是无限循环的二进制,保留位数 52 位,不算最左边整数位。最终计算机中存储的数位:
0.1 = 2^-4 * 1.100 11001100 11001100 11001100 11001100 11001100 11001100 1
0.2 = 2^-3 * 1.100 11001100 11001100 11001100 11001100 11001100 11001100 1
0.00011001100110011001100110011001100110011001100110011001
+
0.0011001100110011001100110011001100110011001100110011001
=
0.01001100110011001100110011001100110011001100110011001011
// 保留 52 为,末位进位(逢1进1)
0.01 00110011 00110011 00110011 00110011 00110011 00110011 0011
≈ 0.30000000000000004
原生解决办法:
parseFloat((0.1 + 0.2).toFixed(10))
或者使用像 decimal.js
这样的库,它们提供了更加精确的浮点数处理能力。
3. JavaScript 对象的底层数据结构是什么?
JavaScript 对象的底层数据结构并没有在规范中明确定义,不同的 JavaScript 引擎可能会采用不同的实现方式。在当前的主流 JavaScript 引擎中,比如 V8 引擎(Chrome 和 Node.js 使用的引擎),JavaScript 对象的底层实现是基于哈希表的。
哈希表是一种能够快速查找元素的数据结构,它通过计算元素的哈希值(一个用于确定元素位置的数字)来存储元素。当你访问对象的属性时,引擎会计算属性名的哈希值,并用它来快速找到相应的属性值。
需要注意的是,虽然大多数现代 JavaScript 引擎使用哈希表作为对象的底层数据结构,但这并不是规范规定的实现方式,不同的实现可能会有所不同。
4. 理解值类型和引用类型
值类型和引用类型是编程语言数据类型的两种基本分类,它们在内存中的存储和传递方式不同。
值类型又称为原始类型或原始值(primitive value),这类值直接存储在栈(Stack)内存中,基础数据类型的值不可修改。当我们定义一个变量,为它赋值时,可以理解为,我们为这个变量绑定了一个内存空间,值就存储在这个内存空间中。
我们定义两个基本类型的变量,把一个值赋值给另一个值,会进行值的复制,这两个变量的值是相互独立的,修改一个变量的值不会影响另一个变量的值。
引用类型的变量存储在栈上,但它们指向的对象存储在堆(Heap)上。堆是内存中的动态区域,相当于自留空间,程序在运行期间会动态分配给堆栈。堆中存储的一般都是对象,然后在栈内存中存储一个变量指针,计算机通过这个变量指针,找到堆中的数据块进行操作,这种访问方式叫做按引用访问。
我们定义两个变量,其中一个为引用类型,当我们把引用类型的变量赋值给另一个变量,只是复制了对象的引用(地址),两个变量指向的是同一个值,修改一个变量会影响另一个变量的值。
5. JavaScript 中的变量在内存中的具体存储形式
同上
6. Symbol类型在实际开发中的应用,手动实现一个简单的 Symbol
了解 JavaScript Symbol 类型及其应用场景
手动实现一个 Symbol:https://segmentfault.com/a/1190000015262174
7. 至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型
let obj = { str: "", num: 0, bool: false, nul: null, und: undefined, sym: Symbol(), arr: [] }
- typeof:直接返回数据的类型字段。但无法区分
Null
、Object
、Array
,它们的返回值都是object
。
console.log(typeof obj.str) // 'string'
console.log(typeof obj.num) // 'number'
console.log(typeof obj.bool) // 'boolean'
console.log(typeof obj.nul) // 'object'
console.log(typeof obj.und) // 'undefined'
console.log(typeof obj.sym) // 'symbol'
console.log(typeof obj.arr) // 'object'
console.log(typeof obj) // 'object'
- instanceof:只能判断引用类型数据,null、understand不支持判断
obj.str instanceof String // false
obj.num instanceof Number // false
obj.bool instanceof Boolean // false
obj.nul instanceof Null // Uncaught ReferenceError: Null is not defined
obj.und instanceof Undefined // ReferenceError: Undefined is not defined
obj.sym instanceof Symbol // false
obj.arr instanceof Array // true
obj instanceof Object // true
- Object.prototype.toString.call():完美的判断方法。
Object.prototype.toString.call(obj.str) // '[object String]'
Object.prototype.toString.call(obj.num) // '[object Number]'
Object.prototype.toString.call(obj.bool) // '[object Boolean]'
Object.prototype.toString.call(obj.nul) // '[object Null]'
Object.prototype.toString.call(obj.und) // '[object Undefined]'
Object.prototype.toString.call(obj.sym) // '[object Symbol]'
Object.prototype.toString.call(obj.arr) // '[object Array]'
Object.prototype.toString.call(obj) // '[object Object]'
- Array.isArray():数组类型内置的判断方法。
Array.isArray(obj.arr) // true
Array.isArray(obj) // false
参考:
js的7中语言类型详解 - https://www.cnblogs.com/lilistyle/p/13613756.html
解析 js 中 0.1 + 0.2 != 0.3 - https://www.php.cn/faq/386908.html