前端基础 —— 数据类型

819 阅读14分钟

ECMAScript 中有 6 种简单数类型(也称基本数据类型):UndefinedNullBooleanNumberStringsymbolES6 新增) 以及 1 种复杂数据类型:Object

typeof 操作符

由于 ECMAScript 是松散类型的,所以我们需要检测变量的数据类型,而 typeof 就是负责检测数据类型的操作符,它的返回值是字符串:

  • 'undefined' —— 未定义
  • ‘boolean’ —— 布尔
  • ’string’ —— 字符串
  • ’number’ —— 数值
  • ‘object’ —— 对象或者 null
  • ‘function’ —— 函数
  • ‘symbol’ —— 符号(ES6 新增) 如下列例子:
var message = 'some string'
console.log(typeof message) // string
console.log(typeof (message)) // string
console.log(typeof 1) // number
console.log(typeof true) // boolean
var message1;
console.log(typeof message1) // undefined
console.log(typeof null) // object
function test () {}
console.log(typeof test) // function

上面👆 例子中的 typeof null 的值为 object 字符串,是因为 null 被认为是一个空的对象引用。 而 typeof test 值为 function 字符串,这里我们得知道,函数在 ECMAScript 中是对象,不是一种数据类型,但是可以通过 typeof 操作符来区分函数和其他对象。

需要注意的一点是,上面👆 我们使用了 typeof (message),这并不代表 typeof 是一个函数,它只是一个操作符,因此尽管可以使用圆括号,但是圆括号不是必须使用的。

undefined

undefined 类型只有一个值,它在使用 var/let 声明变量没有初始化时,这时候这个变量的值就是 undefined

还有一个特别的地方就是,对于未声明的变量,typeof 也是返回 undefined

var a;
typeof a // undefined
typeof b // undefined
let c;
typeof c // undefined

结果表明,对未初始化和未声明的变量执行 typeof 操作符都返回了 undefined 这个值,对于这个结果有符合逻辑上的合理性,因为虽然这两种变量从技术角度看有本质区别,但是实际上无论对哪种变量也不可能执行真正的操作。

对于以上的总结就是 —— 我们应该在初始化的时候为变量赋上初始值,这样就可以检测到变量还没有被声明而不是尚未初始化。

null

nullundefined 一样,也是只有一个值的数据类型,它表示一个空对象指针,这也就是 typeof 检测 null 返回 object 的原因。 实际上 undefined 值派生自 null,因此 ECMA-262 规定对它们相等性测试要返回 true

console.log(undefined == null) // true

虽然我们的 undefinednull 有这样的关系,但是当我们初始化变量时没有必要将变量的值赋值为 undefined,同样的规则在 null 这里就不一样了,我们可以将一个保存变量的对象赋值为 null,这样在使用的时候就可以知道相应的变量是否已经保存了一个对象的引用:

var car = null;
if (car != null) {
  // 对 car 对象执行某些操作
}

并且这样做的好处也有助于区分 undefinedunll

Boolean

boolean 有两个字面值:truefalse,这两个值与数字值不是一回事,所以,true 不一定等于 1,false 不一定等于 0。 我们需要注意一点,boolean 类型的字面值是区分大小写的,也就是说,TrueFalse 或者其它形式的混合大小写都不是 boolean 值,只是标识符。 我们可以使用 Boolean() 将一个值转换为对应的 Boolean 值:

var message = "abc"
var messageBoolean = Boolean(message)
console.log(messageBoolean) // true

上面的例子是字符串转 boolean 值,那么其它类型转 Boolean 值如下表:

数据类型转换为 true 的值转换为 false 的值
Booleantruefalse
String任何非空字符串“”(空字符串)
Number任何非零数字值(包括无穷大)0 和 NaN
Object任何对象null
undefined不适用undefined

Number

Number 分为整数和浮点数值(浮点数值在某些语言中也成为双精度数值)。为支持各种数据类型,ECMA-262 定义了不同的数值字面量格式:

  • 十进制整数
  • 八进制(以 8 为基数),八进制字面值的第一位必须是0,数字序列为 0 ~7,如果字面值中的数值超出了范围,那么前面的 0 就会被忽略,后面的数值就会被当做十进制数值解析。
  • 十六进制(以 16 为基数),十六进制字面值的前两位必须是 0x,数字序列为 0 ~ 9A ~ F/ a~f
