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语法指南