JavaScript数据类型进阶
数字类型
常见数字类型
-
十进制:
121
-
二进制:
01011
-
八进制:
0o377
-
十六进制:
0xff
-
科学计数法:
1.23e6
-
NaN
转换为字符串:
.toString(base)
舍入
Math.floor
Math.ceil
Math.round
Math.trunc
:移除小数点后的内容,IE浏览器不支持
0.1+0.2
简单理解,0.1、0.2在计算机中其实是无线循环的小数
-
理解这个之前需要知道计算机中浮点数的表示IEEE 754浮点数标准详解,在JavaScript中数字是以64位双精度浮点数来存储的,表示方法:
符号 指数 尾数 占位1 占位11 占位52 整数部分作为隐藏位,所以最大值会到53次幂
-
在计算机中,数字都是以二进制存储的,所以我们要先将
0.1
和0.2
转化成二进制,对于十进制转二进制,整数部分除二取余,倒序排列,小数部分乘二取整,顺序排列得到:0.1
→0.0 0011 0011 0011 0011 0011 0011 ...
0.2
→0.0 011 0011 0011 0011 0011 0011 0011 ...
-
使用IEEE 754 双精度64位浮点数来表示
// 0.1
e = -4; // 2^-4 = 0.0625
m = 1.1001100110011001100110011001100110011001100110011010 (52位)
// 0.2
e = -3; // 2^-3 = 0.125
m = 1.1001100110011001100110011001100110011001100110011010 (52位)
- 做加法运算是首先进行对阶,指数小的做右移操作,于是有
e = -3; m = 0.1100110011001100110011001100110011001100110011001101 (52位)
// +
e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位)
得
e = -3; m = 10.0110011001100110011001100110011001100110011001100111 (52位)
// 保留一位整数
e = -2; m = 1.00110011001100110011001100110011001100110011001100111 (53位)
// 已经溢出,处理规则为保留偶数
e = -2; m=1.0011001100110011001100110011001100110011001100110100
// 那么最终结果位
0.1 + 0.2 === 1.0011001100110011001100110011001100110011001100110100 * 2 ^ -2
// 转化位小数
x === 0.010011001100110011001100110011001100110011001100110100
// 即:
x === 0.30000000000000004
如何解决0.1+0.2问题
- 使用
num.toFixed()
方法可把 Number 四舍五入为指定小数位数的数字。它返回的是字符串,可以使用一元符号进行转换
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3
- 将数字临时乘以一个数
isFinite 和 isNaN
-
NaN
是独一无二的,它不等于任何东西,包括它自身alert( NaN === NaN ); // false
-
isNaN(value)
将其参数转换为数字,然后测试其是否为NaN
,如果是返回true
-
isFinite(value)
将其参数转换为数字,如果是常规数字,则返回true
,如果为NaN/Infinity/-Infinity
返回false
**注意:**在所有数字函数中,包括 isFinite
,空字符串或仅有空格的字符串均被视为 0
。
parseInt 和 parseFloat
- 它们可以从字符串中读取数字,直到无法读取为止。如果发生 error,则返回收集到的数字。函数
parseInt
返回一个整数,而parseFloat
返回一个浮点数,如果未读取到返回NaN
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12,只有整数部分被返回了
alert( parseFloat('12.3.4') ); // 12.3,在第二个点出停止了读取
- 第二个参数:
parseInt()
函数具有可选的第二个参数。它指定了数字系统的基数,因此parseInt
还可以解析十六进制数字、二进制数字等的字符串
alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255,没有 0x 仍然有效
alert( parseInt('2n9c', 36) ); // 123456
进制转换
Number.parseInt(string , radix)
Number.toString(radix)
其他数学函数
Math.random()
Math.max()
Math.min()
Math.pow(n, power)
- …
数组
数组时一种有序集合,JavaScript中的数组可以存储任意数据类型
数组创建
数组创建的两种方法
let arr = new Array()
let arr = []
数组的长度
arr.length
,他会自动更新,而且它是可写的
数组方法
改变原数组的方法 | 不改变原数组的方法 |
---|---|
push | concat |
shift | slice |
pop | indexOf |
unshift | lastIndexOf |
splice | includes |
sort | find |
reverse | findIndex |
filter | |
map | |
split | |
join |
-
作为队列
push
在末端添加一个(或多个)元素,在原数组上修改,返回修改后数组的长度shift
取出队列首端的一个元素,整个队列往前移,在原数组修改,返回取出的元素
-
作为栈
push
pop
从末端取出一个元素
-
搜索方法
请注意,以下这些方法使用的是严格相等
===
比较。所以如果我们搜索false
,会精确到的确是false
而不是数字0
- 常用
arr.indexOf(item, from)
从索引from
开始搜索item
,如果找到则返回索引,否则返回-1
arr.lastIndexOf(item, from)
和上面相同,只是从右向左搜索arr.includes(item, from)
从索引from
开始搜索item
,如果找到则返回true
,如果没找到,则返回false
arr.find(function(item, index, array)
寻找满足特定条件的对象,找到返回true
并停止搜索,找不到返回undefined
arr.findIndex
方法(与arr.find
方法)基本上是一样的,但它返回找到元素的索引,而不是元素本身。并且在未找到任何内容时返回-1
arr.filter(fn)
语法与find
大致相同,但是filter
返回的是所有匹配元素组成的数组,它可以返回多个
- 常用
-
数组操作
首先,在数组和数组方法中,负向索引是允许的
unshift
在数组的首端添加元素,可以是一个或多个delete
,数组时对象,可以使用如delete arr[1]
,会留下一个undefined
空位splice
可以添加,删除和插入元素arr.splice(start[, deleteCount, elem1, ..., elemN])
,它从索引start
开始修改arr
:删除deleteCount
个元素并在当前位置插入elem1, ..., elemN
。最后返回已被删除元素的数组。slice
可提取字符串的某个部分,并以新的字符串返回被提取的部分,不会改变原始数组。arr.slice([start], [end])
会返回一个新数组,将所有从索引start
到end
(不包括end
)的数组项复制到一个新的数组。start
和end
都可以是负数,在这种情况下,从末尾计算索引concat
可以合并数组并但会合并的新数组map
通过arr.map
它对数组的每个元素都调用函数,并返回结果数组。sort
对数组进行排序并返回排序后的数组,默认是转换成字符串比较,会造成2>13
的情况,需要传入一个函数作为参数进行比较两个值返回0
、1
、-1
进行排序,可以是倒序也可以是顺序,看返回值怎么设定,注意,不一定要用1
和-1
只要是正负数就可以了,正数代表大于,负数代表小于,使用箭头函数更佳
function compareNumeric(a, b) {
if (a > b) return 1;
if (a == b) return 0;
if (a < b) return -1;
}
let arr = [ 1, 2, 13 ];
arr.sort(compareNumeric);
alert(arr); // 1, 2, 13
arr.reverse
方法用于颠倒arr
中元素的顺序split
,可选第二个参数限制数组长度,超长不在运行,传入空字符串返回拆解的字母join
reduce
:应用函数时,上一个函数调用的结果将作为第一个参数传递给下一个函数
// 累加,可省略初始值(第二个参数)
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15
reduceRight
:reduce
只不过数组从右边开始遍历Array.isArray(value)
判断是否为数组
-
push/pop
、shift/unshift
性能比较push/pop
方法运行的比较快,而shift/unshift
比较慢。因为操作数组的前端部分需要移动所有元素并修改索引 -
可以但不要用的几种方式
arr.test=1
给arr
添加一个非数字的书写,可以实现,但不能通过pop
、push
操作它,和length
一样,它是属性,不会改变数组的长度- 制造空洞,比如:添加
arr[0]
,然后添加arr[1000]
(它们中间什么都没有)。使用pop
得到的会是undefined
- 以倒序填充数组,比如
arr[1000]
,arr[999]
等等。
数组的循环
for
for-of
for-in
,也可以遍历数组值,但不推荐,也会遍历出length等属性名(显示undefined
)和其他自建的非数字索引,且速度慢arr.forEach(func)
数组的toString
arr.toString
和String(arr)
都会返回以逗号隔开的元素列表。如果嵌套了数组,他会展开
数组相等
数组时对象,不应该使用 ==
运算符比较 JavaScript 中的数组
/* 这里都是对象,除非地址相同,否则永不相等 */
alert( [] == [] ); // false
alert( [0] == [0] ); // false
/* 这里是因为数组被转换成了空字符串'' */
alert( 0 == [] ); // true
alert('0' == [] ); // false
可迭代对象
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。具有iterable
类型的集合可以通过新的for ... of
循环来遍历。
Symbol.iterator
for_of
运行机制- 当
for..of
循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有next
方法的对象。 - 从此开始,
for..of
仅适用于这个被返回的对象。 - 当
for..of
循环希望取得下一个数值,它就调用这个对象的next()
方法。 - next()
方法返回的结果的格式必须是
{done: Boolean, value: any},当
done=true时,表示迭代结束,否则
value` 是下一个值
- 当
- 实现迭代器
range
/* 可迭代对象range实现 */
let range = {
from: 1,
to: 5
};
// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {
// ……它返回迭代器对象(iterator object):
// 2. 接下来,for..of 仅与此迭代器一起工作,要求它提供下一个值
return {
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 运行
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
range
自身没有 next()
方法。相反,是通过调用 range[Symbol.iterator]()
创建了另一个对象,即所谓的“迭代器”对象,并且它的 next
会为迭代生成值。
隐藏以上代码可以简写为:
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this; // 这里返回的时自己
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
显式调用迭代器
let str = "Hello";
// 和 for..of 做相同的事
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value);
}
可迭代(iterable)和类数组(array-like)
- Iterable 如上所述,是实现了
Symbol.iterator
方法的对象。 - Array-like 是有索引和
length
属性的对象,所以它们看起来很像数组。
Array.from
全局方法 Array.from
可以接受一个可迭代或类数组的值,并从中获取一个“真正的”数组。然后我们就可以对其调用数组方法了。
Array.from(obj[, mapFn, thisArg])
:可选的第二个参数mapFn
可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外thisArg
允许我们为该函数设置this
。
Map and Set(映射和集合)
Map
Map是一个带键的数据项的集合,就像一个 Object
一样。 但是它们最大的差别是 Map
允许任何类型的键(key)。
map的属性和方法
-
new Map()
—— 创建 map -
map.set(key, value)
—— 根据键存储值,每一次map.set
调用都会返回 map 本身,所以我们可以进行“链式”调用 -
map.get(key)
—— 根据键来返回值,如果map
中不存在对应的key
,则返回undefined
。 -
map.has(key)
—— 如果key
存在则返回true
,否则返回false
。 -
map.delete(key)
—— 删除指定键的值。 -
map.clear()
—— 清空 map。 -
map.size
—— 返回当前元素个数。 -
map.keys()
—— 遍历并返回所有的键,主要返回的不是数组,而是MapIterator
-
map.values()
—— 遍历并返回所有的值,主要返回的不是数组,而是MapIterator
map.entries()
—— 遍历并返回所有的实体(key、value),对map
使用for..of
在默认情况下使用的就是这个。
-
map.forEach(value,key,map)
创建map
new Map()
let map = new Map()
map.set('1', 'str1')
map.set(1, 'num1')
map.set(true, 'bool1')
new Map([[key, value],[key,value]])
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
- 从对象创建map
new Map(Object.entries(obj))
// Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组
let obj = {
name: "John",
age: 30
}
let map = new Map(Object.entries(obj))
从map创建对象
// 创建一个普通对象(plain object)
// obj = { banana: 1, orange: 2, meat: 4 }
let map = new Map()
map.set('banana', 1)
map.set('orange', 2)
map.set('meat', 4)
let obj = Object.fromEntries(map.entries())
let obj = Object.fromEntries(map) // 也可以
Set
Set
是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。
set的属性和方法
new Set(iterable)
—— 创建一个set
,如果提供了一个iterable
对象(通常是数组),将会从数组里面复制值到set
中。set.add(value)
—— 添加一个值,返回 set 本身set.delete(value)
—— 删除值,如果value
在这个方法调用的时候存在则返回true
,否则返回false
。set.has(value)
—— 如果value
在 set 中,返回true
,否则返回false
。set.clear()
—— 清空 set。set.size
—— 返回元素个数。
set迭代
- 我们可以使用
for..of
或forEach
来遍历 Set:
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// 与 forEach 相同:
set.forEach((value, valueAgain, set) => {
alert(value);
});
forEach
的回调函数有三个参数:一个 value
,然后是 同一个值 valueAgain
,最后是目标对象.forEach
的回调函数有三个参数,是为了与 Map
兼容。当然,这看起来确实有些奇怪。但是这对在特定情况下轻松地用 Set
代替 Map
很有帮助,反之亦然。
Map
中用于迭代的方法在Set
中也同样支持:
set.keys()
—— 遍历并返回所有的值(returns an iterable object for values),set.values()
—— 与set.keys()
作用相同,这是为了兼容Map
,set.entries()
—— 遍历并返回所有的实体(returns an iterable object for entries)[value, value]
,它的存在也是为了兼容Map
。
弱映射和弱集合
如果把一个对象放入到数组中,那么只要这个数组存在,那么这个对象也就存在,即使没有其他对该对象的引用,不会进行垃圾回收掉。弱映射和弱集合在这方面有根本的不同
WeakMap
WeakMap
的键必须是对象,不能是原始值
let weakMap = new WeakMap();
let obj = {name: "John"};
weakMap.set(obj, "ok"); // 以对象作为键
- 在
weakMap
中使用一个对象作为键,并且没有其他对这个对象的引用 —— 该对象将会被从内存(和map(map中无法访问到它))中自动清除
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // 覆盖引用 john 被从内存中删除了!
-
WeakMap
不支持迭代以及keys()
,values()
和entries()
方法。所以没有办法获取WeakMap
的所有键或值。WeakMap
只有以下的方法:-
weakMap.get(key)
-
weakMap.set(key, value)
-
weakMap.delete(key)
-
weakMap.has(key)
-
WeakSet
- 只能向
WeakSet
添加对象(而不能是原始值) - 对象只有在其它某个(些)地方能被访问的时候,才能留在 set 中
- 跟
Set
一样,WeakSet
支持add
,has
和delete
方法,但不支持size
和keys()
,并且不可迭代
Date
创建时间
-
new Date()
:// 当前的日期/时间 -
new Date(number)
// 时间戳:1970 年 1 月 1 日 UTC+0 之后经过的毫秒数 -
new Date(datestring)
:如let date = new Date("2017-01-26")
运行代码时会根据时区进行调整 -
new Date(year, month, date, hours, minutes, seconds, ms)
:年必须是四位数,月为0-11,,日缺失默认为1,其他参数缺失默认为0
访问日期组件
date.getFullYear()
date.getMonth()
:返回的是0-11date.getDate()
date.getHours()
date.getMinutes()
date.getSeconds()
date.getMillisenconds()
设置日期组件
date.setFullYear(year, [month], [date])
date.setMonth(month, [date])
date.setDate(date)
date.setHours(hour, [min], [sec], [ms])
date.setMinutes(min, [sec], [ms])
date.setSeconds(sec, [ms])
date.setMilliseconds(ms)
date.setTime(milliseconds)
:时间戳
自动校准
// 1st Feb 2013
let date = new Date(2013, 0, 32);
// 1 Mar 2016 闰年,加两天他会自动调整,这里2可以是0甚至负值
let date = new Date(2016, 1, 28);
date.setDate(date.getDate() + 2);
转化
- 转化为时间戳
// 使用一元运算符转换
let date = new Date();
alert(+date); // 以毫秒为单位的数值,与使用 date.getTime() 的结果相同
// 时间测量
let date1 = new Date()
doSomething()
let date2 = new Date()
let runtime = date2 - date1
// 更快的时间测量方法
let start = Date.now(); // Date.now() 它相当于 new Date().getTime(),但它不会创建中间的 Date 对象并进行类型转化。因此它更快,而且不会对垃圾处理造成额外的压力。
doSomething()
let end = Date.now();
runtime = start - end
- 字符串解析为Date
Date.parse(str)
返回时间戳- 其中
str
格式应该为YYYY-MM-DDTHH:mm:ss.sssZ
,字符"T"
是分隔符,可选字符'Z'
为+-hh:mm
格式的时区。单个字符Z
代表 UTC+0 时区。 - 简短形式如
YYYY-MM-DD
或YYYY-MM
,甚至可以是YYYY
let ms = Date.parse('2012-01-26T13:51:50.417-07:00');
alert(ms); // 1327611110417
JSON
JSON(JavaScript Object Notation)是表示值和对象的通用格式
JSON.stringify()
将对象转换为JSON,得到的json
字符串是一个被称为 JSON 编码(JSON-encoded) 或 序列化(serialized) 或 字符串化(stringified) 或 编组化(marshalled) 的对象JSON.parse()
将JSON转换回对象
JSON.stringify
-
JSON 编码的对象与对象字面量的区别:
- 字符串使用双引号。JSON 中没有单引号或反引号,单引号被转化为双引号
- 对象属性名称也是双引号的
-
JSON.stringify
支持的数据类型转换后都是字符串类型
- Object
- Arrays
JSON.stringify([1,2,3])
=> ‘[1,2,3]’ - Primitives:
- strings
JSON.stringify('test')
=> ‘ “test” ’ - numbers
JSON.stringify(1)
=> ‘1’ - boolean
JSON.stringify('test')
=> ‘false’ - null
JSON.stringify(null)
=> ‘null’
- strings
-
特定于 JavaScript 的对象属性会被
JSON.stringify
跳过:- 函数属性(方法)
- Symbol 类型的属性
- 存储
undefined
的属性
let user = {
sayHi() { // 被忽略
alert("Hello");
},
[Symbol("id")]: 123, // 被忽略
something: undefined // 被忽略
};
alert( JSON.stringify(user) ); // {}(空对象)
- 支持嵌套,但不能循环引用,会报错
- 可选参数:
replacer
和space
replacer
:要编码的属性数组或映射函数function(key, value)
,解决循环引用问题space
:用于格式化的空格数量,用于格式化,美观
// 如果我们传递一个属性数组给它,那么只有这些属性会被编码
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup 引用了 room
};
room.occupiedBy = meetup; // room 引用了 meetup
r = JSON.stringify(meetup, ['title', 'participants']); // '{"title":"Conference","participants":[{},{}]}'
// 使用replcer函数(不一定就交这个函数名)跳过循环引用
r2 = JSON.stringify(meetup, function replacer(key, value) {
return (key == 'occupiedBy') ? undefined : value;
}); // '{"title":"Conference","participants":[{"name":"John"},{"name":"Alice"}],"place":{"number":23}}'
-
toJSON
方法像
toString
进行字符串转换,对象也可以提供toJSON
方法来进行 JSON 转换。如果可用,JSON.stringify
会自动调用它。
JSON.parse
-
JSON.parse(str, [reviver])
str
:要解析的 JSON 字符串reviver
:可选的函数 function(key,value),该函数将为每个(key, value)
对调用,并可以对值进行转换- 可以用于嵌套对象
-
可选参数reviver
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert(meetup.date.getDate()); // 如果不转换它是字符串,没有这个方法