var intNum = 1; // 十进制整数
var octalNum1 = 070; // 八进制的 56
var octalNum2 = 079; // 无效的八进制数值 —— 解析为 79
var octalNum3 = 08; // 无效的八进制数值 —— 解析为 8
var hexNum1 = 0xA; // 十六进制的10
var hexNum2 = 0x1f; // 十六进制的 31

其中需要注意的是,八进制在严格模式下是无效的,这会导致该模式的 JavaScript 引擎抛出错误。

当然,在进行算术计算时,所有八进制和十六进制表示的数值最终都会被转换成十进制数值。

1. 浮点数值

浮点数值就是这个数值中必须包含一个小数点,并且小数点后面必须至少有一位是数字:

var floatNum = 1.1
var floatNum1 = 0.1
var floatNum2 = .1

在上面的例子中,var floatNum2 = .1 省略了前面的数字,这种写法是有效的,但是不推荐这种写法。

当然,如果我们的小数点后面没有跟任何数字或者是跟的 0,那么这个值会被转换为整数,因为保存浮点数值需要的内存空间是保存整数值的两倍

var floatNum = 1. // 小数点后面没有数字,会转换为整数 1
floatNum = 1.0 // 小数点后面跟的是0,会转换为整数 1

我们平时会遇到一些极大或极小的数值,这时就可以用科学计数法 —— e 表示浮点数值。使用 e 表示的数值等于 e 前面的数值乘以 10 的指数次幂,比如:前面是一个数值,可以是整数也可以是浮点数,中间是一个大写或者小写的字母 e,后面是 10 的指数次幂,这个幂值会用来与前面的数相乘,如下面这个例子,就是 3.12345 乘以 10710^7

var floatNum = 3.12345e7 // 等于 31234500

当然,我们的 e 表示法也可以表示极小的数值,如 0.00000000000001 就可以使用 1e-14 来表示。

值得注意的是,浮点数的最高精度是 17 位小数,但是在进行算术计算时其精度远远不如整数。一个典型的例子:0.1 + 0.2 的结果不是 0.3,而是 0.30000000000000004

2. 数值范围

  • 最小数值:Number.MIN_VALUE,在大多数浏览器中为 5e-324
  • 最大数值:Number.MAX_VALUE,在大多数浏览器中为 1.7976931348623157e+308 如果某次计算的结果得到了一个超出了上面👆的这两个范围,那么这个值将自动转换成特殊的 Infinity,若这个数是负数,则会被转换成 -Infinity,若是正数则会被转换成 Infinity

而我们访问 Number.NEGATIVE_INFINITY,则会得到 -Infinity;访问 Number.POSITIVE_INFINITY,则会得到 Infinity。由此可知,这两个属性中分别保存着正无穷和负无穷。

3.NaN

NaN 是一个特殊的数值,它表示一个非数值(Not a Number),这个数值用于表示一个本来要返回数值的操作数未返回数值的情况。 NaN 有两个特点:

  • 任何与 NaN 相关的操作都会返回 NaN
  • NaN 与任何值都不相等,包括 NaN 本身
NaN + 1 // NaN
NaN * 1 // NaN
NaN == NaN // false

针对 NaN 的这两个特点,ECMAScript 定义了 isNaN() 函数。

isNaN() 接收一个参数,这个参数可以是任何类型,它会在接收到这个值之后,去尝试将这个值转换为数值,如果转换完成后,结果不是一个数值则会返回 true,否则返回 false

isNaN(NaN) // true
isNaN(1) // false,1 本身就是一个数值
isNaN("1") // false,字符串 1 可以被转换成一个数值 1
isNaN('blue') // true,blue 不能被转换成数值
isNaN(true) // false,true 可以被转换成数值 1
isNaN('true') // false,同上

4. 数值转换

