简介

ECMAScript 6.0,简称ES6,是JavaScript语言的下一代标准,已经在2015年6月正式发布。其目标为使JavaScript可以用来编写复杂的大型应用程序,成为企业级开发语言。

参考资料

ECMAScript 6语法和应用

  • ECMAScript 2015 花费6年时间敲定,是一个很大的发行版
  • 认识ES6
  • 函数的参数
  • ES6兼容性解决
  • 解构赋值
  • let和 const
  • Classl的用法
  • 箭头函数
  • JSON的新应用
  • 数组的新增方法
  • Module模块
  • Map数据结构
  • ES7-ES11的一些新特性介绍
  • 字符串和新增方法和模版字符串

ECMAScript和 JavaScrip的关系

  • ECMA是“European Computer Manufacturers Association”的缩写,中文称欧洲计算机制造联合会。这个组织的目标是评估,开发和认可电信和计算机标准。

  • ECMA是标准, JavaScript是实现

    • 类似HTML5是标准,IE10、 Chrome、FF都是实现
    • 目的是让所有前端脚本都实现ECMA
    • 目前只有 JavaScript实现ECMA标准, ECMAScript≈Js
  • ECMAScript简称ECMA或ES(ES6)

  • 目前版本

    • 高级浏览器支持ES6
    • 低级浏览器主要支持ES3.1

版本记录

时间 版本 说明
1996 年 11月 JavaScript 的创造者 Netscape 公司将 JavaScript 提交给标准化组织 ECMA。
1997年 ES1 ECMA 发布 262 号标准文件(ECMA-262),规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript (ES).
1998年 ES2 内容编辑加工,没有特性修改。
1999年 ES3 有较完善的修改,成为JavaScript的通行标准,得到了广泛支持。
2000年 ES4 2008年终止也称作JavaScript 2,因改动太大没有通过,Harmony项目启动来跟进,ES4大部分内容被 ES6 继承. Harmony部分内容放到ES6之后。
2009年 ES5(ES3.1) 新功能主要包括:JSON对象(包含parse/stringify等方法)、Array和Object增加一些方法,严格模式(use strict),函数的bind方法。
2011年 ES5.1 成为 ISO 国际标准(ISO/IEC 16262:2011), 到了2012年底,主流浏览器都支持ECMAScript 5.1的全部功能
2015年 ES2015(ES6) 做了大量的更新,但向后兼容。ES6是一次重大改进。 部分功能:let/const、变量的解构赋值、Promise、箭头函数…
2016年 ES2016(ES7) 新功能主要包括: 1. Array.prototype.includes检查数组中是否存在值;(区别ES6字符串的includes方法) 2. Exponentiation Operator 求幂运算 (a ** b等价于Math.pow(a,b))
2017年 ES2017(ES8) 部分功能: 1.Object.values/entries/getOwnPropertyDescriptors 2.String.prototype.padStart/padEnd 3.函数参数列表和调用中的尾逗号(Trailing commas) 4.Async Functions 异步函数(async/await)
2018年 ES2018(ES9) 现在已经可用了。新增异步迭代、Promise.finally()、Rest/Spread 属性、正则表达式命名捕获组、正则表达式反向断言、非转义序列的模板字符串……

let&const

简介

  • 为什么ES6新增 let 关键字声明变量:因为最起初 JavaScript 设计是写一写小的项目,当时没有考虑的很全面,后面使用的人多了,var 声明变量就暴露了它的缺陷

缺陷

var可以重复声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
<!-- 重复声明-->
var a = 10;
var a = 20;
a = 35;
console.log(a)
</script>


</body>
</html>

var重复声明

var无法限制修改

  • var无法限制修改
  • var没有块级作用域{} if(){块内} for(){块内}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
{
var a = 18;
console.log(a);
}
console.log(a); //这里没有限制代码块中变量 a 的作用域,其实是希望这里没办法使用到变量 a

var name = "ada";
if (true) {
var name = "阿达";
console.log(name);
}
console.log(name); //这里在 if 块内的赋值修改了 if 块外的 name 变量,期望的是 if 块内打印的是【阿达】,块外打印的是 【ada】 没有限制修改
</script>
</body>
</html>

未限制修改和没有块级作用域

变量提升

  • let 不支持变量提升
  • var 不管放在哪里定义 都会把 var 语句提升到最前面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
<!-- var 支持变量提升-->
a = 10;
console.log(a);
var a;
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
<!-- let 不支持变量提升-->
a = 10;
console.log(a);
let a;
</script>
</body>
</html>

变量提升

解决方法(闭包&let)

  • ES6 以前的解决方法是通过闭包的方式解决
  • var 在块级中没有作用域但是在方法中有作用域

