数据类型
原始类型的方法
JavaScript 中有七种原始类型,分别是 number、string、boolean、undefined、null、symbol、bigint。这七种原始类型是没有方法的。
但是在开发中,我们经常会对原始类型调用一些方法,比如:
let str = "hello";
console.log(str.toUpperCase()); //HELLO
这就和上面的结论产生了矛盾,这是为什么呢?
因为在 js 中,原始类型是不可变得。但是当 JavaScript 发现你是在用原始类型调用方法时,它会创建一个原始类型的临时包装对象。调用对象上的方法,调用结束后临时包装对象被销毁。
具体步骤:
- 创建一个包含字符串字面量的特殊对象,并且具有可用的方法。
- 运行方法返回一个新的字符串。
- 特殊对象被销毁,只留下原始值。
注意: null/undefined 没有任何方法。
数字类型
- 常规数字以 64 位的格式 IEEE-54 存储,也被称为“双精度浮点数”
- Bigint 用于表示任意长度的整数,存在的原因是常规整数不能安全的超过
(2^53-1)
或小于-(2^53-1)
。
toString(base)
方法num.toString(base)
返回给定base
进制数字系统中num
的字符串表现形式。
let num = 255;
alert(num.toString(16)); //ff
alert(num.toString(2)); //11111111
base
的范围可以从2
到36
。默认情况下是10
。
常见的用例如下:
- base = 16 用于十六进制颜色,字符编码等,数字可以是
0...9
或A...F
。 - base = 2 用于调试按位操作,数字可以是
0
或1
。 - base = 36 是最大进制,数字可以是
0...9
或A...Z
。所有拉丁字母都被用于表示数字。
alert((123456).toString(36)); //2n9c
舍入
- Math.floor:向下舍入 3.1 变成 3,-1.1 变成-2。
- Math.ceil:向上舍入:3.1 变成 4,-1.1 变成-1。
- Math.round:四舍五入:3.1 变成 3,3.5 变成 4,-1.1 变成-1。
- Math.trunc:去掉小数部分:3.1 变成 3,-1.1 变成-1。
想把数字舍入到指定位数有两种实现方式:
- 乘除法
let num = 1.23456;
alert(Math.round(num * 100) / 100); //1.23
- toFixed(n)
返回的是字符串,如果保留的位数不够会自动补 0。
let num = 12.34;
alert(num.toFixed(1)); //12.3
alert(num.toFixed(0)); //12
let num2 = 12.36;
alert(num2.toFixed(1)); //12.4
alert(num2.toFixed(3)); //12.360
不精确的计算
在内部,数字是以 64 位格式IEEE-754存储的,所以正好有 64 位可以存储一个数字:其中 52 位被用于存储这些数字,其中 11 位用于存储小数点的位置,而 1 位用于存储符号。
如果一个数字真的很大,则可能会溢出 64 位存储,变成一个特殊值Infinity
//最经典的一个问题
console.log(0.1 + 0.2 == 0.3); //false
console.log(0.1 + 0.2); //0.30000000000000004
为什么出现这个问题?
一个数字以二进制的形式存储在内存中,一个 1 和 0 的序列。但是在十进制数字系统中看起来很简单的0.1
,0.2
这样的小数,在二进制形式中是无限循环小数。
这也就是为什么在相加的时候,最后结果不是一个精确的 0.3
。IEEE-754 数字格式通过将数字舍入到最接近的可能数字来解决此问题。这些舍入规则通常不允许我们看到‘极小的精度损失’,但他确实存在。
alert((0.1).toFixrd(20)); //// 0.10000000000000000555
当两个数字求和时,它们的'精度损失'会叠加起来。这也就是为什么0.1+0.2 != 0.3
的原因。
其实不止是 JavaScript,其他语言也有这个问题,比如 PHP,Java 等。因为它们都是基于相同的数字格式。
怎么解决问题?
最可靠的方式是借助方法 toFixed(n)对结果进行舍入。
let sum = 0.1 + 0.2;
alert(+sum.toFixed(2)); //0.30
alert(9999999999999999); //10000000000000000
(10000000000000000).toString(2);
(9999999999999999).toString(2);
// '100011100001101111001001101111110000010000000000000000'
测试 isFinite 和 isNaN
isFinite(num) 检查参数是不是一个有效数字 NaN/Infinity/-Infinity 都返回 false
isNaN(num) 检查参数是不是一个数字 NaN 返回 true,其他返回 false
Object.is(a,b) 比较两个值是否严格相等,类似于
===
一样对值进行比较。- 适用于 NaN:Object.is(NaN,NaN);//true
- 适用
0
和-0
:从技术上讲这两个并不是一个值,所以 Object.is(0,-0)值为 false
parseInt 和 parseFloat
- parseInt(str,radix) radix 的作用是将 str 以 radix 为基数,转换为 10 进制的数字。
- parseFloat(str) 将字符串转换为浮点数,只解析第一个小数点。
特点:
- 都可以将字符串转换为数字,如果转换失败会返回 NaN。
- parseInt 会忽略字符串开头的空格,直到找到第一个非空格字符。如果它不是数字或负号,则返回 NaN。
- parseFloat 和 parseInt 类似,但是 parseFloat 会解析第一个小数点,直到找到非数字字符为止。
完整的Math对象
字符串
在 js 中,文本数据被以字符串的形式存储,单个字符没有单独的类型。
字符串的内部格式始终是 UTF-16 编码,它不依赖于页面编码。
引号
- ''
- ""
- ``
特殊字符
\n
换行\r
在 windows 文本文件中,两个字符\r\n
的组合表示一个换行。而在非 Windows 操作系统中,它就是\n
。这是历史原因造成的,大多数的 Windows 软件也理解\n
。\'
,\"
引号\\
反斜线\t
制表符\b
,\f
,\v
退格,换页,垂直标签---为了兼容性已经不在使用。-xXX
具有给定十六进制 UnicodeXX
的 unicode 字符,例如:'\x7A'
和'z'
相同。- uXXXX 以 UTF-16 编码的十六进制代码
XXXX
的 Unicode 字符,例如\u0049
--- 是版权符号©
的 UTF-16 代码。它必须正好是 4 个十六进制数字。 \u{X...XXXXX}
(1 到 6 个十六进制字符) 具有给定 UTF-32 编码的 Unicode 符号。一些罕见的字符用两个 Unicode 符号编码,占用 4 个字节。这样我们就可以插入长代码了。
alert("\u00A9"); // ©
alert("\u{20331}"); // 佫,罕见的中国象形文字(长 Unicode)
alert("\u{1F60D}"); // 😍,笑脸符号(另一个长 Unicode)
字符串长度
length
属性表示字符串长度:
alert(`My\n`.length); //3
访问字符
let str = `Hello`;
console.log(str[0]); //H
console.log(str.charAt(0)); //H
//charAt在没有访问到字符的时候会返回空字符串串,而str[0]会返回undefined
字符串可以使用for...of
遍历字符。
字符串是不可变的
字符串不可变,只能通过新创建的字符串来修改。
改变大小
toLowerCase()
和toUpperCase()
方法可以改变大小写。
查找子字符串
- str.indexOf(substring,start) 返回子字符串在字符串中第一次出现的位置,如果没有找到返回-1。
- str.lastIndexOf(substring,start) 从字符串的末尾开始搜索到开头。返回第一次出现的位置,从后往前计数。
includes,startsWith,endsWith
- str.includes(substring,start) 检查字符串是否包含某个字符。
- str.startsWith(substring,start) 检查字符串是否以某个字符开头。
- str.endsWith(substring,start) 检查字符串是否以某个字符结尾。
获取子字符串
- str.slice(start,end) 返回从 start 到 end(但不包括)的子字符串。start,end 可以是负数,表示从字符串末尾开始计数。
- str.substring(start,end) 返回从 start 到 end 的子字符串,不支持负数,负数被视为 0。
- substr(start,length) 返回从 strart 开始,取长度为 length 的字符串。start 支持负数,length 不支持负数,负数被视为 0。
比较字符串
字符串是按照字母顺序逐字比较。比对的是 Unicode 码点。
str.codePointAt(pos)
返回位置pos
在 Unicode 中的码数。String.fromCodePoint(code)
通过code
创建字符
alert(String.fromCodePoint(90)); // Z
代理对
处理 emoji、罕见的数字或象形文字或其他罕见的符号。 。。。。
额外的方法
str.trim()
去除字符串两端的空格。str.repeat(n)
重复字符串 n 次。str.padStart(n, str)
在字符串的开头填充字符,直到达到长度 n。str.padEnd(n, str)
在字符串的结尾填充字符,直到达到长度 n。
数组
数组是值的有序集合,里面的元素都是按照顺序排列的。
声明
let arr = new Array();
let arr = [];
使用 ‘at’ 获取最后一个元素
新增属性
array.at(n)
n 表示数组下标,如果 n 是负数,则从数组末尾开始计数。
获取最后一个元素:
arr[arr.length-1]
;arr.at(-1)
;
pop/push,shift/unshift
pop 删除并返回数组的最后一个元素
push 在数组的末尾添加一个或多个元素,并返回新数组的长度
shift 删除并返回数组的第一个元素
unshift 在数组的开头添加一个或多个元素,并返回新数组的长度
队列是最常见的使用数组的方法之一。末端添加,首部删除。同时也支持栈。尾部添加,尾部删除。
内部
数组是一种特殊的对象。使用方括号来访问属性arr[0]
实际上是来自对象的语法。他其实和obj[key]
相同,其中arr
是对戏那个,而数字用作键(key)。只是扩展了对象,提供了特殊的方法来处理有序的数据集合以及length
属性。本质上来说,它仍然是个对象。
性能
push/pop
方法运行的比较快,而shift/unshift
比较慢。
因为shift/unshift
修改数据后需要对数组中的数据重新编号。
循环
- for 循环
- for...of
- for...in
- for...in 循环会遍历所有的属性,不仅仅是这些数字属性。
- for...in 循环适用于普通对象,并且做了对应的优化,但是不适用于数组,因此速度会慢 10-100 倍。
length
length
实际上是最大的数字索引值加一。
也可以用来清空数组
arr.length = 0;
new Array()
let arr = new Array(1, 2, 3, 4, 5);
它很少被使用,因为方括号[]
更加简洁。而且,这种语法还有一个很棘手的特性。
如果使用单个数字,它会创建一个指定长度,却没有任何项的数组。
let arr = new Array(2); // 会创建一个 [2] 的数组吗?
alert(arr[0]); // undefined!没有元素。
alert(arr.length); // length 2
多维数组
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
alert(matrix[1][1]); // 最中间的那个数
toString
数组有自己的toString
方法的实现,会返回以逗号隔开的元素列表。
let arr = [1, 2, 3];
alert(arr); // 1,2,3
alert(String(arr) === "1,2,3"); // true
alert([]); //''
数组没有Symbol.toPrimitive
,也没有valueOf
,所以只能执行toSting
进行转换。
不要使用 == 比较数组
数组的本质是对象,除非你是引用的同一个数组,否则比较结果都是 false
数组方法
splice
从数组中删除元素?
arr.splice(start,deleteCount,elem1,elem2,...)
start
开始位置deleteCount
删除的个数elem1,elem2,...
添加的元素- 支持反向索引,start 可以是负数。
slice
返回一个新数组,将所有从索引start
到end
(不包括end
)的数组项复制到一个新的数组。start
和end
都可以是负数。
let arr = [1, 2, 3, 4, 5];
console.log(arr.slice(1, 3)); // [2,3]
concat
连接数组。
arr.concat(arg1,arg2...)
对象也可以和数组进行合并,需要有三个条件:
- 对象必须具有
length
属性 - 对象的属性必须是数字
- [Symbol.isConcatSpreadable] 属性必须为 true
let arr = [1, 2, 3];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2,
};
alert(arr.concat(arrayLike)); // 1,2,something,else
在数组中搜索
indexOf/lastIndexOf 和 includes
indexOf/lastIndexOf 返回找到的元素的索引,如果没有找到则返回-1。includes 返回布尔值。
格式:
arr.indexOf(item, from); arr.includes(item, from); // from表示开始搜索的位置,默认为0
**注意:**indexOf 和 includes 是使用严格相等
===
进行比较的。并且 includes 是可以正确处理NaN
的,但是 indexOf 不行。find 和 findIndex/findLastIndex
它们接受一个回调函数,并且返回第一个满足条件的元素。如果没有找到,find 会返回 undefined,而 findIndex/fundLastIndex 会返回-1。
filter 它会返回一个新数组,包含所有通过测试的元素。
转换数组
map
返回一个新数组,每个元素都是回调函数的结果。
sort
对数组进行原地排序,返回排序后的数组
reverse
对数组进行反转,返回反转后的数组
split 和 join
split 将字符串分割成数组,join 将数组连接成字符串。
reduce/reduceRight
用于计算数组中所有元素的总和。 区别是一个是从左到右,一个是从右到左。
let value = arr.reduce(function (accumulator, item, index, array) {}, initial);
Array.isArray
因为数组是基于对象的,所以使用 typeof 是没有办法判断出来的。 返回结果是 true 或者 false。
alert(typeof {}); // object
alert(typeof []); // object(相同)
大多数方法都支持'thisArg'
出了 sort 其他的数组方法都支持 附加参数thisArg
。
arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg 是可选的最后一个参数
thisArg
参数的值在func
中变为this
。
let army = {
minAge: 18,
maxAge: 27,
canJoin(user) {
return user.age >= this.minAge && user.age < this.maxAge;
},
};
let users = [{ age: 16 }, { age: 20 }, { age: 23 }, { age: 30 }];
// 找到 army.canJoin 返回 true 的 user
let soldiers = users.filter(army.canJoin, army);
alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23
补充方法
some(fn)/every(fn)
some(fn)只要有一个元素满足条件就返回 true,every(fn)只有所有元素都满足条件才返回 true。
let arr = [1, 3, 4, 5, 6]; console.log(arr.some((item) => item > 5)); console.log(arr.every((item) => item > 5));
fill(value,start,end)
从索引 start 到 end 填充 value。
arr.copyWithin(target, start, end);
start 到 end 的所有元素复制到自身的 target 位置(覆盖现有元素)。
let arr = [1, 2, 3, 4, 5]; arr.copyWithin(1, 3, 5); // [1, 4, 5, 4, 5]
arr.flat(depth)/arr.flatMap(fn);从多维数组创建一个新的扁平数组。
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: Array [0, 1, 2, 3, 4]
const arr2 = [0, 1, [2, [3, [4, 5]]]];
console.log(arr2.flat());
// expected output: Array [0, 1, 2, Array [3, Array [4, 5]]]
console.log(arr2.flat(2));
// expected output: Array [0, 1, 2, 3, Array [4, 5]]
console.log(arr2.flat(Infinity));
// expected output: Array [0, 1, 2, 3, 4, 5]
//flatMap(func)等价于调用map()方法后在调用深度为1的flat()方法。但是会比单独调用这两种方法更高效。
const arr1 = [1, 2, 1];
const result = arr1.flatMap((num) => (num === 2 ? [2, 2] : 1));
console.log(result);
// Expected output: Array [1, 2, 2, 1]
- Array.of(value1, value2, value3, ...) 基于可变数量的参数创建一个新的 Array 实例,而不考虑参数的数量或类型。
完整的手册
Iterable Object(可迭代对象)
**可迭代(Iterable)**对象是数组的泛化。这个概念是说任何对象都可以定制为可在for...of
循环中使用的对象。
Symbol.iterator
Symbol.iterator
是用来定义对象可迭代的方法,当对象存在这个方法时,它就可以被for...of
运行。
当
for..of
循环启动时,它就会调用这个方法(如果没有找到,就会报错)。这个方法必须返回一个迭代器(iterator) --- 一个有next
方法的对象。从此开始,
for...of
仅适用于这个被返回的对象。当
for...of
循环希望取得下一个数值,它就调用这个对象的next()
方法。next
方法返回的格式必须是{done:Boolean,value:any}
,当done=true
时,表示循环结束,否则value
是下一个值。
let range = {
from: 1,
to: 5,
};
range[Symbol.iterator] = function () {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
},
};
};
简化操作:
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) {
console.log(num);
}
无穷迭代器: 将range
设置为range.to = Infinity
,这时range
则称为了无穷迭代器。
字符串是可迭代的
for (let char of "test") {
// 触发 4 次,每个字符一次
alert(char); // t, then e, then s, then t
}
显式调用迭代器
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(object, mapFn, thisArg);
Array.from
方法从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例。经过它的转换,就可以调用数组的方法了。
Map and Set(映射和集合)
Map
Map 是一个带键的数据项集合,就像Object
一样。但是它们最大的差别是Map
允许任何类型的键(key)。
它的方法和属性如下:
new Map()
—— 创建 map。map.set(key,value)
-- 根据键存储值map.get(key)
根据键来返回值,如果map
中不存在对应的 key,则返回undefined
。map.has(key)
如果key
存在则返回true
,否则返回false
。map.delete(key)
删除指定的键,并返回true
,如果key
不存在则返回false
。map.clear()
清空map
。map.size
返回当前元素个数。
let map = new Map();
map.set("1", "str1"); // 字符串键
map.set(1, "num1"); // 数字键
map.set(true, "bool1"); // 布尔值键
// 还记得普通的 Object 吗? 它会将键转化为字符串
// Map 则会保留键的类型,所以下面这两个结果不同:
alert(map.get(1)); // 'num1'
alert(map.get("1")); // 'str1'
alert(map.size); // 3
**Map 怎么比较键:**使用 SameValueZero 算法来比较键是否相等。它和严格等于 ===
差不多,但区别是NaN
被看成是等于NaN
。所以NaN
也是可以被用作键。
链式调用: 每一次map.set
调用都会返回 map 本身,所以可以进行链式调用:
map.set("1", "str1").set(1, "num1").set(true, "bool1");
Map 迭代
map.keys()
-- 遍历返回一个包含所有键的可迭代对象。map.values()
-- 遍历返回一个包含所有值的可迭代对象。map.entries()
-- 遍历并返回一个包含所有实体[key,value]
的可迭代对象,for...of
在默认情况下使用的就是这个。
let recipeMap = new Map([
["cucumber", 500],
["tomatoes", 350],
["onion", 50],
]);
//可迭代对象
for (let vegetable of recipeMap.keys()) {
console.log(vegetable); // cucumber, tomatoes, onion
}
for (let amount of recipeMap.values()) {
console.log(amount); // 500, 350, 50
}
for (let entry of recipeMap.entries()) {
console.log(entry); // cucumber,500 (and so on)
}
顺序: 迭代的顺序和插入值的顺序相同。与普通的Object
不同,Map
保留了此顺序。
Object.entries:从对象创建 Map
let obj = {
name: "John",
age: 30,
};
let map = new Map(Object.entries(obj));
Object.fromEntries:从 map 创建对象
let map = new Map([
["banana", 1],
["orange", 2],
["meat", 4],
]);
let prices = Object.fromEntries(map);
console.log(prices); //{banana: 1, orange: 2, meat: 4}
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 迭代(iteration)
我们可以使用for...of
或forEach
来遍历 Set。
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
set.forEach((value, key, set) => alert(value));
set.keys()
-- 遍历并返回一个包含所有值的可迭代对象。set.values()
-- 与set.keys()
作用相同,这是为了兼容Map
。set.entries()
-- 遍历并返回一个包含所有的实体[value,value]
的可迭代对象。
总结:
Map
是一个带键的数据项的集合。
方法和属性:
new Map([iterable])
创建 map,可选择带有[key,value]
对的iterable
(例如数组)来进行初始化。map.set(key,value)
根据键存储值,返回 map 自身。map.get(key)
根据键来获取值,如果map
中不存在对应的 key 就返回undefined
。map.has(key)
根据键来判断是否存在map.delete(key)
根据键来删除值,如果key
存在就返回 true,否则返回false
。map.clear()
清空 map。map.size
返回当前元素个数。
与普通对象Object
的区别:
- 任何键、对象都可以作为键
- 有其他的便捷方法,如
size
属性
Set
是一组唯一值的集合
方法和属性:
new Set([iterable])
创建 set,可选择带有iterable
(例如数组)来进行初始化。set.add(value)
添加一个值,返回 set 本身。set.delete(value)
删除值,如果value
存在则返回true
,否则返回false
。set.has(value)
如果value
在 set 中返回true
,否则返回false
。set.clear()
清空 set。set.size
返回元素个数。
在Map
和Set
中,迭代总是按照值插入的顺序进行的,所以这些集合不是无序的,但是我们不能对元素进行重新排序,也不能直接按编号来获取元素。
WeakMap 和 WeakSet
WeakMap
和WeakSet
是Map
和Set
的“兄弟”版本,它们对于null
的垃圾回收(GC)非常重要。
WeakMap
WeakMap
是一种特殊的Map
,它的键必须是对象,并且是弱引用。这意味着如果没有其他引用指向这个对象,该对象就可以被垃圾回收。
WeakMap
不支持迭代以及keys()
、values()
、entries()
方法。所以没有办法获取WeakMap
的所有键或值。
WeakMap
只有以下方法:
weakMap.get(key)
weakMap.set(key,value)
weakMap.delete(key)
weakMap.has(key)
为什么会有这些限制?
因为当一个对象丢失了其他所有引用,那么它就会被垃圾回收机制自动回收。但是技术层面又不能准确知道它何时被回收。
这些都是由 JavaScript 引擎决定的。所以WeakMap
的当前元素的数量是未知的。这也就是为什么不能支持访问WeakMap
的所有键/值的方法。
WeakMap
的主要应用场景是 额外数据的存储。
- 示例 1:
let john = { name: "John" };
//记录john的访问次数
let weakMap = new WeakMap(); //使用对象作为键
weakMap.set(john, 42);
WeakSet
WeakSet
的表现类似:
- 与
Set
类似,但是我们只能向WeakSet
添加对象(而不能是原始值)。 - 对象只能在其他某个(些)地方能被访问的时候,才能留在
WeakSet
中。 - 跟
Set
一样,WeakSet
支持add
,has
和delete
方法,但不支持size
和keys()
,并且不可迭代。
let visitedSet = new WeakSet();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
visitedSet.add(john); // John 访问了我们
visitedSet.add(pete); // 然后是 Pete
visitedSet.add(john); // John 再次访问
// visitedSet 现在有两个用户了
// 检查 John 是否来访过?
alert(visitedSet.has(john)); // true
// 检查 Mary 是否来访过?
alert(visitedSet.has(mary)); // false
john = null;
// visitedSet 将被自动清理(即自动清除其中已失效的值 john)
WeakMap
和WeakSet
最明显的局限性就是不能迭代,并且不能获取所有当前内容。那样可能会造成不便。
Object.keys(), Object.values(), Object.entries()
- Object.keys(obj) 返回一个包含该对象所有的键的数组。
- Object.values(obj) 返回一个包含该对象所有的值的数组
- Object.entries(obj) 返回一个包含该对象所有[key,value]键值对的数组。
和 map
的区别:map、set 返回的都是一个可迭代的对象,Object.keys()、Object.values()、Object.entries()返回的都是数组。
注意: Object.keys/values/entries 会忽略 symbol 属性。
转换对象
Object.entries
对象转换为[key,value]的键值对数组Object.fromEntries
将数组转换为对象。同样也需要[key,value]格式的数组
解构赋值
数组解构
let arr = ["John", "Smith"];
let [firstName, lastName, name] = arr;
console.log(firstName, lastName, name); //John Smith undefined
- 忽略使用逗号的元素
let [firstName, , name] = ["John", "Smith"];
console.log(firstName, name); //John undefined
- 等号右侧可以是任何可迭代对象
let [a, b, c] = "abc";
let [one, two, three] = new Set([1, 2, 3]);
- 赋值给等号左侧的任何内容
let user = {};
[user.name, user.surname] = ["John", "Smith"];
console.log(user.name, user.surname); //John Smith
- 与.entries()方法进行循环操作
let user = {
name: "John",
age: 30,
};
for (let [key, value] of Object.entries(user)) {
console.log(`${key}:${value}`);
}
- 用于
Map
的类似代码更简单,因为 Map 是可迭代的:
let user = new Map();
user.set("name", "John");
user.set("age", 30);
for (let [key, value] of user) {
console.log(`${key}:${value}`);
}
- 交换变量值的技巧
let guest = "Jane";
let admin = "Pete";
[guest, admin] = [admin, guest];
- 剩余'...'
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "Imperator"];
- 默认值
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
// 默认值
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius(来自数组的值)
alert(surname); // Anonymous(默认值被使用了)
对象解构
let {prop:varName = default,...rest} = object;
JSON 方法,toJSON
JSON.stringify
- JSON.stringify(value, [replacer, space])
以下属性会被忽略:
- 函数属性(方法)
- Symbol 类型的键和值
- 存储
undefined
的属性
let user = {
sayHi() {
// 被忽略
alert("Hello");
},
[Symbol("id")]: 123, // 被忽略
something: undefined, // 被忽略
};
alert(JSON.stringify(user)); // {}(空对象)
限制:
不能有循环引用
let room = {
number: 23,
};
let meetup = {
title: "Conference",
participants: ["john", "ann"],
};
meetup.place = room; // meetup 引用了 room
room.occupiedBy = meetup; // room 引用了 meetup
JSON.stringify(meetup); // Error: Converting circular structure to JSON
replacer
- 可以是一个数组,数组中的元素是要被序列化的属性名。
- 也可以是一个函数,函数的参数是当前处理的键值对。
let meetup = {
title:'Conference',
participants:[{name:'John'},{name:'Alice'}],
}
JSON.stringify(meetup,['title'])
//'{"title":"Conference"}'
// 函数
let room = {
number:23
}
let meetup = {
title:'Conference',
participants:[{name:'John'},{name:'Alice'}],
place:room
}
room.occupiedBy = meetup;
JSON.stringify(meetup, function replacer(key,value){
console.log(`key:${key},value:${value}`);
return (key == 'occupiedBy') ? undefined : value;
})
space
- 可以是一个数字,指定缩进用的空白字符的数量
- 也可以是一个字符串,指定缩进用的字符
let user = {
name: "John",
age: 30,
};
// 缩进 2 个空格
JSON.stringify(user, null, 2);//'{\n "name": "John",\n "age": 30\n}'
// JSON.stringify(user, null, '----');//'{\n----"name": "John",\n----"age": 30\n}'
自定义‘toJSON’
如果对象定义了toJSON方法,在执行JSON.stringify时,会调用该方法,返回值会被序列化。
JSON.parse
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
room
};
alert( JSON.stringify(room) ); // 23
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"room": 23
}
*/
- JSON.parse(text, [reviver])
将JSON字符串解析为JavaScript对象。
text:要解析的JSON字符串
reviver:可选参数,函数,用来转换解析出的属性值。
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str);
//{title: 'Conference', date: '2017-11-30T12:00:00.000Z'}
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
//嵌套对象同样适用。
let meetup = JSON.parse(str,(key,value)=>{
if(key ==='date'){
return new Date(value);
}else{
return value
}
});