数据类型
es6有六种简单类型:Undefined, Null, Boolean, Number, String, Symbol, 其中 Symbol 是 es6 新增的,还有一种复杂数据类型 Object, Object 是一种无序明值对的集合
typeof 操作符
因为 ECMAScript 的类型是松散的,所以需要一种手段来判断数据类型, typeof 就是为此而生的
typeof的返回值如下
'undefined'未定义'boolean'布尔值'string'字符串'number'数字'object'对象(不是函数),或者null'function'函数'symbol'符号(唯一值)
💡 typeof null 返回的结果是
object, 是因为null是对空对象的一个引用
Undefined
undefined类型的值只有一种,就是特殊值undefined,使用let或者var声明的变量但是没有赋值就是undefined
一般情况下,不要显示的给一个变量赋值为undefined,因为默认值就为undefined
在对未初始化的值判断的值为undefined,但是对未声明的变量调用,返回的值也是undefined,
let message;
// let age;
console.log(typeof message) // undefined
console.log(typeof age) // undefined
console.log(typeof age === typeof message) // true
无论是未声明还是声明了,typeof返回的值都是undefined。逻辑上是对的,因为严格来说,两个变量存在根本性差异,但是它们都无法进行实际的操作
Null
null值表示一个空对象指针,这就是typeof null返回object的原因
在定义将来要保存对象值的变量时,建议使用null来初始化
undefined是由null派生而来的,因此,ecmascript将他们定义为表面相等
console.log(null == undefined) // true
即使null和undefined有关系,但是他们的用途是完全不一样的,永远不要主动声明一个变量为undefined,但是null不一样,只要保存对象值,都可以用null来进行填充,这样就可以保证null是空对象指针的语义
Boolean
Boolean类型是js中使用最频繁的一个类型之一,它只有两个值,true或者false
要将一个其他的值转为boolean值,可以调用特定的Boolean()函数
下表总结了不同的值与布尔值之间的转换规则
| 数据类型 | 转换为true的值 | 转换为false的值 |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | 空字符串 |
| Number | 非0(包括无穷值) | 0,NaN |
| Object | 任意对象 | null |
| Undefined | 不存在 | undefined |
Number
js中最有意思的类型也许就是Number了, 不同的数值类型相应的也有不同的数值字面量格式
整数
书写十进制整数
let num1 = 1;
整数也可以用八进制表示,对于八进制字面量,第一个数字必须是0,(在严格模式下必须是0o开头)
let num2 = 012; // 八进制012 转十进制为 10
function test() {
"use strict";
let n1 = 012; // error: Octal literals are not allowed in strict mode.
let n2 = 0o12; // 正确
}
test()
整数也可以用十六进制表示,对于十六进制字面量,第一个数字必须是0x
let n3 = 0x12; // 十六进制转十进制为18
浮点数
要定义浮点数,数值中必须包含小数点,而且小数点后面必须要有至少一个数字,虽然小数点前面不是必须的,但是推荐加上
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效但是不推荐
存储浮点数的使用内存是整数的两倍,所以说js总是想着把值转为整数, 在小数点后面没有数字,或者数字是0 的情况下会被转为整数
let floatNum4 = 1.; // 转换为1
let floatNum5 = 1.0; // 转换为1
对于非常大或者非常小的数据,浮点数可以用科学记数法表示,
let floatNum6 = 3.12e5; // 312000
浮点数的京都最高可以达到17位小数,但是在算术计算中远不如整数精确,例如0.1 + 0.2 不等于0.3
console.log(0.1 + 0.2 == 0.3) // false
如果两个数为0.05和0.25,或者0.15和0.15,那是没问题的,但是如果是0.1和0.2 会出现问题,所以说,永远不要测试某个特定的浮点值
之所以存在这种情况,是因为使用了 IEEE754 数值, 这种错误并非ECMAScript独有的,其他使用同格式的语言也会有
值的范围
ECMAScript 可以表示的最大值保存在 Number.MAX_VALUE 中,最小值保存在 Number.MIN_VALUE, 如果某一个值超过了这个范围,它会被自动转为一个特殊的Infinity(无穷)值,任何无法表示的负数用-Infinity表示, 正数用Infinity表示
如果计算返回正负Infinity,那么这个数将不能做进一步运算,要确定一个值是不是有限大,可以使用isFinte()函数判断
NaN
有一个特殊的值是NaN,(not a number),用于表示本来要返回数值的操作失败了(而不是抛出错误),比如,用0除任何数值
console.log(0/0) // NaN
如果分子是非0值,分母为0,则会返回Infinity或者-Infinity
console.log(-1/0) // -Infinity
console.log(1/0) // Infinity
NaN有几个独特的特性,涉及NaN的任何运算都会返回NaN, 其次NaN不等于包括NaN在内的任何值
console.log(NaN == NaN) // false
为此,ECMAScript提供了isNaN这个函数来判断
console.log(isNaN(NaN)) // true
console.log(isNaN(10)) // false
console.log(isNaN('10')) // false
console.log(isNaN('blue')) // true 'blue'转数字为NaN
console.log(isNaN(true)) // false true转数字为1
数值转换
有三个函数可以将非数值转换为数值:Number(), parseInt(), 和parseFloat()
Number()是转型函数,可以用于任何数据,后面两个主要针对字符串
Number()函数基于如下规则进行转换
- 布尔值,true转为1,false为0
- 数值,直接返回
- null 返回0
- undefined 返回NaN
- 字符串
-
- 如果字符串只包含数值字符,则转换为数值(十进制)
Number('012') // 12
-
- 如果字符串包含有效浮点数字符,转换为浮点数
-
- 如果包含有效的十六进制字符,则转换为十进制
Number('0x13') // 19
-
- 如果是空字符串,则返回0
-
- 如果包含上述之外的其他字符,则返回NaN
- 对象,调用对象valueOf()方法,并按上述规则进行转换,如果结果是NaN,则调用toString()方法在进行转换
parseInt更专注于字符串是否包含数值模式,从第一个非空的字符开始转换,如果第一个不是有效的数值字符,加号或者减号,则返回NaN, 这意味着空字符串也会返回NaN(这一点和Number不一样,它返回0)
如果字符串的第一个字符是数值字符,parseInt能识别到不同的整数格式,如果字符串是“0x”开头的,则会被解释为十六进制
console.log(parseInt('012')) // 12
console.log(parseInt('0x23')) // 35
console.log(parseInt('a12')) // NaN
console.log(parseInt('')) // NaN
不同的数值格式很容易混淆,所以parseInt提供了第二个参数,用于表示几进制进行解析
console.log(parseInt('123', 2)) // 1 2进制遇2进一,这里123后面23是不符合的,所以解析了第一位
console.log(parseInt('123', 8)) // 83
console.log(parseInt('123', 10)) // 123
console.log(parseInt('123', 16)) // 291
如果提供了十六进制参数,那么字符串前面的'0x'可以忽略掉
console.log(parseInt('AF', 16)) // 175
console.log(parseInt('AF')) // NaN
String
字符串可以使用单引号('),双引号("),或者反引号(`)表示
字符字面量
字符串数据类型包含一些字符字面量
| 字面量 | 含义 |
|---|---|
| \n | 换行 |
| \t | 制表 |
| \b | 退格 |
| \r | 回车 |
| \f | 换页 |
| \ | 反斜杠() |
| ' | 单引号(') |
| " | 双引号(") |
| ` | 反引号(`) |
| \xnn | 以十六进制编码nn表示的字符 |
| \unnnn | 以十六进制编码nnnn表示的Unicode字符 |
这些字符字面量可以出现在字符串的任意位置,转义字符被视为是一个字符
let str1 = 'this is the letter sigma" \u03a3.'
console.log('str1.length',str1.length) // 28
字符串的特点
ECMAScript 中的字符串是不可变的(immutable),一旦被创建,他的值就不能变了,要修改的话,必须先销毁之前的字符串,并且赋值一个新的变量
let lang = 'java';
lang += 'script'
转换为字符串
有两种方式可以转为字符串
第一种为toString(),这个方法可以用于数值,布尔值,对象,字符字面量,注意,null和undefined没有toString()
在对数值做toString处理的时候,可以接受一个底数参数,这个参数表示按照几进制进行转换
let num = 10;
console.log(num.toString(2)) // '1010'
console.log(num.toString(8)) // '12'
console.log(num.toString(10)) // '10'
console.log(num.toString(16)) // 'a'
如果你不确定一个值是不是null和undefined,可以使用String转型函数,他始终会返回相应值的字符串
String 有如下规则
- 如果值有toSring()方法,则调用该方法
- 如果值是null,返回'null'
- 如果值是undefined,返回'undefined'
console.log('String(true)',String(true)) // 'true'
console.log('String(null)',String(null)) // 'null'
console.log('String(undefined)',String(undefined)) // 'undefined'
console.log('String(123)',String(123)) // '123'
模板字符串
ES6新增的使用模板字面量定义字符串的能力,与单双引号不同的是,模板字面量保留换行字符,可以夸行定义字符串
let str1 = 'first line \nsecond line'
let str2 = `first line
second line`
console.log('str1==str2', str1 == str2) // true
模板字符串在定义模板时特别有用
let pageHTML = `
<div>
<a href="#"></a>
</div>
`
由于模板字符串会保留换行时候缩紧的空格,所以使用的时候一定要小心
let str3 = 'first line \nsecond line'
let str4 = `first line
second line`
console.log('str3',str3.length) // 23
console.log('str4',str4.length) // 39
字符串插值
字符串插值通过${}中使用一个js表达式实现
let name = 'zhang san';
let str5 = 'my name is ' + name;
let str6 = `my name is ${name}`
所有插入的值都会使用toString()强制转为字符串
let name = { toString() { return 'zhang san' } };
let str6 = `my name is ${name}`
console.log('str6',str6)
模板字面量标签函数
模板字面量也支持定义标签函数
标签函数是一个常规的函数,接收的参数依次是原始字符串数组,和对每个表达式求值的结果
let a = 1;
let b = 2;
function simpleTag(...args) {
console.log(args)
}
simpleTag`${a} + ${b} = ${a + b}`; // ["", " + ", " = ", ""] 1, 2, 3
原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容
console.log(`\u00A9`) // ©
如果不想获取原始的内容,可以使用String.raw标签函数
console.log(String.raw`\u00A9`) // \u00A9
Symbol
Symbol(符号)是 ECMAScript 6 新增的数据类型。是原始值,是唯一,不可变的,符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
基本用法
需要使用 Symbol()函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol
const sym = Symbol();
console.log(typeof sym) // symbol
调用 Symbol()函数时,也可以传入一个字符串参数
const genericSymbol = Symbol();
const otherGenericSymbol = Symbol();
const fooSymbol = Symbol('foo');
const otherFooSymbol = Symbol('foo');
console.log(genericSymbol === otherGenericSymbol) // false
console.log(fooSymbol === otherFooSymbol) // false
Symbol()函数不能与 new 关键字一起作为构造函数使用。这样做是为了避免创建符 号包装对象
const mySymbol = new Symbol(); // Symbol is not a constructor
如果你确实想使用符号包装对象,可以借用 Object()函数
const myWrapperSymbol = Object(Symbol());
console.log(typeof myWrapperSymbol) // object
使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,为此,需要使用 Symbol.for()方法
Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运 行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同 字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例
let foo1 = Symbol.for('foo');
let foo2 = Symbol.for('foo');
console.log(foo1 === foo2) // true
采用相同的符号描述,在全局注册表中定义的符号跟使用 Symbol()定义的符号也并不等同
let foo3 = Symbol('foo');
console.log(foo1 === foo3) // false
可以使用 Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字 符串键。如果查询的不是全局符号,则返回 undefined
let for1 = Symbol.for('for1')
console.log(Symbol.keyFor(foo2)) // foo
console.log(Symbol.keyFor(for1)) // for1
如果传给 Symbol.keyFor()的不是符号,则该方法抛出 TypeError
Symbol.keyFor(123); // 123 is not a symbol
使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号,包括了对象字面量属性和 Object.defineProperty()/Object.defineProperties()定义的属性
let obj = {
[Symbol.for('123')]: 123,
}
console.log(obj[Symbol.for('123')]) // 123
obj[Symbol.for('456')] = 456;
console.log(obj[Symbol.for('456')]); // 456
Object.defineProperty(obj, Symbol.for('789'), {
value: 789,
})
console.log(obj[Symbol.for('789')]) // 789
Object.getOwnPropertyNames()返回对象实例的常规属性数组, Object.getOwnProperty- Symbols()返回对象实例的符号属性数组,这两个方法的返回值彼此互斥, Reflect.ownKeys()会返回两种类型 的键
console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(123), Symbol(456), Symbol(789)]
console.log(Reflect.ownKeys(obj)) // [Symbol(123), Symbol(456), Symbol(789)]
obj[Symbol('10')] = 10
console.log(Object.getOwnPropertySymbols(obj[Symbol('10')][3])) // 10
常用内置符号
ECMAScript 6 也引入了一批常用内置符号,用于暴露语言内部行为,开发者 可以直接访问、重写或模拟这些行为
💡: 在提到ECMAScript规范时,经常会引用符号在规范中的名称,前缀为@@。比如, @@iterator 指的就是 Symbol.iterator。
Symbol.asyncIterator
该方法返回对象默认的 AsyncIterator。 由 for-await-of 语句使用,换句话说,这个符号表示实现异步迭代器 API 的函数
class Foo {
async *[Symbol.asyncIterator]() {
}
}
const f = new Foo();
console.log(f[Symbol.asyncIterator]()) // AsyncGenerator {<suspended>}
class Emitter {
constructor(max) {
this.max = max;
this.asyncIndex = 0
}
async *[Symbol.asyncIterator]() {
while (this.asyncIndex < this.max) {
yield new Promise((resolve, reject) => {
resolve(this.asyncIndex++)
})
}
}
}
async function test() {
const emitter = new Emitter(5);
for await(const x of emitter) {
console.log(x); // 0 1 2 3 4
}
}
test()
Symbol.hasInstance
该方法决定一个构造器对象是否认可一个对象是它的实例。由 instanceof 操作符使用
function Foo() { }
const f = new Foo();
console.log(f instanceof Foo) // true
console.log(Foo[Symbol.hasInstance](f)) // true
class Bar extends Foo {
static [Symbol.hasInstance]() {
return false;
}
}
const b = new Bar();
console.log(b instanceof Bar) // false
console.log(Bar[Symbol.hasInstance](f)) // false
Symbol.isConcatSpreadable
这个符号作为一个属性表示“一个布尔值,如果是 true,则意味着对象应 该用 Array.prototype.concat()打平其数组元素”
ES6 中的 Array.prototype.concat()方法会 根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。覆盖 Symbol.isConcat- Spreadable 的值可以修改这个行为
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
console.log(arr1.concat(arr2)) // [1, 2, 3, 4, 5, 6]
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2)) // [1, 2, 3, [4, 5, 6]]
Symbol.iterator
这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。 由 for-of 语句使用”。换句话说,这个符号表示实现迭代器 API 的函数
class Foo {
constructor(max) {
this.max = max;
this.idx = 0
}
*[Symbol.iterator]() {
while (this.idx < this.max) {
yield this.idx++
}
}
}
const f = new Foo(5);
for (const iterator of f) {
console.log(iterator) // 0 1 2 3 4
}
Symbol.match
这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式 去匹配字符串。由 String.prototype.match()方法使用”
class SymbolMatch {
static [Symbol.match](str) {
return str.includes('foo')
}
}
console.log('fooBar'.match(SymbolMatch)) // true
console.log('fBar'.match(SymbolMatch))// false
class SymbolMatch2 {
constructor(str) {
this.str = str;
}
[Symbol.match]() {
return this.str.includes('foo')
}
}
console.log('fooBar'.match(new SymbolMatch2('foo'))) // true
Symbol.replace
这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符 13 串中匹配的子串。由 String.prototype.replace()方法使用”
class SymbolReplace {
static [Symbol.replace](str) {
return str.replace('foo', 'foo2')
}
}
console.log('fooBar'.replace(SymbolReplace)) // foo2bar
console.log('fBar'.replace(SymbolReplace))// fBar
class SymbolReplace2 {
constructor(str) {
this.str = str;
}
[Symbol.replace]() {
return this.str.replace('foo', 'foo2')
}
}
console.log('fooBar'.replace(new SymbolReplace2('fooBar'))) // foo2bar
Symbol.search
这个符号作为一个属性表示“一个正则表达式方法,该方法返回字符串中 匹配正则表达式的索引。由 String.prototype.search()方法使用”
class SymbolSearch {
static [Symbol.search](str) {
return str.indexOf('foo')
}
}
console.log('fooBar'.search(SymbolSearch)) // 0
console.log('fBar'.search(SymbolSearch))// -1
class SymbolSearch2 {
constructor(str) {
this.str = str;
}
[Symbol.search]() {
return this.str.indexOf('foo')
}
}
console.log('fooBar'.search(new SymbolSearch2('fooBar'))) // 0
Symbol.species
这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构 造函数”。这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方 法。用 Symbol.species 定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义
class Bar extends Array { }
class Baz extends Array {
static get [Symbol.species]() {
return Array
}
}
let bar = new Bar();
console.log(bar instanceof Bar) // true
bar = bar.concat(1);
console.log(bar instanceof Bar) // true
let baz = new Baz();
console.log(baz instanceof Baz) // true
baz = baz.concat(1);
console.log(baz instanceof Baz) // false
Symbol.split
这个符号作为一个属性表示“一个正则表达式方法,该方法在匹配正则表 达式的索引位置拆分字符串。由 String.prototype.split()方法使用”
class FooSplitter {
static [Symbol.split](target) {
return target.split('foo');
}
}
console.log('barfoobaz'.split(FooSplitter)) // ['bar', 'baz']
class StringSpliter {
constructor(str) {
this.str = str;
}
[Symbol.split](target) {
return target.split(this.str)
}
}
console.log('barfoobaz'.split(new StringSpliter('foo'))) // ['bar', 'baz']
Symbol.toPrimitive
这个符号作为一个属性表示“一个方法,该方法将对象转换为相应的原始 值。由 ToPrimitive 抽象操作使用”。很多内置操作都会尝试强制将对象转换为原始值,包括字符串、 数值和未指定的原始类型。对于一个自定义对象实例,通过在这个实例的 Symbol.toPrimitive 属性 上定义一个函数可以改变默认行为
class Bar extends Array { }
class Baz extends Array {
static get [Symbol.species]() {
return Array
}
}
let bar = new Bar();
console.log(bar instanceof Bar) // true
bar = bar.concat(1);
console.log(bar instanceof Bar) // true
let baz = new Baz();
console.log(baz instanceof Baz) // true
baz = baz.concat(1);
console.log(baz instanceof Baz) // false
*/
/*
class FooSplitter {
static [Symbol.split](target) {
return target.split('foo');
}
}
console.log('barfoobaz'.split(FooSplitter)) // ['bar', 'baz']
class StringSpliter {
constructor(str) {
this.str = str;
}
[Symbol.split](target) {
return target.split(this.str)
}
}
console.log('barfoobaz'.split(new StringSpliter('foo'))) // ['bar', 'baz']
*/
/*
class Foo { }
let foo = new Foo();
console.log(3 + foo); // 3[object Object]
console.log(3 - foo); // NaN
console.log(String(foo)) // [object Object]
class Bar {
constructor() {
this[Symbol.toPrimitive] = function (hint) {
switch (hint) {
case 'number':
return 3;
case 'string':
return 'string bar'
default:
return 'default bar'
}
}
}
}
let bar = new Bar();
console.log(3 + bar); // 3default bar
console.log(3 - bar); // 0
console.log(String(bar)) // string bar
Symbol.toStringTag
这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认 字符串描述。由内置方法 Object.prototype.toString()使用”
let set = new Set();
console.log(set.toString()) // [object Set]
console.log(set[Symbol.toStringTag]) // Set
class Foo { }
let foo = new Foo();
console.log(foo.toString()) // [object Object]
console.log(foo[Symbol.toStringTag]) // undefined
class Baz {
[Symbol.toStringTag] = 'Baz'
}
let baz = new Baz();
console.log(baz.toString()) // [object Baz]
console.log(baz[Symbol.toStringTag]) // Baz
Symbol.unscopables
这个符号作为一个属性表示“一个对象,该对象所有的以及继承的属性, 都会从关联对象的 with 环境绑定中排除”
let o = {
foo: 'bar',
foo1: 'bar1',
}
with (o) {
console.log(foo) // bar
console.log(foo1) // bar1
}
o[Symbol.unscopables] = {
foo1: true
}
with (o) {
console.log(foo) // bar
console.log(foo1) // foo1 is not defined
}
Object类型
对象其实就是一组数据和功能的合集, 可以通过new操作符创建
let obj = new Object();
如果没有参数,如 上面的例子所示,那么完全可以省略括号(不推荐)
let obj2 = new Object; // 可以,但是不推荐
ECMAScript 中的 Object 也是派生其他对象的基类。Object 类型的所有属性和方法在派生 的对象上同样存在
每个Object实例都有如下属性和方法
| 属性/方法 | 描述 |
|---|---|
| constructor | 用于创建当前对象的函数 |
| hasOwnProperty | 用于判断当前对象实例(不是原型)上是否存在给定的属性 |
| isPrototypeOf | 用于判断当前对象是否为另一个对象的原型 |
| propertyIsEnumerable | 用于判断给定的属性是否可以使用 |
| toLocaleString | 返回对象的字符串表示,该字符串反映对象所在的本地化执行环境 |
| toString | 返回对象的字符串表示 |
| valueOf | 返回对象对应的字符串,或者数值,或者布尔值表示,通常与toString()的返回值相同 |
因为在 ECMAScript 中 Object 是所有对象的基类,所以任何对象都有这些属性和方法