示例一

  • 需求:块内输出块内自己的变量值,块外输出块外的变量值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var name = "ada";

if (true) {
(function (nameStr) {
var name = nameStr;
console.log("闭包中的输出:" + name);
})("阿达"); // 括号中的值是方法中 nameStr 传递的参数
}
console.log("变量 var 的输出" + name);
</script>
</body>
</html>

示例一

示例二

  • 需求:有 5 个按钮,每次点击打印对应的按钮序号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮一</button>
<button>按钮二</button>
<button>按钮三</button>
<button>按钮四</button>
<button>按钮五</button>
<script>
//按钮添加监听事件
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function () {
console.log(i);
});
}
</script>
</body>
</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
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮一</button>
<button>按钮二</button>
<button>按钮三</button>
<button>按钮四</button>
<button>按钮五</button>
<script>
//按钮添加监听事件
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
(function (i) {
btns[i].addEventListener("click", function () {
console.log(i);
});
})(i)
}
</script>
</body>
</html>

示例二

示例三(let)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮一</button>
<button>按钮二</button>
<button>按钮三</button>
<button>按钮四</button>
<button>按钮五</button>
<script>
//按钮添加监听事件
let btns = document.getElementsByTagName("button");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function () {
console.log(i);
});
}
</script>
</body>
</html>

示例三

const

  • 限制修改(常量方式)

总结

  • 以后都不用 var
  • 如果变量需要修改就用 let
  • 如果变量不希望修改就用 const 定义为常量
  • 箭头函数定义的方法用 const 对象用 const 普通变量和数组用 let(我的个人习惯)

let

  • ES6 新增 let 和 const 命令,用来声明变量,用法类似于 var
  • let 声明的变量,只在 let 命令所在的代码块內有效
  • let 命令不存在变量提升
  • let 命令不允许在相同作用域内,重复声明同一个变量

const

  • let 拥有的特性 const 也拥有
  • const 不可以像 let 一样分步声明,例如: let a; a=10; 因为 const 的值不可改变,一旦定义了就不能修改,所以后面也不能进行赋值了
  • const 命令声明一个只读的常量。一旦声明,常量的值就不能改变
  • const 命令声明的常量不得改变值。即一旦声明就必须立即初始化。
  • const 命令声明的常量,只在声明所在的块级作用域内有效
  • const 命令声明的常量不提升,只能在声明的位置后使用
  • const 命令声明的常量,与let一样不可重复声明
  • const 命令声明的复合类型的数据(主要是对象和数组),变量指向的内存地址
  • const定义的基本类型不可修改,复合类型(对象,数组)也不可以修改,复合类型的属性是可以修改的 直接修改基本类型或者复合类型相当于修改栈内存中的地址是不允许的,修改复合类型对象的属性其实是修改该类型指向堆内存中的属性不受影响
1
2
3
4
5
//const定义的常量不可修改,对象也不可以修改,对象的属性是可以修改的

const obj ={name:'张三',age:10,sex:'男'};
obj.name='lisi'; //这个是支持的,因为修改的是对象在堆里面的值
obj={name:'李四',age:10,sex:'男'}; //不支持,因为是重新创建了一个堆的对象{name:'李四',age:10,sex:'男'}然后赋值给栈里面的obj所以不可修改(栈内存)

对比

  • const:不可修改(可以理解常量 java 中的 final)
  • let:可以修改,(可以理解为变量)

箭头函数

简介

  • ES6 允许使用“箭头”(=>)定义
  • 不需要参数或需要多个参数,就用圆括号代
  • 代码块部分多于一条语句,就用大括号括起来,并且用return返回
  • 箭头函数返回对象时,必须在对象外面加上括号
  • 箭头函数使得表达更加简洁
  • 箭头函数能够简化回调函数
  • 箭头函数类似于 jdk8 中的 lamda 表达式

示例一(箭头函数基本使用)

  • 基本规则:1.把 function 换成 => 2.把 => 放在参数和 {} 的中间
  • 如果只有一个参数 = 等号和箭头中间写形参名 =>
  • 如果 { } 大括号代码块内只有一条语句不用写 return 和大括号{ } 这里是同时都不写
  • return 和大括号{ }一般是成对出现的,有大括号一般都有 return,如果有大括号里面哪怕只有一条语句也要加上 return 不然调用该方法的时候会出现 undefined
  • 如果有多个参数或者没有参数也必须要用 () 来定义参数列表,没有参数的时候括号内就空着
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function fun1(a) {
return a * a;
}
console.log(fun1(1));

const fun2 = function (b) {
return b * b;
}
console.log(fun2(2));

//第一次演变:1.把 function 换成 => 2.把 => 放在参数和 {} 的中间
const fun3 = (c) => {
return c * c;
};
console.log(fun3(3));

