ES6常用语法

ES6 的变量声明: let 和 const

ES6 中新增了 let 和 const 来定义变量:

  • var:ES5 和 ES6 中,定义全局变量(是 variable 的简写)。
  • let:定义局部变量,替代 var。
  • const:定义常量(定义后,不可修改)。

var:全局变量

看下面的代码:

1
2
3
4
5
{
var a = 1;
}

console.log(a); //这里的 a,指的是 区块 里的 a

上方代码是可以输出结果的,输出结果为 1。因为 var 是全局声明的,所以,即使是在区块里声明,但仍然在全局起作用。

再来看下面这段代码:

1
2
3
4
5
6
var a = 1;
{
var a = 2;
}

console.log(a); //这里的 a,指的是 区块 里的 a

上方代码的输出结果为 2 ,因为 var 是全局声明的。

总结:

用 var 定义的全部变量,有时候会污染整个 js 的作用域。

let:定义局部变量

1
2
3
4
5
6
var a = 2;
{
let a = 3;
}

console.log(a);

上方代码的输出结果为 2。用 let 声明的变量,只在局部(块级作用域内)起作用。

let 是防止数据污染,我们来看下面这个 for 循环的例子,很经典。

1、用 var 声明变量:()

1
2
3
4
5
for (var i = 0; i < 10; i++) {
console.log("循环体中:" + i); // 每循环一次,就会在 { } 所在的块级作用域中,重新定义一个新的 i
}

console.log("循环体外:" + i);

上方代码可以正常打印结果,且最后一行的打印结果是 10。说明循环体外定义的变量 i,是在全局起作用的。

2、用 let 声明变量:

1
2
3
4
5
for (let i = 0; i < 10; i++) {
console.log("循环体中:" + i);
}

console.log("循环体外:" + i);

上方代码的最后一行无法打印结果,也就是说打印会报错。因为用 let 定义的变量 i,只在{ }这个块级作用域里生效。

总结:我们要习惯用 let 声明,减少 var 声明带来的污染全局空间

为了进一步说明 let 不会带来污染,需要说明的是:当我们定义了let a = 1时,如果我们在同一个作用域内继续定义let a = 2,是会报错的。

const:定义常量

在程序开发中,有些变量是希望声明后,在业务层就不再发生变化,此时可以用 const 来定义。

举例:

1
const name = "smyhvae"; //定义常量

用 const 声明的变量,只在局部(块级作用域内)起作用。

let 和 const 的作用【重要】

let 和 const 的作用如下:

  • 禁止重复声明

  • 支持块级作用域

  • 控制修改

相反, 用var声明的变量:可以重复声明、没有块级作用域、不能限制。

暂时性死区问题说明:其实 let 和 const 是有变量提升的,但是没有初始化提升:

1
2
3
4
5
6
7
var name = "zs";

function fn() {
console.log(name);
let name = "sunshin_lin";
}
fn(); // Cannot access 'name' before initialization

块级作用域解决问题:

1
2
3
4
5
6
7
8
9
10
11
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
});
} // 5 5 5 5 5

for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
});
} // 0 1 2 3 4

for 循环举例【经典案例】

代码 1、我们先来看看如下代码:(用 var 定义变量 i)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="">
<head>
<meta />
<meta />
<meta />
<title>Document</title>
</head>
<body>
<input type="button" value="aa" />
<input type="button" value="bb" />
<input type="button" value="cc" />
<input type="button" value="dd" />

<script>
var myBtn = document.getElementsByTagName("input");

for (var i = 0; i < myBtn.length; i++) {
myBtn[i].onclick = function () {
alert(i);
};
}
</script>
</body>
</html>

上方代码中的运行效果如下:

你可能会感到诧异,为何点击任何一个按钮,弹出的内容都是 4 呢?这是因为,我们用 var 定义的变量 i,是在全局作用域声明的。整个代码中,自始至终只有一个变量。当我们还没点击按钮之前,变量 i 已经循环到 4 了。

也就是说,上面的 for 循环,相当于如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var i = 0;
myBtn[0].onclick = function () {
alert(i);
};
i++;

myBtn[1].onclick = function () {
alert(i);
};
i++;

myBtn[2].onclick = function () {
alert(i);
};
i++;

myBtn[3].onclick = function () {
alert(i);
};
i++; // 到这里,i 的值已经是4了。因此,当我们点击按钮时,i的值一直都是4

