JavaScript 入门之数据类型与变量

170 阅读7分钟

站在各路大神与文档的肩膀上总结出的一篇文章,描述了 JavaScript 数据类型与变量的相关内容。

数据类型

ECMAScript 中有6中简单数据类型(原始类型)UndefinedNullBooleanNumberStringSymbol(ES6),一种复杂数据类型(引用类型)Object,ES10 新增一种特殊的原始类型:BigInt

简单数据类型

Undefined 类型

Undefined 类型有且仅有一个值undefined,表示未定义。返回undefined值的场景如下:

  1. 使用 varlet 声明变量但未初始化;
  2. 访问一个对象不存在的属性
  3. 没有返回值的函数的返回值
  4. 调用函数时被省略的参数
  5. undefined 或者 null 的链式调用 ?.
  6. void <expression> 计算表达式的返回结果

Null 类型

Null 类型有且仅有一个值null,表示一个空对象指针。null 是 JavaScript 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取 null

Undefined 和 Null 的区别是什么?

  1. typeof undefined => 'undefined',typeof null => 'object'
  2. undefined 转换为数字是 NaNNull 转换为数字是 0
  3. undefined 是全局对象的一个属性(全局变量),表示原始值 undefined,如需显示赋值使用void 0
  4. undefined == null => true, undefined === null => false

Boolean 类型

Boolean(布尔值)类型有两个字面值:true 和 false。

Number 类型

ECMAScript 中 Number 类型使用 IEEE 754 格式表示整数和浮点数。不同数值类型的数字字面量表示如下:

// 十进制
let intNum = 56
// 八进制
let octalNum1 = 0o70 // 56
// 十六进制
let hexNum1 = 0xA // 10

ECMAScript 能够存储 2-1074Number.MIN_VALUE)和 21024Number.MAX_VALUE)之间的正浮点数,以及 -2-1074 和 -21024 之间的负浮点数,但是它仅能安全地存储在 -253 + 1(Number.MIN_SAFE_INTEGER)到 253 − 1(Number.MAX_SAFE_INTEGER)范围内的整数。数值超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity

let min = Number.MIN_VALUE // 5e-324
let max = Number.MAX_VALUE // 1.797 693 134 862 315 7e+308
let negativeInfinity = Number.NEGATIVE_INFINITY // -Infinity
let positiveInfinity = Number.POSITIVE_INFINITY // Infinity
/* Number.isFinite() 要确定一个值是不是有限大 */
Number.isFinite(min) // true
Number.isFinite(positiveInfinity) // true

NUmber 类型有一个特殊值:NaN(not a number),其特性如下:

  • 0、+0、-0 相除会返回 NaN
  • 任何涉及 NaN 的操作始终返回 NaN
  • NaN == NaN => false

String 类型

String(字符串)类型表示零或多个 16 位 Unicode 字符序列。String 有最大长度是 253 - 1。ECMAScript 中的字符串是不可变的。

Symbol 类型

Symbol 是 ES6 新增的数据类型,Symbol 是原始值,且唯一、不可变。其声明方式如下:

let sym = Symbol()

BigInt 类型

BigInt 可以表示任意大的整数。其声明方式如下:

const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);

在某些方面类似于Number,但是也有几个关键的不同点:

  • 不能用于Math对象中的方法;
  • 不能和任何Number实例混合运算

复杂数据类型

Object

ECMAScript 中的对象是一组数据和功能的集合。在 ECMAScript 中, Object 是派生其他对象的基类。创建对象实例如下:

let obj = new Object()

每个 Object 实例存在的属性和方法:

  • constructor 用于创建当前对象的函数
  • hasOwnProperty(propertyName)  判断当前对象是否存在给定的属性
  • isPrototypeof(object)  判断当前对象是否存在于另一个对象的原型链上
  • propertyIsEnumerable(propertyName)  判断指定的属性是否可枚举
  • toLocalString()  返回对象的字符串表示
  • toString()  返回对象的字符串表示
  • valueOf()  返回对象对应的字符串、数值或布尔值表示