//第二次演变:1.如果只有一个参数 = 等号和箭头中间写形参名 => 2.如果 { } 大括号代码块内只有一条语句不用写 return 和大括号{ }
const fun4 = d => d * d;
console.log(fun4(4));

//第三次演变:如果有多个参数或者没有参数也必须要用 () 来定义参数列表,没有参数的时候括号内就空着
const fun5 = (e, f, g) => {
let add = e + f + g;
return add;
}
console.log(fun5(1, 2, 3));

//第四次演变:1.没有参数也要用括号 () 来定义参数列表 2.方法体内只有一条语句不用加大括号 { } 和 return 关键字
const fun6 = () => 10 * 10;
console.log(fun6());
</script>
</body>
</html>

箭头函数基本使用

示例二(数组排序)

  • 方法的演变还是和上面示例一相同,1.把 function 换成 => 2.把 => 放在参数和 {} 的中间
  • 箭头函数的函数体只有一条语句 不用加 { } 和 return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let array = [1, 3, 5, 7, 9, 15, 88, 99];
//数组升序排列 从小到大 i - j 表示从小到大 j - i 表示从大到小
const asc = array.sort(function (i, j) {
return i - j;
})
console.log(asc);

//箭头函数数组降序序排列 从大到小 j - i 表示从大到小
const desc = array.sort((i, j) => j - i);
console.log(desc);

</script>
</body>
</html>

数组排序

示例三(箭头函数返回对象)

  • 返回的对象 obj 需要用法大括号包裹起来和箭头函数的大括号有冲突,所以可通过外面包裹一层 大括号+return 或者用小括号包括返回的对象
  • 不能直接像上面那样只有一条语句连对象的大括号都不写,对象需要用大括号包裹的不然 IDE 工具就会报错
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">
<title>Title</title>
</head>
<body>
<script>
//方式一:箭头函数返回对象时语法 { return {返回的对象内容} } 如果只写里面的对象的括号语法会以为是箭头函数的大括号
const fun1 = id => {
return {id: id, name: "阿达"}
};
console.log(fun1(10001));
console.log(fun1(10001).id);
console.log(fun1(10001).name);

//方式二:其实我们只有一条语句不想写 return 语句,就用小括号括住返回的对象 ( { 返回的对象 } )
const fun2 = (id, name) => ({id: id, name: name});
console.log(fun2(10002, "adalucky"));
console.log(fun2(10002, "adalucky").id);
console.log(fun2(10002, "adalucky").name);
</script>
</body>
</html>

箭头函数返回对象

示例四(构造方法)

  • 箭头函数没有构造方法,因此不能用箭头函数定义一个方法然后还去 new 这个方法
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">
<title>Title</title>
</head>
<body>
<script>
function demo1() {

}

const fun1 = new demo1();
console.log(fun1);

const demo2 = () => {

}
const fun2 = new demo2();
// 控制台会提示 Uncaught TypeError: demo2 is not a constructor demo2不是一个构造方法,因为 dome2 是用箭头函数定义的,箭头函数没有构造方法
console.log(fun2);
</script>
</body>
</html>

构造方法

关于箭头函数中的this的指向

简介

  • 普通函数的this:指向它的调用者,如果没有调用者则默认指向window.
  • 箭头函数的this: 指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this.
  • 综上:箭头函数没有自己的this,它的this是继承而来,默认指向在定义它时所处的对象(宿主对象)。
  • 上面三条看起很懵,没关系接着看下面的两个示例看完再回来理解这三条的含义

示例一

  • 普通函数,谁调用这个方法,this 就代表谁,没有人调用直接运行就代表最大的 window 对象
  • 箭头函数跟调用者无关,指向的是函数定义时所处的对象,可以理解为父级的 this 对象,如果父级没有,就再往上找,最后是到 window 对象
  • 下图中 arrowThis2.html 的 fun1 定义在 adaThis2对象,该对象直接是在 window 里面定义的,因此上下文的 tihs 就是 window 所以无论怎么调用都会指向 window 对象
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//定义了一个对象 adaThis1 里面 代码块的形式定义了一个方法fun1
//定义的方法语法有点类似 json 格式 f:f(){}
const adaThis1 = {
fun1: function () {
console.log(this);
}
};
//用 adaThis1 这个对象调用运行 fun1--->
// this 就会指向它的调用者 也就会是这个 adaThis1
adaThis1.fun1();

function fun2() {
console.log(this);
}

//没有对象调用,直接用运行 fun2() 就会指向 window 对象
fun2();