ECMAScript 中,有 3 个函数可以把非数值转换为数值:

  • Number() 可用于任何数据类型
  • parseInt() 用于字符串转换成数值
  • parseFloat()用于字符串转换成数值
Number()

Number() 转换规则如下:

  • Boolean —— true1false0
  • 数值,传入什么返回什么,但是会去掉无用的 0,如: Number(0.10) 则会返回 0.1
  • null —— 0
  • undefined —— NaN
  • 字符串有以下规则:
    • 只包含数字
      • 整数:若前导有多余的 0,则省略。如:Number('011') -> 11
      • 浮点数:转换为对应的浮点数,若前导和结尾有多余的 0,则忽略。如:Number('00.110') -> 0.11
    • 十六进制则会转换为相同大小的十进制数:Number(0xf) -> 15
    • 空字符串 —— 0
    • 字符串包含除上述外的其它字符,则为 NaN
  • 对象,使用 valueOf() 依照上述规则转换返回的值,若结果为 NaN,则调用对象的 toString() 方法,然后再依次按照前面的规则转换返回的字符串值。 根据上面的条例,我们来看几个例子:
Number('Hello world!') // NaN,遵循字符串包含除数字值的任意字符
Number('') // 0,遵循空字符串转换为 0
Number("00011000") // 11000,遵循字符串为整数时,删除前导的零
Number("00.1010100") // 0.10101,遵循字符串为浮点数时,删除前导和结尾多余的 0
Number(true) // 1,遵循 Boolean true 为 1
Number(null) // 0,遵循 null 为 0
Number(undefined) // NaN,遵循 undefined 为 NaN

由于 Number() 函数转换字符串时比较复杂且不够合理,因此就有了我们处理字符串时常用的两个函数:parseInt()parseFloat()parseInt() 用于处理整数字符串,而 parseFloat() 则用于处理我们的浮点数字符串。

parseInt()

那我们就先来看看 parseInt()

parseInt()ECMAScript 3 JavaScript 引擎中,会自动识别八进制,就是会将以 0 开头的字符串解析为八进制。但是在 ECMAScript 5 JavaScript 引擎中,parseInt() 就已经不具有解析八进制的能力,因此,parseInt() 提供了第二个参数,这个参数就是指定转换为哪个进制。

当然 parseInt() 也有一些其它规则:

  • 若第一个字符不是数字字符或者负号,则返回 NaN
  • 若第一个字符是数字字符,则会继续解析第二个字符,直到遇到一个非数字字符。
  • 若以 0x 开头的字符,则会当做十六进制数来解析。
  • 虽然指定基数不填时,parseInt() 会自动解析为十进制数,但为了避免错误的解析,应明确指定基数。
parseInt('123abc') // 123,遵循第二条例
parseInt('010') // 10,自动转换为十进制数
parseInt('AB') // NaN,省略了第二个参数,转换为十进制时,不识别 AB
parseInt('AB', 16) // 171,第二个参数指定为十六进制
parseInt('10', 2) // 2,按二进制解析
parseInt('10', 8) // 8,按八进制解析
parseInt('10', 10) // 10,按十进制解析
parseInt('10', 16) // 16,按十六进制解析
parseInt('0x1A') // 26,按十六进制解析

以上就是 parseInt() 的用法及示例,在多数情况下我们解析的都是十进制数值,所以可以始终将 10 作为第二个参数是非常必要的。

parseFloat()

parseFloat()parseInt() 函数类似,但还是有以下区别:

  • 解析浮点数时,第一个小数点有效,第二个小数点无效
  • 只解析十进制格式的字符串
  • 十六进制的格式始终会被转换为 0
  • 字符串包含一个可解析为整数的数(没有小数点或者小数点后面是零),则返回一个整数
parseFloat('1234blue') // 1234,与 parseInt 规则一一样
parseFloat('0xA') // 0,十六进制的格式始终会被转换为 0
parseFloat('123.45') // 123.45,正常格式鸳鸯输出
parseFloat('123.45.6') // 123.45,值识别第一个小数点
parseFloat('1.2345e7') // 12345000
parseFloat('1.00') // 1,小数点后都是零,返回整数

