-------------------------武汉加油!陕西加油!中国加油!-------------------2020-02-18------
终于要学es6,开心~
2-1声明变量的问题
<body>
// 使用var声明变量的问题
<!-- 1.允许重复的变量声明:导致数据被覆盖 -->
<!-- 2.变量提升:闭包问题 -->
<div id="divButtons">
</div>
</body>
<script>
var div = document.getElementById("divButtons");
for(var i = 0;i <= 10;i++){
var btn = document.createElement('button');
btn.innerHTML = '按钮' + i;
div.appendChild(btn);
btn.onclick = function(){
console.log(i);
}
}
// 每次只会输出11
<!-- 3.全局变量挂载到全局对象:全局对象成员污染问题 -->
2-2使用let声明变量
// ES6不仅引入let关键字解决变量声明的问题,同时引入了块级作用域。
// 块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域。
let a = 123;//全局作用域定义a;
{
let a = 456;//块级作用域定义
console.log(a);//输出456
}
console.log(a);//输出123
// 1.let声明的变量不会挂载到全局对象
// 2.let声明的变量,不允许当前作用域范围内重复声明,
// 在块级作用域中用let定义的变量外面不能访问
// 3.使用let不会有变量提升,因此不能在定义let变量之前使用它。
// 底层实现上,let声明的变量实际上也会有提升,但是提升后会将其放入到“暂时性死区”
// 如果访问的变量位于暂时性死区会报错;当代码运行到该变量的声明时,会将变量从暂时性死区移除
// 在循环中,用let声明的循环变量会特殊处理,每次进入循环体,都会开启一个新的作用域。
// 并且将循环变量绑定到该作用域(每次循环使用的是一个全新的循环变量)
// 在循环中使用let声明的循环变量,在循环结束后会销毁。
2-3使用const声明常量
// 使用const声明常量
// const和let完全相同,const必须在声明时赋值,并且不可以重发赋值;
// 开发时最好使用const 1.开发中的很多变量都不应该更改;
// 2.后续的很多框架或者第三方JS库都要求数据不可变,
// 注意细节
// 1.常量不可变是指大恒铭的常量的内存空间不可变,并不保证内存空间中方的地址指向的其他空间不可变。
//
const a = {
name: "karry",
age:123
};
a.name = "kk";
console.log(a);//输出kk
a = kk;//报错,常量不可修改
//2.常量的明明
//1.特殊常量从字面意义来看一定不可变,通常该常量的名称使用大写。PI/MOON/EARTH/MOON_EARTH;
//2.普通常量:使用和之前一样的命名就行;
// 3.在for循环中不能使用常量;for in循环里可以使用;
3-1使用const声明常量
// 更好的Unicode支持
// 早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字,我们将一个16位的二进制编码叫做一个码元(Code Unit)
// 后来,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)
// Es6为了解决这些问题的困扰,为字符串提供了方法:charCodePointAt,根据字符串码元的位置得到码点
const text = "凯";//占了一个码元(16位)(有的字占两个码元,36位)
console.log("字符串的长度:",text.length);//字符串的长度: 1
console.log("使用正则测试",/^.$/.test(text));//使用正则测试 true
console.log("使用正则测试",/^.$/u.test(text));//使用正则测试码点 true
console.log("得到第一个码元:",text.charCodeAt(0));//得到第一个码元: 20975
console.log("得到第二个码元:",text.charCodeAt(1));//得到第二个码元: NaN
console.log("得到第一个码点:",text.codePointAt(0));//得到第一个码点: 20975
console.log("得到第二个码点:",text.codePointAt(2));//得到第二个码点: undefined
/**
*判断字符串char,是32位还是16位
*/
function is32bit(char,i){
// 如果码点大于了16位二进制的最大值,则其是32位;
return char.codePointAt(0) > 0xffff;
}
console.log("凯是否是32位:",is32bit("凯"))//凯是否是32位: false
/**
*得到一个字符串的真实长度
*/
function getLengthCodePoint(str){
var len = 0;
for (let i = 0; i < str.length; i++) {
// i在索引码元
if (is32bit(str,i)) {
// 当前字符串在这个位置占用了两个码元
i++;
}
len++;
}
return len;
}
console.log("aaa凯的码点长度是:",getLengthCodePoint("aaa凯"));//4
3-2更多的字符串API
includes startsWith endsWith repeat
// 以下都是字符串的实例(原型)方法
// includes 判断字符串中是否包括指定的子字符串
const text = "王俊凯真好看";
console.log(text.includes("凯",3));//从下标为3开始查找
// startsWith 判断字符串中是否以指定的字符串开始
console.log("是否以王开头",text.startsWith("王,3"))
console.log("是否以王结尾",text.endsWith("王"))
console.log("重复",text.repeat(2))
3-3模板字符串
// ES6之前处理字符串繁琐的两个方面:
// 1.多行字符串
const a1 = "apple";
const a2 = "orange";
const text1 = `俊凯
静静`//直接换行,如果第二行前面有空格也会展示出来
console.log(text1);
// 2.字符串拼接
const text2 = `俊凯${a1}
静静${a2}${1+2}`
console.log(text2);
4-1参数默认值
// 在书写形参时,直接给参数赋值,附的值即为默认值。
/**
*创建一个元素
*@param {*} name元素的名称
*@param {*} container 元素的父元素
*@param {*} content 元素的内容
*/
function sum(a,b = 1,c = 3){
return a+b+c;
}
console.log(sum(11));//15
console.log(sum(1,undefined,4))//6
function createElement(name,container,content){
const ele = document.createElement(name)
if(content){
ele.innerHTML = content;
}
container.appendChild(ele);
}
createElement('div',document.getElementById("container"),"欣欣欣欣欣欣欣欣静")
// ------>
function createElement(name = div,container = document.getElementById("container"),content){
const ele = document.createElement(name)
if(content){
ele.innerHTML = content;
}
container.appendChild(ele);
}
createElement(undefined,undefined,"欣欣欣欣欣欣欣欣静")
// 只要给函数加上参数默认值,该函数会自动变量严格模式下的规则:arguments和形参脱离
// 形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区
4-2剩余参数
// arguments的缺陷
// 1.如果和形参配合使用,容易导致混乱
// 2.从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图
function sum(...args){
// args收集了所有的参数,形成了一个数组;
let sum = 0;
for(let i = 0; i < args.length;i++){
sum += args[i];
}
return sum;
}
console.log(sum());//0
console.log(sum(1));//1
console.log(sum(1,3,2));//6
// 一个函数只能出现一个剩余参数
// 剩余参数只能出现在最后
function sum(a,b,...args){
}
4-3展开运算符
function getRandomNumber(length){
const arr = [];
for(let i = 0;i < length;i ++){
arr.push(Math.random());
}
return arr;
}
const numbers = getRandomNumber(10);//获取到了一个随机数组,里面有十个值
console.log(sum(...numbers))//相当于传递了10个参数
console.log(sum(1,2,...numbers,10,3));//十个值相加后再加上1,2,10,3;
// 克隆数组arr1到arr2
const arr1 = [3,1,2,3];
const arr2 = [...arr1];
console.log(arr2,arr1 === arr2);// [3, 1, 2, 3] false
// 克隆对象
const obj1 = {
name:'俊凯',
age :19,
love: '欣欣'
}
// 克隆到obj2(浅克隆)
const obj2 = {
...obj1
love:"静静"
}
console.log (obj2);//俊凯love静静
4-4剩余参数和展开运算符练习
<div>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
<p><input type="number" name="" id="" value="0"></p>
</div>
<p>
<button>计算</button>
</p>
<p>
<h2>最大值<span id="spanmax"></span></h2>
<h2>最小值<span id="spanmin"></span></h2>
</p>
</body>
<script>
function getValues(){
const numbers = [];
const inps = document.querySelectorAll('input');
for(let i = 0;i < inps.length; i++){
numbers.push(+inps[i].value);
}
return numbers;
}
const btn = document.querySelector('button')
btn.onclick = function(){
const numbers = getValues();//得到文本框中所有数字形成的数组
spanmax.innerHTML = Math.max(...numbers);
spanmin.innerHTML = Math.min(...numbers);
}
// curry:柯里化,用户固定某个函数前面的参数,得到一个新的函数,新的函数调用时,接收剩余的参数
function curry(func,...args){
return function(...subArgs){
const allArgs = [...args,...subArgs];
if(allArgs.length >= func.length){
// 参数够了
return func(...allArgs);
}
else{
// 参数不够,继续固定
return curry(func,...allArgs);
}
}
}
function cal(a,b,c,d){
return a+b*c-d;
}
const newCal = curry(cal,1,2)
console.log(newCal(3,4));//1+2*3-4
console.log(newCal(4,5));//1+2*4-5
const newCal2 = newCal(8);
console.log(newCal2(9)); //1+2*8-9
4-5明确函数的双重用途
function Person(firstName,lastName){
// 判断是否使用new的方式来调用的函数
// 过去的判断方式
if(new.target === undefined){
throw new Error('该函数没有使用new来调用')
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName}${lastName}`
}
const p1 = new Person('王','俊凯');
const p2 = Person('王','俊凯');//demo.html:15 Uncaught Error: 该函数没有使用new来调用
// ES6提供了一个api判断函数内部是否使用new来调用
// new.target
// 该表达式得到的是:如果没有使用new来调用函数,则返回undefined
// 如果使用new调用函数,得到new关键字的函数本身
4-6箭头函数
// this指向
// 1.通过对象调用函数,this指向对象
// 2.直接调用函数,this指向全局对象
// 3.如果通过new调用函数,this指向新创建的对象
// 4.如果通过apply,call,bind调用函数,this指向指定的数据
// 5.如果是DOM事件函数,this指向事件源。
// 箭头函数:是一个函数表达式
// 箭头函数函数体中的this,取决于箭头函数定义的位置的this指向,而与如何调用无关
const obj = {
count:0,
start : function (){
setInterval(() => {
this.count++;
console.log(this.count);
},1000)
},
regEvent:function(){
window.onclick = () => {
console.log(this.count);
}
}
}
obj.start();
obj.regEvent();
// 参数只有一个可以省略()
const print = num => {
console.log(num);
}
// 如果箭头函数只有一条返回语句,可以省略大括号和return关键字
// 判断一个数是不是奇数
const isOdd = function (num){
return num%2 !==0;
}
// 箭头函数
const isOdd = num => num%2 !== 0;
console.log(isOdd(8));
// 跟对象
const sum = (a,b) => ({
a:a,
b:b,
sum:a + b
})
console.log(sum(2,4));
// 箭头函数中不存在this,arguments,new.target,如果使用了是使用了外层的
// 箭头函数没有原型
// 箭头函数不能作为构造函数使用
// 箭头函数是临时性使用的函数,并不会刻意调用它
//
// 为了绑定外层的this函数
// 数组方法中的回调函数
// 练习
const func = () => {
console.log(this)
}
const obj = {
method: function(){
const func = () =>{
console.log(this) //指向obj
console.log(arguments) //指向234
}
func()
}
}
obj.method(234);
5-1新增的对象字面量语法
// 1.成员速写
// 如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写
function createUsers(loginId,loginPwd,nikeName){
const sayHello = function(){
console.log('loginId:',this.loginId,'nikeName:',this.nikeName);
}
return{
loginId,
loginPwd,
nikeName,
sayHello,
id:Math.random()
}
}
const u = createUsers('abc','123','aaa');
u.sayHello();//loginId: abc nikeName: aaa
console.log(createUsers('abc','123','aaa'));
// 2.方法速写
// 对象字面量初始化时,方法可以省略冒号和function等关键字
const user = {
name:'俊凯',
age:19,
sayHello(){
console.log(this.name,this.age)
}
}
// 3.计算属性名
// 有的时候某些属性名可能来自于某个表达式的值,es6可以使用中括号表示
const p1 = 'name';
const p2 = 'age';
const p3 = 'sayHello';
const user = {
[p1]:'俊凯',
[p2]:18,
[p3](){
console.log(this[p1],this[p2])//俊凯,18
}
}
user[p3]();
console.log(user)//name:俊凯,age:18
5-1Object的新增api
// 1.Object.is 用来判断两个数据是否相等,基本上跟===一致除了以下两点
//JS中
console.log(NaN === NaN);//false
console.log(+0 === -0);//true
// es6中
console.log(Object.is(NaN,NaN));//true
console.log(Object.is(+0,-0));//true
// 2.Object.assign 混合对象
const obj1 = {
a:'123',
b:'234',
c:'asd'
}
const obj2 = {
a:123,
d:'kkk'
}
const obj3 = {
...obj1,
...obj2
}
console.log(obj3)//a: 123, b: "234", c: "asd", d: "kkk"
//const obj = Object.assign(Obj1,Obj2);
// 3.Object.getOwnPropertyNames 的枚举顺序,如何排序完全由浏览器厂商决定,es6规定排序方式
// -先排数字 按照升序排序
// -再排其他 按照书写顺序排序
const obj = {
d:1,
b:2,
a:3,
0:6,
5:2,
4:2
}
const props = Object.getOwnPropertyNames(obj)
console.log(props);
// 4.Object.setProtoyeOf 设置某个对象的隐式原型
// 比如:Object.setPrototypeOf(obj1,obj2),
// 相当于:obj1._proto_ = obj2
Object.setPrototype(obj1,obj2)
console.log(obj1)
5-2面向对象简介,构造函数语法
// 面向对象是一种编程思想,跟具体的语言
// 面向过程:思考的切入点是功能步骤
// 面向对象:思考的切入点是对象的划分
// 传统构造函数的问题
// 1.属性和方噶定义分离,降低了可读性
// 2.原型成员可以被枚举
// 3.默认情况下,构造函数仍然可以被当做普通函数使用
// 类的特点
// 1.类声明不会被提升,与let和const一样,存在暂时性死区
// 2.类中的所有代码均在严格模式下执行
// 3.类的所有方法都是不可以枚举的
// 4.类的所有方法都无法被当做构造函数使用
// 5.类的构造器必须使用new来调用
class Animal {
constructor(type,name,age,sex){
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}
print(){
console.log(`[种类]:${this.type}`)
}
}
const a = new Animal("dog",'kk',2,'male');
a.print();
for (const prop in a){
console.log(prop);
}
5-5类的其他书写方式
// 1.可计算的成员名
// 2.getter和setter
// Object.defineProperty可定义某个对象成员属性的读取和设置
// 3.静态成员:构造函数本身的成员
// 4.字段初始化器(ES7)注意:使用static字段初始化器添加的是静态成员,没有使用static初始化器,添加的成员位于对象上;箭头函数在字段初始化器上指向当前对象
const printName = 'print';
class Animal {
constructor(type,name,age,sex){
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}
// 为创建age属性,并给他加上getter,读取该属性时,会运行该函数
get age(){
return this._age+'岁';
}
// 创建一个age属性,并给他加上setter,该属性赋值时,会运行该函数
set age(age){
if(typeof age !== 'number'){
throw new TypeError('age property must be a number');
}
if(age<0){
age = 0;
}
else if(age>1000){
age = 1000;
}
this._age = age;
}
[printName](){
console.log(`[种类]:${this.type}`)//[种类]:dog
}
}
const a = new Animal('dog','kk',1,'it');
a[printName]();
// 字段初始化器
class Test{
static a = 1;
b = 2;
c = 3;
// 构造函数
constructor(){
this.d = this.b + this.c;//5
}
}
const t = new Test();
console.log(t);//b: 2, c: 3
// 5. 类表达式
const A = class{
// 匿名类,类表达式、
a = 1;
b = 2;
}
const a = new A();
console.log(a);//1
5-6类的继承
6-1对象解构
// 什么是解构
// 使用es6的一种语法规则,讲一个对象或数组的某个属性提取到某个变量中
const user = {
name:'kevin',
age:11,
sex:'男',
address:{
province:'陕西',
city:'西安'
}
}
// let name ,age,sex,address;
// name = user.name;
// age = user.age;
// sex = user.sex;
// address = user.address;
// 解构
let name,age,sex,address;
({name,age,sex,address} = user);
// 合成一句
// 先定义变量,然后从对象中读取同名属性,放到变量中
let {name,age,sex,address} = user;
console.log(name,age,sex,address);
// kevin 11 男
// 在解构中使用默认值
let {name,age,sex,address,abc = 123} = user;
// 非同名属性解构
// 先定义四个变量:name,age,gender,address
// 再从对象user中读取同名属性赋值(其中gender读取的是sex属性)
let {name,age,sex:gender = 123,address} = user
console.log(name,age,gender,address);
const user = {
name:'jk',
age:12,
sex:"male",
address:{
province:'陕西',
city:'重庆'
}
}
// 解构不会对被解构的目标造成任何影响
// 解构出name和province
const {name,address:{provience}} = user;
console.log(name,address,provience);
6-2数组解构
const numbers = ['a','b','c','d'];
const [n1,n2] = numbers;
const [n3,,n4] = numbers;
console.log(n1,n2);//a,b
console.log(n3,n4);//a,c
const numbers = ['a','b','c','d',{
a:1,
b:2
}];
// 得到numbers下标为4的属性a,赋值给变量a
const [,,,,{a}] = numbers;
console.log(a);//1
// 解构剩余项
const{name,...obj} = user;
console.log(name,obj);
const article = {
title:'文章标题',
content:'文章内容',
comments:[{
content:'评论1',
user:{
id:1,
name:'用户名1'
}
},{
content:'评论2',
user:{
id:2,
name:'用户名2'
}
}]
}
// 解构出第二条评论的用户名和评论内容
// name:"用户名2" content:"评论2"
const{
comments:[,{
content,user:{
name
}
}]
} = article;
6-3参数解构
// 参数结构
// function printer(user){
// console.log(`姓名:${user.name}`)
// console.log(`年龄:${user.age}`)
// console.log(`城市:${user.address.city}`)
// }
function print({name,age,address:{
city
}}){
console.log(`姓名:${name}`)
console.log(`年龄:${age}`)
console.log(`城市:${city}`)
}
const user = {
name:'karry',
age:11,
address:{
city:'中国',
}
}
print(user)
function ajax({
method = 'get',
url
}){
console.log(method,url)//get abc
}
ajax({
url:'/abc'
})
7-1普通符号
// 符号具有以下特点
// 1.没有字面量
// 2.使用typeof 得到的类型是symbol
// 3.每次调用symbol函数得到的符号永远不相等,无论符号名是否相同
// 5.
// 创建符号
const syb1 = Symbol();
const syb2 = Symbol('abc');
console.log(typeof syb1,typeof syb2)//symbol symbol
// 4.符号可以作为对象的属性名存在,这种属性称为符号属性。
const syb3 = Symbol('这时用于对象的一个属性');
const obj = {
a:1,
b:2,
[syb3]:3//符号属性
}
console.log(obj);//a: 1, b: 2, Symbol(这时用于对象的一个属性): 3
// 5.符号属性无法通过常规方式被外界访问
const hero = (function(){
const getRandom = Symbol('用于产生随机数的符号')
return {
attack:30,
hp:300,
defence:10,
gongji(){//攻击
// 伤害:攻击力*随机数(0.8~1.1)
const dmg = this.attack * this[getRandom](0.8,1.1);
console.log(dmg);
},
[getRandom](min,max){//根据最小值和最大值产生一个随机数
return Math.random() * (max-min) + min;
}
}
})()
console.log(hero);
// 符号属性是不能枚举的,因此在for-in循环中无法读取到符号属性,object.keys方法也无法读取到符号属性
// Es6的Object。getOwnProperty方法,可读取符号属性
const syb = Symbol();
const obj = {
[syb]:1,
a:2,
b:3
}
for(const prop in obj){
console.log(prop);//a,b
}
console.log(Object.getOwnPropertySymbols(obj));//[Symbol()]
7-2共享符号
8-1事件循环
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 事件循环
// JS运行的环境成为数组环境
// 执行栈:call stack,一个数据结构吧,用于存放各种函数的执行环境,每一个函数执行之前,他的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈,函数调用之后销毁执行环境
// JS引擎永远执行的是执行栈的最顶部
// 斐波那契数列
function getFeibo(n){
if(n == 1 || n == 2){
return 1;
}
return getFeibo(n-1) + getFeibo(n-2);
}
// 异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数
// 比如事件处理函数,异步函数的执行时机,会被宿主环境控制。
// 浏览器宿主环境中包含5个线程
// 1.JS引擎:负责执行执行栈的最顶部代码
// 2.GUI线程:负责渲染页面
// 3.事件监听线程:负责监听各种事件
// 4.计时线程:负责计时
// 5.网络线程:负责网络通信
// 当上面的线程发生了某些事情,如果该线程发现,这件事情有处理程序,它会将该处理程序加入到
//一个叫做实践队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一
// 个函数加入到执行栈中执行。
// JS引擎对事件队列的去除执行方式,以及与宿主环境的配合,称之为事件循环
// 事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中
// 事件队列被分为两种:
// -宏任务(队列):macroTask,计时器结束的回调,事件回调,http回调等等绝大部分异步函数进入宏队列。
// -微任务(队列):MutationObserver,Promise产生的回调进入微队列。
// 当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束后,如果没有微任务,执行宏任务。
console.log("a")
setTimeout(() => {
console.log('b')
},0)//异步函数会放到事件队列里面,等执行栈清空了之后再执行
for(let i = 0;i < 1000;i ++){
console.log('c');
}
//a,1000个c,b
<ul id = "container"></ul>
let count = 1;
const ul = document.getElementById('container');
document.getElementById('btn').onclick = function(){
var li = document.createElement('li')
li.innerText = count++
ul.appendChild(li);
}
//该函数是异步的 一定会到执行队列中,同步的不会
</script>
</html>
8-1事件和回调函数的缺陷
// 事件:某个对象的属性是一个函数,当发生某一件事情时,运行该函数。
// 回调:运行某个函数以实现某个功能,传入一个函数作为参考,当发生某件事情的时候,会运行该函数
// 本质上事件和回调并没有本质的区别,只是把函数放置的位置不同而已。
// 目前,该模式主要面临以下两个问题
// 1.回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套
// 2.异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让对吗的复杂度剧增
8-2异步处理的通用模型
<script>
// 为了兼容旧系统,ES6并不打算抛弃过去的做法,总结出了一套异步通用的模型,基于该模型
// 推出了个全新的Api,会让异步处理更加简洁优雅
// 1.ES6将某一件可能发生异步操作的事情,分为两个阶段:unsettled和settled
// -unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通过结果的那件事情
// -settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转
// 事情总是从未决阶段 逐步发展到 已决阶段。并且,未决阶段拥有控制何时通过向已决阶段的能力
//ES6将事情划分为三种状态:pending,resovled,rejected
// -pending:挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)
// -resolved:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
// -rejected: 已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果
// 未决阶段有权利决定事情的走向,可以决定事情最终得到状态。
// 无论是阶段还是状态都是不可逆的
// resovled -->后续处理thenable
// rejected -->后续处理catchable
//后续处理可能有多个,因此会形成作业队列,这些后续处理会形成状态,依次处理
</script>
8-3Promise的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// △Promise并没有消除回调,只是让回调变得可控
//创建promise
const pro = new Promise((resolve,reject) => {
console.log(`俊凯像欣欣表白了]`);
setTimeout(()=>{
if(Math.random()<0.1){
// 同意啦
// resolve
resolve(true);
}else{
// 拒绝
resolve(false);
}
},1000);
})
//
const pro = new Peomise((resolve,reject) => {
console.log('未决状态')
resolve(112);
// 未决阶段的处理
// 通过调用resolve函数将Promise推向已决阶段的resolve状态
// 通过调用reject函数将Promise推向已决阶段的reject状态
// resolve和reject均可以传递最多一个参数
})
pro.then(data =>{
// 这时thenable函数,如果当前的promise已经是resolve状态,该函数会立即执行
// 如果当前是未决的,会加入到作业队列,等待到达resolved状态后执行
// data为状态数据
},err=>{
// 这是catchable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
// 如果是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
// err为状态数据
})
// 细节
// 1.未决状态的处理函数是同步的,会立即执行
const pro = newPromise((resolve,reject) => {
console.log('a')
})
console.log('b')//a b
// 2.thenable和catchable函数是异步的,就算立即执行,也会加入到事件队列中等待执行,并且加入的队列是微队列
const pro = newPromise((resolve,reject) => {
console.log('a')
resolve(1);//立即推向成功
console.log('b')
})
// then里面放的是异步函数,加入到微队列
pro.then(data => {
console.log(data);
})
console.log('c')//a b c 1
// 练习输出顺序是什么?
const pro = newPromise((resolve,reject) => {
console.log('a')
resolve(1);
setTimeout(() => {
console.log('b')
},0)
})
// pro:resolved
pro.then(data => {
console.log(data)
})
console.log('c');
// pro同步所以先输出a,然后setT函数加入到宏队列,then加入到微队列
// 然后输出c 然后输出微队列的1 最后输出宏队列的b
// a c 1 b
// 3.在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并被catchable捕获
const pro = new Promise((resolve,reject) => {
throw new Error('123');//pro:rejected
})
pro.then(data=>{
console.log(data)
})
pro.catch(err=>{
console.log(err)
})
// 4.一旦状态推向了已决无法做任何修改
// 练习
function biaobai(god){
return new Promise((resolve,reject)=>{
console.log(`俊凯向${god}表白`);
setTimeout(()=>{
if(Math.random() < 0.1){
resolve(true)
}else{
resolve(false)
}
},3000)
})
}
biaobai('XX').then(result => {
console.log(result);
})
</script>
</html>
// 辅助函数,把传进来的对象拼接成url的字符串
function toData(obj) {
if (obj === null) {
return obj;
}
let arr = [];
for (let i in obj) {
let str = i + "=" + obj[i];
arr.push(str);
}
return arr.join("&");
}
// 封装Ajax
function ajax(obj) {
return new Promise((resolve, reject) => {
//指定提交方式的默认值
obj.type = obj.type || "get";
//设置是否异步,默认为true(异步)
obj.async = obj.async || true;
//设置数据的默认值
obj.data = obj.data || null;
// 根据不同的浏览器创建XHR对象
let xhr = null;
if (window.XMLHttpRequest) {
// 非IE浏览器
xhr = new XMLHttpRequest();
} else {
// IE浏览器
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 区分get和post,发送HTTP请求
if (obj.type === "post") {
xhr.open(obj.type, obj.url, obj.async);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
let data = toData(obj.data);
xhr.send(data);
} else {
let url = obj.url + "?" + toData(obj.data);
xhr.open(obj.type, url, obj.async);
xhr.send();
}
// 接收返回过来的数据
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve(JSON.parse(xhr.responseText))
} else {
reject(xhr.status)
}
}
}
})
}
ajax({
url: "./data/students.json?name=李华"
}).then(resp => {
console.log(resp)
}, err => {
console.log(err)
})
8-4Promise的组合
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 当后续的Promise需要用到之前的Promise的处理结果时,需要Promise的串联
// Promise对象中,无论是then方法还是catch方法,他们都具有一个返回值,返回的是一个全新的Promise对象
// 1.如果当前的Promise是未决的,name新的Promise也是挂起状态。
// 2.如果当前Promise是已决状态,会运行后续的处理函数,并将后续的处理函数结果(返回值)作为resolved状态数据数据,应用
// 到新的Promise中,如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到Promise中
// 后续的Promise一定会等到前面的Promise等到结果后才会变成已决状态
const pro1 = new Promise((resolve,reject) =>{
resolve(1);
})
const pro2 = pro1.then(result => result*2);
console.log(pro2)//挂起
// pro2的类型:promise
// pro2的状态:
pro2.then(result => console.log(result),err => console.log(err));
// 输出2
// 练习
const pro1 = new Promise((resolve,reject) => {
throw 1;
})
const pro2 = pro1.then(result => {
return result *2
},err => err*3);
pro2.then(result => console.log(result*2),err => console.log(err * 3));
// 输出:6
// 练习、
const pro1 = new Promise((resolve,reject) => {
throw 1;
})
const pro2 = pro1.then(result => {
return result * 2
},err => {
return err * 3;
});
pro1.catch(err => {
return err * 2;
})//catch和rthen互不干扰
pro2.then(result => console.log(result * 2),err => console.log(err*3))
// 输出6
// 表白函数改变
biaobai('女神1').then(resp => {
if(resp){
console.log('1同意了');
return
}else{
return biaobai('女神2');
}
}).then(resp => {
if(resp === undefined){
return
}else if(resp){
console.log('2同意了');
return ;
}else{
return biaobai('女神3');
}
})
// 优化:
const gods = ['女神1','女神2','女神3'];
let pro;
for(let i = 0;i < gods.length;i++){
if(i === 0){
pro = biaobai(gods[i]);
}
pro = pro.then(resp => {
if(resp === undefined){
return
}else if(resp){
console.log(`${gods[i]}同意了`)
return;
}else {
console.log(`${gods[i]}拒绝了`)
if(i<gods.length -1){
return biaobai(gods[i + 1])
}
}
})
}
</script>
</html>
8-5Promise的其他API
原型成员 (实例成员)
- then:注册一个后续处理函数,当Promise为resolved状态时运行该函数
- catch:注册一个后续处理函数,当Promise为rejected状态时运行该函数
- finally:[ES2018]注册一个后续处理函数(无参),当Promise为已决时运行该函数
## 构造函数成员 (静态成员)
- resolve(数据):该方法返回一个resolved状态的Promise,传递的数据作为状态数据
- 特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象
- reject(数据):该方法返回一个rejected状态的Promise,传递的数据作为状态数据
- all(iterable):这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
- race(iterable):当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象
8-7 async和await
# async 和 await
async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
## async
目的是简化在函数的返回值中对Promise的创建
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
```js
async function test(){
console.log(1);
return 2;
}
//等效于
function test(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
await
await关键字必须出现在async函数中!!!!
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。
async function test1(){
console.log(1);
return 2;
}
async function test2(){
const result = await test1();
console.log(result);
}
test2();
等效于
function test1(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
function test2(){
return new Promise((resolve, reject)=>{
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
test2();
如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 获取梨花所在班级的老师信息
// 1.获取梨花的班级ID
// 2.根据班级ID获取梨花所在班级的老师ID
// 3.根据老师的id查询老师的信息
async function getTeacher(){
const stus = await ajax({
url:''
})
let cid;
for(let i = 0;i < stus.length;i++){
if(stus[i].name === '梨花'){
cid = stud[i].classId;
}
}
const cls = await ajax({
url:''
})
let tid;
for(let i = 0;i < cls.length;i++){
if(cls[i].id === cid){
tid = cls[i].teacherId;
}
}
const ts = await ajax({
url:''
})
for(let i = 0;i < ts.length;i++){
const element = ts[i];
if(element.id ===tid){
console.log(element);
}
}
}
getTeacher();
// 练习
async function biaobaiall(){
const gods = ['1','2','3'];
for(let i = 0;i < gods.length;i++){
const g = gods[i];
const result = await biaobai(g);
if(result){
console.log(`${g}同意了,不用再表白了!!`)
break;
}else{
console.log(`${g}没有同意`)
}
}
}
</script>
</body>
</html>
9-1Fetch Api概述
# 9-1. Fetch Api 概述
**XMLHttpRequest的问题**
1. 所有的功能全部集中在同一个对象上,容易书写出混乱不易维护的代码
2. 采用传统的事件驱动模式,无法适配新的 Promise Api
**Fetch Api 的特点**
1. 并非取代 AJAX,而是对 AJAX 传统 API 的改进
2. 精细的功能分割:头部信息、请求信息、响应信息等均分布到不同的对象,更利于处理各种复杂的 AJAX 场景
3. 使用 Promise Api,更利于异步代码的书写
4. Fetch Api 并非 ES6 的内容,属于 HTML5 新增的 Web Api
5. 需要掌握网络通信的知识
9-2Fetch Api的使用
# 基本使用
> 请求测试地址:http://101.132.72.36:5100/api/local
使用 ```fetch```函数即可立即向服务器发送网络请求
## 参数
该函数有两个参数:
1. 必填,字符串,请求地址
2. 选填,对象,请求配置
**请求配置对象**
- method:字符串,请求方法,默认值GET
- headers:对象,请求头信息
- body: 请求体的内容,必须匹配请求头中的 Content-Type
- mode:字符串,请求模式
- cors:默认值,配置为该值,会在请求头中加入 origin 和 referer
- no-cors:配置为该值,不会在请求头中加入 origin 和 referer,跨域的时候可能会出现问题
- same-origin:指示请求必须在同一个域中发生,如果请求其他域,则会报错
- credentials: 如何携带凭据(cookie)
- omit:默认值,不携带cookie
- same-origin:请求同源地址时携带cookie
- include:请求任何地址都携带cookie
- cache:配置缓存模式
- default: 表示fetch请求之前将检查下http的缓存.
- no-store: 表示fetch请求将完全忽略http缓存的存在. 这意味着请求之前将不再检查下http的缓存, 拿到响应后, 它也不会更新http缓存.
- no-cache: 如果存在缓存, 那么fetch将发送一个条件查询request和一个正常的request, 拿到响应后, 它会更新http缓存.
- reload: 表示fetch请求之前将忽略http缓存的存在, 但是请求拿到响应后, 它将主动更新http缓存.
- force-cache: 表示fetch请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取. 除非没有任何缓存, 那么它将发送一个正常的request.
- only-if-cached: 表示fetch请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取. 如果没有缓存, 它将抛出网络错误(该设置只在mode为”same-origin”时有效).
## 返回值
fetch 函数返回一个 Promise 对象
- 当收到服务器的返回结果后,Promise 进入resolved状态,状态数据为 Response 对象
- 当网络发生错误(或其他导致无法完成交互的错误)时,Promise 进入 rejected 状态,状态数据为错误信息
**Response对象**
- ok:boolean,当响应消息码在200~299之间时为true,其他为false
- status:number,响应的状态码
- text():用于处理文本格式的 Ajax 响应。它从响应中获取文本流,将其读完,然后返回一个被解决为 string 对象的 Promise。
- blob():用于处理二进制文件格式(比如图片或者电子表格)的 Ajax 响应。它读取文件的原始数据,一旦读取完整个文件,就返回一个被解决为 blob 对象的 Promise。
- json():用于处理 JSON 格式的 Ajax 的响应。它将 JSON 数据流转换为一个被解决为 JavaScript 对象的promise。
- redirect():可以用于重定向到另一个 URL。它会创建一个新的 Promise,以解决来自重定向的 URL 的响应。
function getProvinces(){
const url = '';
const config = {
method:'POST',
headers:{
'Content-type':'application',
a:1
}
}
fetch(url,config)
}
document.querySelector('button').onclick = function(){
getProvinces();
}
<script>
async function getProvinces(){
const url = '';
const config = {
method:'POST',
headers:{
'Content-type':'application',
a:1
}
}
try{
const resp = await fetch(url,config);
console.log(resp)
}
catch(err){
console.log(err);
}
}
document.querySelector('button').onclick = function(){
getProvinces();
}
</script>
9-3Request对象
# Request 对象
除了使用基本的fetch方法,还可以通过创建一个Request对象来完成请求(实际上,fetch的内部会帮你创建一个Request对象)
```js
new Request(url地址, 配置)
注意点:
尽量保证每次请求都是一个新的Request对象
// 除了使用基本的fetch还可以通过创建一个request对象来完成请求(实际上,fetch
// 的内部会帮你创建一个Request对象)
// 通用配置
function getRequestInfo(){
const url = '';
const req = new Request(url,{});
return req;
}
async function getProvinces(){
const resp = await fetch(req)
const Request = await resp.json();
console.log(result);
}
document.querySelector('button').onclick = function(){
getProvinces();
}
9-4Response对象
9-5Header对象
# Headers 对象
在Request和Response对象内部,会将传递的请求头对象,转换为Headers
Headers对象中的方法:
- has(key):检查请求头中是否存在指定的key值
- get(key): 得到请求头中对应的key值
- set(key, value):修改对应的键值对
- append(key, value):添加对应的键值对
- keys(): 得到所有的请求头键的集合
- values(): 得到所有的请求头中的值的集合
- entries(): 得到所有请求头中的键值对的集合
9-6文件上传
# 文件上传
流程:
1. 客户端将文件数据发送给服务器
2. 服务器保存上传的文件数据到服务器端
3. 服务器响应给客户端一个文件访问地址
> 测试地址:http://101.132.72.36:5100/api/upload
> 键的名称(表单域名称):imagefile
请求方法:POST
请求的表单格式:multipart/form-data
请求体中必须包含一个键值对,键的名称是服务器要求的名称,值是文件数据
> HTML5中,JS仍然无法随意的获取文件数据,但是可以获取到input元素中,被用户选中的文件数据
> 可以利用HTML5提供的FormData构造函数来创建请求体
<!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>Document</title>
</head>
<body>
<img src="" alt="" id="imgAvatar">
<input type="file" id="avatar">
<button>上传</button>
<script>
async function upload() {
const inp = document.getElementById("avatar");
if (inp.files.length === 0) {
alert("请选择要上传的文件");
return;
}
const formData = new FormData(); //构建请求体
formData.append("imagefile", inp.files[0]);
const url = "http://101.132.72.36:5100/api/upload"
const resp = await fetch(url, {
method: "POST",
body: formData //自动修改请求头
});
const result = await resp.json();
return result;
}
document.querySelector("button").onclick = async function() {
const result = await upload();
const img = document.getElementById("imgAvatar")
img.src = result.path;
}
</script>
</body>
</html>
10-1迭代器和生成器
# 迭代器
## 背景知识
1. 什么是迭代?
从一个数据集合中按照一定的顺序,不断取出数据的过程
2. 迭代和遍历的区别?
迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完
遍历强调的是要把整个数据依次全部取出
3. 迭代器
对迭代过程的封装,在不同的语言中有不同的表现形式,通常为对象
4. 迭代模式
一种设计模式,用于统一迭代过程,并规范了迭代器规格:
- 迭代器应该具有得到下一个数据的能力
- 迭代器应该具有判断是否还有后续数据的能力
## JS中的迭代器
JS规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下:
```js
{value: 值, done: 是否迭代完成}
则认为该对象是一个迭代器
含义:
- next方法:用于得到下一个数据
- 返回的对象
- value:下一个数据的值
- done:boolean,是否迭代完成
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 迭代器:一个个拿数据,调用next拿数据
const obj = {
next(){
return{
value:xxx.next,
done:xx
}
}
}
const arr = [1,2,3,4,5];
const arr2 = [1,22,22,2];
// 迭代器创建函数
function createIterator(arr){
let i = 0;//当前数组下标
return{
next(){}
}
var result = {
value:arr[i],
data:i >= arr.length
}
i++;
return result;
}
// // 2.迭代数组arr
// const iterator = {
// i : 0,//当前的数组下标
// next(){
// var result = {
// value: arr[this,i],
// done:this.i >= arr.length
// }
// this.i++;
// return result;
// }
// }
// console.log(iterator.next());//value:0,done:false;
// 让迭代器不断取出下一个数据,知道没有数据为止
let data = iterator.next();
while(!data.done){
console.log(data.value)
// 进行下一次迭代
data = iterator.next();
}
console.log('迭代完成');
const iter1 = createIterator(arr);
const iter2 = createIterator(arr2);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 依次得到斐波那契数列前面n位的值
// 创建一个斐波那契数列的迭代器(封装了取数据的过程)
function createFeiboInterator(){
let prev1 = 1,
prev2 = 1,
n = 1;//当前是第几位
return{
next(){
let value;
if(n <= 2){
value = 1;
}else{
value = prev1+prev2
}
const result = {
value,
done:false
};
prev2 = prev1;
prev1 = result.value;
n++;
return result;
}
}
}
const iterrator = createFeiboInterator();
</script>
</body>
</html>
10-2可迭代协议与for-of循环
概念回顾
- 迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成
- 迭代器创建函数(iterator creator):一个返回迭代器的函数
可迭代协议
ES6规定,如果一个对象具有知名符号属性Symbol.iterator
,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)
思考:如何知晓一个对象是否是可迭代的?
思考:如何遍历一个可迭代对象?
for-of 循环
for-of 循环用于遍历可迭代对象,格式如下
//迭代完成后循环结束
for(const item in iterable){
//iterable:可迭代对象
//item:每次迭代得到的数据
}
展开运算符与可迭代对象
展开运算符可以作用于可迭代对象,这样,就可以轻松的将可迭代对象转换为数组。
// 可迭代对象
var obj = {
[Symbol.iterator](){
return{
next(){
return{
value:1,
Document:false
}
}
}
}
}
// 数组就是个可迭代对象
const arr = [1,2,3,4,3,2];
const iterator = arr[Symbol.iterator]();
// 用迭代器取出数组的所有属性、
const arr = [1,2,1,2,3,3,2]
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while(!result.done){
const item = result.value;
console.log(item);
// 下一次迭代
result=iterator.next();
}
// for-of
for(const item of arr){
console.log(item)
}
10-3生成器 (Generator)
- 什么是生成器?
生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象
- 如何创建生成器?
生成器的创建,必须使用生成器函数(Generator Function)
- 如何书写一个生成器函数呢?
//这是一个生成器函数,该函数一定返回一个生成器
function* method(){
}
- 生成器函数内部是如何执行的?
生成器函数内部是为了给生成器的每次迭代提供的数据
每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置
yield是一个关键字,该关键字只能在生成器函数内部使用,表达“产生”一个迭代数据。
- 有哪些需要注意的细节?
1). 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中
2). 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值
3). 第一次调用next方法时,传参没有任何意义
4). 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
- 生成器的其他API
- return方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束
- throw方法:调用该方法,可以在生成器中产生一个错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function* test(){
console.log('第一次运行')
yield 1;
console.log('第二次运行')
yield 2;
console.log('第三次运行')
}
const generator = test();
// 练习去处数组中每一项
const arr1 = [1,2,2,2,1];
const arr2 = [1,3,2,1,3];
function* createIterator(arr){
for(const item of arr){
yield item;
}
}
const iter1 = createIterator(arr1);
const iter2 = createIterator(arr2);
// 练习 斐波那契数列的迭代器er
function* createFeiboIterator(){
let prev1 = 1,
prev2 = 1.
n = 1;
while(true){
if(n <= 2){
yield 1;
}else{
const newValue = prev1 + prev2
yield newValue;
prev2 = prev1;
prev1 = newValue;
}
n++;
}
}
const iterator = createFeiboIterator();
//生成器函数之间的相互调用
function* t1(){
yield 'a'
yield 'b'
}
function* test(){
yield* t1();
yield 1;
yield 2;
yield 3;
}
const generator = test();
</script>
</body>
</html>
10-4生成器应用-异步任务控制
<!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>Document</title>
</head>
<body>
<script>
function* task() {
const d = yield 1;
console.log(d)
// //d : 1
const resp = yield fetch("http://101.132.72.36:5100/api/local")
const result = yield resp.json();
console.log(result);
}
run(task)
function run(generatorFunc) {
const generator = generatorFunc();
let result = generator.next(); //启动任务(开始迭代), 得到迭代数据
handleResult();
//对result进行处理
function handleResult() {
if (result.done) {
return; //迭代完成,不处理
}
//迭代没有完成,分为两种情况
//1. 迭代的数据是一个Promise
//2. 迭代的数据是其他数据
if (typeof result.value.then === "function") {
//1. 迭代的数据是一个Promise
//等待Promise完成后,再进行下一次迭代
result.value.then(data => {
result = generator.next(data)
handleResult();
})
} else {
//2. 迭代的数据是其他数据,直接进行下一次迭代
result = generator.next(result.value)
handleResult();
}
}
}
</script>
</body>
</html>
11-1set集合
set 集合
一直以来,JS只能使用数组和对象来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。因此,ES6新增了两种集合类型(set 和 map),用于在不同的场景中发挥作用。
set用于存放不重复的数据
- 如何创建set集合
new Set(); //创建一个没有任何内容的set集合
new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
- 如何对set集合进行后续操作
- add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
- set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
- has(数据): 判断set中是否存在对应的数据
- delete(数据):删除匹配的数据,返回是否删除成功
- clear():清空整个set集合
- size: 获取set集合中的元素数量,只读属性,无法重新赋值
- 如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);
// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
const arr = [...s];
- 如何遍历
1). 使用for-of循环
2). 使用set中的实例方法forEach
注意:set集合中不存在下标,因此forEach中的回调的第二个参数和第一个参数是一致的,均表示set中的每一项
// set集合,用来存放不重复的数据
const s1 = new Set([1,2,3,4,6,6]);
console.log(s1);//会自动去重{1,2,3,4,6}
const s2 = new Set('aassssas');
console.log(s2);//{a,s}
//add添加一个数据到set集合末尾,数据存在不进行任何操作
const s1 = new Set();
s1.add(1);
s1.add(2);
s1.add(2);//无效
s1.add(-0);
s1.add(+0);//无效
// 得到一个去重后的数组
const arr = [1,2,2,3];
const result = [...new Set(arr)];
console.log(result)
//
arr.forEach((item,index,s)=>{
console.log(item,index,s)
})
// 字符串去重
const str = 'aaaas';
const s = [...new Set(str)].join('');
<script>
// 求两个数组的并集交集差集
// 并集
const arr1 = [1,9,8,0];
const arr2 = [8,0,9,8];
const s = new Set(arr1,concat(arr2));
const result = [...s];
// const result = [...new Set(arr1.concat(arr2))];
console.log(result);
//交集
const r = [...new Set(arr1)].filter(item => arr2.indexOf(item) >= 0);
// 差集
const y = [...new Set([arr1,arr2])].filter(item => arr1.indexOf(item)>=0 && arr2.indexOf(item)<0 || arr2.indexOf(item)>=0 && arr1.indexOf(item) < 0)
// 或
const i = [...new Set([...arr1,...arr2])].filter(item => cross.indexOf(item)<0);
</script>
11-4Map集合
map集合
键值对(key value pair)数据集合的特点:键不可重复
map集合专门用于存储多个键值对数据。
在map出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。
使用对象存储有以下问题:
-
键名只能是字符串
-
获取数据的数量不方便
-
键名容易跟原型上的名称冲突
-
如何创建map
new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
- 如何进行后续操作
- size:只读属性,获取当前map中键的数量
- set(键, 值):设置一个键值对,键和值可以是任何类型
- 如果键不存在,则添加一项
- 如果键已存在,则修改它的值
- 比较键的方式和set相同
- get(键): 根据一个键得到对应的值
- has(键):判断某个键是否存在
- delete(键):删除指定的键
- clear(): 清空map
- 和数组互相转换
和set一样
- 遍历
- for-of,每次迭代得到的是一个长度为2的数组
- forEach,通过回调函数遍历
- 参数1:每一项的值
- 参数2:每一项的键
- 参数3:map本身
12-1属性描述符
Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息
通过Object.getOwnPropertyDescriptor(对象, 属性名)
可以得到一个对象的某个属性的属性描述符
- value:属性值
- configurable:该属性的描述符是否可以修改
- enumerable:该属性是否可以被枚举
- writable:该属性是否可以被重新赋值
Object.getOwnPropertyDescriptors(对象)
可以得到某个对象的所有属性描述符
如果需要为某个对象添加属性时 或 修改属性时, 配置其属性描述符,可以使用下面的代码:
Object.defineProperty(对象, 属性名, 描述符);
Object.defineProperties(对象, 多个属性的描述符)
存取器属性
属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。
get 和 set配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行get方法,将get方法得到的返回值作为属性值;如果给该属性赋值,则会运行set方法。
存取器属性最大的意义,在于可以控制属性的读取和赋值。
12-2Reflect
- Reflect是什么?
Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能
由于它类似于其他语言的反射,因此取名为Reflect
- 它可以做什么?
使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能
- 这些功能不是已经存在了吗?为什么还需要用Reflect实现一次?
有一个重要的理念,在ES5就被提出:减少魔法、让代码更加纯粹
这种理念很大程度上是受到函数式编程的影响
ES6进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了Reflect对象
因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。
- 它里面到底提供了哪些API呢?
- Reflect.set(target, propertyKey, value): 设置对象target的属性propertyKey的值为value,等同于给对象的属性赋值
- Reflect.get(target, propertyKey): 读取对象target的属性propertyKey,等同于读取对象的属性值
- Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定this和参数列表。等同于函数调用
- Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
- Reflect.defineProperty(target, propertyKey, attributes):类似于Object.defineProperty,不同的是如果配置出现问题,返回false而不是报错
- Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
- Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
- 其他API:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
12-3Proxy 代理
代理:提供了修改底层实现的方式
//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现
//返回一个代理对象
new Proxy(target, handler)
12-4 观察者模式
有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。
13-1新增的数组API
静态方法
- Array.of(…args): 使用指定的数组项创建一个新数组
- Array.from(arg): 通过给定的类数组 或 可迭代对象 创建一个新的数组。
-(ES5中类数组转化成数组) Array.prototype.slice.call(likeArray);
实例方法
- find(callback): 用于查找满足条件的第一个元素
- findIndex(callback):用于查找满足条件的第一个元素的下标
- fill(data):用指定的数据填充满数组所有的内容
- copyWithin(target, start?, end?): 在数组内部完成复制
- includes(data):判断数组中是否包含某个值,使用Object.is匹配