类型转换

因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算(如加减乘除大于小于)都会先进行类型转换。常见的转换规则如下:

数据类型(from\to)BooleanNumberStringObject
Nullfalse0'null'TypeError
UndefinedfalseNaN'undefined'TypeError
Boolean-true -> 1
false -> 0
true -> 'true'
false -> 'false'
#装箱转换
Number0/NaN -> false-#NumberToString#装箱转换
String'' -> false#StringToNumber-#装箱转换
SymboltrueTypeErrorTypeError#装箱转换
Objecttrue#拆箱转换#拆箱转换-

所谓的隐式转换,就是偷偷调用 Number、String、Boolean 进行一个类型转换。

StringToNumber

  • Number()

    数据类型转化为数值NaN
    Booleantrue -> 1 false -> 0N/A
    Nullnull -> 0N/A
    UndefinedN/Aundefined
    Number有效的数值字面量格式 -> 对应数值
    '' -> 0
    其他字符串
  • Number.parseFloat()

    从第一个非空格字符开始转换,解析到字符串末尾或者解析到一个无效的浮点数值字符为止。

  • Number.parseInt()

    1. 第二个参数指定进制数。
    2. 从第一个非空格字符开始转换,解析到字符串末尾或者解析到一个无效的浮点数值字符为止。
    3. 如果第一个字符不是数字字符、加号或减号,parseInt() 立即返回 NaN。

多数情况下,Number() 是比 parseInt()parseFloat() 更好的选择。

NumberToString

在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。当 Number 绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。

装箱转换

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象。

var symObj = Object(Symbol("a"));
console.log(typeof symObj); // object
console.log(Object.prototype.toString.call(symObj)); // [object Symbol]

拆箱转换

拆箱转换是对象类型到基本类型的转换。拆箱转换会尝试调用 valueOftoString 来获得拆箱后的基本类型。如果 valueOftoString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。到 String 的拆箱转换会优先调用 toString

var o = {
  valueOf: () => {
    console.log('valueOf')
    return {}
  },
  toString: () => {
    console.log('toString')
    return {}
  },
}

o * 2 // valueOf toString TypeError
String(o) // toString valueOf TypeError

变量

ECMAScript 变量是松散类型的,即变量可以用于保存任何类型的数据。ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。

声明变量

有 3 个关键字可以声明变量:varconstlet

varconstlet 有何异同?

  1. 变量提升var 声明变量会自动提升到作用域顶部,即在函数作用域中可以先使用再声明变量;
  2. letconst声明的范围是块级作用域,而var声明的范围是函数作用域
  3. letconst 声明的变量不会在作用域中被提升,在其声明之前的执行瞬间被称为暂时性死区
  4. var 在全局作用域中生命的变量会成为全局对象的属性,而 letconst 声明的变量不会;
  5. 在同一个作用域中,var 可以重复声明同一个变量。由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明。letconst 则会报错:SyntaxError。
  6. const声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。

动态属性

对于引用值而言,可以随时添加、修改和删除其属性和方法。

let student = new Object()
student.age = 16
student.name = '张三'

存储方式

原始值是保存在栈内存中的简单数据段,值有固定的大小,保存原始值的变量是按值(by value)访问的。引用值是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,保存引用值的变量是按引用(by reference)访问的。

const a = 1
const arr = { name: '张三' }
graph LR
    arr:访问地址 --> arrV
    subgraph 堆内存
    arrV["[0,1,2]"]
    end
    subgraph 栈内存
    a:1
    arr:访问地址
    end

复制值

变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针。

// 原始值复制
const a = 1
const b = a
// 引用值复制
const obj = { name: '张三'}
const objC = obj
graph LR
    obj:访问地址1 --> V
    objC:访问地址1 --> V
    subgraph 堆内存
    V["{ name: '张三' }"]
    end
    subgraph 栈内存
    a:1
    b:1
    obj:访问地址1
    objC:访问地址1
    end