String

String 类型就是我们日常所说的字符串,它是由零或多个 16Unicode 字符组成的字符串。

字符串有两种写法:双引号("")和单引号('')。这两种表示方法的字符串时完全相同的,需要注意的是以双引号开头的字符串需要以双引号结束。

var firstName = 'a" // 这种写法的字符串是错误的。

1.字符串字面量

String 数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,如下表:

字面量含义
\n换行符
\t制表符
\b退格符
\r回车符
\f换页符
\\斜杠符
\'单引号,在单引号表示的字符串中使用。如:'Hi, I'm Shinkai'
\"双引号,在双引号表示的字符串中使用。如:"He said, "hey""
\xnn以十六进制代码 nn 表示一个字符(其中 n 为 0~F),如:\x41,表示 "A"
\unnn以十六进制代码nnnn表示的一个Unicode字符(其中n为0~F)。例如,\u03a3表示希腊字符Σ

上表的字面量可以出现在字符串中的任意位置,如:``,

var test = 'Hi, I\'m Shinkai, \x456' // Hi, I'm Shinkai, E6
test.length // 19

这例子中的变量 test19 个字符, 其中 \x456 表示两位字符。

2.字符串的特点

ECMAScript 中的字符串时不可变的,意思是一旦创建,它们的值就不能变了,要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量中。

let a = 'ECMA'
a = a + 'Script'

上面的 a 变量一开始只包含字符串 ECMA,后面又被重新定义包含 ECMAScript 的组合。在这个过程中,首先会先分配一个容纳 10 个字符的空间,然后填充 ECMAScript,最后会销毁原始的字符串 ECMAScript,因为这两个字符串都没有用了。这些处理都是在后台发生的,这也是早期浏览器在拼接字符串时非常慢的原因。但现在的浏览器都针对性的解决了这个问题。

3.转换为字符串

  • toString() —— 可用于数值、布尔值、对象和字符串值等,是字符串本身的方法。
    • 接受的参数是进制,比如八进制、二进制、十六进制等,或者任何其他有效基数的字符串表示。
  • String() —— 可用于数值、布尔值、对象、字符串值、nullundefined,是转型函数。
    • 如果值是 null,则返回 "null"
    • 如果值是 undefined,则返回 "undefined"
    • 不可转进制 toString() 示例:
let example = 11;
example.toString() // "11"
example.toString(2) // "1011", 二进制的字符串数值
example.toString(8) // "3",八进制的字符串数值
example.toString(16) // "b",十六进制的字符串数值
example = true
example.toString() // "true"
example = undefined
example.toString() // 报错,undefined 没有 toString() 方法
example = null
example.toString() // 报错,null 没有 toString() 方法

String() 示例:

String() // 空字符串
let example = 10
String(example) // 字符串“10”
example = true
String(example) // 字符串 “true”
example = undefined
String(example) // “undefined”
example = null
String(example) // "null"

4.模板字面量

ECMAScript 6 新增字符串方法

它与使用单引号或双引号不同,模板字面量保留换行字符,它是可以跨行定义的字符串 —— ``

let example = `a \n b`
console.log(example)
// a
// b
example = `a
b`
console.log(example)
// a
// b

// 可以定义 HTML 模板:
example = `
<div> 
 <a href="#"> 
 <span>Shinkai</span> 
 </a> 
</div>
`

模板字符会保留所有空格及换行符,因此,我们在使用模板字符的时候,尽量不要使用多余的空格。

5.字符串插值

ECMAScript 6 新增字符串方法

JavaScript 有一种句法表达式 —— 模板字面量,这个东西就是字符串插值中最常用的特性,它可以在一个连续定义中插入一个或多个值,最后得到一个完整的字符串。

字符串插值通过在 ${} 中使用一个 JavaScript 表达式实现:

let value = 'apple'
let example = 'an'
// 以前的字符串拼接
let str = 'I have' + example + value;
console.log(str) // "I have an apple"
// 使用字符串插值
str = `I have ${example} ${value}`
console.log(str) // "I have an apple"