</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//对象adaThis2 代码块箭头函数的形式定义了一个方法 fun1
//定义的方法语法有点类似 json 格式 f:f(){}
const adaThis2 = {
fun1: () => {
console.log(this);
}
}
//用 adaThis2 这个对象调用运行 fun1--->
//this 指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this
adaThis2.fun1();

</script>
</body>
</html>

this 指向示例一

示例二

  • 再次强调普通函数的 this 指向和调用者有关
  • 下面的内容主要是针对示例一更加直观的解释 this 指向问题(普通函数)
  • 左侧代码大致的意思是定义了一个 box,设置了css 属性,当发生点击时添加新的样式进来,右侧是把添加新样式的方法写在了 setTimeout 里面等待三秒,因为 setTimeout 是 window 的对象,所以调用者不是 box 因此无法改变颜色,通过对比来阐述普通函数 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<style>
#box {
height: 100px;
width: 100px;
background: red;
}

#box.newColor {
background: yellow;
}
</style>

<div id="box"></div>

<script>
const box = document.getElementById("box");
box.onclick = function () {
console.log(this);
this.className = "newColor";
}

</script>
</body>
</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
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<style>
#box {
height: 100px;
width: 100px;
background: red;
}

#box.newColor {
background: yellow;
}
</style>

<div id="box"></div>

<script>
const box = document.getElementById("box");
box.onclick = function () {
setTimeout(function () {
console.log(this);
this.className = "newColor";
}, 3000)
}

</script>
</body>
</html>

this 指向示例二

示例三

  • 本内容是对示例二中添加了 setTimeout 函数无法改变颜色获取我们期望的对象做修复
  • 普通函数就在 setTimeout 前获取 this 对象,命名为 obj,此时的 obj 指向的是 box 对象,再用这个 obj 调用就相当于用 box 调用 普通函数 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<style>
#box {
height: 100px;
width: 100px;
background: red;
}

#box.newColor {
background: yellow;
}
</style>

<div id="box"></div>

<script>
const box = document.getElementById("box");
box.onclick = function () {
//此时的 this 是 box,把 this 赋给 obj
let obj = this;
setTimeout(function () {
console.log(this);
console.log(obj);
//用上面赋值的 obj 调用就相当于用 box 调用
obj.className = "newColor";
}, 3000)
}

</script>
</body>
</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
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<style>
#box {
height: 100px;
width: 100px;
background: red;
}

#box.newColor {
background: yellow;
}
</style>

<div id="box"></div>

<script>
const box = document.getElementById("box");
box.onclick = function () {
setTimeout(() => {
//箭头函数 this 指向和调用者无关,指向箭头函数定义时所处的对象
console.log(this);
this.className = "newColor";
}, 3000)
}

</script>
</body>
</html>

this 指向示例三

数组新方法

常用新增方法

  • filter 过滤器(过滤掉不符合条件的数据)
  • map 映射(把数据映射到新的对象中)
  • reduce 汇总(统计求和)
  • some() 数组中有一个为真就返回真—>待补充(判断时候用)
  • every() 数组中有全部为真才返回真—>待补充(判断时候用)
  • 需求:有一批商品,现在要做促销活动,把价格大于等于 10 的商品打 5 折后计算此次促销会少赚多少钱 通过该需求进行 ES6 高级函数的演示

示例一

  • 示例一中将用以前原始的写法进行实现需求
  • let n of array 语法可以了解一下
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
//需求:有一批商品,现在要把大于或等于 10 元的商品促销打 5 折处理
// ①过滤 >=10元的商品 ②把过滤的商品打 5 折 ③列出促销产品打折前后的价格 ④计算此次促销少赚多少钱
let goods = [10, 20, 3, 30, 2, 5, 90, 100];

let step1 = []; //步骤一:定义了一个空的数组用于存放 >= 10的商品价格(过滤)
//let n of goods 该语法相当于会去循环 goods 数组,循环的次数就是数组的长度,n 是每次循环对应下标的值
for (let n of goods) {
if (n >= 10) {
step1.push(n);
}
}
console.log(step1);

let step2 = []; //步骤二:定义了一个空的数组用于存放 >= 10的商品价格打 5 折以后的价格(打折)
for (let n of step1) {
step2.push(n * 0.5)
}
console.log(step2);

let beforeSum = 0; //定义两个数组用于存放促销商品打折前后的价格
let afterSum = 0;
for (let n of step1){
beforeSum += n; //步骤三:促销产品打折前价格
}
for (let n of step2){
afterSum += n; //步骤三:促销产品打折后价格
}
console.log("促销产品原始总价:"+beforeSum+",促销产品打折后总价:"+afterSum+",少盈利:"+(beforeSum-afterSum));

原始写法

