原始类型的方法

对象包装器

为什么能对一个基本类型调用其方法?因为JS引擎内部先帮我们创建了对象,调用完方法后,会自动销毁对象,只留下原本的基本类型。

给字符串添加一个属性?

得到的是一个 undefined
原因:访问属性时,创建了一个对象包装器,但使用完后会销毁。因此后面在访问,没有属性值。

  1. let s = 's'
  2. s.name = 'jack'
  3. console.log(s.name) // undefined

数字类型

写法和其他进制

  • 科学计数法 1e3 即 1000,e代表1后多少个0。 1e-3 ,即 1 / 1000。
  • 二进制, 0b 开头。 16进制,0x 开头。八进制, 0o 开头。

toString(base = 10)

数字转字符串,默认10进制。

数字.调用

整数时, 100 JS认为该语法省略了小数部分,因此对JS来说是 100. ,因此调用需要 100..toString()

四舍五入

  • Math.floor 向下取整
  • Math.ceil 向上取整
  • Math.round 四舍五入

精确度问题

IEEE754,用64位(8字节)存储一个数字,52位用于存储数字,11位存储小数点的位置,1位用作符号(正负)。所以最多存储64位,超出的话,就无限循环了,二进制里面是0舍1入。

解释

  • 符号位S,1.0 正数,-1.0 负数。 0代表正数,1代表负数。
  • 指数位E,即指数的位数,转为二进制后,看科学计数法以2为底的次方。比如1000,转为二进制是 1000..toString(2)1111101000 ,科学计数法为1.111101 * 2^9,所以指数为9。
    • 指数偏移量,因为只有11位用于存储小数点,2进制下11位满打满算是 11111111111 ,转换为10进制就是 2047 (可以通过 parseInt(11111111111, '2')得到 )。科学计数法下指数可以是正数,也可以是负数,中间值1023**,**所以[0,1022]表示负数,[1024,2047]表示正数。上面1000的二进制下的偏移量是 1023 + 9 = 1032,转为二进制位为 10000001000
  • 尾数位M, 上面1000二进制,再科学计数法后的小数部分是 111101 ,然后补齐到52位(用0补),即 1111 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

所以1000在计算机内存里面存储的是 符号位+指数偏移量+小数部分

  1. 0 + 10000001000 + 1111010000000000000000000000000000000000000000000000 =>
  2. 0100000010001111010000000000000000000000000000000000000000000000 // 正好64位

这个网站可以验证十进制数在内存中的存储是什么样的http://www.binaryconvert.com/convert_double.html

0.1 + 0.2 != 0.3

运算时,0.1和0.2先转换为二进制,本身2个数就会无限循环,相加后后依旧无限循环,因此会溢出。

解决方法

  1. 使用专业的数字处理库,比如 number.js
  2. 通过10或100来缩小误差
  3. 通过 toPrecision 来取需要的精度。
  4. 通过 toFixed 来四舍五入,但有时并不能满足你的需要,因为二进制精度问题,导致其四舍五入也会出问题

toFixed的误差

如下图
image.png
理想是4.6,但是这里是4.5。可以通过精度取舍来查看4.55到底是什么样子的。image.png
可以看到。因为精度问题,内部实际上是4.549999,所以四舍五入后是4.5

isNaN

判断是不是一个有效数。

注意

  • NaN不等于自身。但如果使用 Object.is 则相等,因为判断的是内存。
  • 0 === -0,计算时是相等的。但是如果使用 Object.is 不相等,因为在内存中正负的第一位是不同的。正是0,1是负。

isFinite

判断是否是无穷

一些数学方法

  • parseInt(str, radix) 可以将字符串转换为整数,默认是10进制。
  • parseFloat(str) 可以将字符串转为小数。
  • Math.random 返回 [0, 1) ,注意右边是开区间。
  • Math.max & Math.min
  • Math.pow(n, power) 以n为底的power次幂。

测试题