当然,我们的模板字面量不仅仅可以是变量,也可以是函数和方法:

function capitalize(word) { 
 return `${ word[0].toUpperCase() }${ word.slice(1) }`; 
} 
console.log(`${ capitalize('hello') }, ${ capitalize('world') }!`); // Hello, World!

模板字面量也支持定义标签函数(tag function),标签函数本身是一个常规函数,我们可以通过前缀模板字面量来应用自定义行为:

let a = 6;
let b = 9;
function example(strings, aExpression, bExpression, sumExpression) {
	console.log(strings) // ["", "+", "=", ""]
    console.log(aExpression) // 6
    console.log(bExpression) // 9
    console.log(sumExpression) // 15
    return 'foobar'
}
let untaggedResult = `${a} + ${b} = ${a + b}`
let taggedResult = example`${a} + ${b} = ${a + b}`
// ["", "+", "=", ""]
// 6
// 9
// 15
console.log(untaggedResult) // "6 + 9 = 15"
console.log(taggedResult) // 'foobar'

6.原始字符串

String.raw —— 这个标签函数可以获取原始字符串

// Unicode 示例
// \u00A9 是版权符号
console.log(`\u00A9`); // © 
console.log(String.raw`\u00A9`); // \u00A9 
// 换行符示例
console.log(`first line\nsecond line`); 
// first line 
// second line 
console.log(String.raw`first line\nsecond line`); // "first line\nsecond line"

Symbol

ECMAScript 6 新增基本数据类型

  • Symbol 是一种基本数据类型,所以 typeof 操作符返回 symbol
  • 使用 Symbol() 函数会返回 symbol 类型的值,这个值具有静态属性和静态方法。
  • 不能与 new 关键字一起作为构造函数使用。
  • 符号实例是唯一、不可改变的,这也是这个数据类型仅有的目的。

使用 Symbol() 创建新的 symbol 类型,可传入字符串作为描述。

let symbol1 = Symbol() // Symbol()
let symbol2 = Symbol(2) // Symbol(2)
let symbol3 = Symbol(true) // Symbol(true)
let symbol4 = Symbol('foo') // Symbol(foo)

// 使用 typeof
typeof symbol1 // "symbol"

// symbol 数据类型的唯一性
symbol1 == Symbol() // false
symbol2 == 2 // false
Symbol('foo') === Symbol('foo') // false

// 使用 new 运算符会报错
var sym = new Symbol() // Uncaught TypeError: Symbol is not a constructor

使用 new 会阻止创建一个显式的 Symbol 包装器对象而不是一个 Symbol 值。围绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持。 然而,现有的原始包装器对象,如 new Booleannew String 以及 new Number,因为遗留原因仍可被创建。

若真想使用 Symbol 包装对象,可以借用 Object() 函数:

let sym = Symbol("foo")
typeof sym; // "symbol"
let symObj = Object(sym)
typeof symObj; // "object"

其余属性及特性可去 MDN 或者 JavaScript 高级程序设计(第4版)查看哦。

Object

Object 就是一组数据和功能的集合,它是派生其他对象的基类,所以 Object 类型的所有属性和方法在派生的对象向同样存在。 Object 实例有以下属性和方法:

  • constructor:用于创建当前对象的函数。
  • hasOwnProperty(propertyName):返回一个布尔值,判断对象自身属性中是否具有指定的属性。
  • isPrototypeOf(object):用于测试一个对象是否存在于另一个对象的原型链上。
  • propertyIsEnumerable(propertyName):返回一个布尔值,检测对象是否可枚举。
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地话执行环节。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString() 的返回值相同。 对象可以通过执行 new 操作符后跟要创建的对象类型的名称来创建,而创建 Object 类型的实例并为其添加属性和(或)方法,就可以创建自定 义对象,如下所示:
var o = new Object();

结语

ES6 新增:

  • 字符串的模板字面量 —— ``
  • 字符串的字符串插值 —— ${}
  • 字符串的原始字符串 —— String.raw
  • Symbol

参考文献

MDN web docs

JavaScript 高级程序设计(第4版)