示例二

  • 示例二中采用 ES6 中新增的高级方法 filter map reduce 实现
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
//需求:有一批商品,现在要把大于或等于 10 元的商品促销打 5 折处理
// ①过滤 >=10元的商品 ②把过滤的商品打 5 折 ③列出促销产品打折前后的价格 ④计算此次促销少赚多少钱
let goods = [10, 20, 3, 30, 2, 5, 90, 100];

let step1 = goods.filter(function (n) {
//步骤一:过滤 >= 10的商品价格,参数 n 为每次遍历数组 goods 后的结果,当 n >=10(布尔表达式) 才返回到数组中
return n >= 10; // step1 = [ 10, 20, 30, 90, 100 ]
})
console.log(step1);

let step2 = step1.map(function (n) {
//步骤二:打折后映射到新的数组 step2 中,参数 n 为每次遍历数组 step1 后的结果,打 5 折后返回至 step2 中
return n * 0.5; //step2 = [ 5, 10, 15, 45, 50 ]

})
console.log(step2);

let beforeSum = step1.reduce(function (s, n) {
//有两个参数 s,n s给了一个初始值就是后面的 0(不给值默认也是 0) ,n 遍历数组中的每一个对象值
//第一次:s=0,n=10(step1中第一个元素),return 后 s=10(s+=n -> s=s+n),beforeSum=10;
//第二次:s=10,n=20, return 后 s=30,beforeSum=30; 第三次:s=30,n=30,return 后 s=60,beforeSum=60
return s += n; //步骤三:促销产品打折前价格
}, 0)

let afterSum = step2.reduce(function (s, n) {
return s += n; //步骤三:促销产品打折后价格
}, 0)
console.log("促销产品原始总价:" + beforeSum + ",促销产品打折后总价:" + afterSum + ",少盈利:" + (beforeSum - afterSum));

高级函数语法

示例三

  • 在示例二的基础上进行链式编程方式,更加优雅的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//需求:有一批商品,现在要把大于或等于 10 元的商品促销打 5 折处理
// ①过滤 >=10元的商品 ②把过滤的商品打 5 折 ③列出促销产品打折前后的价格 ④计算此次促销少赚多少钱

let goods = [10, 20, 3, 30, 2, 5, 90, 100];

//促销产品打折前价格:采用链式编程,过滤->映射->汇总

let afterSum = goods.filter(n => n >= 10)
.map(n => n * 0.5)
.reduce((s, n) => s += n);


//-----这两个其实都是一样的故意用两种写法 因为箭头函数中只有一条语句可写可不写 return + {} -----
//reduce 如果不传参默认就是 0,如果传参的话写在箭头函数 } 的后面,用逗号分割参数

//促销产品打折后价格: 采用链式编程,过滤->映射->汇总

let beforeSum = goods.filter(n => { return n >= 10})
.map(n =>{ return n})
.reduce((s, n) => { return s += n},0)

console.log("促销产品原始总价:"+beforeSum+",促销产品打折后总价:"+afterSum+",少盈利:"+(beforeSum-afterSum));

高级函数链式编程

数据结构集合

set

简介

  • set 集合和 java 的 set 集合差不多
  • 集合中只能存放值,没有键,相同的值只能存储一个
  • 一个 set 集合中可以同时存放不同的数据类型,比如整型,字符串,数组等

示例

  • 常用方法:add delete has size forEach clear
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
const obj = new Set();
// add 添加数据
obj.add(1);
// 添加数据重复数据是无效的
obj.add(1);
console.log(obj);


obj.add(5);
obj.add(["ada", 25, "测试"]);
console.log(obj);

// 判断集合中是否有 1,返回的是一个布尔值
console.log(obj.has(1));

//删除集合中等于 2 的值,删除成功返回 true ,删除失败返回 false,这里面没有 2 就会删除失败返回 false
console.log(obj.delete(2));

// 两种遍历方式
obj.forEach(n => console.log("forEach遍历结果:" + n))
for (let n of obj) {
console.log(n);
}

//清空集合
obj.clear();
//获取集合大小
console.log(obj.size);

set 示例

map

简介

  • 也和 java 的差不多,通过键值对存储 key->value
  • Map 数据结构类似于对象,是键值对的集合,传统的键只能用字符串,Map 的键不限于字符串,各种类型的值(包括对象)都可以当作键。
  • 属性和操作方法
    • size 属性,返回 Map 结构的成员总数
    • set(key,value)方法,设置set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该
    • get(key) 方法,读取key对应的键值,如果找不到key,返回undefined
    • has(key) 方法,返回一个布尔值,表示某个键是否在当前 Map 对象之中。
    • delete(key) 方法,删除某个键,返回true。如果删除失败,返回false。
    • clear(key) 方法,清除所有成员,没有返回值。
  • Map 遍历
    • keys() 返回键名的遍历器
    • values() 返回键值的遍历器
    • entries()返回所有成员的遍历器
    • forEach()遍历Map的所有成员