代码 2、上面的代码中,如果我们改为用 let 定义变量 i:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="">
<head>
<meta />
<meta />
<meta />
<title>Document</title>
</head>
<body>
<input type="button" value="aa" />
<input type="button" value="bb" />
<input type="button" value="cc" />
<input type="button" value="dd" />

<script>
var myBtn = document.getElementsByTagName("input");

for (let i = 0; i < myBtn.length; i++) {
myBtn[i].onclick = function () {
alert(i);
};
}
</script>
</body>
</html>

上方代码中的运行效果如下:

上面这个运行结果,才是我们预期的效果。我们用 let 定义变量 i,在循环的过程中,每执行一次循环体,就会诞生一个新的 i。循环体执行 4 次,就会有四个 i。

let 与 const 都是只在声明所在的块级作用域内有效。

let 声明的变量可以改变,值和类型都可以改变,没有限制。而 const 声明的是常亮,值和类型都不能改变。
对于引用类型的变量,const 命令只是保证变量名指向的地址不变,并不保证该地址的数据不变。如果想让定义的对象或数组的内部数据也不能够修改和改变,可以使用 object.freeze(names)进行冻结,这样为对象添加新属性就不起作用。
Example:

1
2
3
4
5
6
7
8
9
10
11
12
{
let a = 1;
const b = 2;
var c = 3;
}

a; // ReferenceError: a is not defined.
b; // ReferenceError: a is not defined.
c; // 3

const d = 4;
d = 5; //TypeError: Assignment to constant variable.

本质
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

1
2
3
4
5
6
7
8
const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop; // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量 foo 储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 foo 指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
下面是另一个例子。

1
2
3
4
const a = [];
a.push("Hello"); // 可执行
a.length = 0; // 可执行
a = ["Dave"]; // 报错

上面代码中,常量 a 是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给 a,就会报错。
如果真的想将对象冻结,应该使用 Object.freeze 方法。

1
2
3
4
5
const foo = Object.freeze({});

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

上面代码中,常量 foo 指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。
除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

1
2
3
4
5
6
7
8
var constantize = obj => {
Object.freeze(obj);
Object.keys(obj).forEach((key, i) => {
if (typeof obj[key] === "object") {
constantize(obj[key]);
}
});
};

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
Example:

1
2
3
4
5
6
7
//ES5
let a = 1;
let b = 2;
let c = 3;

//ES6
let [a, b, c] = [1, 2, 3];

数组嵌套解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let [a, [[b], c]] = [1, [[2], 3]];
a; // 1
b; // 2
c; // 3

let [, , third] = ["a", "b", "c"];
third; // "c"

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; // []

数组解构设置默认值

1
2
let [x, y = "b"] = ["a"]; // x='a', y='b'
let [x, y = "b"] = ["a"]; // x='a', y='b'

对象解构赋值

1
2
3
4
5
6
7
let { a, b } = { a: "aaa", b: "bbb" };
a; // "aaa"
b; // "bbb"
let { c } = { a: "aaa", b: "bbb" };
c; // undefined
let { d: e } = { e: "eee", f: "fff" };
e; // "eee"

undefinednull的区别

如果我们在赋值时,采用的是 undefined或者null,那会有什么区别呢?

1
2
3
4
5
6
7
8
9
{
let [a, b = "smyhvae"] = ["生命壹号", undefined]; //b 虽然被赋值为 undefined,但是 b 会采用默认值
console.log(a + "," + b); //输出结果:生命壹号,smyhvae
}

{
let [a, b = "smyhvae"] = ["生命壹号", null]; //b 被赋值为 null
console.log(a + "," + b); //输出结果:生命壹号,null
}

上方代码分析:

  • undefined:相当于什么都没有,此时 b 采用默认值。

  • null:相当于有值,但值为 null。

对象解构设置默认值

1
2
let { x, y = "x" } = { x: "x", y: "y" };
let [x, y = "y"] = { x: "x" }; // x='x', y='y'

圆括号的使用

如果变量 foo 在解构之前就已经定义了,此时你再去解构,就会出现问题。下面是错误的代码,编译会报错:

1
2
3
4
let foo = 'haha';
{ foo } = { foo: 'smyhvae' };
console.log(foo);

要解决报错,只要在解构的语句外边,加一个圆括号即可:

1
2
3
let foo = "haha";
({ foo } = { foo: "smyhvae" });
console.log(foo); //输出结果:smyhvae

字符串解构