给定max和min,取一个在其范围内的小数(不包括max)。

分析:假设取100 ~ 200(不包括200)之间的小数。

  1. Math.random 会产生[0, 1)的之间的一个数。所以 (200-100) * [0,1) 会得到 [0, 99.99...]
  2. 然后加上 min = 100 ,可以得到 [100, 199.99...]的数,满足条件了。

结果:

  1. function random(min, max) {
  2. return min + (max - min) * Math.random()
  3. }

给定max和min,取在其范围内的整数(包括min, max)

分析:假设取100 ~ 200,也包括100和200。

  1. Math.random 产生[0, 1)之间的随机数。 Math.floor 可以向下取整。
  2. 我们可以算 [100, 201) 的取值范围,取到一个数,然后向下取整。
  3. 构造这个范围 100 + Math.random() * (200 - 100)
    1. function randomInt(min, max) {
    2. // 这里 用max+1-min就可以得201 - 100 = 101
    3. // 101 再乘以 [0, 1)可以得到[0, 101)
    4. // min + [0, 101) => [100, 201)中的一个随机数,然后向下取整200.999... => 200。
    5. // 这样每个整数的概率都是均等的
    6. return Math.floor(min + Math.random() * (max + 1 - min))
    7. }

字符串类型

引号

双引号,单引号,反引号都可以包裹。其中反引号支持模板变量,如下:

  1. const name = 'Jack'
  2. log(`${name} is boy`)

反引号的优点

可跨行书写

这样会在某些 eslint 校验一行代码长度时通过。

  1. `this
  2. is
  3. name
  4. `

特殊字符

常用的如换行符 \n ,特殊符号需要转义输出它,如引号 \'

unicode字符

\uXXXX 开头,必须跟着4个十六进制数字。在一些汉字转码会用到,比如我的名字 小民 ,不想让人一眼看出来。

  1. const name = '小民'
  2. const unicode = escape(name) // "%u5C0F%u6C11"
  3. // 这里把%替换为\,然后在控制台输入就可以看到对应的汉字
  4. // 同理转回汉字
  5. const backtoStr = unescape("%u5C0F%u6C11") // 小民

\u{X...XXXXXX} 1个或6个十六进制组成的符号,比如

  1. "\u{1F60D}"; // "😍"

字符串长度

具有length属性

访问字符

可以通过下标访问,甚至可以通过 for of 来迭代。

不可变性

不能改动字符串中的某个字符,可以自己截取拼接。

一些常用方法

  • str.indexOf,这个匹配不到的话返回值是 -1 ,可以使用 ~ 运算符方便判断 ~1 => 0
  • str.includes
  • str.lastIndexOf 用的不多
  • startsWith
  • endsWith
  • slice(start, [, end]) 截取字符串,不包括 end

字符串比较

实际比较的是编码。通过 codePointAt 获取字符对应的码。

数组

是对象,但是比较特殊,是有序集合,JS引擎对它做了优化,内存中存储的位置是连续的。

声明方法

  • new Array()
  • [] 字面量声明

常见方法

pop和push,shift和unshift

pop从末位删除一个,push从末位添加一个。
shift从头部删除一个,unshift从头部添加一个。

数组有2种常用的数据结构

  • 队列:先进先出。 FIFO
  • 栈:后进先出。 LIFO

    F:first I:in O:out

数组内部

数组也是对象,因此可以通过 key 访问成员,它的 key 是数字, 0, 1, 2
因此可以给数组添加属性。

注意

  • 不要跳跃式给数组分配一个很大的索引,会导致数组中间出现空洞。
  • 不要给数组添加属性,JS引擎优化会失效。

性能

pop/push 相比 shift/unshift ,性能更好,因为在末端删除或添加,不会影响前面的元素重新赋予新的下标。如果是在头部添加或删除,会导致数组每个元素下标全部增加或减少。

其他方法

splice

