ES6语法

〇、let 和 const 命令

1、let命令
(1)基本用法
let所声明的变量,只在let命令所在的代码块内有效。

{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

for循环很合适使用let命令,计数器i只在for循环体内有效,在循环体外引用就会报错。

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。 函数内部的console.log(i),里面的i就指向全局变量i。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
a[5](); // 10

声明的变量let仅在块级作用域内有效。当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量。

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

for循环设置循环变量的部分是一个父作用域,循环体内是一个单独的子作用域。函数内部的变量i与循环变量i不在同一个作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

(2)let不允许变量提升
变量提升:变量可以在声明之前使用,值为undefined。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

(3)暂时性死区
暂时性死区:在代码块内,使用let命令声明变量之前,该变量都是不可用的。(变量一定要在声明之后使用)

var tmp = 123;
if (true) {
  tmp = 'abc'; // 报错 ReferenceError
  let tmp;
}

在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。

let x = x;
// 报错 ReferenceError: x is not defined

(4)不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

不能在函数内部重新声明参数。

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;//在封闭作用域内
  }
}
func() // 不报错

2、块级作用域
ES5 只有全局作用域和函数作用域。ES6的let为 JavaScript 新增了块级作用域。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

ES6 的块级作用域必须有大括号。

// 第一种写法,报错
if (true) let x = 1;

// 第二种写法,不报错
if (true) {
  let x = 1;
}

3、const命令
const声明一个只读的常量。一旦声明,常量的值就不能改变。只声明不赋值,也会报错。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const foo;
// SyntaxError: Missing initializer in const declaration

const的作用域与let命令相同: 只在声明所在的块级作用域内有效。声明的常量不提升,存在暂时性死区,只能在声明的位置后面使用。

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

如果真的想将对象冻结,应该使用Object.freeze方法,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

4、顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。

ES6规定: var命令和function命令声明的全局变量,是顶层对象的属性;let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

顶层对象可以拿到全局变量的值。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined
一、数值

JavaScript 数值始终以双精度浮点数来存储。

1、进制转换

var myNumber = 128;
myNumber.toString(16);     // 返回 80
myNumber.toString(8);      // 返回 200
myNumber.toString(2);      // 返回 10000000

2、方法
toPrecision() 返回字符串值,它包含了指定长度的数字:
toFixed() 返回字符串值,它包含了指定位数小数的数字:
Number() 可用于把 JavaScript 变量转换为数值;

x = true;
Number(x);        // 返回 1
x = false;     
Number(x);        // 返回 0
x = new Date();
Number(x);        // 返回 1404568027739
x = "10"
Number(x);        // 返回 10
x = "10 20"
Number(x);        // 返回 NaN

MAX_VALUE 返回 JavaScript 中可能的最大数字
MIN_VALUE 返回 JavaScript 中可能的最小数字

var x = Number.MAX_VALUE;
var x = Number.MIN_VALUE;
二、变量

1、数组
(1)基本用法

let [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

(2)默认值

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

下面报错,是因为x用y做默认值时,y还没有声明。

let [x = y, y = 1] = [];  // ReferenceError: y is not defined

2、对象的解构赋值
(1)基本用法
对象的属性没有次序,变量属性同名,就能取到正确的值。

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

(2)默认值

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

(3)字符串的解构赋值

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"

数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

(4)函数参数的解构赋值

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3

(5)变量解构赋值的用途
1)交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

2)从函数返回多个值

// 返回一个数组
function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象
function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

3)函数参数的定义

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

4)提取 JSON 数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

5)遍历 Map 结构

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world
三、字符串

1、字符串的遍历器接口
ES6 为字符串添加了遍历器接口

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"

2、字符串JSON转换
JSON转字符串:

JSON.stringify(object)

字符串转JSON:

JSON.parse(jsonString);