字符串也可以解构,这是因为,此时字符串被转换成了一个类似数组的对象。举例如下:

1
2
3
4
5
6
7
const [a, b, c, d] = "smyhvae";
console.log(a);
console.log(b);
console.log(c);
console.log(d);

console.log(typeof a); //输出结果:string

输出结果:

字符串的扩展

ES6 中的字符串扩展,用得少,而且逻辑相对简单。如下:

  • includes(str):判断是否包含指定的字符串

  • startsWith(str):判断是否以指定字符串开头

  • endsWith(str):判断是否以指定字符串结尾

  • repeat(count):重复指定次数

举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let str = "abcdefg";

console.log(str.includes("a")); //true
console.log(str.includes("h")); //false

//startsWith(str) : 判断是否以指定字符串开头
console.log(str.startsWith("a")); //true
console.log(str.startsWith("d")); //false

//endsWith(str) : 判断是否以指定字符串结尾
console.log(str.endsWith("g")); //true
console.log(str.endsWith("d")); //false

//repeat(count) : 重复指定次数a
console.log(str.repeat(5));

打印结果:

模板字符串

我们以前让字符串进行拼接的时候,是这样做的:(传统写法的字符串拼接)

1
2
3
var name = "smyhvae";
var age = "26";
console.log("name:" + name + ",age:" + age); //传统写法

这种写法,比较繁琐,而且容易出错。

现在有了 ES6 语法,字符串拼接可以这样写:

1
2
3
4
5
6
var name = "smyhvae";
var age = "26";

console.log("name:" + name + ",age:" + age); //传统写法

console.log(`name:${name},age:${age}`); //ES6 写法

注意,上方代码中,倒数第二行用的符号是单引号,最后一行用的符号是反引号(在 tab 键的上方)。

Number 的扩展

  • 二进制与八进制数值表示法: 二进制用0b, 八进制用0o

举例:

1
2
console.log(0b1010); //10
console.log(0o56); //46
  • Number.isFinite(i):判断是否为有限大的数。比如Infinity这种无穷大的数,返回的就是 false。

  • Number.isNaN(i):判断是否为 NaN。

  • Number.isInteger(i):判断是否为整数。

  • Number.parseInt(str):将字符串转换为对应的数值。

  • Math.trunc(i):去除小数部分。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Number.isFinite(i) : 判断是否是有限大的数
console.log(Number.isFinite(NaN)); //false
console.log(Number.isFinite(5)); //true
console.log(Number.isFinite(Infinity)); //false

//Number.isNaN(i) : 判断是否是NaN
console.log(Number.isNaN(NaN)); //true
console.log(Number.isNaN(5)); //falsse

//Number.isInteger(i) : 判断是否是整数
console.log(Number.isInteger(5.23)); //false
console.log(Number.isInteger(5.0)); //true
console.log(Number.isInteger(5)); //true

//Number.parseInt(str) : 将字符串转换为对应的数值
console.log(Number.parseInt("123abc")); //123
console.log(Number.parseInt("a123abc")); //NaN

// Math.trunc(i) : 直接去除小数部分
console.log(Math.trunc(13.123)); //13

数组的新增方法

数组的相关文档,可参考:Array 对象

扩展运算符

1
2
3
4
5
6
7
8
9
10
console.log(...[1, 2, 3])
// 1 2 3

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

includes(value,index)

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

1
2
3
4
5
6
[1, 2, 3]
.includes(2) // true
[(1, 2, 3)].includes(4) // false
[(1, 2, NaN)].includes(NaN) // true
[(1, 2, 3)].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true

filter 方法

filter 用来过滤数组中不满足条件的元素, 把满足条件的元素放到新的数组里, 并且不会改变原数组

1
2
3
4
5
6
let array = [1, 2, 3, 4, 5];
let temp = array.filter((item, index, arr) => {
return item > 3;
});
console.log(temp); //[4, 5]
console.log(array); //[1, 2, 3, 4, 5]

every 方法:

every 会遍历数组, 判读是否满足给定的条街, 如果每一项都是 true, 就会返回 true, 只要有一个是 false, 就会返回 false

1
2
3
4
5
let array = [1, 2, 3, 4, 5];
let bo = array.every((item, index, arr) => {
return item > 2;
});
console.log(bo);

some 方法

some 会遍历数组, 判读是否满足给定的条件, 只要有一项 true, 就会返回 true, 否则返回 false