示例

  • 常用方法:set get forEach has size
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const mapObj = new Map();

mapObj.set("name", "ada");
mapObj.set("age", 25);
console.log(mapObj);
console.log(mapObj.get("name"));

// 重复的key,value 会覆盖 上面输出的 ada 下面输出的 阿达
mapObj.set("name", "阿达");
console.log(mapObj.get("name"));

//遍历 和 java 的 lambda 一样一样的
mapObj.forEach((k,v)=>console.log("key:"+k+",value:"+v))
//删除 age
console.log(mapObj.delete("age"));
//判断是否有 name
console.log(mapObj.has("age"));
//获取大小
console.log(mapObj.size);

map 示例

字符串新功能

  • startsWith 判断以什么字符串开头
  • endsWith 判断以什么字符串结尾
  • 模板字符串

判断开头与结尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let url = "www.adalucky.com"

/**
*
* @param s 域名.前面部分
* @param e 域名.后面部分
* @returns {string}
*/
const fun = (s, e) => {
if (url.startsWith(s+".") && url.endsWith("."+e)) {
// return "是一个标准的【"+s+"】开头,【"+e+"】结尾的域名"
// 上面注释的是普通字符串拼接的变量,下面是用模板字符串 `` ${} 引用变量
return `是一个标准的【${s}】开头,【${e}】结尾的域名`
} else {
return "不是一个标准的【www】开头,【com】结尾的域名"
}
}

console.log(fun("www", "com"));
console.log(fun("www.", ".com"));
console.log(fun("test", "vip"));

判断开头结果

模板字符串

  • 模板字符串 ``定义字符 ${} 引用变量
  • 支持换行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let title = "这是标题";
let slogan = "这是标语 slogan";

//里面还可以拼接标签 <title>${title}</title> 这使用方法,换行会直接换行
let str = `模板字符串中直接引用变量不需要用加号隔开
${title}
${slogan}
`

console.log(str);

//换行也会自动用加号拼接 最后打印都是一行
let str2 = "字符串拼接"
+ title
+ slogan

console.log(str2);

模板字符串示例

解构赋值

简介

  • 解构赋值,序列解包,
  • 注意事项:结构构必须一样,比如数组两边都需要用数组[a,b,c]=[1,2,3] 对象{UserName,UserAge,UserSex}={“name”: “ada”, “age”: “25”, “sex”: “男”}
  • 接受的参数个数不能操作数据的长度,可以少,当少于长度时按照从左到右顺序解构
  • 解构赋值不能像定义变量一样分开声明和赋值,例如 let [a,b]; [a,b]=[1,2]

基础的解构

1
2
3
4
5
6
7
8
9
10
11
12
let arr = [1, 2, 3];
let a = arr[0];
let b = arr[1];
let c = arr[2];
console.log(`a=${a},b=${b},c=${c}`);

const obj = {"name": "ada", "age": "25", "sex": "男"};
let UserName = obj.name;
let UserAge = obj.age;
let UserSex = obj.sex;

console.log(`name:${UserName},age:${UserAge},sex:${UserSex}`);
1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3,4];
//等号右边可以是引用的数组,也可以直接显示声明数组 [a, b, c] = [1, 2, 3,4]
//两边的结构保持一致都是数组,接受的参数个数可以小于数组的长度 不能超出长度
let [a, b, c] = arr;
console.log(`a=${a},b=${b},c=${c}`);

//直接显示声明,对象结构的是按照 key 取得所有参数名和对象中的 key 要一致,顺序无所谓
let {age,sex,name} = {"name": "ada", "age": "25", "sex": "男"};
console.log(`name:${name},age:${age},sex:${sex}`);

基础解构

复杂的解构

  • 这种复杂的一般比较少不常用
  • ①先确定外层结构两边保持一至 ②内层结构中有对象+数组的 那么用 对象名:[] 来二次结构 ③对象的数组中又有对象用,对象名:[{对象名}]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//较为复杂的组合结构  ①先确定外层结构两边保持一至 ②内层结构中有对象+数组的 那么用 对象名:[] 来二次结构 ③对象的数组中又有对象用,对象名:[{对象名}]

let [one, two, three, four] = [{"name": "ada", "age": "25"}, [1, 2, 3], "字符串", 10];
console.log(one, two, three, four);

let [{name, age}, [a1, b1, c1], str1, number1] = [{"name": "ada1", "age": "25"}, [1, 2, 3], "字符串", 1];
console.log(name, age, a1, b1, c1, str1, number1)

