ES6拾遗

ES6中常用的知识点回顾

作用域

this的指向

箭头函数的特性

1. ()和{}的省略

1
2
3
4
// 有且只有一个形参时,可以省略()
let fun = a => {}
//当只有一个return语句时,并且只return一个值或一句代码,可以省略{}和return
let fun = a => a*5 //返回a的五倍

2. 没有arguments对象

arguments对象是一个类数组对象,普通函数传入的实参都存在这个对象中,因此声明函数的时候不规定形参也可以通过arguments对象拿到调用函数时传入的实参

1
2
3
4
function test(){
console.log(arguments[0],arguments[1],arguments[2],arguments[3])
}
test(11,22,33,44) //打印11 22 33 44

箭头函数没有arguments对象,因此无法做如上操作

3. 没有this,里面的this是父级作用域的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// setTimeout 方法的this指向的是window
text.oninput = function(){
setTimeout(()=>{
console.log(this); //这里的this是setTimeout的this,即window
},1000)
}

// 用一个变量储存this再传入setTimeout
text.oninput = function(){
let that = this;
setTimeout(()=>{
console.log(that); //这里的that就是oniput这个函数的this了
},1000)
}

解构赋值

所有对象和可迭代对象都可以解构赋值,比如对象,数组,

快速交换两个变量

对象简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let name = 'tom'
let age = '18'
let favfood = 'apple'

//当对象的属性和要传入的变量同名时,可以直接写变量名
//方法直接写方法
let obj = {
name,
age,
favfood,
getName(){
return this.name;
},
}
//相当于:
let obj = {
name:name,
age:age,
favfood:favfood,
getName:function getName(){
return this.name;
},
}

展开运算符

1. 用法

1
2
3
4
5
6
7
8
9
let arr = [10,20,30,40,50]
...arr // 相当于将数组展开,去掉中括号,变成 10,20,30,40,50

let obj = {
name: Jacket,
age: 17,
weight: 120,
}
...obj // 和数组同理,相当于去掉大括号,name: Jacket, age: 17,weight: 120,

2. 合并数组或对象

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
26
27
28
29
30
//	合并数组
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [...arr1,...arr2]
console.log(arr3) // [1,2,3,4,5,6]

// 合并对象
let obj1 = {
name: "XiaoMing",
age: 18,
}
let obj2 = {
age: 20,
favfood: "apple",
height: 180,
}
let obj3 = {
...obj1,
...obj2,
weight: 60,
}
console.log(obj3)
//结果为
// obj3{
// name: XiaoMing,
// age: 20, //同名属性以后面一个为准
// favfood: apple,
// height: 180,
// weight: 60,
//}

3. 快速拷贝数组

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

4. 参数的简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在形参中使用----------------------------------------------------------
function test1(a,b,...arr){ //可以传入多个参数,先赋值a,b,剩下的都在数组arr里
console.log(arr);
}
test(1,5,6,8,10) //输出[6,8,10]
//!!!注意:形参中的展开运算符必须作为最后一个参数
//可以代替arguments,方便箭头函数
let test2 = (...arr) => {
console.log(arr)
}
test2(1,2,4,5)

// 在实参中使用,用数组一次传入多个参数
arr3 = [1,2,4]
function test3(a,b,c){
console.log(a,b,c)
}
test3(...arr3)

5. 转换伪数组

1
2
3
4
5
6
7
// 用arguments对象举例,将它转化为真数组
let argu_arr;
function test(){
argu_arr = [...arguments];
}
test(1,2,3,4,5);
console.log(argu_arr); //[1,2,3,4,5]

模块化

多个js文件场景下,没有模块化时的一些痛点:

  • 私密性不好:html调用的方法大家都可以访问修改
  • 重名函数覆盖:两个js函数中如果有同名函数,那么后调用的会覆盖先调用的
  • 依赖容易混乱:js调用的先后顺序会影响代码的执行顺序

模块化语法

新建一个a.js文件

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
26
// 📁这里是a.js
function A1(){
console.log("方法A1")
common()
}
function A2(){
console.log("方法A2")
common
}
function A3(){
console.log("方法A3")
common
}
function test(){
console.log("这里是a的test")
}
function common(){
console.log("公共方法")
}
export{ //导出这些方法
A1,
A2,
A3,
test
}

新建一个b.js文件

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
// 📁这里是b.js
function B1(){
console.log("方法B1")
common()
}
function B2(){
console.log("方法B2")
common
}
function B3(){
console.log("方法B3")
common
}
function test(){
console.log("这里是b的test")
}
function common(){
console.log("公共方法")
}
export{ //导出这些方法
B1,
B2,
B3,
test
}

新建一个index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--要导入方法,必须将script标签的属性type设置为module-->
<script type="module">
import {A1,A2,A3,test as test_a} from './module/a.js' //导入方法语法
import {B1,B2,B3,test as test_b} from './module/b.js' //as可以将导出的方法重命名,可以规避同名方法
A1();
A2();
A3();
B1();
B2();
B3();
test_a();
test_b(); //没有导入公共方法common,保证了私密性
</script>
</body>
</html>

