es6系统学习

-------------------------武汉加油!陕西加油!中国加油!-------------------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

# asyncawait

asyncawaitES2016 新增两个关键字,它们借鉴了 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)

  1. 什么是生成器?

生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象

  1. 如何创建生成器?

生成器的创建,必须使用生成器函数(Generator Function)

  1. 如何书写一个生成器函数呢?
//这是一个生成器函数,该函数一定返回一个生成器
function* method(){

}
  1. 生成器函数内部是如何执行的?

生成器函数内部是为了给生成器的每次迭代提供的数据

每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置

yield是一个关键字,该关键字只能在生成器函数内部使用,表达“产生”一个迭代数据。

  1. 有哪些需要注意的细节?

1). 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中
2). 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值
3). 第一次调用next方法时,传参没有任何意义
4). 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号

  1. 生成器的其他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用于存放不重复的数据

  1. 如何创建set集合
new Set(); //创建一个没有任何内容的set集合

new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果

  1. 如何对set集合进行后续操作
  • add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
    • set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
  • has(数据): 判断set中是否存在对应的数据
  • delete(数据):删除匹配的数据,返回是否删除成功
  • clear():清空整个set集合
  • size: 获取set集合中的元素数量,只读属性,无法重新赋值
  1. 如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);
// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
const arr = [...s];
  1. 如何遍历

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出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。

使用对象存储有以下问题:

  1. 键名只能是字符串

  2. 获取数据的数量不方便

  3. 键名容易跟原型上的名称冲突

  4. 如何创建map

new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
  1. 如何进行后续操作
  • size:只读属性,获取当前map中键的数量
  • set(键, 值):设置一个键值对,键和值可以是任何类型
    • 如果键不存在,则添加一项
    • 如果键已存在,则修改它的值
    • 比较键的方式和set相同
  • get(键): 根据一个键得到对应的值
  • has(键):判断某个键是否存在
  • delete(键):删除指定的键
  • clear(): 清空map
  1. 和数组互相转换

和set一样

  1. 遍历
  • 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

  1. Reflect是什么?

Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能

由于它类似于其他语言的反射,因此取名为Reflect

  1. 它可以做什么?

使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能

  1. 这些功能不是已经存在了吗?为什么还需要用Reflect实现一次?

有一个重要的理念,在ES5就被提出:减少魔法、让代码更加纯粹

这种理念很大程度上是受到函数式编程的影响

ES6进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了Reflect对象

因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。

  1. 它里面到底提供了哪些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匹配
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值