3、模板字符串
模板字符串(template string)是增强版的字符串,用 反引号(`) 标识。

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串之中能调用函数。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

4、字符串方法
split() 将字符串转换为数组。

var txt = "Hello";       // 字符串
txt.split("");           // 分隔为字符

var txt = "a,b,c,d,e";   // 字符串
txt.split(",");          // 用逗号分隔

includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

let s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

repeat:返回一个新字符串,表示将原字符串重复n次

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

padStart():如果某个字符串不够指定长度,会在头部补全。
padEnd():如果某个字符串不够指定长度,会在尾部补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba

trim() 方法删除字符串两端的空白符。
trimStart():消除字符串头部的空格。
trimEnd():消除尾部的空格。
matchAll():法返回一个正则表达式在当前字符串的所有匹配
replaceAll():替换所有匹配,返回一个新字符串

'aabbcc'.replaceAll('b', '_')
// 'aa__cc'

at():接受一个整数作为参数,返回参数指定位置的字符

const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"

slice() 提取字符串的某个部分并在新字符串中返回被提取的部分。
substring() 类似于 slice(),但 无法接受负的索引。

var str = "Apple, Banana, Mango";
var res = str.slice(-13,-7);

var str = "Apple, Banana, Mango";
var res = str.substring(7,13);

substr() 类似于 slice(),在第二个参数规定被提取部分的长度。

var str = "Apple, Banana, Mango";
var res = str.substr(7,6);

toUpperCase() 把字符串转换为大写
toLowerCase() 把字符串转换为小写

concat() 方法,连接两个或多个字符串

5、转义字符
在这里插入图片描述
在这里插入图片描述
在字符串中换行,通过一个反斜杠即可:

document.getElementById("demo").innerHTML = "Hello \
Kitty!";
四、函数 function

1、函数参数的默认值
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。默认值的参数,一般为尾参数,不然没法做参数的省略。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

2、rest参数(动态参数)
rest 参数(...变量名),用于获取函数的多余参数,得到的是一个数组。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

注意:
rest 参数之后不能再有其他参数

3、箭头函数
使用“箭头”(=>)定义函数。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }
[1,2,3].map(x => x * x);

4、isNaN() 方法
如果参数是 NaN,则全局 isNaN() 方法返回 true。否则返回 false。

5、isFinite() 方法
如果参数为 Infinity 或 NaN,则全局 isFinite() 方法返回 false,否则返回 true。

五、数组

1、复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。

const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]

克隆数组的简便写法:

const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

2、合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

这种合并方式是一种浅拷贝。如果修改了引用指向的值,会同步反映到新数组:

const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];

const a4 = [...a1, ...a2];

a4[0] === a1[0] // true

3、与解构赋值结合

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

4、字符串
扩展运算符还可以将字符串转为真正的数组。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

5、Map 和 Set 结构,Generator
函数扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

6、方法

(1)Array.from

用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)

arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];

(2)Array.of()

用于将一组值,转换为数组。Array.of()基本上可以用来替代Array()或new Array()。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

(3)find() 和 findIndex()

[1, 4, -5, 10].find((n) => n < 0)

find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组:

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

findIndex方法可以借助Object.is方法识别数组的NaN成员。

[NaN].findIndex(y => Object.is(NaN, y))

(4)fill()
fill方法使用给定值,填充一个数组。

['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]

fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束:

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

(5)entries(),keys() 和 values()
ES6 提供三个新的方法——entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

(6)includes()

includes表示某个数组是否包含给定的值

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

[NaN].includes(NaN)
// true

(7)flat()
用于将嵌套的数组“拉平”,变成一维的数组,flat()方法将子数组的成员取出来,添加在原来的位置。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]

flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

(8)at()
接受一个整数作为参数,返回对应位置的成员,支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组(TypedArray)。

const arr = [5, 12, 8, 130, 44];
arr.at(2) // 8
arr.at(-2) // 130

如果参数位置超出了数组范围,at()返回undefined。

(9 )join()
join() 方法也可将所有数组元素结合为一个字符串。

var fruits = ["Banana", "Orange","Apple", "Mango"];
document.getElementById("demo").innerHTML = fruits.join(" * "); 
// Banana * Orange * Apple * Mango

(10)pop()
pop() 方法从数组中删除最后一个元素

(11)push
在数组结尾处向数组添加一个新的元素:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.push("Lemon");               
 // 向 fruits 添加一个新元素 (Lemon)

(11)shift()
shift() 方法会删除首个数组元素,并把所有其他元素“位移”到更低的索引。
shift() 方法返回被“位移出”的字符串:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.shift();             // 返回 "Banana"

(12)unshift()
在开头)向数组添加新元素,并“反向位移”旧元素

(13)length
length 属性提供了向数组 追加新元素 的简易方法:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits[fruits.length] = "Kiwi";          // 向 fruits 追加 "Kiwi"

(14)delete
既然 JavaScript 数组属于对象,其中的元素就可以使用delete 运算符来删除:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
delete fruits[0];           // 把 fruits 中的首个元素改为 undefined

注意:
使用 delete 会在数组留下未定义的空洞。请使用 pop() 或 shift() 取而代之。

(15)splice()
使用 splice() 来删除元素

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(0, 1);        // 删除 fruits 中的第一个元素

splice() 方法可用于向数组添加新项:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2, 0, "Lemon", "Kiwi");

第一个参数(2)定义了应添加新元素的位置(拼接)。

第二个参数(0)定义应删除多少元素。

其余参数(“Lemon”,“Kiwi”)定义要添加的新元素。

(16)concat()
通过合并(连接)现有数组来创建一个新数组:

var myGirls = ["Cecilie", "Lone"];
var myBoys = ["Emil", "Tobias", "Linus"];
var myChildren = myGirls.concat(myBoys);   // 连接 myGirls 和 myBoys

(17)toString()
把数组转换为字符串

var fruits = ["Banana", "Orange", "Apple", "Mango"];
document.getElementById("demo").innerHTML = fruits.toString(); 
以逗号分隔的字符串返回数组:
Banana,Orange,Apple,Mango

7、排序
(1)sort()
字符排序: sort() 方法默认以字母顺序对数组进行排序:
数字排序:定义一个比较函数来进行排序

var points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return a - b}); 

(2)reverse()
reverse() 方法反转数组中的元素。

8、数组迭代
(1)forEach()
每个数组元素调用一次函数(回调函数)

(2)Array.map()
map() 方法通过对每个数组元素执行函数来创建新数组。

注意:

  • map() 方法不会对没有值的数组元素执行函数。
  • map() 方法不会更改原始数组。

(3)Array.filter()
filter() 方法创建一个包含通过测试的数组元素的新数组。
(4)Array.reduce()
reduce() 方法在每个数组元素上运行函数,以生成单个值。

var numbers1 = [45, 4, 9, 16, 25];
var sum = numbers1.reduce((total,value)=>{
   return total+value
});
总和是:99

(5)Array.every()
every() 方法检查 所有数组值 是否通过测试。

var numbers = [45, 4, 9, 16, 25];
var allOver18 = numbers.every((value)=>{
  return value>18
});
//  全部大于18时,才为true

(6)Array.some()
some() 方法检查某些数组值是否通过了测试。

var numbers = [45, 4, 9, 16, 25];
var allOver18 = numbers.every((value)=>{
  return value>18
});
//  有18的元素时,就为true
六、对象

1、对象的简洁表示法

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

2、方法
(1)Object.is()
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

(2)Object.keys(),Object.values(),Object.entries()

Object.keys()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

(3)Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }
七、运算符

1、指数运算符

2 ** 2 // 4
2 ** 3 // 8

2、链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

三元运算符?:常用于判断对象是否存在。

ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。

const firstName = message?.body?.user?.firstName || 'default';

iterator.return如果有定义,就会调用该方法,否则iterator.return直接返回undefined,不再执行?.后面的部分。

iterator.return?.()
a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

3、Null 判断运算符
读取对象属性的时候,如果某个属性的值是null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。

const headerText = 
response.settings.headerText || 'Hello, world!';

只有运算符左侧的值为null或undefined时,才会返回右侧的值。

const headerText = 
response.settings.headerText ?? 'Hello, world!';

这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值。

const animationDuration = 
response.settings?.animationDuration ?? 300;

算数运算符:
在这里插入图片描述
比较运算符:
在这里插入图片描述
逻辑运算符:
在这里插入图片描述
类型运算符:
在这里插入图片描述

typeof "Bill"              // 返回 "string"
typeof 3.14                // 返回 "number"
typeof true                // 返回 "boolean"
typeof false               // 返回 "boolean"
typeof x                   // 返回 "undefined" (假如 x 没有值)

typeof {name:'Bill', age:62} // 返回 "object"
typeof [1,2,3,4]             // 返回 "object" (并非 "array",参见下面的注释)
typeof null                  // 返回 "object"
typeof function myFunc(){}   // 返回 "function"
八、Symbol

1、基本用法
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它属于 JavaScript 语言的数据类型之一,其他数据类型是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。

const sym = Symbol('foo');

sym.description // "foo"

2、作为属性名的Symbol
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

注意,Symbol 值作为对象属性名时,不能用点运算符。因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值

const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!'; //mySymbol会被当作字符串处理
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

3、用作常量
Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。

const log = {};

log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');
九、Set数据结构

1、基本用法
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4

Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

去除数组重复成员的方法。

// 去除数组的重复成员
[...new Set(array)]

去除字符串里面的重复字符。

[...new Set('ababbc')].join('')
// "abc"

注意:
Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。另外,两个对象总是不相等的。

2、Set实例的属性和方法

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。
  • Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。

Array.from方法可以将 Set 结构转为数组。

const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

3、Set实例的属性和方法
Set 结构的实例有四个遍历方法,可以用于遍历成员。
Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
Set.prototype.entries():返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历每个成员

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

它的默认遍历器生成函数就是它的values方法,所以可以省略values方法,直接用for…of循环遍历 Set。

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue

forEach方法:

let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

扩展运算符(…)内部使用for...of循环,所以也可以用于 Set 结构

let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']

数组的map和filter方法也可以间接用于 Set

let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
十、 Map

1、基本用法
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。

const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

注意:
下面代码的set和get方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

注意:
虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

2、实例属性和操作方法
size属性返回 Map 结构的成员总数。

const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。

const m = new Map();

m.set('edition', 6)        // 键是字符串
m.set(262, 'standard')     // 键是数值
m.set(undefined, 'nah')    // 键是 undefined

set方法返回的是当前的Map对象,因此可以采用链式写法。

let map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

get方法读取key对应的键值,如果找不到key,返回undefined。

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
delete方法删除某个键,返回true。如果删除失败,返回false。
clear方法清除所有成员,没有返回值。

3、遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。

Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员。

需要特别注意的是,Map 的遍历顺序就是插入顺序

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)。

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)。

const map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

const map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

forEach方法:

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

forEach方法还可以接受第二个参数,用来绑定this。
forEach方法的回调函数的this,指向reporter。

const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};

map.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);

4、Map 转为对象
如果所有 Map 的键都是字符串,它可以无损地转为对象。

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }

5、对象转为 Map
对象转为 Map 可以通过Object.entries()

let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));

6、Map 转为 JSON
Map 的键名都是字符串,这时可以选择转为对象 JSON。

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

Map 的键名有非字符串,这时可以选择转为数组 JSON。

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'

7、JSON 转为 Map

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
十一、Promise 对象

Promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise对象有以下两个特点:

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

1、基本用法
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})

2、then
then方法返回的是一个新的Promise实例,因此可以采用链式写法。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

采用箭头函数

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);

3、catch
Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

promise抛出一个错误,会被catch()方法指定的回调函数捕获。

const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});

等价于:
// 写法一
const promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
promise.catch(function(error) {
  console.log(error);
});

// 写法二
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});

建议总是使用catch()方法,而不使用then()方法的第二个参数。理由是catch()方法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。

promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

4、finally
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。一般写成功或失败都要执行的语句。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

5、Promise.all
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

十二、Iterator 和 for…of 循环

主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合。

ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

Iterator 的遍历过程是这样的。

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

for of 与其他遍历语法的比较

forEach循环,break命令或return命令都不能中途跳出。

myArray.forEach(function (value) {
  console.log(value);
});

for…in循环有几个缺点:

  • 数组的键名是数字,但是for…in循环是以字符串作为键名“0”、“1”、“2”等等。
  • for…in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下,for…in循环会以任意顺序遍历键名。

for…of

  • 有着同for…in一样的简洁语法,但是没有for…in那些缺点。
  • 不同于forEach方法,它可以与break、continue和return配合使用
  • 提供了遍历所有数据结构的统一操作接口。
十三、async函数

async 函数是 Generator 函数的语法糖。async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

async函数的返回值是 Promise 对象。

当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

十四、Class的基本语法

ES6 引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

constructor()就是构造方法,而this关键字则代表实例对象。

类的数据类型就是函数,类本身就指向构造函数。

1、construntor方法
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

class Point {
}

// 等同于
class Point {
  constructor() {}
}

注意:
类必须使用new调用,否则会报错

参考文章:
[1] ES6语法指南

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值