所谓模块就是一个脚本文件,比如这里的a.js或b.js。他们有自己的作用域,只对外暴露export的内容,其他文件无法访问没有暴露的内容。

模块化的特性

  1. 模块化只能通过HTTP(s)服务器工作,直接通过本地打开会报错。

  2. 一个模块导入另一个模块,必须把import语句写在开头

  3. 每个<script type="module"></script> 都有自己独立的作用域,不能访问彼此的变量

    1
    2
    <script type="module">	import {a} from './a.js'</script>
    <script type="module"> console.log(a) </script> <!--会报错-->
  4. 代码被多次导入,只有第一次导入会执行。因此尽量导出函数,而非直接执行。

  5. 一个模块中的顶级this是undefined

  6. import.meta对象包含当前模块的信息,比如import.meta.url,在模块中是脚本的url,在html是当前页面的url

  7. 兼容性问题,可以用打包工具如webpack解决

JS面向对象

ES6之前,面向对象创建类使用构造函数实现的,而类的继承和公共方法是依托this指向的改变原型链实现的。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//创建一个类---------------
function creator(data){ //使用构造函数创建一个模板
this.name = data.name
this.age = data.age
this.weight = data.weight
}
creator.prototype.infor = function(){ //将共用属性和方法挂载到构造函数的原型上
console.log(`My name is ${this.name},I'm ${this.age} years old.`)
}
//创建一个子类-----------------
function student(data){ //用creator类创建一个子类
//用call方法将creator的指向改为student,这样参数就能挂载到student的属性上,实现属性继承
creator.call(this,data)
this.id = data.id //student类特有的属性
this.idinfor = function(){
console.log(`My ID is ${this.id}`)
}
}
student.prototype = new creator({}) //继承方法
//继承方法不要用student.prototype = creator.prototype,因为一旦前者改动,会影响后者的prototype

//数据---------------
let data1 = {
name: 'Mike',
age: 16,
weight: 70
}
let data2 = {
name: 'Tom',
age: 22,
weight: 60
}
let data3 = {
name: 'Maria',
age: 18,
weight: 50,
id: 1145
}
let mike = new creator(data1) //创建一个对象实例
let tom = new creator(data2) //创建另一个对象实例
let maria = new student(data3) //创建student实例
//测试-----------------
mike.infor()
tom.infor()
maria.infor()
maria.idinfor()
//结果
//My name is Mike,I'm 16 years old.
//My name is Tom,I'm 22 years old.
//My name is Maria,I'm 18 years old.
//My ID is 1145

扩展父类的方法,先执行父类方法代码再执行拓展代码,有两种方案,以上面student为例

1
2
3
4
5
6
7
8
9
10
11
//方案一,创建一个不同名的新方法
student.prototype.infor2 = function(){
this.infor() //直接调用老方法,避免重复代码
console.log('这是拓展代码')
}

//方案二,同名,但是改变this指向再执行老方法
student.prototype.infor = function(){
creator.protoype.infor.call(this) //直接调用父类的prototype中的方法,改变this指向子类
console.log('这是拓展代码')
}

ES6语法糖

相对于其他语言来说(比如java),js的类写法是有点奇怪的和不规范的,因此ES6参考其他语言规定了一种更友好的语法

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
26
27
28
29
30
31
32
//创建一个父类--------
class Person{ //class name{} 整个类的内容都包括在里面
constructor(name,age){ //属性的定义可以直接在里面写构造函数
this.name = name
this.age = age
}
infor(){ //类的方法可以直接卸载class里面
console.log(`My name is ${this.name},I'm ${this.age} years old.`)
}
}
//创建一个子类-----------
class Student extends Person{ //extends可以继承方法,相当于将prototype挂到父类的实例上
constructor(name,age,id,grade){
super(name,age) //super() 可以快捷的将属性继承
this.id = id
this.grade = grade
}
stu_infor(str = 'Hllo.'){
console.log(str,`My Id is ${this.id},my grade is ${this.grade}`)
}
}
//创建实例---------
let person = new Person('Mike',18)
let student = new Student('Maria',17,11455,4)
//测试----------
person.infor()
student.infor()
student.stu_infor('你好啊')
//结果
//My name is Mike,I'm 18 years old.
//My name is Maria,I'm 17 years old.
//你好啊 My Id is 11455,my grade is 4

super其实就是父类这个构造函数,但是super的this指向的是子类,要扩展父类的方法,也可以使用super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student extends Person{	
constructor(name,age,id,grade){
super(name,age)
this.id = id
this.grade = grade
}
stu_infor(str = 'Hllo.'){
console.log(str,`My Id is ${this.id},my grade is ${this.grade}`)
}
infor(){
super.infor() //这里的super和之前的 父类.prototype.infor().call(this)是一个道理
console.log('这是拓展代码')
}
}