let {obj, arr, str2, number2} = {"obj": [{"name": "ada", "age": "25"}], "arr": [15, 25, 35], "str2": "字符", "number2": 2}
console.log(obj, arr, str2, number2)

{
let {obj:[{name,age}], arr:[a,b,c], str3, number3} = {"obj": [{"name": "ada3", "age": "27"}], "arr": [25, 35, 45], "str3": "字符", "number3": 10}
console.log(name, age,a,b,c ,str3, number3)
}

{
let {name, age, child:{firstChild}}= {name: 'zxj', age: 12, child: {firstChild: '小明'}};
console.log(name,age,firstChild)
}

复杂解构示例

扩展运算符

简介

  • …三点运算符
  • 展开数组
  • 默认参数 和java 中的可变参数一样

数组展开放入

  • 先展开数组然后按照所在位置存入数组中
1
2
3
4
5
6
7
8
9
10
11
let arr1 = [1, 2, 3];
let arr2 = [15, 30, 50];
// 三个点展开数组然后放进数组中
let arr3 = [...arr1, 7, 8, 20, ...arr2];
console.log(arr3);
console.log(arr3[5]);

const argsFun = (arr1,arr2) => {
console.log(arr1,arr2)
}
argsFun(...arr1)

数组展开放入

参数传递

  • 如果方法中有自定义的参数和需要可变参数,可变参数需要放在最后一个形参中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let arr1 = [1, 5, 3,7,9];

//调用方法是作为参数传递,方法中只需要 2 个参数就会取数组中的前 2 个值
const argsFun = (arr1, arr2) => console.log(arr1, arr2)
argsFun(...arr1)

//方法中用 ...args 作为可变参数,传进来的会当成一个数组处理,也可根据 ...args 的下标取值
const argsFun2 = (...args) => {
console.log(...args)
console.log(...args[2])
}

argsFun2(20, "测试", "男", "ada")

//如果方法中有自定义的参数和需要可变参数,可变参数需要放在最后一个形参中
//定义的argsFun3中有 2个参数str,number和一个可变参数 ...args 调用的时候只传了一个 str 和一个展开的数组,展开的数组的第一个就会赋给 number,剩下的给可变参数 ...args
const argsFun3 = (str,number,...args)=>console.log(`str的值为: ${str},number的值为: ${number},args的值为: ${args}`)
argsFun3("字符",...arr1)

参数传递

class

简介

  • ES6之前 JavaScript 叫做基于对象,ES6 新增了 Class(类)的概念,可以说是面向对象的
  • constructor 是构造方法
  • this关键字代表实例对象
  • 通过extends关键字实现继承
  • super关键字,表示父类的构造函数,用来新建父类的this对象

对比

  • 通过截图中左右两段代码可看出来 ES6 的定义类、方法、构造方法层次更加清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ES6 前既是构造函数又是方法
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.say = function () {
console.log(`我是${this.name}`);
}
this.run = function () {
console.log("跑起来吧");
}
}

let user = new Person("ada", 25, "男")
console.log(user);
console.log(user.age);
user.say();
user.run();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
//constructor 构造方法关键字
constructor(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
say() {
console.log(`我是${this.name}`);
}
run() {
console.log("ES6后面跑起来了")
}
}

let es6User = new Person("阿达", 20, "男性")
console.log(es6User);
console.log(es6User.sex);
es6User.say();
es6User.run();

对比

继承

  • 继承通过关键字 extends 实现
  • 子类如果重写了父类的方法调用的是子类自己的方法
  • 子类的方法中如果需要调用父类的其它方法可以使用 super.run() super.say() 这种方式
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
class Person {
constructor(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
say() {
console.log(`我是父类的${this.name}`);
}
run() {
console.log("ES6后面跑起来了")
}
}

class students extends Person{
//子类的构造方法,因为我们子类只新增了一个school参数,那么可以用之前的可变参数 ...args 优雅
constructor(school,...args) {
//在子类的构造方法中调用父类的构造方法后再把新增的 school也给初始化
super(...args);
this.school = school;
}
//子类重写父类的对象方法
say() {
console.log(`我是子类的${this.name}`);
}
}
let es6Student = new students("十八线学校","阿达", 20, "男性")
console.log(es6Student);
es6Student.say();

继承

JSON

简介

  • JSON.stringify() 串行化
  • JSON.parse() 反串行化
  • 简写
    • (属性和值)名字一样可以简写
    • 方法一样可以简写(:function省)

简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const user1 = {name: "ada", age: "20", sex: "男"}
console.log(user1);

let name = "阿达";
let age1 = 20;
//简写:name:name --> 简写 name 既是键又是值的引用, age不行,因为对象的 key 叫 age,变量名叫 age1
const user2 = {name, age: age1, sex: "男"}
console.log(user2);

const user3 = {
// 对象里面再套一个方法 say:function ()--->简写为 say()
name, age1, sex: "男", say() {
console.log(this.name);
}
}

user3.say();

json 简写

串行化、 反串行化

