秋雨
首页
  • HTML
  • CSS
  • JavaScript
设计模式
webpack
  • 前端常识
  • ES
  • React
  • Redux
  • ReactRouter
  • 事件循环
  • 浏览器渲染流程
  • Vue项目性能优化
  • Vue
  • App
Github
首页
  • HTML
  • CSS
  • JavaScript
设计模式
webpack
  • 前端常识
  • ES
  • React
  • Redux
  • ReactRouter
  • 事件循环
  • 浏览器渲染流程
  • Vue项目性能优化
  • Vue
  • App
Github
  • JavaScript基础
  • Object(基础)
  • 数据类型
  • 函数进阶
  • 对象属性配置
  • 原型、继承
  • class
  • 错误处理
  • Promise

数据类型

原始类型的方法

JavaScript 中有七种原始类型,分别是 number、string、boolean、undefined、null、symbol、bigint。这七种原始类型是没有方法的。

但是在开发中,我们经常会对原始类型调用一些方法,比如:

let str = "hello";

console.log(str.toUpperCase()); //HELLO

这就和上面的结论产生了矛盾,这是为什么呢?

因为在 js 中,原始类型是不可变得。但是当 JavaScript 发现你是在用原始类型调用方法时,它会创建一个原始类型的临时包装对象。调用对象上的方法,调用结束后临时包装对象被销毁。

具体步骤:

  1. 创建一个包含字符串字面量的特殊对象,并且具有可用的方法。
  2. 运行方法返回一个新的字符串。
  3. 特殊对象被销毁,只留下原始值。

注意: 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])

以下属性会被忽略:

  1. 函数属性(方法)
  2. Symbol 类型的键和值
  3. 存储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
    }
});
最后更新:
贡献者: qiuyulc
Prev
Object(基础)
Next
函数进阶