1
2
3
4
5
let array = [1, 2, 3, 4, 5];
array.some((item, index, arr) => {
console.log(item);
return item > 3;
});

reduce 方法

接收一个函数作为累加器, 数组中每个值(从左到右)开始缩减, 最终为一个值,也就是计算总合

1
2
3
4
5
let array = [1, 2, 3, 4, 5];
let total = array.reduce((prev, next) => {
return prev + next;
});
console.log(total); // 15

example:
输出字符串中每个字符出现的次数

1
2
3
4
5
6
7
var arr = "abcdaabc";

// { a: 3, b: 2, c: 2, d: 1 }

var info = arr.split("").reduce((p, k) => (p[k]++ || (p[k] = 1), p), {});

console.log(info);

数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let person = [
{ id: 0, name: "小明" },
{ id: 1, name: "小张" },
{ id: 2, name: "小李" },
{ id: 3, name: "小孙" },
{ id: 1, name: "小周" },
{ id: 2, name: "小陈" }
];

let obj = {};

person = person.reduce((cur, next) => {
obj[next.id] ? "" : (obj[next.id] = true && cur.push(next));
return cur;
}, []); //设置 cur 默认类型为数组,并且初始值为空的数组
console.log(person);

for … of 循环

ES6 中,如果我们要遍历一个数组,可以这样做:

1
2
3
4
5
let arr1 = [1, 2, 3, 4, 5];

for (let value of arr1) {
console.log(value);
}

输出结果:

for…of 的循环可以避免我们开拓内存空间,增加代码运行效率,所以建议大家在以后的工作中使用 for…of 循环。

注意,上面的数组中,for ... of获取的是数组里的值;for ... in获取的是 index 索引值。

对象的新增方法

对象的相关文档,可参考:Object 对象

Object.is

1
Object.is(v1, v2);

判断两个数据是否完全相等。底层是通过字符串来判断的

我们先来看下面这两行代码的打印结果:

1
2
console.log(0 == -0);
console.log(NaN == NaN);

打印结果:

1
2
true
false

上方代码中,第一行代码的打印结果为 true,这个很好理解。第二行代码的打印结果为 false,因为 NaN 和任何值都不相等。

但是,如果换成下面这种方式来比较:

1
2
console.log(Object.is(0, -0));
console.log(Object.is(NaN, NaN));

打印结果却是:

1
2
false
true

代码解释:还是刚刚说的那样,Object.is(v1, v2)比较的是字符串是否相等。

Object.assign

Object.assign 用于对象的合并,方法的第一个参数是目标对象,后面的参数都是源对象。

1
2
3
4
5
6
7
const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target; // {a:1, b:2, c:3}

Object.keys 和 Object.values

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

1
2
3
4
5
var obj = { a: "aaa", b: "bbb" };
Object.keys(obj);
// ["a", "b"]
Object.values(obj);
// ["aaa", "bbb"]

Object.entries 和 Object.fromEntries

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

1
2
3
4
5
6
7
8
var obj = { a: "aaa", b: "bbb" };
Object.entries(obj);
// [["a", "b"],["aaa", "bbb"]]
Object.fromEntries([
["a", "b"],
["aaa", "bbb"]
]);
// { foo: "bar", baz: 42 }

Promise 对象

Promise 对象的相关文档,可参考:Promise 对象

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
基本用法:

1
2
3
4
5
6
7
const promise = new Promise(function (resolve, reject) {
if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
});

Promise.prototype.then()

then 方法参数是 resolved 状态的回调函数。

1
2
3
axios.get("/xxx").then(function (result) {
return result;
});

Promise.prototype.catch()

catch 方法的参数是 reject 状态的回调函数。

1
2
3
axios.get("/xxx").catch(function (err) {
return err;
});

Promise.prototype.finally()

finally 方法的参数是 reject 状态的回调函数。

1
2
3
axios.get("/xxx").finally(() => {
return "完成";
});

Promise.all()

Promise.all 方法接受一个数组作为参数,p1、p2、p3 必须为 Promise 实例

1
Promise.all([p1, p2, p3]);

Promise.race()

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

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

Promise.resolve()

Promise.resolve(result)会返回一个新的 Promise 实例,该实例的状态为 fulfilled。

1
const p = Promise.resolve("成功了");

Promise.reject()

Promise.reject(err)会返回一个新的 Promise 实例,该实例的状态为 rejected。

1
const p = Promise.reject("出错了");