文章目录
01 ECMASript 相关介绍
1.1 什么是 ECMA
ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该组织改名为 Ecma 国际
1.2 什么是 ECMAScript
ECMAScript 是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言
1.3 什么是 ECMA-262
Ecma 国际制定了许多标准,而 ECMA-262 只是其中的一个,所有标准列表查看
https://www.ecma-international.org/publications-and-standards/standards/
1.4 ECMA-262 历史
ECMA-262(ECMAScript)历史版本查看网址
https://www.ecma-international.org/publications-and-standards/standards/ecma-262/
版本 | 年份 | 不同 |
---|---|---|
第 1 版 | 1997 年 | 制定了语言的基本语法 |
第 2 版 | 1998 年 | 较小改动 |
第 3 版 | 1999 年 | 引入正则、异常处理、格式化输出等。IE 开始支持 |
第 4 版 | 2007 年 | 过于激进,未发布 |
第 5 版 | 2009 年 | 引入严格模式、JSON,扩展对象、数组、原型、字符串、日期方法 |
第 6 版 | 2015 年 | 模块化、面向对象语法、Promise、箭头函数、let、 const、数组解构赋值等等 |
第 7 版 | 2016 年 | 幂运算符、数组扩展、Async/await 关键字 |
第 8 版 | 2017 年 | Async/await、字符串扩展 |
第 9 版 | 2018 年 | 对象解构赋值、正则扩展 |
第 10 版 | 2019 年 | 扩展对象、数组方法 |
ES.next | 动态指向下一个版本 |
注:从 ES6 开始,每年发布一个版本,版本号比年份最后一位大 1
1.5 谁在维护 ECMA-262
TC39(Technical Committee 39)是推进 ECMAScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席
1.6 为什么要学习 ES6
- ES6 的版本变动内容最多,具有里程碑意义
- ES6 加入许多新的语法特性,编程实现更简单、高效
- ES6 是前端发展趋势,就业必备技能
1.7 ES6 兼容性
http://kangax.github.io/compat-table/es6/ 可查看兼容性
02 ECMASript 6 新特性
2.1 let 关键字
let a; // 单个声明
let b,c,d; // 批量声明
let e = 100; // 单个声明并赋值
let f = 521, g = 'iloveyou', h = [] // 批量声明并赋值
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
-
不允许重复声明
-
块级作用域
-
不存在变量提升
-
不影响作用域链
应用场景:以后声明变量使用 let 就对了
// 1.变量不能重复声明
let a = 12;
let a = 'aaa'; // a已经声明,此处为错误用法
// 2.块级作用域 函数,全局,eval
// if function while for ...
{
let b = 'bbb';
}
console.log(b); // 报错
// 3.不存在变量提升
console.log(c1); // ccc1
console.log(c2); // 报错
var c1 = 'ccc1'; // 存在变量提升
let c2 = 'ccc2'; // 不存在变量提升
// 4.不影响作用域链
{
let d = 'ddd';
function fn() {
console.log(d);
}
fn(); // ddd
}
2.1.1 实例:点击 div 更换背景颜色
<!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>demo01-let</title>
<style>
.item{
height: 50px;
width: 100px;
border: 1px solid black;
margin-bottom: 30px;
}
</style>
</head>
<body>
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<script>
// 当var=3的时候,点击事件开始向外层作用域找,找不到,就是windows.i,此时是3,如果是let i,具有块级作用域,所以每一次触碰事件的i都是不同的
let items = document.getElementsByClassName('item');
for(let i = 0; i < items.length; i++){
items[i].onclick = function () {
items[i].style.background = 'pink'; // 正常
}
}
// for(var i = 0; i < items.length; i++){
// items[i].onclick = function () {
// // this.style.background = 'pink'; // 正常
// items[i].style.background = 'pink'; // error
// }
// }
</script>
</body>
</html>
2.2 const 关键字
常量的含义是指向的对象不能修改,但是可以改变对象内部的属性
const 关键字用来声明常量,const 声明有以下特点:
- 声明必须赋初始值,一经声明,则不允许修改
- 标识符一般为大写(潜规则,不强制要求)
- 不允许重复声明
- 值不允许修改
- 当我们修饰的标识符不会被再次赋值时,就可以使用 const 来保证数据的安全性
- 对数组元素的修改和对对象内部的修改是可以的(数组和对象存的是引用地址)
- 块级作用域
应用场景:声明对象类型使用 const,非对象类型声明选择 let
// 1.声明必须赋初始值
const fruit; // 未赋值,报错
const fruit = "apple";
console.log(fruit); // "apple"
// 2.标识符一般为大写
const FRUIT = "apple";
console.log(FRUIT); // "apple"
// 3.不允许重复声明
const FRUIT = "apple";
const FRUIT = "apple"; // 报错,不可重复声明
// 4.值不允许修改
// 当我们修饰的标识符不会被再次赋值时,就可以使用const来保证数据的安全性
const FRUIT = "apple";
FRUIT = "banana"; // 报错
// 对数组元素的修改和对对象内部的修改是可以的(数组和对象存的是引用地址)
const FRUIT = ['apple', 'banana', 'peach', 'orange'];
FRUIT.push('watermalen'); //不报错,常量地址没有发生变化
// 5.块级作用域(局部)
{
const FRUIT = "apple";
console.log(FRUIT); // "apple"
}
console.log(FRUIT); // 错误,FRUIT未定义
2.3 变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值
应用场景:频繁使用对象方法、数组元素,就可以使用解构赋值形式
- 数组解构
- 对象解构
- 在
let {name,age,gender,speak} = ZHANGSAN;
的 { } 中,里面的变量名需要和对象中的属性名相同 - 可以通过
let {speak} = ZHANGSAN;
只获取里面的 speak 方法,之后也是通过speak() 调用
- 在
// 1.数组解构
const BOOKS = ['三体','海子的诗','西游记'];
let [san,hai,xi] = BOOKS;
console.log(san); // '三体'
console.log(hai); // '孩子的诗'
console.log(xi); // '西游记'
// 2.对象解构
const ZHANGSAN = {
name : '张三',
age : 23 ,
gender : '男',
speak : function() {
console.log("Hello,I'm ZhangSan!")
}
}
let {name,age,gender,speak} = ZHANGSAN;
console.log(name); // '张三'
console.log(age); // 23
console.log(gender); // '男'
console.log(speak); // function(){...}
speak(); // "Hello,I'm ZhangSan!"
2.4 模板字符串
模板字符串(template string)是增强版的字符串,用反引号 `` 标识,特点:
- 字符串中可以出现换行符
- 变量拼接(替换 / 插入),可以使用 ${xxx} 形式输出变量
注意:当遇到字符串与变量拼接的情况使用模板字符串
// 1.里面可以直接使用换行
let str = `<ul>
<li>第一行</li>
<li>第二行</li>
</ul>`;
let hello = `噢早上好呀,
海绵宝宝~`;
// 2.变量拼接(替换/插入)
let name = '海绵宝宝';
let hi = `早上好呀${name},好久不见`;
console.log(hello); // 早上好呀海绵宝宝,好久不见
2.5 简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁
注意:对象简写形式简化了代码,所以以后用简写就对了
let name = '海绵宝宝'
let sayHello = function() {
console.log('早上好呀,珊迪');
}
// 原来写法
const hello = {
name : name,
sayHello : sayHello,
sayBye : function() { console.log('再见,珊迪'); }
}
// ES6写法
const hello = {
name,
sayHello,
sayBye() { console.log('再见,珊迪'); }
}
2.6 箭头函数
ES6 允许使用箭头 => 定义函数
函数声明
// let 函数名 = (参数) => {
// 执行体
// }
let fn = (a,b) => {
return a + b;
}
console.log(fn(2,3)); // 5
箭头函数的注意点:
- this 是静态的,this 始终指向函数声明时所在作用域下的 this 的值
- 箭头函数不能作为构造函数实例化对象
- 不能使用 arguments
- 箭头函数简写
- 如果形参只有一个,则小括号可以省略
- 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
注意:箭头函数不会更改 this 指向,用来指定回调函数会非常合适
箭头函数适合与 this 无关的回调 ==> 定时器、数组…
箭头函数不适合与 this 有关的回调 ==> 事件回调、对象的方法…
// 1.this 是静态的,this 始终指向函数声明时所在作用域下的 this 的值
function getName1(){
console.log(this.name);
}
let getName2 = () => {
console.log(this.name);
}
window.name = '蛙哈哈';
const school = {
name: 'wahaha'
}
// 直接调用
getName1(); // 蛙哈哈
getName2(); // 蛙哈哈
// call
getName1.call(school); // wahaha
getName2.call(school); // 蛙哈哈
// 2.箭头函数不能作为构造函数实例化对象
let Person = (name,age) => {
this.name = name;
this.age = age;
}
let zhangsan = new Person('张三',20); // 报错
// 3.不能使用 arguments
let fn = () => {
console.log(arguments); // 错误
}
fn(1,2,3);
// 4.箭头函数简写
let add = (n) => {
console.log(n + n);
}
add(3); // 6
// 如果形参只有一个,则小括号可以省略
let add = n => {
console.log(n + n);
}
add(3); // 6
// 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
let pow = n => n * n;
console.log(pow(8)); // 64
// 箭头函数适合与 this 无关的回调 ==> 定时器、数组...
// 箭头函数不适合与 this 有关的回调 ==> 事件回调、对象的方法...
{
name: 'lemon',
getName:() => {
this.name; // 无法正确调用
}
}
2.6.1 实例1:点击 div,1s 之后变成粉色
<!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>demo02-箭头函数</title>
<style>
#a{
width: 200px;
height: 200px;
background: #58a;
}
</style>
</head>
<body>
<div id="a"></div>
<script>
// 1.点击div,1s之后变成粉色
let a = document.getElementById('a');
a.addEventListener('click', function () {
let _this = this;
// setTimeout(function () {
// console.log(this);
// this.style.background = 'pink'; // 报错
// }, 1000);
// 普通写法
// setTimeout(function () {
// _this.style.background = 'pink';
// }, 1000);
// 箭头函数写法
setTimeout(() => {
console.log(this);
this.style.background = 'pink';
}, 1000);
});
</script>
</body>
</html>
2.6.2 实例2:从数组中返回偶数元素
<!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>demo02-箭头函数</title>
<style>
#a{
width: 200px;
height: 200px;
background: #58a;
}
</style>
</head>
<body>
<div id="a"></div>
<script>
// 2.从数组中返回偶数元素
const arr = [1,6,9,10,110,281,98];
// const result = arr.filter(function(item){
// if(item % 2 === 0){
// return true;
// }else{
// return false;
// }
// });
const result = arr.filter(item => item %2 ===0);
console.log(result);
</script>
</body>
</html>
2.7 参数
2.7.1 函数参数默认值
ES6 允许给函数参数赋值初始值
- 可以给形参赋初始值,一般位置要靠后(潜规则)
- 与解构赋值结合
// 1.可以给形参赋初始值,一般位置要靠后,潜规则
// 如下代码没有给形参c赋初始值,则执行add(1,2)时,形参c没有对应的参数,默认为NaN,所以add(1,2)的执行结果为NaN
function add(a,b,c=12){
return a+b+c;
}
let result = add (1,2);
console.log(result); // 15
// 2.与解构赋值结合
function ap({host='127.0.0.1', username, password, port}){
console.log(host,username,password,port);
}
ap({
host: 'localhost',
username:'admin',
password:'000000',
port:3000
})
// 执行结果:localhost admin 000000 3000
2.7.2 rest 参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
rest参数:以 ...
为前缀,例如下面的 ...args
注意:rest 参数非常适合不定个数参数函数的场景
function date(...args){
console.log(args);
}
date(1,2,3,4,5,6,7);
// 执行结果:[1, 2, 3, 4, 5, 6, 7]
function date(a,b,c,...args){
console.log(args);
}
date(1,2,3,4,5,6,7);
// 执行结果:[4, 5, 6, 7]
2.8 spread 扩展运算符
扩展运算符(spread)也是三个点 ...
,它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包
- 扩展运算符
...
,能将数组转换为逗号分隔的参数序列
应用场景:
- 数组的合并
- 数组的克隆:如果数组里面有引用类型的数据,则整个为浅拷贝 ;否则,就是完全拷贝
- 将伪数组转为真正的数组
// 扩展运算符...能将数组转换为逗号分隔的参数序列
const tfboys=['易烊千玺','王源','王俊凯']
function show(){
console.log(arguments)
}
show(tfboys) // 一个参数,数组:['易烊千玺', '王源', '王俊凯']
show(...tfboys) //0: "易烊千玺" 1: "王源" 2: "王俊凯"
// 1.数组的合并
const arr1 = ['aa','bb'];
const arr2 = ['cc','dd'];
// const arr = arr1.concat(arr2); // ['aa', 'bb', 'cc', 'dd']
const arr = [...arr1, ...arr2];
console.log(arr); // ['aa', 'bb', 'cc', 'dd']
// 2.数组的克隆
const arr1 = ['a','b','c'];
const arr2 = [...arr1];
console.log(arr2); // ['a', 'b', 'c']
// 3.将伪数组转换为真正的数组
const divs = documents.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr); // [div,div,div]
2.9 Symbol
https://es6.ruanyifeng.com/#docs/symbol
2.9.1 Symbol 基本使用
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型
Symbol 特点:
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算,加减乘除拼接比较等都不能
- Symbol 定义的对象属性不能使用 for…in 循环遍历
- 可以用
Reflect.ownKeys
来获取对象的所有键名,包括常规键名和 Symbol 键名 Object.getOwnPropertySymbols()
可以获取 Symbol 键名
- 可以用
应用场景:遇到唯一性的场景时要想到 Symbol,e.g 给对象添加方法
// 不能与其他数据进行运算
let result = s + 100; //error
let result = s > 100; //error
let result = s + s; //error
// 给对象添加方法
// 方式一
let game1 = {
name: '超级玛丽',
hhh(){}
}
let methods = {
up: Symbol(),
down: Symbol()
}
game1[methods.up] = function () {
console.log('up');
}
game1[methods.down] = function () {
console.log('down');
}
console.log(game1); // {name: '超级玛丽', hhh: ƒ, Symbol(): ƒ, Symbol(): ƒ}
// 方式二
let game2 = {
name: '狼人杀',
hhh(){},
[Symbol('say')](){
console.log('say');
},
[Symbol('close')]: function () {
console.log('close');
}
}
console.log(game2); // {name: '狼人杀', hhh: ƒ, Symbol(say): ƒ, Symbol(close): ƒ}
2.9.2 Symbol() 与 Symbol.for() 区别
Symbol.for()
与 Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的 key
是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用 Symbol("cat")
30 次,会返回 30 个不同的 Symbol 值
// Symbol() 与 Symbol.for() 区别
let s1 = Symbol('aa');
let s2 = Symbol('aa');
console.log(s1 === s2); // false
let s3 = Symbol.for('bb');
let s4 = Symbol.for('bb');
console.log(s3 === s4); // true
2.9.3 Symbol创建对象属性及调用方法
三种创建对象属性:
- 向对象中添加方法
game[methods.up] = fn
- 配置对象时直接加入
[Symbol('say')]
方法 - 外部创建
let up1 = Symbol();
,配置对象时直接加入[up1]
方法
三种调用方法:
obj[Reflect.ownKeys(obj)[index]]()
Object.getOwnPropertySymbols(obj)[index]();
obj[方法名]()
// 1.obj[Reflect.ownKeys(obj)[index]]()
// 向对象中添加方法 up down
let game = {
name: '超级玛丽',
up(){
console.log('向上');
},
down() {
console.log('向下');
}
}
// 声明一个对象
let methods = {
up: Symbol(),
down: Symbol()
}
game[methods.up] = function () {
console.log('up');
}
game[methods.down] = function () {
console.log('down');
}
game.up; // 向上
game.down; // 向下
// 调用Symbol创建的函数
game[Reflect.ownKeys(game)[3]]();
game[Reflect.ownKeys(game)[4]]();
// 2.Object.getOwnPropertySymbols(obj)[index]();
// 直接加入方法
let game = {
name: '狼人杀',
[Symbol('say')](){
console.log('say');
},
[Symbol('close')]: function () {
console.log('close');
}
}
console.log(game); // {name: '狼人杀', Symbol(say): ƒ, Symbol(close): ƒ}
console.log(game['name']); // 狼人杀
// 调用Symbol创建的函数
const a = Object.getOwnPropertySymbols(game);
game[a[0]](); // say
game[a[1]](); // close
// 3.obj[方法名]()
let up = Symbol();
let down = Symbol();
let game = {
name: '超级玛丽',
up() {
console.log('up1');
},
down() {
console.log('down1');
},
[up]() {
console.log('up2');
},
[down]() {
console.log('down2');
}
}
console.log(game); // {name: '超级玛丽', up: ƒ, down: ƒ, Symbol(): ƒ, Symbol(): ƒ}
game.up(); // up1
game.down(); // down1
// 调用Symbol创建的函数
game[up](); // up2
game[down](); //down2
2.9.4 Symbol 内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行
属性 | 描述 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对 象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可以展开 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会 调用它,返回该方法的返回值 |
Symbol.replace | 当该对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值 |
Symbol.search | 当该对象被 str. search (myObject) 方法调用时,会返回该方法的返回值 |
Symbol.split | 当该对象被 str. split (myObject) 方法调用时,会返回该方法的返回值 |
Symbol.iterator | 对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法返回该对象对应的原始类型值 |
Symbol. toStringTag | 在该对象上面调用 toString 方法时,返回该方法的返回值 |
Symbol. unscopables | 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除 |
2.10 迭代器
Symbol.iterator
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费 - 原生具备 iterator 接口的数据(可用
for...of
遍历)- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
- 工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的
next()
方法,指针自动指向数据结构的第一个成员 - 接下来不断调用
next()
方法,指针一直往后移动,直到指向最后一个成员 - 每调用
next()
方法返回一个包含 value 和 done 属性的对象 - done 的值为 true 的时候表示循环完成了
注:需要自定义遍历数据的时候,要想到迭代器
// 自定义遍历数据
const letter = {
name: '字母',
stus: [
'aa',
'bb',
'cc',
'dd'
],
[Symbol.iterator](){
// 索引变量
let index = 0;
let _this = this;
return{
next: function(){
if(index < _this.stus.length){
const result = { value: _this.stus[index], done: false};
index++;
return result;
}else{
return {value: undefined, done: true};
}
}
}
}
}
for(let v of letter){
console.log(v);
}
// 执行结果 aa bb cc dd
// 查看iterator.next()返回结果
const array = ['AA','BB','CC','DD'];
let iterator = array[Symbol.iterator]();
console.log(iterator.next()); // {{value:'AA',done:false}}
console.log(iterator.next()); // {{value:'BB',done:false}}
console.log(iterator.next()); // {{value:'CC',done:false}}
console.log(iterator.next()); // {{value:'DD',done:false}}
console.log(iterator.next()); // {{value:undefined,done:true}}
2.10.1 for ... of
和 for ... in
区别
// for ... of 和 for ... in 区别
let arr = ['a','b','c','d']
for(let n of arr) {
console.log(n);
}
// 执行结果 a b c d
for(let n in arr) {
console.log(n);
}
// 执行结果 0 1 2 3
2.11 生成器
生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
代码说明:
*
的位置没有限制- 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
- yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次
next()
方法,执行一段代码 next()
方法可以传递实参,作为 yield 语句的返回值
// 使用方法:函数名和function中间有一个 *
function * generator (){
console.log('111');
yield '耳朵';
console.log('222');
yield '尾巴';
console.log('333');
yield '真奇怪';
}
let iterator = generator()
// 执行第一段,并且返回yield后面的值
console.log(iterator.next()); // 111 {value:'耳朵',done:false}
console.log(iterator.next()); // 222 {value:'尾巴',done:false}
console.log(iterator.next()); // 333 {value:'真奇怪',done:false}
// 生成器函数的参数传递
function * gen(args){
console.log(args);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
let iterator = gen('AAA');
console.log(iterator.next());
console.log(iterator.next('BBB')); //next中传入的BBB将作为yield 111的返回结果
console.log(iterator.next('CCC')); //next中传入的CCC将作为yield 222的返回结果
console.log(iterator.next('DDD')); //next中传入的DDD将作为yield 333的返回结果
// 执行结果
// AAA {value: 111, done: false}
// BBB {value: 222, done: false}
// CCC {value: 333, done: false}
// DDD {value: undefined, done: true}
2.11.1 回调地狱
异步编程 ==> 文件操作、网络操作(ajax、request)、数据库操作
<!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>demo03-生成器</title>
</head>
<body>
<script>
// 回调地狱
setTimeout(() => {
console.log(111);
setTimeout(() => {
console.log(222);
setTimeout(() => {
console.log(333)
}, 3000);
}, 2000);
}, 1000);
</script>
</body>
</html>
2.11.2 实例1:用生成器函数的方式解决回调地狱问题
<!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>demo03-生成器</title>
</head>
<body>
<script>
// 实例1:用生成器函数的方式解决回调地狱问题
function one(){
setTimeout(()=>{
console.log('111');
iterator.next();
},1000)
}
function two(){
setTimeout(()=>{
console.log('222');
iterator.next();
},2000)
}
function three(){
setTimeout(()=>{
console.log('333');
iterator.next();
},3000)
}
function * gen(){
yield one();
yield two();
yield three();
}
let iterator = gen();
iterator.next();
</script>
</body>
</html>
2.11.3 实例2:模拟异步获取数据
<!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>demo03-生成器</title>
</head>
<body>
<script>
// 实例2:模拟异步获取数据
function one(){
setTimeout(()=>{
let data='用户数据';
iterator.next(data);
},1000)
}
function two(){
setTimeout(()=>{
let data='订单数据';
iterator.next(data);
},2000)
}
function three(){
setTimeout(()=>{
let data='商品数据';
iterator.next(data);
},3000)
}
function * gen(){
let users = yield one();
console.log(users);
let orders = yield two();
console.log(orders);
let goods = yield three();
console.log(goods);
}
let iterator = gen();
iterator.next();
</script>
</body>
</html>
2.12 Promise
Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果
Promise 基本语法:
const p = new Promise(function(resolve, reject){
// let data = '数据读取成功';
let data;
setTimeout(() => {
if(data){
resolve(data);
}else{
let err = '数据读取失败';
reject(err);
}
}, 1000);
});
// 调用promise对象的then方法
p.then(function(value){ // 成功则执行第一个回调函数
console.log(value);
}, function(reason){ // 失败则执行第二个
console.error(reason);
})
- Promise 构造函数:
new Promise(function(resolve, reject){ ... }
Promise.prototype.then()
方法then()
函数返回的实际也是一个 Promise 对象- 当回调后,返回的是非 Promise 类型的属性时,状态为 fulfilled,
then()
函数的返回值为对象的成功值,如 return 123,返回的 Promise 对象值为 123,如果没有返回值,是 undefined - 当回调后,返回的是 Promise 类型的对象时,
then()
函数的返回值为这个Promise 对象的状态值 - 当回调后,如果抛出的异常,则
then()
函数的返回值状态也是 rejected
Promise.prototype.catch()
方法then()
的语法糖,是.then(null, rejection)
的别名,⽤于指定发⽣错误时的回调函数
// then() 方法
//创建 promise 对象
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
// resolve('用户数据');
reject('出错啦');
}, 1000)
});
const result = p.then(value => {
console.log(value); // 用户数据
// 返回值
// 1. 是非 Promise 类型的属性
return 'you'; // 控制台输出结果:图1-2
return; // 控制台输出结果:图1-3
// 2. 是 Promise 类型的对象
return new Promise((resolve, reject)=>{
resolve('ok'); // 控制台输出结果:图1-4
reject('error'); // 控制台输出结果:图1-5
});
// 3. 抛出错误
throw new Error('出错啦!'); // 控制台输出结果:图1-6
throw '出错啦!'; // 控制台输出结果:图1-7
}, reason=>{
console.warn(reason); // // 控制台输出结果:图1-8
});
console.log(result); // 输出为Promise对象
// 链式调用,解决回调地狱
p.then(value=>{
}, reason => {
}).then(value=>{
}, reason => {
}).then(value=>{
});
// catch() 方法
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
//设置 p 对象的状态为失败, 并设置失败的值
reject("出错啦!");
}, 1000)
});
// p.then(function(value){}, function(reason){
// console.error(reason);
// });
p.catch(function(reason){
console.warn(reason);
});
2.12.1 实例1:Promise 封装读取文件
通过以下代码执行
node demo04.js
// 引入 fs 模块
const fs = require('fs');
// 调用方法读取文件
// fs.readFile('./resources/为学.md', (err, data)=>{
// // 如果失败, 则抛出错误
// if(err) throw err;
// // 如果没有出错, 则输出内容
// console.log(data.toString());
// });
// 使用 Promise 封装
const p = new Promise(function(resolve, reject){
fs.readFile("./resources/为学.md", (err, data)=>{
//判断如果失败
if(err) reject(err);
//如果成功
resolve(data);
});
});
p.then(function(value){
console.log(value.toString());
}, function(reason){
console.log("读取失败!!");
});
2.12.2 实例2:Promise 发送 AJAX 请求
需要开启服务器
demo04.html 文件
<!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>demo04-Promise</title>
</head>
<body>
<script>
// 发送Ajax请求
// 接口地址: http://localhost/demo/getNews.php
const p = new Promise((resolve, reject) => {
//1. 创建对象
const xhr = new XMLHttpRequest();
//2. 初始化
xhr.open("GET", "http://localhost/demo/data.php");
//3. 发送
xhr.send();
//4. 绑定事件, 处理响应结果
xhr.onreadystatechange = function () {
//判断
if (xhr.readyState === 4) {
//判断响应状态码 200-299
if (xhr.status >= 200 && xhr.status < 300) {
//表示成功
resolve(xhr.response);
} else {
//如果失败
reject(xhr.status);
}
}
}
})
//指定回调
p.then(function(value){
console.log(value);
}, function(reason){
console.error(reason);
});
</script>
</body>
</html>
data.php 文件
{"name":"amiee","age":18}
2.12.3 实例3:读取多个文件
通过以下代码执行
node demo04.js
// 引入 fs 模块
const fs = require("fs");
// // 原始方法 回调地狱
// fs.readFile('./resources/为学.md', (err, data1) => {
// fs.readFile('./resources/插秧诗.md', (err, data2) => {
// fs.readFile('./resources/观书有感.md', (err, data3) => {
// let result = data1 + '\r\n' + data2 + '\r\n' + data3;
// console.log(result);
// })
// })
// })
// 使用promise实现连续调用
new Promise((resolve, reject) => {
fs.readFile('./resources/为学.md', (err, data) => {
resolve(data);
})
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./resources/插秧诗.md', (err, data) => {
resolve([value, data]);
})
})
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./resources/观书有感.md', (err, data) => {
value.push(data);
resolve(value);
})
})
}).then(value => {
console.log(value.join('\r\n'));
})
2.13 Set
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of』进行遍历
集合的属性和方法:
- size:返回集合的元素个数
- add():增加一个新元素,返回当前集合
- delete():删除元素,返回 boolean 值
- has():检测集合中是否包含某个元素,返回 boolean 值
- clear():清空集合,返回 undefined
// 声明一个set
let s1 = new Set();
let s2 = new Set(['aaa','bbb','ccc','ddd']);
// 1.查看元素个数
console.log(s2.size); // 4
// 2.添加新的元素
s2.add('eee');
console.log(s2); // Set(5) {'aaa', 'bbb', 'ccc', 'ddd', 'eee'}
// 3.删除元素
s2.delete('aaa');
console.log(s2); // Set(3) {'bbb', 'ccc', 'ddd'}
// 4.是否含有某得元素
console.log(s2.has('ccc')); // true
// 5.清空
s2.clear();
console.log(s2); // Set(0) {size: 0}
// 6.遍历 s2
for(let v of s2){
console.log(v); // aaa bbb ccc ddd
}
2.13.1 实例1:数组去重
<!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>demo05-set</title>
</head>
<body>
<script>
let arr1 = [1,2,4,3,5,4,3,5,3,2,1];
// 数组去重
let result = [...new Set(arr1)];
console.log(result); // (5) [1, 2, 4, 3, 5]
</script>
</body>
</html>
2.13.2 实例2:交集
<!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>demo05-set</title>
</head>
<body>
<script>
let arr1 = [1,2,4,3,5,4,3,5,3,2,1];
let arr2 = [4,6,7,5,6];
// 交集
let result = [...new Set(arr1)].filter(item => new Set(arr2).has(item));
console.log(result); // (2) [4, 5]
</script>
</body>
</html>
2.13.3 实例3:并集
<!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>demo05-set</title>
</head>
<body>
<script>
let arr1 = [1,2,4,3,5,4,3,5,3,2,1];
let arr2 = [4,6,7,5,6];
// 并集
let result = [...new Set([...arr1,...arr2])];
console.log(result); // (7) [1, 2, 4, 3, 5, 6, 7]
</script>
</body>
</html>
2.13.4 实例4:差集
<!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>demo05-set</title>
</head>
<body>
<script>
let arr1 = [1,2,4,3,5,4,3,5,3,2,1];
let arr2 = [4,6,7,5,6];
// 差集
let result = [...new Set(arr1)].filter(item => !(new Set(arr2).has(item)));
console.log(result); // (3) [1, 2, 3]
</script>
</body>
</html>
2.14 Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是”键“的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历
Map 的属性和方法:
- set():增加一个新元素,返回当前 Map
- get():返回键名对象的键值
- size():返回 Map 的元素个数
- has():检测 Map 中是否包含某个元素,返回 boolean 值
- delete():删除指定元素
- clear():清空集合,返回 undefined
// 声明一个Map
let m = new Map();
// 1.添加元素
m.set('name', 'apple');
m.set('change', function(){
console.log('change');
});
let key = {
school: 'lemon'
};
m.set(key, ['aaa','bbb','ccc']);
console.log(m); // Map(3) {'name' => 'apple', 'change' => ƒ, {…} => Array(3)}
// 2.获取元素
console.log(m.get('name')); // apple
console.log(m.get('change')); // ƒ (){ console.log('change'); }
console.log(m.get(key)); // (3) ['aaa', 'bbb', 'ccc']
// 3.查看元素个数
console.log(m.size); // 3
// 4.是否含有某个元素
console.log(m.has(key)); //true
// 5.删除元素
m.delete('name');
console.log(m); // Map(2) {'change' => ƒ, {…} => Array(3)}
// 6.清空元素
m.clear();
console.log(m); // Map(0) {size: 0}
// 7.遍历元素
for(let v of m){
console.log(v); // (2) ['name', 'apple'] (2) ['change', ƒ] (2) [{…}, Array(3)]
}
2.15 class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
知识点:
- class:声明类
- constructor:定义构造函数初始化
- extends:继承父类
- super:调用父级构造方法
- static:定义静态属性和方法,属于类而不属于实例对象
- 父类方法可以重写
// 1.声明类
// ES5
function Phone1(brand, price){
this.brand = brand;
this.price = price;
}
// 添加方法 --> 加在了Object上,其他类的实例对象也能用到这个方法
Phone1.prototype.color = function(){
console.log('silver');
}
// 实例化对象
let Huawei1 = new Phone1('华为', 2999);
Huawei1.color(); // silver
console.log(Huawei1); // Phone1 {brand: '华为', price: 2999}
// ES6
class Phone2{
// 构造方法 ==> 添加属性
constructor(brand, price){
this.brand = brand;
this.price = price;
}
// 添加方法方法必须使用 方法名(){} 的形式,不能使用ES的对象完整形式
color(){
console.log('gold');
}
}
// 实例化对象
let Huawei2 = new Phone2('1+', 1999);
console.log(Huawei2); // Phone2 {brand: '1+', price: 1999}
// 2.静态成员 -- 属于类而不属于实例对象
// ES5
function Phone1() {
}
Phone1.color = 'lemon';
Phone1.change = function(){
console.log('change');
}
Phone1.prototype.size = '5.5inch';
let nokia1 = new Phone1();
console.log(nokia1.color); // undefined
console.log(nokia1.size); // 5.5inch
nokia1.change(); // Uncaught TypeError: nokia.change is not a function
console.log(Phone1.color); // lemon
// ES6
class Phone2{
// 静态属性和方法
static name = 'lemon';
static change(){
console.log('change');
}
}
let nokia2 = new Phone2();
console.log(nokia2.name); // undefined
nokia2.change(); // Uncaught TypeError: nokia.change is not a function
console.log(Phone2.name); // lemon
// 3.对象继承
// ES5
function Phone1(brand, price){
this.brand = brand;
this.price = price;
}
Phone1.prototype.color = function(){
console.log('silver');
}
function SmartPhone1(brand, price, size){
Phone1.call(this, brand, price);
this.size = size;
}
// 设置子级构造函数原型
SmartPhone1.prototype = new Phone1;
SmartPhone1.prototype.constructor = SmartPhone1;
// 声明子类方法
SmartPhone1.prototype.photo = function(){
console.log('photo');
}
const chuizi1 = new SmartPhone1('chuizi', 2999, '5.5inch');
console.log(chuizi1); // SmartPhone1 {brand: 'chuizi', price: 2999, size: '5.5inch'}
// ES6
class Phone2{
constructor(brand, price){
this.brand = brand;
this.price = price;
}
color(){
console.log('gold');
}
}
class SmartPhone2 extends Phone2{
constructor(brand, price, size){
super(brand, price); // 相当于 Phone1.call(this, brand, price);
this.size = size;
}
playGame(){
console.log('playGame');
}
}
let onePlus = new SmartPhone2('1+', 1999, '4.0inch');
console.log(onePlus); // SmartPhone2 {brand: '1+', price: 1999, size: '4.0inch'}
// 4.get和set
class Phone{
get price(){
console.log('价格被读取了');
return 'get';
}
set price(newVal){
console.log('价格被改成了' + newVal);
}
}
let change = new Phone();
console.log(change.price); // 价格被读取了 get
change.price = 'free'; // 价格被改成了free
console.log(change.price); // 价格被读取了 get
2.16 数值扩展
2.16.1 二进制和八进制
ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b 和 0o 表示
2.16.2 Number.isFinite() 与 Number.isNaN()
- Number.isFinite():用来检查一个数值是否为有限的
- Number.isNaN():用来检查一个值是否为 NaN
2.16.3 Number.parseInt() 与 Number.parseFloat()
ES6 将全局方法 parseInt 和 parseFloat,移植到 Number 对象上面,使用不变
2.16.4 Math.trunc()
用于去除一个数的小数部分,返回整数部分
2.16.5 Number.isInteger()
Number.isInteger() 用来判断一个数值是否为整数
// 1.Number.EPSILON是 JavaScript的最小精度,属性的值接近于 2.22044...E-16
function equal(a,b){
if(Math.abs(a-b) < Number.EPSILON){
return true;
}else {
return false;
}
}
console.log(equal(0.1 + 0.2 === 0.3)); //false
console.log(equal(0.1+0.2,0.3)); //true
// 2.二进制和八进制
let b = 0b1010; // 2进制
let o = 0o777; // 8进制
let d = 100; // 10进制
let x = 0xff; // 16进制
console.log(x); // 255
// 3.检测一个数是否为有限数
console.log(Number.isFinite(100)); // true
console.log(Number.isFinite(100/0)); // false
console.log(Number.isFinite(Infinity)); // false
// 4.检测一个数值是否为NaN
console.log(Number.isNaN(123)); // false
// 5.字符串转整数
console.log(Number.parseInt('5213123love')); // 5213123
console.log(Number.parseFloat('5.123123神器')); // 5.123123
// 6.判断是否为整数
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(2.5)); // false
// 7.将小数部分抹除
console.log(Math.trunc(3.45345345345)); // 3
// 8.检测一个数到底是正数、负数、还是0
console.log(Math.sign(100)); // 1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-123)); // -1
2.17 对象扩展
ES6 新增了一些 Object 对象的方法
-
Object.is():比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
-
Object.assign():对象的合并,将源对象的所有可枚举属性,复制到目标对象
-
proto、setPrototypeOf、setPrototypeOf:可以直接设置对象的原型
// 1.Object.is 判断两个值是否完全相等
console.log(Object.is(120,120)); // true
console.log(Object.is(NaN,NaN)); // true 与===不同
console.log(NaN===NaN); // false
// 2.Object.assign 对象的合并
const a = {
name: 'ran',
age: 12
}
const b = {
name: 'lemon',
pass: 'i love you'
}
console.log(Object.assign(a,b)) // {name: 'lemon', age: 12, pass: 'i love you'}
// 3.Object.setPrototypeOf 设置原型对象 Object.getPrototypeof
const school = {
name:'哆啦A梦'
}
const cities = {
xiaoqu:['北京','上海']
}
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school)); // {xiaoqu: Array(2)}
console.log(school); // {name: "哆啦A梦"}
2.18 模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
2.18.1 模块化的好处
模块化的优势有以下几点:
- 防止命名冲突
- 代码复用
- 高维护性
2.18.2 模块化规范产品
ES6 之前的模块化规范有:
- CommonJS ==> NodeJS、Browserify
- 针对浏览器端:
- AMD ==> requireJS
- CMD ==> seaJS
2.18.3 ES6 模块化语法
模块功能主要由两个命令构成:export 和 import
- export:命令用于规定模块的对外接口
- import:命令用于输入其他模块提供的功能
export 暴露方式
- 分别暴露
- 统一暴露
- 默认暴露
// 1.分别暴露 m1.js
export let school = 'lemon';
export function teach() {
console.log("我们可以教给你开发技能");
}
// 2.统一暴露 m2.js
let school = 'lemon';
function findJob(){
console.log("我们可以帮助你找工作!!");
}
export {school, findJob};
// 3.默认暴露 m3.js
export default {
school: 'lemon',
change: function(){
console.log("我们可以改变你!!");
}
}
import 引入方式
- 通用的导入方式
- 解构赋值形式
- 简便形式,针对默认暴露
// 1.通用的导入方式
// 引入 m1.js 模块内容
import * as m1 from "./src/js/m1.js";
//引入 m2.js 模块内容
import * as m2 from "./src/js/m2.js";
//引入 m3.js
import * as m3 from "./src/js/m3.js";
// 2.解构赋值形式
import {school, teach} from "./src/js/m1.js";
import {school as guigu, findJob} from "./src/js/m2.js";
import {default as m3} from "./src/js/m3.js";
// 3.简便形式 针对默认暴露
import m3 from "./src/js/m3.js";
console.log(m3);
2.18.4 模块化 —— 使用入口文件
安装工具:babel-cli babel-preset-env broserify/webpack jquery
npm i babel-cli babel-preset-env broserify jquery -D
对 src 路径下 js 文件语法转换,并设置保存位置,-d 表示保存到
npx babel src/js -d dist/js --presets-babel-preset-env
打包,并设置位置,-o 表示保存到
npx broserify dist/app.js -o dist/bundle.js
index.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES6 模块化</title>
</head>
<body>
<script src="./src/js/app.js" type="module"></script>
</body>
</html>
app.js 文件
//入口文件
//模块引入
import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";
//修改背景颜色为粉色
import $ from 'jquery';// const $ = require("jquery");
$('body').css('background','pink');
m1.js 文件
//分别暴露
export let school = 'lemon';
export function teach() {
console.log("我们可以教给你开发技能");
}
m2.js 文件
//统一暴露
let school = 'lemon';
function findJob(){
console.log("我们可以帮助你找工作!!");
}
export {school, findJob};
m3.js 文件
//默认暴露
export default {
school: 'lemon',
change: function(){
console.log("我们可以改变你!!");
}
}