  • 串行化:JSON 对象转成 字符串
  • 反串行化:JSON 格式的字符串转成 JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const user3 = {
// 对象里面再套一个方法 say:function ()--->简写为 say()
name:"ada", age:25, sex: "男", say() {
console.log(this.name);
}
}

let str = JSON.stringify(user3);
console.log(str);
console.log(str.name); //会输出 undefined,此时的 str 已经被串行成了字符串,没有 name 的属性

let json = JSON.parse(str);
console.log(json.name);
json.say(); //这一句就会报错,因为上面串行化的时候已经丢失了 say 的方法

串行化和反串行化

Module模块化

简介

  • 模块化优点:减少命名冲突 避免引入时的层层依赖 可以提升执行效率
  • export命令:用于规定模块的对外接口,一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
  • import命令:用于输入其他模块提供的功能,import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

冲突示例

  • 当我们在 html 中引入的 js 时,多个 js 存在重复的变量名、方法名、函数名,就会发生冲突,已经命名的不能重复命名
  • 解决方法:给每个 js 文件类型都设置成 module,这样都是独立的了

命名冲突报错

导入导出

  • :如果是用 node 运行,导入导出使用的关键分别为 require module.exports
  • :如果是用 浏览器 运行,导入导出使用的关键分别为 import export
  • 注意事项:如果是用 node 运行,文件后的 .js 可以不写,如果是浏览器运行导出的文件必须写后缀 .js (这里不包含打包工具处理哈,可能有些打包工具会自动处理上述问题)
  • 导出的时候可以整体用一个 export {导出1,导出 2,…} 也可以在单个上用 export 导出
  • 导入导出时需要使用到解构赋值 JSON 简写 的方式比较简便

示例一

  • 导入时有命名冲突的可在导入方用 as 起别名
1
2
3
4
5
6
7
8
9
//导出 简写 x:x y:y   json中支持的简写方式 既是Key又是value
//如果是用 node 运行,导出关键字为 module.exports,如果是浏览器运行导出的关键字为 export
// module.exports ={x, y,mun};
export {x, y, mun};
let x = 10;
let y = 5;

let mun = (i, j) => "这是one.js返回的:" + i * j;
// console.log(mun(x, y));
1
2
3
4
5
6
7
8
9
10
11
12
13
export {add, mun,Person}
let name = "ada";
let age = 25;
let sex = "男";
const add = (a, b) => a + b;

let mun = (i, j) => "这是two.js返回的:" + i * j;

class Person {
say() {
console.log("two.js中 Person 类的 say 方法")
}
}
1
2
3
4
5
6
7
8
9
//如果是用 node 运行,导入关键字为 require,文件后的 .js 可以不写,如果是浏览器运行导出的关键字为 import,文件后的 .js 必须写
// let {x, y, mun} = require('./two');
import {x,y,mun} from "./one.js";
import {add,mun as mun2,Person} from "./two.js" //如果有命名冲突的可以再导入方 as 起个别名
console.log(mun(x, y));
console.log(mun2(x, y));

let per =new Person();
per.say();
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="index.js" type="module"></script>
</head>
<body>

</body>
</html>

导入导出示例一

示例二

  • 导出时一个 js 文件最多可以定义一个匿名的 default 导出,由导入方定义名称
  • 导出的时候也可以通过 as 起别名
  • 导入的时候可以用 * as name 把暴露的接口都整合在一个对象中,使用时直接用这个对象来操作
1
2
3
4
5
export {x, y };
let x = 10;
let y = 5;
//匿名的,导出方不需要定义名称,导入方自定义,一个 js 文件中只能有一个 default,需要单独写 export
export default (i, j) => "这是one.js,default返回的:" + i * j;
1
2
3
4
5
6
7
8
9
10
11
export {name as myName, age, sex, Person}

let name = "ada";
let age = 25;
let sex = "男";

class Person {
say() {
console.log("user.js中 Person 类的 say 方法")
}
}
1
2
3
4
5
6
7
8
9
10
11
import {x,y} from "./dome.js";
//导入方自定义 dome.js 中的匿名函数,需要单独写一个 import 不能放在 {x,y} 中
import test from "./dome.js";
// 导入user.js 中暴露出来的接口,并起个别名为 user
import * as user from "./user.js"

console.log(test(x, y));

console.log(user.myName);
let p = new user.Person();
p.say();
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="index.js" type="module"></script>
</head>
<body>

</body>
</html>

导入导出示例二