一.let 和 const 命令
- let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。
- const 声明的就是常量,保证
指针是固定
的;
- 1.使用 const 声明常量,一旦声明,就必须
立即初始化
,不能留到以后赋值
- 2.const 声明的常量,允许在不重新赋值的情况下修改它的值,所以基本数据类型是不能修改,但是引用数据类型是可以修改的。
- 3.不知道用 let 和 const 优先用 const。
- 详细解读const 其实保证的不是变量的值不变,而是保证变量指向的
内存地址
所保存的数据不允许改动,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。
const person = { username: 'Alex' }; // person = {};报错 person.username = 'ZhangSan';//不报错 console.log(person); 复制代码
let、const 与 var 的区别:
1.重复声明
- var 允许重复声明,let、const 不允许
2.变量提升
- var 会提升变量的声明到当前作用域的顶部
- let、const 不存在变量提升
3.暂时性死区
- 只要作用域内存在 let、const,它们所声明的变量或常量就自动“绑定”这个区域,不再受到外部作用域的影响
- let、const 存在暂时性死区
var name='jack'; { name='bob'; let name; //Uncaught ReferenceError: Cannot access 'name' before initialization } 复制代码
- ES6 中,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。因为JS清楚地感知到了 name 是用 let 声明在当前这个代码块内的,所以会给这个变量 name 加上了暂时性死区的限制,它就不往外探出头了。
- 那么,如果我们把上面的let name;去掉,程序也将正常运行, name 的值也被成功修改为了bob,就是正常地按照作用域链的规则,向外探出头去了。
4.window 对象的属性和方法:
全局作用域中,var 声明的变量,以及通过 function 声明的函数,会自动变成 window 对象的属性或方法,let、const 不会。
5.块级作用域
var 没有块级作用域,let/const 有块级作用域。
{} for(){} while(){} do{}while() if(){} switch(){} function(){}//函数只有在执行时才生成函数作用域 const person = { getAge: function () {} };//对象没有块级作用域 复制代码
小题:点击哪个按钮就显示当前索引
方案一:行不通
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>let 和 const 的应用</title>
<style>
body {
padding: 50px 0 0 150px;
}
.btn {
width: 100px;
height: 100px;
margin-right: 20px;
font-size: 80px;
cursor: pointer;
}
</style>
</head>
<body>
<button class="btn">0</button>
<button class="btn">1</button>
<button class="btn">2</button>
<script>
var btns = document.querySelectorAll(".btn");
// 方案一:不行(var)
for (var i = 0; i < btns.length; i++) {
/*
首先: 进来就一轮循环
i=0,按钮0绑定事件
i=1,按钮1绑定事件
i=2,按纽2绑定事件
i=3,不绑定了
所以,i=3了
然后:点击任何一个按钮都会打印3
*/
btns[i].addEventListener(
"click",
function () {
console.log(i); //3
},
false
);
}
</script>
</body>
</html>
复制代码
方案二:可行(立即执行)
var btns = document.querySelectorAll(".btn");
for (var i = 0; i < btns.length; i++) {
(function (index) {
btns[index].addEventListener(
"click",
function () {
console.log(index);
},
false
);
})(i);
}
/*
for循环遍历的过程中,立即执行函数会形成三个作用域,同时会将i=0,1,2分别传入到函数作用域中,
以index=0,1,2的形式存入,当用户点击按钮时,就会打印相应的数字。
*/
复制代码
方案三:可行(let)
let btns = document.querySelectorAll(".btn");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener(
"click",
function () {
console.log(i);
},
false
);
}
/*
1.循环遍历会创建三个块级作用域,
2.点击事件会在当前的块级作用域内创建函数作用域,同时触发函数打印i
*/
复制代码
二.模板字符串
- 1.模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式
- 2.模板字符串中,所有的空格、换行或缩进都会被保留在输出之中
- 3.模板字符串的注入
只要最终可以得出一个值的就可以通过 ${} 注入到模板字符串中
const username = "alex";
const person = { age: 18, sex: "male" };
const getSex = function (sex) {
return sex === "male" ? "男" : "女";
};
const info = `${username}, ${person.age + 2}, ${getSex(person.sex)}`;
console.log(info);
复制代码
三.箭头函数
1.箭头函数的结构
const/let 函数名 = 参数 => 函数体
const add = () => {};
复制代码
2.箭头函数简化
- 单个参数可以省略圆括号
const add = x => {};
- 单行函数体可以省略花括号以及 return
const add = (x, y) => x + y;
- 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
const add = (x, y) => ( {value: x + y} );
复制代码
3.this 指向
1.全局作用域中的 this 指向
console.log(this); // window
2.一般函数(非箭头函数)中的 this 指向
// 'use strict';
function add() {
console.log(this);
// 严格模式就指向 undefined
// undefined->window(非严格模式下)
}
add();
3.点击事件的this指向
document.onclick = function () {
console.log(this); //this指向document
};
// 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
// this 指向和函数在哪儿调用没关系,只和谁在调用有关
// 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window
4.箭头函数中的 this 指向
箭头函数没有自己的 this,所以会一层一层的往外找,最后找到window,他的this指向window
复制代码
4.不适用箭头函数的场景
- 1.箭头函数不能作为构造函数
- 2.需要 this 指向调用对象的时候,不能用箭头函数
document.onclick = function () { console.log(this); }; document.addEventListener( 'click', () => { console.log(this); //window }, false ); 复制代码
- 3.需要使用 arguments 的时候
箭头函数中没有 arguments
5.箭头函数的应用(3-11)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>箭头函数的应用</title>
<style>
body {
padding: 50px 0 0 250px;
font-size: 30px;
}
#btn {
width: 100px;
height: 100px;
margin-right: 20px;
font-size: 30px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="btn">开始</button>
<span id="result">0</span>
<script>
const btn = document.getElementById("btn");
const result = document.getElementById("result");
const timer = {
time: 0,
start: function () {
// this
btn.addEventListener(
"click",
() => {
// this
setInterval(() => {
console.log(this);
this.time++;
result.innerHTML = this.time;
}, 1000);
},
false
);
},
};
timer.start();
</script>
</body>
</html>
复制代码
四.解构赋值
解构赋值是指解析某一数据的结构,将我们想要的东西提取出来,赋值给变量或常量;
4.1 数组解构赋值
数组解构赋值非常简单,遵循模式匹配原则就可以了,如果不需要结构的用逗号跳过即可。
// 1.模式(结构)匹配
[] = [1, 2, 3];
// 2.索引值相同的完成赋值
const [a, b, c] = [1, 2, 3];
console.log(a, b, c);
// 3.不取的,可以直接用逗号跳过
const [a, [, , b], c] = [1, [2, 4, 5], 3];
console.log(a, b, c);
复制代码
4.1.1 默认值:
- 1.只有当一个数组成员严格等于(===)undefined 时,对应的默认值才会生效
const [a = 1, b = 2] = [3, 0];//3 0 const [a = 1, b = 2] = [3, null]; 3 null 复制代码
- 2.如果默认值是表达式,默认值表达式是惰性求值的
const func = () => { console.log("我被执行了"); return 2; }; // const [x = func()] = [1]; // x=1 且不执行函数 const [x = func()] = []; //执行函数体代码 console.log(x); //2 复制代码
4.1.2 应用
- 1.常见的类数组的解构赋值
//arguments
const [a, b] = arguments;
console.log(a, b);
//NodeList
console.log(document.querySelectorAll('p'));
const [p1, p2, p3] = document.querySelectorAll('p');
console.log(p1, p2, p3);
复制代码
- 2.函数参数的解构赋值
let [x, y] = [1, 1];
const add = ([x = 0, y = 0]) => x + y;
console.log(add([x, y]));
复制代码
- 3.交换变量的值
let x = 1;
let y = 2;
/*
之前的做法
let tmp = x;
x = y;
y = tmp;
console.log(x, y);
*/
[x, y] = [y, x];
console.log(x, y);
复制代码
4.2 对象解构赋值
4.2.1 基本结构
1.模式(结构)匹配
{}={}
2.属性名相同的完成赋值
const { age, username } = { username: 'Alex', age: 18 };
const { age: age, username: username } = { username: 'Alex', age: 18 };
3.可以取别名
const { age: age, username: uname } = { username: 'Alex', age: 18 };
console.log(age, uname);//解构赋值之后再赋值给uname
复制代码
4.2.2 默认值
- 默认值(=)的生效条件
对象的属性值严格等于 undefined 时,对应的默认值才会生效const { age = 16, username: uname } = { username: "Alex" }; console.log(age, uname);//16 alex 复制代码
-
2.默认值表达式是惰性求值的
-
3.将一个已经声明的变量用于解构赋值
如果将一个已经声明的变量用于对象的解构赋值,整个赋值需在圆括号中进行,防止被当成块级作用域let x = 2; ({ x } = { x: 1 }); console.log(x); //1 复制代码
4.2.3 对象解构赋值的应用
- 1.函数参数的解构赋值
const PersonInfo = (user) => console.log(user.username, user.age);
PersonInfo(({ age, username: username } = { username: "alex", age: 18 })); //alex 18 使用了解构后的值
const person = ({ age = 0, username = "ZhangSan" }) =>console.log(username, age);
person({}); //ZhangSan 0 使用了默认值
person({ username: "alex", age: 18 }); //alex 18 使用了解构后的值
复制代码
- 2.复杂的嵌套
const obj = {
x: 1,
y: [2, 3, 4],
z: {
a: 5,
b: 6,
},
};
const { x, y, z } = obj;
console.log(x, y, z);// 1 [2,3,4] {a:5,b:6}
复制代码
const obj = {
x: 1,
y: [2, 3, 4],
z: {
a: 5,
b: 6,
},
};
const {
y,
y: [, yy],
z,
z: { b },
} = obj;
console.log(yy, y, z, b); //3 [2,3,4] {a:5,b:6} 6
复制代码
4.3 字符串的解构赋值
// 字符串可以按照数组形式的解构赋值
const [a, b, , , c] = "hello";
console.log(a, b, c); //h e o
// 字符串可以按照对象形式的解构赋值(可以按照索引值和length)
const { 0: d, 1: e, length } = "hello";
console.log(d, e, length); //h e 5
console.log("hello".length); //5
复制代码
五.对象字面量增强
5.1 属性简写
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
键名和变量或常量名一样的时候,可以只写一个
const age = 18; const person = { // age: age日常写法 age //简洁写法 }; console.log(person); 复制代码
方法可以省略冒号和 function 关键字
const person = { // speak: function () {}之前写法 speak() {}//简洁表示法 }; console.log(person); 复制代码
5.2.方括号语法增强
1.方括号语法可以写在对象字面量中
const prop = 'age'; const person = { [prop]: 18 }; 复制代码
2.方括号中可以放什么?
- ${}
- [值或通过计算可以得到值的(表达式)]
3.方括号语法和点语法的区别:
点语法是方括号语法的特殊形式 const person = {}; person.age 等价于 person['age'] 属性名由数字、字母、下划线以及 $ 构成,并且数字还不能打头的时候可以使用点语 合法标识符可以用来作为变量或常量名 当你的属性或方法名是合法标识符时,可以使用点语法,其他情况下请使用方括号语法 复制代码
六.函数参数默认值
- 调用函数的时候传参了,就用传递的参数;如果没传参,或者明确的传递 undefined 作为参数,就用默认值;
- 接收很多参数的时候,接收一个对象作为参数
const logUser = ({ username = 'zhangsan', age = 0, sex = 'male' } = {}) => console.log(username, age, sex); logUser(); 复制代码
七.剩余参数与展开运算符
剩余/扩展运算符同样也是ES6一个非常重要的语法,使用3个点(…),后面跟着一个含有iterator接口的数据结构
7.1 剩余参数
- 剩余参数语法允许将不确定数量的参数表示为数组。
- 剩余参数永远是个数组,即使没有值,也是空数组.
- 剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
- 剩余参数不一定非要作为函数参数使用
const add = (x, y, ...args) => { console.log(x, y, args); }; add(1, 2, 3, 4, 5); //1 2 [3, 4, 5] 复制代码
7.1.1 箭头函数的剩余参数
对于箭头函数来说,没有 arguments,使用剩余参数替代 arguments 获取实际参数,此时不能省略圆括号;
const add = (...args) => { console.log(args); }; add(1, 2); 复制代码
7.1.2 剩余参数的应用
- 1.完成 add 函数
const add = (...args) => {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
};
console.log(add(1, 2, 3));
复制代码
- 2.与解构赋值结合使用
const [num, ...args] = [1, 2, 3, 4]; const func = ({ x, y, ...z }) => {}; func({ a: 3, x: 1, y: 2, b: 4 }); 复制代码
7.2 数组展开运算符
将一个数组转为用逗号分隔的参数序列,该运算符主要用于函数调用。
区分剩余参数和展开运算符根本区别 :
展开运算符把数组`转化为参数` [3,1,2]->3,1,2 剩余参数把剩余`参数转化为数组` 3,1,2->[3,1,2] 复制代码
const arr1 = [4, 5, 6];
const arr = [1, 2, 3, ...arr1, 7, 8, 9];
console.log(arr);//[1, 2, 3, 4, 5, 6, 7, 8, 9]
复制代码
console.log(Math.min(...[3, 1, 2]));
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
复制代码
7.2.1 替代函数的 apply 方法
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
复制代码
7.2.2 数组展开运算符的应用
- 1.复制数组
const a = [1, 2];
const c = [...a];
a[0] = 3;
console.log(a);//[3,2]
console.log(c);//[1,2]
复制代码
- 2.合并数组(浅拷贝)
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
复制代码
- 3.字符串转为数组
console.log([...'alex']);//['a','l','e','x']
console.log('alex'.split(''));//es6之前
复制代码
- 4.常见的类数组转化为数组
4.1 arguments
function func() {
console.log([...arguments]);
}
func(1, 2);
4.2 NodeList
console.log([...document.querySelectorAll('p')]);
复制代码
-
- 实现了 Iterator 接口的对象
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
复制代码
7.3 对象展开运算符的基本用法
对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
1. 展开对象(复制对象)
- 对象不能直接展开,必须在 {} 中展开
- 对象的展开:把属性罗列出来,用逗号分隔,放到一个 {} 中,构成新对象
const apple = {
color: "红色",
shape: "球形",
taste: "甜",
};
const Ob = { ...apple };
console.log(Ob);
console.log(Ob === apple); //false
复制代码
2.合并对象
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
};
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
};
console.log({ ...apple, ...pen });
// 新对象拥有全部属性,相同属性,后者覆盖前者
复制代码
3. 用户参数和默认参数
const logUser = userParam => {
const defaultParam = {
username: 'ZhangSan',
age: 0,
sex: 'male'
};
const param = { ...defaultParam, ...userParam };
console.log(param.username);
};
logUser();
复制代码
4.注意事项
- 如果展开一个空对象,则没有任何效果
- 非对象的展开
如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来
如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
console.log({ ...'alex' });//{0: "a", 1: "l", 2: "e", 3: "x"} console.log([...'alex']);// ["a", "l", "e", "x"] console.log(...'alex');// a l e x 复制代码
let arr = ["cccc", "dddd", "eeeee"]; let obj = { ...arr }; console.log(obj);//{0: "cccc", 1: "dddd", 2: "eeeee"} 复制代码
八.Symbol唯一值
ES6新规定的Symbol(符号)是原始值,且符号实例唯一、不可变的,它的用途是确保对象属性使用唯一标识符。
需要使用Symbol()函数初始化
let name1 = Symbol('liming');
let name2 = Symbol('liming');
console.log(name1 == name2); //false
// 希望能够多次使用同一个symbol值
let name1 = Symbol.for('name'); //检测到未创建后新建
let name2 = Symbol.for('name'); //检测到已创建后返回
console.log(name1 === name2); // true
复制代码
Symbol的另一特点是隐藏性,Symbol 作为属性名,遍历对象的时候,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
let id = Symbol("id");
let obj = {
[id]:'symbol'
};
for(let option in obj){
console.log(obj[option]); //空
}
复制代码
但是也有能够访问的方法:Object.getOwnPropertySymbols.该方法会返回一个数组,成员是当前对象的所有用作属性名的Symbol值。
let id = Symbol("id");
let obj = {
[id]:'symbol'
};
let array = Object.getOwnPropertySymbols(obj);
console.log(array); //[Symbol(id)]
console.log(obj[array[0]]); //'symbol'
复制代码
九.Set 和 Map 数据结构
9.1 Set数据结构
- ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
- 要创建一个 Set,需要提供一个 Array 作为输入,或者直接创建一个空 Set:
var s1 = new Set();
var s2 = new Set([1, 2, 3]);
console.log(s1, typeof s1); //Set(0) {} "object"
console.log(s2, typeof s2); //Set(3) {1, 2, 3} "object"
复制代码
9.1.1 Set 实例的方法和属性
-
- add添加成员(可以连写)
const s = new Set();
s.add(1).add(2).add(2);
console.log(s);//{1,2}
复制代码
-
- has判断是否有某个成员
const s = new Set(["ccc", "ssss", "dddd"]);
console.log(s.has("dddd")); //true
复制代码
- 3.delete删除某个成员
使用 delete 删除不存在的成员,什么都不会发生,也不会报错
const s = new Set(["ccc", "ssss", "dddd"]);
s.delete("dddd");
console.log(s);
复制代码
-
- clear全部清除
s.clear();
console.log(s);
复制代码
-
- forEach():使用回调函数遍历每个成员
有两个参数,第一个是回调函数,第二个是改变this指向
Set 中 value = keyconst s = new Set(); s.add(1).add(2).add(2); s.forEach(function (value, key, set) { console.log(this); console.log(value, key, set === s); }, document); // 按照成员添加进集合的顺序遍历 复制代码
- 6.属性size–用来获取成员个数,相当于length
const s = new Set(["ccc", "ssss", "dddd"]);
console.log(s.size); //3
复制代码
- 7.遍历操作
Set 结构的实例有四个遍历方法,可以用于遍历成员。
- Set.prototype.keys():返回键名的遍历器
- Set.prototype.values():返回键值的遍历器
- Set.prototype.entries():返回键值对的遍历器
- Set.prototype.forEach():使用回调函数遍历每个成员
9.1.2 Set 构造函数的参数
可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
//1.数组
const s = new Set([1, 2, 1]);
//2.字符串、arguments、NodeList、Set 等
console.log(new Set('hi'));
function func() {
console.log(new Set(arguments));
}
func(1, 2, 1);
console.log(new Set(document.querySelectorAll('p')));
const s = new Set([1, 2, 1]);
console.log(new Set(s) === s);
console.log(s);
复制代码
9.1.3.Set 的注意事项
1.判断重复的方式
const s = new Set([NaN, 2, NaN]);
console.log(NaN === NaN);
// Set 对重复值的判断基本遵循严格相等(===)
// 但是对于 NaN 的判断与 === 不同,Set 中 NaN 等于 NaN
复制代码
2.什么时候使用 Set?
① 数组或字符串去重时
② 不需要通过下标访问,只需要遍历时
③ 为了使用 Set 提供的方法和属性时(add delete clear has forEach size 等)
9.1.4.Set 应用
1.数组去重
console.log([...new Set([1, 2, 1])]);
复制代码
2.字符串去重 ‘abbacbd’;
把字符串用Set方法变成去重后对象,再把对象转化成数组,用数组的join方法转化成字符串
const s = new Set('abbacbd');
console.log(s);//{'a','b','c','d'}
console.log([...s].join(''));
console.log(s);
//一行搞定
console.log([...new Set('abbacbd')].join(''));
复制代码
3.存放 DOM 元素
<p>1111</p>
<p>22222</p>
<p>3333</p>
// for()
const s = new Set(document.querySelectorAll('p'));
console.log(s);
s.forEach(function (elem) {
console.log(elem);
elem.style.color = 'red';
elem.style.backgroundColor = 'yellow';
});
复制代码
9.2 Map数据结构
- Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数 Map 可以接受一个数组作为参数。
- 有一个size:返回 Map 对象中所包含的键值对个数;
- 如果只是需要 key -> value 的结构,或者需要字符串以外的值做键,使用 Map 比对象更合适,Map 有 forEach 遍历,size 判断个数
9.2.1 Map 和 Object 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
9.2.2 Map 对象的方法
- set(key, val): 向 Map 中
添加
新元素;- get(key): 通过键值
查找
特定的数值并返回;- has(key): 判断 Map 对象中
是否有
Key 所对应的值,有返回 true,否则返回 false;- delete(key): 通过键值从 Map 中
移除
对应的数据;- 使用 delete
删除
不存在的成员,什么都不会发生,也不会报错;- clear(): 将这个 Map 中的所有元素
清空
;- forEach遍历
const m = new Map();
// 1.set()增加
m.set("age", 18).set(true, "true").set("age", 20);
console.log(m); // {"age" => 20, true => "true"}
// 2.get()查找 , get 获取不存在的成员,返回 undefined
console.log(m.get("age")); //20
// 3.has(key):是否有
console.log(m.has("age")); //true
// 4.delete(key)删除
m.delete("age");
console.log(m); //{true => "true"}
// 5.clear():清空
m.clear();
console.log(m); //{}
复制代码
const m = new Map();
m.set("age", 20).set(true, "true");
m.forEach(function (value, key, map) {
console.log(value, key, map === m);
console.log(this);
/*
第一次: 20 "age" true document
第二次: true true true document
*/
}, document);
复制代码
9.2.3 Map 构造函数的参数
1.以二维数组传参
const arr2 = [["key", "value"]];
const m = new Map(arr2);
console.log(m); //Map(1) {"key" => "value"}
复制代码
- Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
复制代码
2.Set和Map作为参数
事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
Set 中也必须体现出键和值
const s = new Set([
[20, "二十"],
[30, "三十"],
]);
console.log(s); // {Array(2), Array(2)}
console.log(new Map(s)); //Map(2) {20 => "二十", 30 => "三十"}
复制代码
Map——复制了一个新的 Map
const m1 = new Map([
["name", "alex"],
["age", 18],
]);
console.log(m1);
const m2 = new Map(m1); //{"name" => "alex", "age" => 18}复制一个新map
console.log(m2, m2 === m1); //{"name" => "alex", "age" => 18} false
复制代码
- 改变三个 P 标签文字的颜色
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Map 的应用</title>
</head>
<body>
<p>1</p>
<p>2</p>
<p>3</p>
<script>
//把三个p标签解构出来
const [p1, p2, p3] = document.querySelectorAll("p");
console.log(p1, p2, p3);
// const m = new Map();
// m.set(p1, 'red');
// m.set(p2, 'green');
// m.set(p3, 'blue');
const m = new Map([
[
p1,
{
color: "red",
backgroundColor: "yellow",
fontSize: "40px",
},
],
[
p2,
{
color: "green",
backgroundColor: "pink",
fontSize: "40px",
},
],
[
p3,
{
color: "blue",
backgroundColor: "orange",
fontSize: "40px",
},
],
]);
m.forEach((propObj, elem) => {
for (const p in propObj) {
elem.style[p] = propObj[p];
}
});
// m.forEach((color, elem) => {
// elem.style.color = color;
// });
console.log(m);
</script>
</body>
</html>
复制代码
十.Iterator 和 for…of 循环
10.1 Iterator遍历器(迭代器)
Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:
- 迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为 Symbol.iterator 的方法来实现。
- 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)。
10.1.1 Iterator 的作用有三个:
- 一是为各种数据结构,提供一个统一的、简便的访问接口;
- 二是使得数据结构的成员能够按某种次序排列;
- 三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。
10.1.2 迭代的过程如下:
- 通过 Symbol.iterator 创建一个迭代器,指向当前数据结*构的起始位置
- 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束。
const items = ["zero", "one", "two"];
const it = items[Symbol.iterator]();
console.log(it.next()); //{value: "zero", done: false}
console.log(it.next()); //{value: "one", done: false}
console.log(it.next()); //{value: "two", done: false}
console.log(it.next());// {value: undefined, done: true}
console.log(it.next()); //{value: undefined, done: true}
复制代码
10.1.3 为什么需要 Iterator 遍历器–for..of 来遍历
我们之前的遍历方法是:
- 遍历数组:for 循环和 forEach 方法
- 遍历对象:for in 循环
而 Iterator 遍历器是一个统一的遍历方式,使用 Iterator 封装好的 for..of 来遍历,不管是数组还是对象都可以遍历
10.2 for..of 来统一遍历遍历
1.什么是可遍历?
只要有 Symbol.iterator 方法,并且这个方法可以生成可遍历对象,就是可遍历的, 只要可遍历,就可以使用 for…of 循环来统一遍历
2.原生可遍历的有哪些?
- 数组
- 字符串
- Set
- Map
- arguments
- NodeList
for (const elem of document.querySelectorAll('p')) { console.log(elem); elem.style.color = 'red'; } 复制代码
3.非原生可遍历的有哪些?
一般的对象可以用for … in遍历,也可以给他添加一个Symbol.iterator然后用for … of 遍历
10.2.1 for…of 遍历数组
//keys() 得到的是索引的可遍历对象,可以遍历出索引值
const arr = [1, 2, 3];
for (const key of arr.keys()) {
console.log(key);
}
//values() 得到的是值的可遍历对象,可以遍历出值
for (const value of arr.values()) {
console.log(value);
}
相当于
for (const value of arr) {
console.log(value);
}
//entries() 得到的是索引+值组成的数组的可遍历对象
for (const entries of arr.entries()) {
console.log(entries);//数组[0,1]
}
for (const [index, value] of arr.entries()) {
console.log(index, value);值0 1
}
复制代码
10.2.2 for…of 遍历普通对象
const person = {
name: "John Smith",
job: "agent",
};
for (const [key, value] of Object.entries(person)) {
console.log(key, value);
}
复制代码
十一.Es6新增方法
11.1 Es6字符串新增方法
1. includes()判断字符串中是否含有某些字符
//判断字符串
console.log('abc'.includes('a', 0));//true
1.第一个参数------表示是否包含该值
2.第二个参数----- 表示开始搜索的位置,默认是 0
//判断数组
arr = ["name", 2, 3, 4, 5, 6, 7, 8];
console.log(arr.includes("name"));//true
复制代码
应用:给url添加参数
// https://www.imooc.com/course/list
// https://www.imooc.com/course/list?c=fe&sort=pop&name=value
let url = "https://www.imooc.com/course/list?";
const addURLParam = (url, name, value) => {
// 第一步:判断url有没有问号,有的话说明后面接了数据,只需加&拼接就欧克,否则先加问号
url += url.includes("?") ? "&" : "?";
// 第二步:传入key=value,添加给url
url += `${name}=${value}`;
return url;
};
url = addURLParam(url, "c", "fe");
console.log(url);//https://www.imooc.com/course/list?&c=fe
url = addURLParam(url, "sort", "pop");
console.log(url);//https://www.imooc.com/course/list?&c=fe&sort=pop
复制代码
2.padStart() 和 padEnd()补全字符串长度
- 用来在字符串本身的头部或尾部补全字符串长度
- 原字符串的长度,等于或大于最大长度,不会消减原字符串,字符串补全不生效,返回原字符串
- 用来补全的字符串与原字符串长度之和超过了最大长度,截去超出位数的补全字符串,原字符串不动
console.log("abc".padStart(10, "0123456789")); //0123456abc
console.log("abc".padEnd(10, "0123456789")); //abc0123456
// 第一个参数表示补全后的字符串长度,第二个参数是要用到的元素补全字符串
// 如果省略第二个参数,默认使用空格补全长度
复制代码
应用—-显示日期格式
3.trimStart() 和 trimEnd()清除字符串的首或尾空格
const s = ' a b c ';
console.log(s);
console.log(s.trimStart());
console.log(s.trimEnd());
复制代码
应用-表单验证提交
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>trimStart() 和 trimEnd()</title>
</head>
<body>
<input type="text" id="username" />
<input type="submit" value="提交" id="btn" />
<script>
const usernameInput = document.getElementById("username");
const btn = document.getElementById("btn");
btn.addEventListener(
"click",
() => {
console.log(usernameInput.value);
// 验证
console.log(usernameInput.value.trim());
if (usernameInput.value.trim() !== "") {
// 可以提交
console.log("可以提交");
} else {
// 不能提交
console.log("不能提交");
}
// 手动提交
},
false
);
</script>
</body>
</html>
复制代码
11.2 Es6数组新增方法
1.includes()
- 判断数组中是否含有某个成员
- 第二个参数表示搜索的起始位置,默认值是 0
//从索引值为2的地方开始,判断后面是否含有2这个元素
console.log([1, 2, 3].includes(2, 2));
// 基本遵循严格相等(===),但是对于 NaN 的判断与 === 不同,includes 认为 NaN === NaN
复制代码
应用-数组去重
const arr = [];
for (const item of [1, 2, 1]) {
if (!arr.includes(item)) {
arr.push(item);
}
}
console.log(arr); //[1,2]
复制代码
2.Array.from()
- Array.from()将类数组对象(一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符。)或可迭代对象(数组、字符串、Set、Map、NodeList、arguments)转化为数组。
- 一般情况下把拥有 length 属性的任意对象转化为数组。
// 类数组:{number:'value',length:1}
a = { 0: "aaa", 1: "bbb", 2: "cccc", 3: "ddd", 4: "eeee", length: 5 };
let array1 = Array.from(a);
console.log(array1); // ["aaa", "bbb", "cccc", "ddd", "eeee"]
复制代码
- 元素属性名不为数值且无法转换为数值,返回长度为 length 元素值为 undefined 的数组
- 第一个参数—能被转化的数据
- 第二个参数—作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组
- 第三个参数–第二个参数为一般回调函数时,第三个参数可以改变 this 指向
// 类数组:{number:'value',length:1}
a = { 0: "aaa", 1: "bbb", 2: "cccc", 3: "ddd", 4: "eeee", length: 5 };
let array1 = Array.from(a, (value) => value + "你真帅");
console.log(array1); // ["aaa你真帅", "bbb你真帅", "cccc你真帅", "ddd你真帅", "eeee你真帅"]
复制代码
3.find() 和 findIndex()
find():find()返回第一个匹配的元素
- 1.find()方法对数组中的每一项元素执行一次 callback 函数,直至有一个 callback 返回 true。
- 2.当找到了这样一个元素后,该方法会立即返回这个元素的值,否则返回 undefined。
- 3.callback函数带有3个参数:当前元素的值、当前元素的索引,以及数组本身。
var num = [10, 20, 30, 40, 50, 60, 70, 80, 90]; var newNum1 = num.find((item, index) => { return item > 40; }); console.log(newNum1); //50 复制代码
findIndex()返回数组中第一个满足条件的索引(从0开始), 不满足返回-1;
var num = [10, 20, 30, 40, 50, 60, 70, 80, 90]; var newNum1 = num.findIndex((item, index) => { return item > 40; }); console.log(newNum1); //4 index=length-1 复制代码
应用-筛选数据
const students = [
{
name: "张三",
sex: "男",
age: 16,
},
{
name: "李四",
sex: "女",
age: 22,
},
{
name: "王二麻子",
sex: "男",
age: 32,
},
];
console.log(students.find((value) => value.sex === "男"));
console.log(students.findIndex((value) => value.sex === "女"));
复制代码
11.3 Es6对象新增方法
1.Object.assign()合并对象
- Object.assign(目标对象, 源对象 1,源对象 2,…): 目标对象
- Object.assign 直接合并到了第一个参数中,返回的就是合并后的对象
应用–合并默认参数和用户参数
const logUser = (userOptions) => {
const DEFAULTS = {
username: "ZhangSan",
age: 0,
sex: "male",
};
const options = Object.assign({}, DEFAULTS, userOptions);
console.log(options);
};
logUser();
复制代码
2.Object.keys()、Object.values() 和 Object.entries()
- 数组的 keys()、values()、entries() 等方法是实例方法,返回的都是 Iterator
- 对象的 Object.keys()、Object.values()、Object.entries() 等方法是构造函数方法,返回的是数组
const person = {
name: "Alex",
age: 18,
};
console.log(Object.keys(person)); //['name',"age"]
console.log(Object.values(person));//["Alex", 18]
console.log(Object.entries(person)); //二维数组 [["name", "Alex"],["age", 18]]
复制代码
应用-和for…of…一起遍历对象
obj = { name: "蔡徐坤", age: "18", hobby: ["唱歌", "写歌", "跳舞"] };
console.log(Object.entries(obj));
for (key of Object.entries(obj)) {
console.log(key[0], key[1]);
}
复制代码
十二.Promise对象
- Promise是ES6中新增的异步编程解决方案, 在代码中的表现是一个对象。
- Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用;
- 企业开发中为了保存异步代码的执行顺序, 那么就会出现回调函数层层嵌套,如果回调函数嵌套的层数太多, 就会导致代码的阅读性, 可维护性大大降低。
Promise对象可以将异步操作以同步流程来表示, 避免了回调函数层层嵌套(回调地狱)。
12.1 基本使用
第一步:创建一个Promise实例对象,并传入
执行器函数
,执行器函数会同时执行
//Promise是一个构造函数
let p = new Promise(executor)
复制代码
第二步:executor执行器函数接受两个函数参数,resolve和reject,这两个函数是改变Promise状态的;
- pending: 默认状态,只要没有告诉Promise任务是成功还是失败就是pending状态
- fulfilled(resolved): 只要调用resolve函数, 状态就会变为fulfilled, 表示操作成功
- rejected: 只要调用rejected函数, 状态就会变为rejected, 表示操作失败
注意点:状态一旦改变既不可逆, 既从pending变为fulfilled, 那么永远都是fulfilled , 既从pending变为rejected, 那么永远都是rejected.
let p = new Promise((resolve, reject) => {
console.log("我同步执行");
resolve("我是resolve携带的数据");
// reject("我是reject携带的失败信息");
}).then(
function result(res) {
console.log(res);
},//期约兑现回调
function reason(reason) {
console.log(reason);
}//期约拒绝回调
);
复制代码
let p=new Promise((resolve,reject)={
/*
这里一般存放的都是我们即将要处理的异步任务(比如网络请求成功),任务成功我们执行resolve吧数据传出去,
任务失败我们执行reject(当然写同步的也可以),之后根据状态就可以调用原型里面的三个方法
*/
})
复制代码
12.2 Promise.prototype.then()方法的使用
-
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
-
then()方法执行后返回的是一个新的 Promise 对象
let promise = new Promise(function (resolve, reject) {
resolve("111");
});
p1 = promise.then(function success(res) {
console.log(res);
return 222222;
});
console.log(p1);
/*
p1是一个新的Promise;
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "222"
*/
复制代码
- 同一个promise对象可以多次调用then方法 . 当该promise对象的状态对应时所有then方法都会被执行。
- 可以通过上一个promise对象的then方法给下一个promise对象的then方法传递参数,传递的参数通过return返回给下一个promise对象的then方法。
- 链式调用then返回的的Promise对象状态都与第一个一样,接受的参数(对象的结果)由上个链式调用函数的返回值有关
第一次使用resolve函数:
let promise = new Promise(function (resolve, reject) {
resolve("我是resolve函数传过来的参数");
});
p1 = promise.then(function success(res) {
console.log(res);
return res;
});
console.log("我是第1次链式调用then方法", p1);
p2 = p1.then(function success(res) {
console.log(res);
return res;
});
console.log("我是第2次链式调用then方法", p2);
p3 = p2.then(function success(res) {
console.log(res);
return res;
});
console.log("我是第3次链式调用then方法", p3);
p4 = p3.then(function success(res) {
console.log(res);
return res;
});
console.log("我是第4次链式调用then方法", p4);
/*
p1,p2,p3,p4是一个新的Promise;
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "我是resolve函数传过来的参数"
*/
复制代码
如果第一次是reject:
第一步:new Promise()并执行reject
let promise = new Promise((resolve, reject) => {
reject("我是reject函数传过来的参数");
// resolve("我是resolve函数传过来的参数");
});
console.log("我是new出来开的promise", promise);
/*
[[PromiseState]]: "rejected"
[[PromiseResult]]: "我是reject函数传过来的参数"//始终不变
*/
复制代码
第二步:
- promise.then方法执行(根据拒绝状态他应该调用then的第二个失败回调);同时使第一次then产生的Promise对象,他的状态是成功的;值是undefined
- 你一定想改他的状态和结果吧?失败函数里面抛出错误,就能改了。
p1 = promise.then(
(res) => {
console.log(res);
},
(err) => {
console.log("失败回调函数被调用了", err);
// throw new Error(err);
}
);
console.log("我是第1次链式调用then方法返回的promise", p1);
/*
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
如果在失败回调里面抛出错误,p1就会发生变化
[[PromiseState]]: "rejected"
[[PromiseResult]]: Error: 我是reject函数传过来的参数
*/
复制代码
第三步:
- then方法执行第2次产生的Promise对象(执行哪个函数根据上一个promise的状态决定),这里假定为成功。
- 之前有一次状态是失败,这里就算执行成功的回调也传不过来原始数据;
- 在成功的回调里面写上返回值改写p2的结果
p2 = p1.then(
(res) => {
console.log('我是第二次执行then',res); // res为undefined,没有数据因为第一次调用的时候是reject,传不过来数据
return 1111;//这里是为了让p2的结果是111
},
(err) => {
console.log("失败回调函数被调用了", err);
}
);
console.log("我是第2次链式调用then方法", p2);
/*
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 1111
*/
复制代码
- 结论:
执行reject()的后果:
让当前Promise对象状态变为失败,不影响后面的,后面的都为成功;
只要调用过程用到了reject(),就再也不能获取原始的数据了;
前面使用reject之后,若果想要后面也跟着状态变为失败,就要在reject()对应的失败函数里面抛出
错误语句: throw new Error(err)
;若果你想要改变当前调用then返回的Promise结果,就需要在当前then里面添加return返回想要的结果。
无论是在上一个promise对象成功的回调还是失败的回调传递的参数, 都会传递给下一个promise对象成功的回调。
12.2 Promise.prototype.catch()方法的使用
- catch 其实是 then(undefined, () => {}) 的语法糖;
- 如果需要分开监听, 也就是通过then监听成功通过catch监听失败,那么必须使用链式编程, 否则会报错;
catch方法使用链式编程的原因是:
a.如果promise的状态是失败, 但是没有对应失败的监听就会报错
b.then方法会返回一个新的promise, 新的promise会继承原有promise的状态,状态改变的话所有的then都会执行
c.如果新的promise状态是失败, 但是没有对应失败的监听也会报错
- 如果 catch里面的回调函数 抛出一个错误或返回一个本身失败的 Promise , 通过 catch() 返回的Promise 被rejected;否则,它将显示为成功(resolved)。
//抛出错误
throw new Error("我失败了")
//返回失败的Promise
return new Promise((resolve, reject) => {
reject();
});
复制代码
- 和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常。
之前这样的做法会报错
let promise = new Promise(function (resolve, reject) {
resolve();
});
promise.then(
function () {
console.log("成功");
xxx; // Uncaught (in promise) ReferenceError: xxx is not defined
},
function () {
console.log("失败");
}
);
复制代码
把成功回调里面的异常抛给catch了
let promise = new Promise(function (resolve, reject) {
resolve();
});
promise
.then(function () {
console.log("成功");
xxx;
})
.catch(function (e) {
console.log("失败", e); //捕获到前面的异常并传递给e了,失败 ReferenceError: xxx is not defined
});
复制代码
js异常处理
利用try{}catch{}来处理异常可以保证程序不被中断, 也可以记录错误原因以便于后续优化迭代更新。
try {
可能遇到的意外的代码
}
catch(e) {
捕获错误的代码块
}
复制代码
catch的特点:
- (1)和then一样, 在修改promise状态时, 可以传递参数给catch方法中的回调函数;
- (2)和then一样, 同一个promise对象可以多次调用catch方法, 当该promise对象的状态时所有catch方法都会被执行.
- (3)和then一样, catch方法每次执行完毕后会返回一个新的promise对象
- (4) 和then方法一样, 上一个promise对象也可以给下一个promise成功的传递参数。
但是
,无论是在上一个promise对象成功的回调还是失败的回调传递的参数, 都会传递给下一个promise对象成功的回调。- (5).和then一样, catch方法如果返回的是一个Promise对象, 那么会将返回的Promise对象的执行结果中的值传递给下一个catch方法。
- (6) .和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常。
12.3 Promise.all()方法的使用
参数:
- all方法接收一个数组.
- 如果数组中有多个Promise对象,只有都成功才会执行then方法,并且会按照添加的顺序, 将所有成功的结果重新打包到一个数组中返回给我们。
- 如果数组中不是Promise对象, 那么会直接执行then方法
返回:
1.all方法会返回一个新的Promise对象
2.会按照传入数组的顺序将所有Promise中成功返回的结果保存到一个新的数组返回
3.数组中有一个Promise失败就会失败, 只有所有成功才会成功
- 应用场景: 批量加载, 要么一起成功, 要么一起失败
arr = [
"http://www.it666.com/files/system/block_picture_1555422597.jpg",
"http://www.it666.com/files/system/block_picture_1555422597.jpg",
"http://www.it666.com/files/system/block_picture_1555419713.jpg",
];
function loadImage(url) {
return new Promise(function (resolve, reject) {
let oImg = new Image();
let time = Math.random() * 1000;
// console.log(time);
setTimeout(function () {
oImg.src = url;
}, time);
// oImg.src = url;
oImg.onload = function () {
resolve(oImg);
};
oImg.onerror = function () {
reject("图片加载失败了");
};
});
}
Promise.all([loadImage(arr[0]), loadImage(arr[1]), loadImage(arr[2])])
.then(function (result) {
// console.log(result);
result.forEach(function (oImg) {
document.body.appendChild(oImg);
});
})
.catch(function (e) {
console.log(e);
});
复制代码
12.4 Promise.race()方法的使用
- 1.race方法接收一个数组,
- 2.如果数组中有多个Promise对象, 谁先返回状态就听谁的, 后返回的会被抛弃
- 3.如果数组中不是Promise对象, 那么会直接执行then方法
- 应用场景: 接口调试, 超时处理
给超时函数和加载函数均包装一个promise对象,然后把这个对象写入race里面,紧接着写处理函数,5s之内没有获取资源,就直接超时处理
let url =
"http://m.360buyimg.com/babel/jfs/t1/159039/13/7927/381213/6030c447Efe10500b/5e852f2dd56c27fd.jpg.webp";
function loadImage(url) {
return new Promise(function (resolve, reject) {
let oImg = new Image();
setTimeout(function () {
oImg.src = url;
}, 2000);
oImg.onload = function () {
resolve(oImg);
};
oImg.onerror = function () {
reject("图片加载失败了");
};
});
}
function timeout() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("超时了");
}, 5000);
});
}
Promise.race([loadImage(url), timeout()])
.then(function (value) {
console.log("成功", value);
})
.catch(function (e) {
console.log("失败", e);
});
复制代码
12.4 Promise.resolve()和Promise.reject()方法的使用
Promise.resolve()返回一个成功的Promise对象
p = Promise.resolve("success");
console.log(p);
/*
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "success"
*/
复制代码
Promise.reject()返回一个失败的Promise对象
p = Promise.reject("failed");
p.catch((err) => console.log(err));
console.log(p);
/*
[[PromiseState]]: "rejected"
[[PromiseResult]]: "failed"
*/
复制代码
Promise封装网络请求放到网络
那一块
总结:Promise如何发挥作用?
相当于告诉你家保姆去做几件事:1.你先去超市买菜。
2.用超市买回来的菜做饭。
3.将做好的饭菜送到老婆单位。
4.送到单位后打电话告诉我。我们知道,上面三步都是需要消耗时间的,我们可以理解为三个异步任务。利用 Promise 的写法来书写这个操作:
//写好任务计划表
function 买菜(resolve,reject) {
setTimeout(function(){
resolve(['西红柿'、'鸡蛋'、'油菜']);
},3000)
}
function 做饭(resolve, reject){
setTimeout(function(){
//对做好的饭进行下一步处理。
resolve ({
主食: '米饭',
菜: ['西红柿炒鸡蛋'、'清炒油菜']
})
},3000)
}
function 送饭(resolve,reject){
//对送饭的结果进行下一步处理
resolve('老婆的么么哒');
}
function 电话通知我(){
//电话通知我后的下一步处理
给保姆加100块钱奖金;
}
//开始做任务
// 告诉保姆帮我做几件连贯的事情,先去超市买菜
new Promise(买菜)
//用买好的菜做饭
.then((买好的菜)=>{
return new Promise(做饭);
})
//把做好的饭送到老婆公司
.then((做好的饭)=>{
return new Promise(送饭);
})
//送完饭后打电话通知我
.then((送饭结果)=>{
电话通知我();
})
复制代码
- 请一定要谨记:如果我们的后续任务是异步任务的话,必须return 一个 新的 promise 对象。
- 如果后续任务是同步任务,只需 return 一个结果即可。
- 我们上面举的例子,除了电话通知我是一个同步任务,其余的都是异步任务,异步任务 return 的是 promise对象。
十三.async 函数
async和await是Promise的语法糖
async是对函数的一个修饰,使一个函数返回Promise实例。
await是等待一个Promise实例成功后执行
- 1.执行async函数后可以写then
async function foo() {
//1.返回成功的
//return Promise.resolve();
return 1;
// 2.返回失败的
// return Promise.reject();
// throw new Error("我错了");
}
console.log(foo()); //Promise实例
foo().then((res) => {
console.log(res); //1
});
复制代码
- 2.必须在async函数里面使用await
P1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("ok");
}, 2000);
});
async function func() {
/*
代码的含义:是等待p1实例状态成功了才去执行await后面的代码,
相当于then里面的的成功处理语句
*/
let result = await P1;
console.log(result);
}
func();
复制代码
面试题:编写一个 sleep 函数,让其等待 1000ms 后再去做其他事情?
- 方案一:用定时器
function sleep(interval, callback) {
if (typeof interval === "function") {
callback = interval;
interval = 2000;
}
setTimeout(() => {
callback();
}, interval);
}
sleep(function () {
console.log("我在2000ms之后执行");
});
复制代码
- 方案二:函数返回值为Promise对象
function sleep(interval = 5000) {
return new Promise((resolve) => {
setTimeout(resolve, interval);
});
}
sleep(2000).then(() => {
console.log("我在2000ms之后执行");
});
复制代码
- 方案三:利用await语句
function sleep(interval = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, interval);
});
}
(async function () {
await sleep();
console.log("我在1000ms之后执行");
await sleep(2000);
console.log("我在2000ms之后执行");
await sleep(5000);
console.log("我在5000ms之后执行");
})();
复制代码
十四.Module 的语法
- ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
- 模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。
14.1 export 命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。下面是一个 JS 文件,里面使用 export 命令输出变量。
1.输出变量
// profile.js
export var firstName = 'Michael'
export var lastName = 'Jackson'
export var year = 1958
//等同于下列代码
var firstName = 'Michael'
var lastName = 'Jackson'
var year = 1958
export { firstName, lastName, year }
复制代码
上面代码是 profile.js 文件,保存了用户信息。ES6 将其视为一个模块,里面用 export 命令对外部输出了三个变量。
2.输出函数或类(class)
export function multiply(x, y) {
return x * y;
};
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
复制代码
另:export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
14.2 import 命令
使用 export 命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import 命令加载这个模块。
import { firstName, lastName, year } from './profile.js'
function setName(element) {
element.textContent = firstName + ' ' + lastName
}
//import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,
//必须与被导入模块(profile.js)对外接口的名称相同。
复制代码
如果想为输入的变量重新取一个名字,import 命令要使用 as 关键字,将输入的变量重命名。
import { lastName as surname } from './profile.js'
复制代码
import 命令输入的变量都是只读的,不允许在加载模块的脚本里面,改写接口。
import { a } from './xxx.js'
a = {} // Syntax Error : 'a' is read-only;
// 但是如果a是一个对象,改写a的属性是允许的
import { a } from './xxx.js'
a.foo = 'hello' // 合法操作
复制代码
import 后面的 from 指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
import { myMethod } from 'util'
// util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。
复制代码
import 命令具有提升效果,会提升到整个模块的头部,首先执行。
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
// circle.js
export function area(radius) {
return Math.PI * radius * radius
}
export function circumference(radius) {
return 2 * Math.PI * radius
}
复制代码
import * as circle from './circle'
console.log('圆面积:' + circle.area(4))
console.log('圆周长:' + circle.circumference(14))
复制代码
export default 命令
为了给用户提供方便,让他们不需要知道所要加载的变量名或函数名,就能加载模块。
// export-default.js
export default function () {
console.log('foo')
}
复制代码
上面代码是一个模块文件 export-default.js,它的默认输出是一个函数。其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default'
customName() // 'foo'
复制代码
module章节内容参考掘金作者:小和山的菜鸟们,在这表示感谢!文章传送门
十五.剩余章节(未完待续)
Class、Generator 函数、Proxy未完待续…..