splice(startIndex[, deleteCount, addElement1, addElement2..., addElementN) 很厉害的方法,可以实现数组的删除,添加,插入。

slice

slice([start], [end]) 通用用于复制数组,复制的不包含end

concat

arr.concat(arr1, arr2...) 拼接数组arr1, arr2等等,得到一个新的数组。

遍历相关方法

forEach

forEach(function(item, index, array){...})

搜索相关

indexOf/lastIndexOf & includes

前2者查找到返回对应位置index,找不到则是-1
后者返回布尔值。

find/findIndex

前者返回找到的元素本身,后者返回元素的下标(找不到返回-1)。

filter

筛选,为每个元素执行一个函数,过滤出返回值为 true 的元素,形成一个新的数组。

转换数组

map

为每个元素执行,并返回处理后的值,形成一个新的数组。

sort

对原数组进行排序(操作了原数组)。
默认按照字符串进行排序。所以

  1. var arr = [1,2,15]
  2. arr.sort(); // arr => [1,15,2]

所以sort接收一个比较函数

  1. arr.sort((firstItem, secondItem) => {return 正数 | 负数 | 0}); // 正数代表大于 负数代表小于 0不变

reverse

颠倒数组(操作原数组)

split & join

str.split 将字符串按照一定规则拆成数组, arr.join 则按照一定规则将数组合并为字符串。

reduce/reduceRight

神方法,可以模拟数组其他方法,实现一些奇思妙想

https://segmentfault.com/a/1190000021737914 reduce25种妙用

实现减少循环次数

  1. // 可以同时模拟map和filter
  2. const arr = [1,2,3,4,5]
  3. const arr2 = arr.map(item => item * 2) // [2,4,6,8,10]
  4. const res = arr2.filter(item > 2) // [4,6,8,10]
  5. // 可以看到上面map和filter,各1层循环了。
  6. // 使用reduce 减少一层
  7. arr.reduce((accu, item) =>{
  8. const double = item * 2
  9. if(double > 2) {
  10. return [...accu, double]
  11. } else {
  12. return [...accu]
  13. }
  14. }, []) // [4,6,8,10] 只有1层reduce的循环

实现斐波那契

  1. function fibonacci(n = 2) {
  2. const arr = [...new Array(n).keys()] // => [0,1,2] 下标拿出来当元素
  3. return arr.reduce((accu, item, index) => {
  4. // index > 1的时候才能开始计算前2个数相加的和。不然 1 - 2 = -1了,数组成员不存在
  5. // index <= 1时,直接返回斐波那契最基础的2个数[0,1]
  6. return index > 1 ? [...accu, (accu[index - 1] + accu[index - 2])] : accu
  7. }, [0,1])
  8. }

redux中的compose函数原理

  1. redux中的compose做的事是:
  2. compose(fn1, fn2, fn3, fn4) 其实就是 fn1(fn2(fn3(fn4())))。前面的写法明显更清晰。
  3. let n = 10
  4. function fn1(x) {
  5. return x + 1
  6. }
  7. function fn2(x) {
  8. return x + 2
  9. }
  10. function fn3(x) {
  11. return x + 3
  12. }
  13. function fn4(x) {
  14. return x + 4
  15. }
  16. fn1(fn2(fn3(fn4(n)))) // 20
  17. 不直观。所以改下。
  18. const compose = (...funs) => funs.reduce((accu, cur) => (...args) => accu(cur(...args)))
  19. // 核心思想
  20. // compose接受一系列函数,返回一个新的函数fn,fn接收一个或多个参数(此时fn接收的参数应该就是funs最右边的函数所接受的参数,所以除了最右边的函数可以接收多个参数意外,其他函数都只接受1个参数)

some/every

返回一个布尔值,前者只要数组中有一个满足条件即可,后者需要每一个都满足条件。

fill

填充(更像是替换)一个数组 fill(value, start, end) 不包括end。

copyWithin

array.copyWithin(targetIndex, startIndex, endIndex) 。把数组的一部分 复制 一下,到数组本身指定的位置(targetIndex)。

flat

array.flat(depth) 数组扁平化,参数默认是0只抹平一层,如果是很深的多维数组,传递depth即可。

  1. var a = [1,2,[[[3,4,[5,[6]]]]]]
  2. a.flat(5) // [1,2,3,4,5,6]

flatMap

先map,然后走flat(1)

  1. var a = [1,2,3]
  2. a.map(item => [item * 2]) // [[2], [4], [6]]
  3. a.flatMap(item => [item * 2]) // [2,4,6]

一些数组训练

字符串转驼峰

  1. var str = 'hello-world-jack'
  2. function camelize(str) {
  3. return str.split('-').map((item,index) => index === 0 ? item : `${item[0].toUpperCase() + item.slice(1)}`).join('')
  4. }

洗牌算法

给定一个数组,随机排序,保证每个乱序的机会是均等的。
核心思想:逆向遍历数组,然后从数组中随机抽取一个元素,将该随机元素和当前遍历的index做交换位置。

为什么要逆向,而不是正向遍历? 逆向,我们可以在取随机数的时候,从数组剩余的0到(n-1)中取一个随机数,每次都能从剩余(已交换好位置的都已经放在数组尾部了)。正向的话,从 n 到 array.length - 1中取,有点麻烦。比如数组长度为10,已经遍历到第五个了。怎么从剩余的(5 到 10位置)中取一个随机的数字呢。逆向的话,就不用担心这个问题。

  1. function shuffle(array) {
  2. for (let i = array.length - 1; i > 0; i--) {
  3. let randomIndex = Math.floor(Math.random() * (i + 1));
  4. [array[i], array[randomIndex]] = [array[randomIndex], array[i]]; // 技巧,数组结构,调换2个元素位置。
  5. }
  6. console.log(array);
  7. }

可迭代对象

可迭代对象是数组的一种泛化,可以使用for of进行循环。

Symbol.iterator

自己定义(或内置)对象的一个方法,会返回一个迭代器(具有next方法的对象)。
next方法返回的结果格式必须是 {done: Boolean, value: any} , 当done = true时,代表迭代结束。

  1. // 自定义一个例子
  2. var obj = { from: 1, to: 5 }; // 希望for of打印1,2,3,4,5
  3. obj[Symbol.iterator] = function () {
  4. return {
  5. current: this.from,
  6. to: this.to,
  7. next() {
  8. if (this.current <= this.to) {
  9. return {
  10. done: false,
  11. value: this.current++,
  12. };
  13. } else {
  14. return {
  15. done: true,
  16. };
  17. }
  18. },
  19. };
  20. };
  21. for (let v of obj) {
  22. console.log(v);
  23. }

字符串可迭代

for of 可以遍历其每个字符串

显示调用迭代器

  1. var s = "hello";
  2. var sIterrator = s[Symbol.iterator]();
  3. while (true) {
  4. let res = sIterrator.next();
  5. if (res.done) {
  6. break;
  7. } else {
  8. console.log(res.value);
  9. }
  10. }

可迭代和类数组

这是2个概念。
具有 length索引的对象,称为类数组。
具有 Symbol.iterator 迭代器的对象,称为可迭代对象。(没有则一定不可以for of)。

  1. var a = {
  2. '0': 'hello',
  3. '1': 'world',
  4. length: 2
  5. }
  6. for(let v of a) {}; //Uncaught TypeError: a is not iterable

字符串既是类数组,又是可迭代对象。

Array.from

这是一个全局方法,接收一个可迭代对象或类数组,从中获取一个真正的数组,然后可以调用一些数组方法了。

  1. // 上面的代码片段,我们把a转换为一个真正的数组
  2. var a = {
  3. '0': 'hello',
  4. '1': 'world',
  5. length: 2
  6. }
  7. var arr = Array.from(a)
  8. for(let v of arr) {console.log(v)} // hello world

Map和Set映射

虽然已经有了有序集合-数组,以及带键的数据集合-对象。

Map

不同于对象的是,允许任何类型的键,比如对象不能以对象作为键,对象会把对象的键转为 [object, Object] ,然后多个对象键,都一样了,后者覆盖前者。

方法,属性

  • new Map() 创建
  • map.set(k, v) 根据建存储值
  • map.get(k)
  • map.has(k)
  • map.delete(k)
  • map.clear() // 清空map
  • map.size 当前map元素个数。

    map支持链式调用

    map.set(1, 1).set(2, 2).get(2)

    Map的迭代

    可以使用for of(说明map是一个可迭代对象)。

  • map.keys() 获取所有键

  • map.values() 获取所有值
  • map.entries() 获取键值对
  • map.forEach 方法,类似数组的。

    顺序,按照插入Map时的顺序

从对象创建map

new Map(some) 可以接受一个带有键值对,或者其他可迭代对象来进行初始化。使用 Object.entries

  1. // 如果你有一个对象,想快速变成map。
  2. // 利用Object.entries 获取键值对构成的数组,然后给new Map 初始化
  3. let obj = {name: 'jack', age: '18'}
  4. let map = new Map(Object.entries(obj))

从map创建对象

反之,可以利用已有的Map具有键值对的数组来创建一个普通对象。使用 Object.formEntries

  1. let prices = Object.fromEntries([
  2. ["banana", 1],
  3. ["orange", 2],
  4. ["meat", 4],
  5. ]);
  6. console.log(prices); // {banana: 1, orange: 2, meat: 4}
  7. let map = new Map().set('jack', 'boy').set('tom', 'boy').set('lili', 'gril');
  8. console.log(Object.fromEntries(map)); // {jack: "boy", tom: "boy", lili: "gril"}

Set

一个特殊的类型集合,值的集合,没有键。 每一个值只能出现一次,所以通常拿来过滤数组重复项。

方法和属性

  • new Set(iterable) 接受一个可迭代对象,一般是数组。
  • set.add(v) 添加一个值,返回set本身
  • set.delete(v) 删除一个值,成功为true,失败为false。
  • set.has(v)
  • set.clear() 清空set
  • set.size 返回元素个数

    迭代

    可使用for of(说明set是一个可迭代对象)或者forEach来遍历。也支持如下

  • set.keys() 返回的不是下标哦,是所有值

  • set.values() 和keys作用相同,为了兼容map
  • set.entries() 键值对相同

    小结

    可以使用Array.from 来 Map Set 转换为数组。
    可以使用Array.fromEntries 来把 Map 转为普通对象。(Set不行哦)

    WeakMap & WeakSet

    不同于 MapSet 的地方只有2个。
  1. 只接受引用类型作为键(Map),作为值(Set)。
  2. 随时可能被垃圾回收,因此不支持遍历相关方法,不支持size属性,因为这些都是不确定的,你调用的时候,可能已经都没了。所以查询的话,使用 has ,获取值的话使用 get

举一个例子

  1. var obj = {}
  2. var arr = []
  3. arr[0] = obj
  4. obj = null // 看似释放了obj,但由于arr[0]还持有引用,所以不会内存没有释放。

同理,如果使用了 Map 也会造成无法释放。

  1. let john = { name: "John" };
  2. let map = new Map();
  3. map.set(john, "...");
  4. john = null; // 覆盖引用
  5. console.log(map.keys()); // {{name: 'Join'}} 尽然还能访问的到,说明没有被内存回收

所以,改用 WeakMap 就会自动释放掉不用的。

  1. let john = { name: "John" };
  2. let map = new WeakMap();
  3. map.set(john, "...");
  4. john = null; // 覆盖引用
  5. console.log(map.get(john)); // undefined 这里不能用keys

Object.keys & values & entries

三个方法返回的都是真正的可迭代的数组。

三者和for in 一样,忽略Symbol作为键的属性。

解构赋值

一种语法,更方便的从数组或对象中取需要的值。

  1. var a = ['jack', 'lili']
  2. var [first, second] = a // first => jack, second => lili

数组解构

可通过额外,丢弃不需要的值

  1. let [a,,c] = ['a', 'b', 'c'] // a = 'a' c = 'c'

右侧可以是任何可迭代对象

  1. let [a, b, c] = 'abc'
  2. let [one, two] = new Set([1, 2])
  3. let [i, k] = new Map([ ['i', 1], ['k', 2] ])

配合Object.entries和map

  1. for(let [key, value] of Object.entries({a: 1, b: 2})){
  2. console.log(key, value)
  3. }

技巧:交换变量值

  1. let guest = 'Jane'
  2. let admin = 'Pete'
  3. [guest, admin] = [admin, guest]

剩余…

通过 ... 操作符获取剩余部分(一个数组),需要在最后一个参数位置。

  1. let [a, b, ...c] = [1,2,3,4,5] // c = [3,4,5]

对象解构

通常左侧包含的是右侧对象响应属性的一个模式(就是key)

  1. let {a, b} = {a: 1, b: 2}

key也支持改名

  1. let {a: one, b: two} = {a: 1, b: 2} // one: 1 two: 2

支持默认值

  1. let {a = 100, b = 300} = {a: 1, b: undefined} // b: 300哦,不是undefined

改名和默认值结合

  1. let {a: one = 100} = {} // one: 100

…剩余模式

类似数组解构,对象解构剩余部分是个对象

嵌套解构

支持对复杂对象提取深层数据,前提是对应的上需要的key部分

  1. let options = {
  2. size: {
  3. width: 100,
  4. height: 200
  5. },
  6. items: ['cake', 'donut'],
  7. extra: true
  8. }
  9. let {size: {width, height}, items: [one, two], extra} = options
  10. // width => 100 height => 200 one => cake two => donut extra => true

智能函数参数(重要技巧)!

写公用函数的时候,可能会有多个参数,参数可能有的是可选,有的是必填,如果可选在前面,可能会这样

  1. function t(a, b) {
  2. a = a || 1
  3. console.log(a+b)
  4. }
  5. t(undefined, 2) // 3

如果参数偏多,传递复杂,也容易记不住。
我们可以将所有参数作为对象传入,然后函数将这个对象进行解构成多个变量来使用

  1. function t({ a, b }) { // 改造1:函数解构对象值获取需要的变量
  2. a = a || 1;
  3. console.log(a + b);
  4. }
  5. t({ b: 2 }); // 将所有参数作为一个对象传入

日期和时间

创建

使用new Date() 创建,支持多种参数格式,如下:

  • 毫秒数
  • 日期字符串,如2020-01-01
  • 年月日时分秒毫秒,逗号隔开,如`new Date(1999, 7, 18)

    获取日期相关

  • getFullYear 获取年份(4位数)

  • getMonth 获取月份,从0开始算,return 0-11。
  • getDate 获取日期,return 1 - 31.
  • getHours, getMinutes, getSeconds, getMilliseconds 获取时,分,秒,毫秒
  • getDay 获取星期,return 0 - 6

    设置日期相关

    通常不太会用到,跟获取对应

  • setFullYear 等等

  • 自动校准

    这个场景会多点。
    1. // 创建时
    2. new Date(2013, 0, 32) // 实际上是 2013年2月1日。因为32号不存在,往后进一天就是2月1号了。

    获取几天之后是哪一天

    这个平时会较多。要考虑到是30天的月份还是31天的月份,亦或是2月的28天还是29天(闰年)。利用校准的特性我们可以:
    1. let date = new Date() // 代码书写时是4月8号
    2. date.setDate(date.getDate() + 23) // 4月8号+23天
    3. console.log(date) // 2021年5月1日。
    同理获取多少分钟,多少秒以后是什么日期,我们响应的调用 setSeconds setMinutes

    获取当前时间戳

    通常我会 new Date().getTime() ,但因为比较常用,提供了 Date.now() 方法。

    Date.parse

    我用的较少,接收一个 YYYY-MM-DDTHH:mm:ss.sssZ 的字符串,返回对应时间戳

    这里的T是分隔符,Z是时区

  1. let ms = Date.parse('2012-01-26T13:51:50.417-07:00');

JSON

不受语言限制,一种描述数据的规范。
键名都是双引号,不能是单引号或反引号。值如果是字符串,也只能是双引号。

JSON.stringify

将对象转换为JSON!一直以为是将对象字符串化。
但是这个函数也可用于原始数据类型,即 JSON.stringify(1) 得到 1

限制

支持的数据格式

因为JSON是通用的数据规范,所以不会支持JS才有的特定对象,如函数属性(方法), Symbol 类型的属性,存储 undefined 的属性。

不能有循环引用

  1. let room = {
  2. number: 23
  3. };
  4. let meetup = {
  5. title: "Conference",
  6. participants: ["john", "ann"]
  7. };
  8. meetup.place = room; // meetup 引用了 room
  9. room.occupiedBy = meetup; // room 引用了 meetup
  10. JSON.stringify(meetup); // Error: Converting circular structure to JSON

如何应对上述限制

这里看下完整语法, JSON.stringify(value, replacer, space) 可以传入第二个参数。
replacer接受一个转换的属性数组,或一个函数 fn(key, value)

  1. let room = {
  2. number: 23
  3. };
  4. let meetup = {
  5. title: "Conference",
  6. participants: [{name: "John"}, {name: "Alice"}],
  7. place: room // meetup 引用了 room
  8. };
  9. room.occupiedBy = meetup; // room 引用了 meetup
  10. // 数组,这里数组只接受title和participants的话,会丢失name,place, number。我们只要排除导致循环引用的key就行了,也就是occupiedBy
  11. // alert( JSON.stringify(meetup, ['title', 'participants']) );
  12. // 所以改进下
  13. alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
  1. // 属性数组形式有点麻烦,我们用函数试下
  2. let room = {
  3. number: 23
  4. };
  5. let meetup = {
  6. title: "Conference",
  7. participants: [{name: "John"}, {name: "Alice"}],
  8. place: room // meetup 引用了 room
  9. };
  10. room.occupiedBy = meetup; // room 引用了 meetup
  11. alert( JSON.stringify(meetup, function replacer(key, value) {
  12. alert(`${key}: ${value}`);
  13. // 这里,排除occupiedBy即可。
  14. return (key == 'occupiedBy') ? undefined : value;
  15. }));

自定义toJSON

如果对象有提供 toJSON 方法,使用stringify时,会自动调用它。

  1. let room = {
  2. number: 23,
  3. toJSON() {
  4. return this.number;
  5. }
  6. };
  7. let meetup = {
  8. title: "Conference",
  9. room
  10. };
  11. alert( JSON.stringify(room) ); // 这里调用了room.toJSON 23
  12. alert( JSON.stringify(meetup) ); // 这里嵌套对象,也会去调用room的toJSON

JSON.parse

将JSON字符串,转化为对象。
直接看下完整语法吧, JSON.parse(string, reviver)

reviver

第二个参数也很少用到,一个可选函数,接收 key value 可以对值做你需要的转换。

  1. var str = '{"a":1}';
  2. var res = JSON.parse(str, (key, value) => {
  3. if (key === "a") {
  4. return value * 3;
  5. }
  6. return value;
  7. });
  8. console.log(res); // {a: 3}

循环引用的解决

通过 stringify 的第二个参数 replacer ,对每个处理的值,如果是对象,则存储其value(是个引用),每次存储都判断下存储的数组中是否已经有同一个对象引用,有的话