JavaScript基础(3)

参考资料:课程链接

1.ES6

我们所说的ESS和ES6其实就是在js语法的发展过程中的一个版本而已

  • ECMAScript 就是js的语法
    • 以前的版本没有某些功能
    • 在ES5这个版本的时候增加了一些功能
    • 在ES6这个版本的时候增加了一些功能
  • 因为浏览器是浏览器商生产的
    • ECMAScript发布了新的功能以后,浏览器商需要让自己的浏览器支持这些功能
    • 这个过程是需要时间的
    • 所以到现在,基本上大部分浏览器都可以比较完善的支持了
    • 只不过有些浏览器还是不能全部支持
    • 这就出现了兼容性问题
    • 所以我们写代码的时候就要考虑哪些方法是ES5或者ES6的,看看是不是浏览器都支持
  • let和const关键字
    • 我们以前都是使用var关键字来声明变量的
    • 在ES6的时候,多了两个关键字let和const.也是用来声明变量的
    • 只不过和var有一些区别
      1. 1et和const不允许重复声明变量
      2. 1et和const不允许提前使用变量
      3. const不可以更改值

1.1.ES定义变量

在定义var变量前使用变量并不会报错,因为发生了变量提升

console.log(a) //在定义var变量前使用变量并不会报错,因为发生了变量提升
var a = 100
console.log(a)
  • let
    与var的区别:
    1. 必须在定义后使用;
    2. 变量不能重名;
    3. 块级作用域(只存活于定义它的区域{}):
// console.log(b) //会报错,不能在定义前使用
let b = 100
// let b = 3
console.log(b)
if (1) {
    var i = 100
    let j = 99 //只存活于定义它的区域
    console.log(i)
    console.log(j)
}
console.log(i)
// console.log(j) //显示未定义,不能访问
  • const
    let 变量
    const 常量,普通类型其值不能更改,复杂数据类型比如obj,可以更改obj里面的值(但其实也没有更改,因为指向obj的指针并未改变);初始化不赋值也不行
// let 变量
let name = 'xiaoming'

const age = 21
name = 'daming'
// age = 23 不能修改
console.log(name, age)

// const num 初始化不赋值会报错

1.2.案例-块级作用域

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        
        ul {
            list-style: none;
        }
        
        .header {
            display: flex;
            width: 500px;
        }
        
        .header li {
            flex: 1;
            height: 50px;
            line-height: 50px;
            text-align: center;
            border: 1px solid #000;
        }
        
        .box {
            position: relative;
        }
        
        .box li {
            position: absolute;
            left: 0;
            top: 0;
            width: 500px;
            height: 200px;
            background-color: yellow;
            display: none;
        }
        
        .header .active {
            background-color: #f00;
        }
        
        .box .active {
            display: block;
        }
    </style>
</head>

<body>
    <ul class="header">
        <li class="active">1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
    <ul class="box">
        <li class="active">111</li>
        <li>222</li>
        <li>333</li>
        <li>444</li>
    </ul>

    <script>
        var oHeaderItems = document.querySelectorAll('.header li')
        var oBoxItems = document.querySelectorAll('.box li')

        /*下面这个方法有坑,可能人为点击来不及响应就已经for循环完了,因此每次点击输出的都是4
        for (var i = 0; i < oHeaderItems.length; i++) {
            oHeaderItems[i].onclick = function() {
                console.log(i)
            }
        } */

        for (let i = 0; i < oHeaderItems.length; i++) {
            // 1.自定义属性
            oHeaderItems[i].dataset.index = i
                // oHeaderItems[i].onclick = handler
            oHeaderItems[i].onclick = function() {
                console.log(i)
                    // var 时输出的全是4,因为for循环结束了,我们还没来得及点击;
                    // let 它有暂存性死区,它会保留当前的值
            }
        }
    </script>
</body>

</html>

1.3.ES6的箭头函数

//普通写法
var test1 = function() {
    console.log('11')
}
// 箭头写法
var test2 = () => {
    console.log('22')
}
test1()
test2()

箭头写法的特点:

  • 1.(只有一个形参时)小括号可以省略;
  • 2.{}可以省略 只有一句代码或只有返回值的时候;
  • 3.没有arguments;
  • 4.箭头函数的this是父级作用域的
//1.(只有一个形参时)小括号可以省略;
var test3 = a => {
    console.log('22', a)
}
test3('a')
//2.{}可以省略 只有一句代码或只有返回值的时候
//返回值,这种用法偏多
// 使用这种方式返回对象类型时,记得在{}对象外面加上一个()比如var test4 = a => ({name:'brisa'})
var test4 = a => 100 * a
console.log(test4(2))

var list = ['aaa', 'bbb', 'ccc']
    // var newList = list.map(function(item) {
    //     return `<li>${item}</li>`
    // })

var newList = list.map(item => `<li>${item}</li>`)
console.log(newList.join())

// 一句话
var test5 = () => console.log('thanks')
test5()
// 3.没有arguments
var test6 = function() {
    console.log(arguments) //函数内自定义的一个特殊变量
    console.log(arguments[0], arguments[1], arguments[2])
        // console.log(Array.from(arguments))//转成数组

    // var test6 = function(a, b, c) {
    // console.log(a, b, c)
}
test6(1, 2, 3)
    /*var test6 = () => {
        console.log(arguments)//报错,箭头函数没有arguments
    }
    test7(1, 2, 3)*/
// 4.箭头函数的this是父级作用域的
mytext.oninput = function() {
    var that = this
    setTimeout(function() {
        console.log(this) //window
        console.log(that) //input 用that赋值,指代父级的this
    }, 1000)
    setTimeout(() => console.log(this), 1000) //input,父级作用域

}
// 函数的默认参数
function test8(a=1, b=2) {//给1个默认参数,防止程序崩坏
    return a + b
}
// var test8 = (a=1,b=2)=>a+b
console.log(test8())

1.4.ES6的解构赋值

ES6的解构赋值:快速的从对象和数组中获取里面的成员

// 快速的从对象和数组中获取里面的成员
var arr = ['xiaoming', 'tiechui', 'shanzhen']
let [x, y, z] = arr //解构赋值
console.log(x) //arr[0]
// 用解构赋值的方式交换值
// 这种交换值方式不能用let定义,会出错
var a = 5
var b = 10
var [a, b] = [b, a] //交换
console.log(a, b)
var arr2 = [1, 2, [3, 4, [5]]] //多维数组
var [i, j, [k, l, [m]]] = arr2 //可以快速获取值
console.log(arr2[2][2][0], m)
var obj = {
    name: 'brisa',
    age: 100,
    location: 'SiChuan',
}
let {
    name,age,location: mylocation //冲突时,可以重命名
    } = obj

// document.write(obj.name)
// document.write(obj.age)
// document.write(obj.location)

document.write(name)
document.write(age)
document.write(location)//window
document.write(mylocation)
var obj2 = {
    name: 'brisa',
    age: 100,
    location: {
        province: 'SiChuan',
        city: 'chengdu'
    },
    hobby: ['1', '2', '3']
}
var {
    name,age,
    location: {
        province,
        city
    },
    hobby: [m, n, l]
} = obj2
console.log(name, age, province, city, m, n, l)

1.5.ES6的对象简写

<input type="text" id="myusername">
<input type="password" id="mypassword">
<button id="mybtn">login</button>
<script>
    mybtn.onclick = function() {
        let username = myusername.value
        let password = mypassword.value
        console.log(usn, psw)

        /*var obj = {
            username: username,
            password: password
        }*/

        // key和value相同(命名<变量名>)时,就可以简写
        var obj = {
            username,
            password
        }
        console.log('发给后端的结构',
            obj)
</script>
        
        var obj1 = {
            a: 1,
            // getName: function() {
            //     console.log(this.a)
            // }

            //简写如下
            getName() {
                console.log(this.a)
            }
        }
        obj1.getName()
    }

1.6.ES6展开运算符

  1. … 展开数组
var a = [1, 2, 3]
var b = [4, 5, 6]

// console.log(a.concat(b))
//数组
var c = [a, b] //(2) [Array(3), Array(3)] 
// 展开
var d = [...a, ...b] //(6) [1, 2, 3, 4, 5, 6]
console.log(c)
console.log(d)
  1. … 复制
var a = [1, 2, 3]
//b改变会改变a
// var b = a 

//b改变不会改变a
// var b = a.slice()
// var b = a.concat()
var b = [...a]
b[0] = 'brisa'
console.log(a, b)

  1. … 参数-实参-形参
var test1 = function() {
        console.log(arguments)
    }
    /*var test2 = () => {
        console.log(arguments) //箭头函数,没有arguments
    }*/

//形参的运用
var test3 = (a, b, ...arr) => { //修改为 ...arr就可以正确执行了(接收的是对应参数组成的数组)
    // ...arr这种形参必须在最后
    console.log(arr)
}

test1(1, 2, 3, 4, 5)
    // test2(1, 2, 3)
test3(1, 2, 3, 4, 5)

// 实参的运用
var arr = [1, 2, 35, 5, 6, 78, 9]
var res = Math.max(...arr)
console.log(res)
  1. … 伪数组转换
function test() {
    // arguments会是一个伪数组
    // 如何将其转成真正的数组?
    // 1. Array.from(arguments)

    // 2.
    var arr = [...arguments]
    console.log(arr)
}
test(1, 2, 3, 4, 5)

var oli = document.querySelectorAll('li')//不是真的数组
var oliarr = [...oli]//转成真的数组
console.log(oli, oliarr)
  1. …对象
var obj1 = {
    name: 'brisa',
    age: 100
}
var obj2 = {
    name: 'tiechui',
    location: 'sichuan'
}
var obj = { //合并对象,同key的会被后面的value覆盖掉
    ...obj1,
    ...obj2
}
console.log(obj)

一个关于 …对象的小案例

<h1>修改</h1>
<input type="text" id="myusername">
<input type="number" id="myage">
<button id="btn">修改</button>
<div id="box"></div>
<script>
var obj = {
    name: 'brisa',
    age: 100,
    location: 'sichuan',
    id: '20001128',
}

// function render(obj) {
// console.log(obj)
// var {
//     name,
//     age,
//     location
// } = obj
function render({
    name,
    age,
    location
}) { //还可以这样写(解构赋值)
    box.innerHTML = `name:${name},age:${age}, location:${location}`
}
render(obj)
btn.onclick = function() {
    var name = myusername.value
    var age = myage.value

    var newObj = { //新的obj,对于原obj没有影响
        ...obj,
        name: name,
        age: age
    }
    //1.传给后端(省略没写)

    //2.重新渲染页面
    render(newObj)
}
</script>

1.7.ES6模块化语法

模块化:
解决如下问题,1.私密不漏;2.重名不怕;3.依赖不乱

  1. 私密不漏
//A.js 需要导出
function A1(){//...
}
//...
export {
    A1,
    A2,
    test,
    A_A,
}
//html中需要导入
<script type="module">
    import {A1, A2, test } from './module/A.js'; A1(); A2(); test(); 
</script>
  1. 重名不怕
<script type="module">
    // 用 as 重命名
    import {A1, A2, test as A_test} from './module/A.js'; 
    import {B1, B2, test as B_test} from './module/B.js'; 

    A_test(); 
    B_test(); 
</script>
  1. 依赖不乱
// js需要用到其他js文件中的函数,也可以直接通过export+import使用
import { A_A } from '../module/A.js'
import { B_B } from '../module/B.js'

A_A()
B_B()
function C() {
    console.log('C')
}
<!-- 一般放在<head>里 -->
<script src="./module/C.js" type="module"></script>

其他:
如果只用导出一个时,可以使用默认导出:export default XX,使用import XXX from '地址'XXXXX名字不要求一样,因为这里只导出了一个,取不同名字相当于重命名了

2.面向对象

  • 首先,我们要明确,面向对象不是语法,是一个思想,是一种编程模式
    • 面向:面(脸),向(朝着)
    • 面向过程:脸朝着过程=》关注着过程的编程模式
    • 面向对象:脸朝着对象=》关注着对象的编程模式
  • 实现一个效果
    • 在面向过程的时候,我们要关注每一个元素,每一个元素之间的关系,顺序,…
    • 在面向过程的时候,我们要关注的就是找到一一个对象来帮我做这个事情,我等待结果
  • 我们以前的编程思想是,每一一个功能,都按照需求一步一步的逐步完成

2.1.创建对象的方式

创建对象的方式
因为面向对象就是一个找到对象的过程,所以我们先要了解如何创建一个对象

  1. 用字面量的方式创建对象
//字面量
var obj1 = {
    name: '蒸羊羔',
    material: ['111', '222', '333'],
    setCook() {
        console.log('如何蒸羊羔')
    }
}
  1. 调用系统内置的构造函数创建对象
    js 给我们内置了一个Object构造函数,这个构造函数就是用来创造对象的
//内置构造函数
var obj2 = new Object()
obj2 = {
    name: '水煮蛋',
    material: ['111', '222', '333'],
    setCook() {
        console.log('如何水煮蛋')
    }
}
  1. 用工厂函数的方式创建对象
//工厂函数
function createObj(name, material) {
    var obj = {}
    obj.name = name
    obj.material = []
    return obj
}
var obj3 = createObj('西红柿')
console.log(obj3)
var obj4 = createObj('番茄炒蛋')
console.log(obj4)
  1. 自定义构造函数创建对象
// 自定义构造函数
// new Object() new String new Array()
function CreateObj2(name) {
    //自动创建对象
    this.name = name
    this.material = []
    this.cook = function(){

    }
    //自动返回对象
}
var obj5 = new CreateObj2('可乐鸡翅')
console.log(obj5)

2.2.构造函数要注意的问题

  1. 首字母大写
// 1.首字母大写
function CreateObj(name) {
    this.name = name
}
var obj1 = new CreateObj('brisa')
console.log(obj1)
  1. 构造函数不写return

  2. 构造函数能当成普通函数用?
    可以,但没必要

// 3.构造函数能当成普通函数用?
//   可以,但没必要
function CreateObj2(name) {
    this.name = name
}
var obj2 = CreateObj2('brisa')
console.log(obj2, window.name) //变成给窗口命名了
  1. this指向(new 完对象之后所创建的那个对象)
// 4.this指向(new 完对象之后所创建的那个对象)
function CreateObj3(name) {
    console.log(this) // new 完对象之后所创建的那个对象
    this.name = name

}
var obj3 = new CreateObj3('brisa')//new的过程也叫实例化的过程
console.log(obj3)

2.3.面向对象的原型

function CreateList(select, data) {
    this.ele = document.querySelector(select)
    this.title = data.title
    this.list = data.list

    this.render = function() {
        // 渲染页面
        var h1 = this.ele.querySelector('h1')
        var ul = this.ele.querySelector('ul')

        h1.innerHTML = this.title
        ul.innerHTML = this.list.map(item => `<li>${item}</li>`).join('')
    }
}

两个object会创建相同的render函数占用更多的内存空间,造成浪费

<div class="box1">
    <h1></h1>
    <ul></ul>
</div>
<div class="box2">
    <h1></h1>
    <ul></ul>
</div>
<script>
    var data = {
        title: '体育',
        list: ['体育-1', '体育-2', '体育-3'],
    }
    var data2 = {
        title: '生物',
        list: ['生物-1', '生物-2', '生物-3'],
    }

    function CreateList(select, data) {
        this.ele = document.querySelector(select)
        this.title = data.title
        this.list = data.list

        // 直接在构造对象里面写函数,会浪费空间
        // this.render = 
    }

    //原型(共享内存,节省空间)
    CreateList.prototype.render = function() {
        // 渲染页面
        var h1 = this.ele.querySelector('h1')
        var ul = this.ele.querySelector('ul')

        h1.innerHTML = this.title
        ul.innerHTML = this.list.map(item => `<li>${item}</li>`).join('')
    }


    var obj1 = new CreateList('.box1', data)
        // 一模一样
    console.log(obj1.__proto__)
    console.log(CreateList.prototype)

    //为什么这里可以访问到?
    // 答:obj会首先看自己有没有render,当发现没有时,会顺着__proto__去找有没有render(原因在于对象.__proto__  === 构造函数.prototype)
    obj1.render()
    console.log(obj1)

    var obj2 = new CreateList('.box2', data2)
    obj2.render()
    console.log(obj2)

    // 对象.__proto__  === 构造函数.prototype

    // 扩展: obj1.toString() 为什么可以找到?
    // 答:通过原型链
</script>

在这里插入图片描述

在这里插入图片描述

为什么obj1.toString()也存在?:

  1. 因为原型链,它会先看自己的构造函数有没有,没有就顺着__proto__,看他的构造函数.prototype,以此沿着原型链继续寻找,最后找到Object.toString(),然后成功发现存在
  2. Object.prototype.__proto__ -> null,原型链顶点就是Object.prototype

2.4.案例-选项卡-面向对象

obj.prototype.函数

当创建了对象后,对该对象重写该函数,也不会干扰其他对象相应的函数,因为(原型链),重写后相当于obj自身包含了getName,不用顺着原型链去到prototype上去找相应函数

// ...

CreateObj.prototype.getName = function(){console.log("11111",this.name)}

var obj1 = new Createobj("kerwin")
// 重写obj1的getName函数不会干扰obj2的getName函数
obj1.getName = function(){console.log('2222222',this.name)}

obj1.getName()

// 重写prototype上的函数就会对所有相关obj都有影响
// CreateObj.prototype.getName = function(){console.log("222",this.name)}

var obj2 = new Createobj("tiechui")
obj2.getName()

案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        
        ul {
            list-style: none;
        }
        
        .header {
            display: flex;
            width: 500px;
        }
        
        .header li {
            flex: 1;
            height: 50px;
            line-height: 50px;
            text-align: center;
            border: 1px solid #000;
        }
        
        .box {
            position: relative;
            height: 200px;
        }
        
        .box li {
            position: absolute;
            left: 0;
            top: 0;
            width: 500px;
            height: 200px;
            background-color: yellow;
            display: none;
        }
        
        .header .active {
            background-color: #f00;
        }
        
        .box .active {
            display: block;
        }
    </style>
</head>

<body>
    <div class="container1">
        <ul class="header">
            <li class="active">1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
        <ul class="box">
            <li class="active">111</li>
            <li>222</li>
            <li>333</li>
            <li>444</li>
        </ul>
    </div>
    <div class="container2">
        <ul class="header">
            <li class="active">1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
        <ul class="box">
            <li class="active">111</li>
            <li>222</li>
            <li>333</li>
            <li>444</li>
        </ul>
    </div>


    <!-- <script src="./js/Tabs.js"></script> -->
    <script type="module">
        import myTabs from './js/Tabs.js'; new myTabs('.container1', 'click'); new myTabs('.container2', 'mouseover')
    </script>
</body>

</html>
function Tabs(select, type) {
    var container = document.querySelector(select)
    this.oHeaderItems = container.querySelectorAll('.header li')
    this.oBoxItems = container.querySelectorAll('.box li')

    console.log(this.oHeaderItems, this.oBoxItems)

    this.type = type//改变触发事件的类型
    // 自行调用
    this.change()
}

Tabs.prototype.change = function() {
    for (let i = 0; i < this.oHeaderItems.length; i++) {
        // 1.自定义属性
        this.oHeaderItems[i].addEventListener(this.type, () => {
            // 2.1获取自身上的属性(使用this,获取当前对象)
            var index = i
            //2.2所有的都移除active
            for (var j = 0; j < this.oHeaderItems.length; j++) {
                this.oHeaderItems[j].classList.remove('active')
                this.oBoxItems[j].classList.remove('active')
            }
            //2.3给对应的加上active
            this.oHeaderItems[index].classList.add('active')
            this.oBoxItems[index].classList.add('active')
        })
    }
}

export default Tabs

2.5.ES6-class

class CreateObj {
    // 构造器函数
    constructor(name) {
        this.name = name
    }
    say() {
        console.log('hi')
    }
}
// CreateObj.prototype.say = function() {
//     console.log('hi')
// }
var obj1 = new CreateObj('brisa')
console.log(obj1)
obj1.say()

2.6.面向对象继承

构造函数继承—>属性
原型函数继承—>方法

构造函数继承

function student(name, age, classroom){
    Person.call(this, name, age)//继承Person中构造函数里的属性
    this.classroom = classroom
}

原型继承

student.prototype = new Person()//继承方法

组合继承!!!
构造函数继承+原型继承

//原有的
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.say = function() {
    console.log(this.name, 'hi')
}

重点!!!

//继承的部分
function Student(name, age, grade) {
    // 继承属性-方法1
    Person.call(this, name, age) //继承属性,强行改变了Person中的this为当前this(当前对象)

    // 继承属性-方法2
    // Person.apply(this, [name, age])

    //自己新增的东西
    this.grade = grade
}


// 1.原型函数继承
Student.prototype = new Person()

// 2.增加其他原型函数
Student.prototype.printGrade = function() {
    console.log(this.name, this.grade)
}

// 3.覆盖继承来的原型函数
/*Student.prototype.say = function() {
    console.log(this.name, 'hello哇')
    console.log(this.name, '你好哇')
}*/

// 4.增强-【要改名】
Student.prototype.say2 = function() {
    this.say() //首先继承过来

    //然后写自己的增强语句
    console.log(this.name, 'hello哇')
    console.log(this.name, '你好哇')
}


var obj = new Student('brisa', 100, 1000)
console.log(obj)
obj.say2()
// 这种也可以继承
var obj2 = {
    grade: 10000
}
Person.call(obj2, 'tiechui', 100)
console.log(obj2)

2.7.继承案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div class="box1">
        <h1></h1>
        <img src="" style="width:100px;">
        <ul></ul>
    </div>
    <div class="box2">
        <h1></h1>
        <img src="" style="width:100px;">
        <ul></ul>
    </div>
    <script>
        var data = {
            title: '体育',
            url: "https://pic.maizuo.com/usr/movie/5011ee407fb407d47e333a3935ec33d1.jpg?x-oss-process=image/quality,070",
            list: ['体育-1', '体育-2', '体育-3'],
        }
        var data2 = {
            title: '生物',
            url: "https://pic.maizuo.com/usr/movie/5011ee407fb407d47e333a3935ec33d1.jpg?x-oss-process=image/quality,070",
            list: ['生物-1', '生物-2', '生物-3'],
        }

        function CreateList(select, data = {}) {
            this.ele = document.querySelector(select)
            this.title = data.title
            this.list = data.list
        }

        //原型(共享内存,节省空间)
        CreateList.prototype.render = function() {
            // 渲染页面
            var h1 = this.ele.querySelector('h1')
            var ul = this.ele.querySelector('ul')

            h1.innerHTML = this.title
            ul.innerHTML = this.list.map(item => `<li>${item}</li>`).join('')
        }


        var obj1 = new CreateList('.box1', data)
        // 一模一样
        console.log(obj1.__proto__)
        console.log(CreateList.prototype)

        obj1.render()
        console.log(obj1)
		/*****************************************************************************/
        function CreateImgList(select, data) {
            //object继承
            CreateList.call(this, select, data)
            this.imgUrl = data.url //新增
        }

        // 原型继承
        CreateImgList.prototype = new CreateList()

		// 增强
        CreateImgList.prototype.enhenceRender = function() {
            // 渲染页面
            this.render()
            var img = this.ele.querySelector('img')
            img.src = this.imgUrl
        }

        var obj2 = new CreateImgList('.box2', data2)
        obj2.enhenceRender()
        // obj2.render()
        console.log(obj2)
    </script>
</body>

</html>

2.8.ES6-继承

extends 和 super关键字

// 父类
class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    say() {
        console.log(this.name, 'hello person')
    }
}
//子类 - extends原型继承
class Student extends Person {//extends继承关键字
    constructor(name, age, grade) {
        super(name, age) //super继承关键字,Person.call(this,name,age)
        this.grade = grade
    }

    //本身就直接继承了方法,但也可以直接重写
    say() {
        super.say() //重写后,仍然想继承
        console.log(this.name, '你好 student')
    }
}

var obj = new Student('Brisa', 100, 100)
console.log(obj)
obj.say()

3.前后端交互

3.1.初识前后端交互

前后端大致交互流程
在这里插入图片描述

  1. 需要加载url对应的网页或事件时,网页就会先给它自己的服务器后台发送一个网络请求,这个请求会发送给后台获取所需要的资源信息
  2. 后端服务器就会从数据库中找来这些资源,并把这些资源信息,比如说文章的地址、标题、封面图片url等等,组装成JSON形式,放在响应体中返回给网页
  3. 网页获取到响应,便把信息解析并排版成前端人员代码中书写的所要展示的样子

3.2.ajax

3.2.1.ajax初识

ajax全名async javascript and XML。是前后台交互的能力,也就是我们客户端给服务端发送消息的工具,以及接受响应的工具,是一个 默认异步 执行机制的功能。

javascript能很好地解析json

AJAX的优势
1. 不需要插件的支持,原生js 就可以使用
2. 用户体验好(不需要刷新页面就可以更新数据)
3. 减轻服务端和带宽的负担
4. 缺点: 搜索引擎的支持度不够,因为数据都不在页面上,搜索引擎搜索不到

AJAX的使用

  • 在js 中有内置的构造函数来创建 ajax 对象
  • 创建 ajax 对象以后,我们就使用 ajax 对象的方法去发送请求和接受响应

ajax 状态码

  • ajax 状态码-xhr.readystate
  • 是用来表示一个 ajax 请求的全部过程中的某一个状态
    • readystate === 0: 表示未初始化完成,也就是 open 方法还没有执行
    • readystate === 1: 表示配置信息已经完成,也就是执行完open 之后
    • readystate === 2: 表示send 方法已经执行完成
    • readystate === 3: 表示正在解析响应内容
    • readystate === 4: 表示响应内容已经解析完毕,可以在客户端使用了
  • 这个时候我们就会发现,当一个ajax 请求的全部过程中,只有当 readystate === 4 的时候,我们才可以正常使用服务端给我们的数据
  • 所以,配合http 状态码为200~299.
    • 一个ajax 对象中有一个成员叫做xhr.status
    • 这个成员就是记录本次请求的 http 状态码的

readystatechange

  • 在ajax 对象中有一个事件,叫readystatechange 事件
  • 这个事件是专门用来监听 ajax 对象的 readystate 值改变的的行为。
  • 也就是说只要 readystate 的值发生变化了,那么就会触发该事件。
  • 所以我们就在这个事件中来监听 ajax的readystate 是不是到4
const xhr = new XMLHttpRequest()
xhr.open('get",./data.php')
xhr.send()
xhr.onreadystateChange = function() {
    // 每次 readystate 改变的时候都会触发该事件
    // 我们就在这里判断 readystate 的值是不是到 4
    // 并且 http 的状态码是不是 200 ~ 299
    if (xhr.readystate === 4 && /^2\d{2|$/.test(xhr.status)) {
        // 这里表示验证通过
        // 我们就可以获取服务端给我们响应的内容了
        ...
        }
}
// 1.创建XHR new XMLHtttpRequest
var xhr = new XMLHttpRequest()
console.log(xhr)

//2.配置 open(请求方式,请求地址,是否异步)
xhr.open('GET', 'http://localhost:5500/31%E5%89%8D%E5%90%8E%E7%AB%AF%E4%BA%A4%E4%BA%92/1.json')
// xhr.open('GET', 'http://localhost:5500/31%E5%89%8D%E5%90%8E%E7%AB%AF%E4%BA%A4%E4%BA%92/2.txt')

//3.send
xhr.send()

//4.接收数据,注册一个事件
//4.1
/*xhr.onreadystatechange = function() {
    console.log(xhr.readyState)
    if (xhr.readyState === 4 && xhr.status === 200) { //状态码200的话,是正确的状态
        console.log('数据解析完成', xhr.responseText)
        document.write(xhr.responseText)
    } else if (xhr.readyState === 4 && xhr.status === 404) {
        console.error('未找到该页面')
        location.href = '404.html'
    }}*/
    
//4.2
xhr.onload = function() {
    // console.log(xhr.readyState)
    // console.log(xhr.responseText) //也会输出
    if (xhr.status === 200) {
        console.log(JSON.parse(xhr.responseText))
    } else if (xhr.status === 404) {
        console.error('未找到该页面')
    }

}

区别:readystatechange VS load

  1. 前者状态改变就会执行,调用更为频繁
  2. 后者只有readyState===4才执行,写法更为简单

3.2.2.ajax案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">获取数据</button>
    <ul id="myList"></ul>
    <!-- <div class="box"></div> -->
    <script>
        //http://www.xiongmaoyouxuan.com/api/tabs
        btn.onclick = function() {
            var xhr = new XMLHttpRequest()
            xhr.open('GET', 'http://www.xiongmaoyouxuan.com/api/tabs')
            xhr.send()
            xhr.onload = function() {
                if (xhr.status === 200) {
                    // console.log(xhr.responseText)
                    var jsondata = JSON.parse(xhr.responseText)
                    render(jsondata)
                }
            }
        }

        function render(jsondata) {
            console.log(jsondata.data.list)
            var html = jsondata.data.list.map(item => `<li>
                <img src=${item.imageUrl}>
                <div>${item.name}</div>
                </li>`)

            myList.innerHTML = html.join('')
                // console.log(html.join(''))
        }
    </script>
</body>

</html>

3.2.3.ajax同步异步

xhr的open方法的第三个参数有什么用?
第三个参数代表是不是异步(async),默认值为true,也就是使用异步的方式不阻塞主线程。如果值为false,则不使用异步的方式,在主线程知道xhr的请求结束之后才会继续执行后面的语句。

var xhr = new XMLHttpRequest()

//相对路径也可以
// true 异步请求; false 同步请求

xhr.open('GET', './1.json', false) //要么不传第三个参数,要传就传true

//同步执行时,若send放前面,会导致数据传回时回调函数堵塞,不能正常执行回调函数,因此建议这种情况下,将回调函数放send前面
//不过更建议使用异步请求
xhr.onload = function() {
    if (xhr.status === 200) {
        console.log(xhr.responseText)
    }
}
xhr.send()

console.log(11111)

3.2.4.请求方式

请求方式
form

getpost

ajax

get:明文传输,不够安全(偏向于获取数据)
post:在body请求体中传递数据,足够安全(偏向于提交数据)
put: 偏向于更新数据(全部更新)
delete:偏向删除信息
patch:(偏向于局部更新)
header: 获取服务器头部信息
options: 获取服务器设备信息
connect: 保留请求方式

使用nodejs中的json-server来模拟后端接口

1.下载安装nodejs
2.集成终端下载(npm install json-server -g)
3.使用 (json-server 路径)

json-server - 基于一个json文件就可以创建很多后端模拟接口

  1. GET方式获取数据

GET传参 在url后加?xx=xxx&&xx=xxx

myget.onclick = function() {
    var xhr = new XMLHttpRequest()
    // xhr.open('GET', 'http://localhost:3000/users')
    // GET传参
    xhr.open('GET', 'http://localhost:3000/users?username=brisa&&password=123456')
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log(JSON.parse(xhr.responseText))
        }
    }
    xhr.send()
}
  1. POST方式传输数据

需要添加传输的数据种类信息(form表单字符串、json字符串),设置请求头

post 提交信息:
1. name=brisa&&age=18 表单字符串 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
2. {“name”:“kerwin”} json字符串 xhr.setRequestHeader("Content-Type", "application/json")

mypost.onclick = function() {
    var xhr = new XMLHttpRequest()
    xhr.open('POST', 'http://localhost:3000/users')
    xhr.onload = function() {
            if (/^2\d{2}$/.test(xhr.status)) {
                console.log(JSON.parse(xhr.responseText))
            }
        }
    // 给指定的HTTP请求头赋值——表单字符串   
    // xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") //name=brisa&&age=18 
    // xhr.send(`username=xixi&&password=123456`)   
     
	//给指定的HTTP请求头赋值——json字符串
    xhr.setRequestHeader("Content-Type", "application/json") //{"name":"kerwin"}
    xhr.send(JSON.stringify({
        name: "kerwin",
        password: "123456"
    }))
}
  1. PUT

完全覆盖哟
只写name:balala的话,password这个会丢失
需要写清楚要修改的是哪条数据(id)

myput.onclick = function() {
    var xhr = new XMLHttpRequest()
    xhr.open('PUT', 'http://localhost:3000/users/12')//修改的是id=12的
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log(JSON.parse(xhr.responseText))
        }
    }
    //设置请求头
    xhr.setRequestHeader("Content-Type", "application/json")
    xhr.send(JSON.stringify({
        name: "balala",
        password: "123456"
    }))

}
  1. PTACH

部分修改,只会修改要修改的部分(要id)

 mypatch.onclick = function() {
    var xhr = new XMLHttpRequest()
    xhr.open('PATCH', 'http://localhost:3000/users/13')
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log(JSON.parse(xhr.responseText))
        }
    }
    //设置请求头
    xhr.setRequestHeader("Content-Type", "application/json")
    xhr.send(JSON.stringify({
        name: "zhouzhou",
        // password: "abcdef"
    }))
}
  1. DELETE

也要传ID

mydelete.onclick = function() {
    var xhr = new XMLHttpRequest()
    xhr.open('DELETE', 'http://localhost:3000/users/13') //给一个ID
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log(JSON.parse(xhr.responseText))
        }
    }
    xhr.send()
}

3.2.5.ajax封装

util.js

function queryStringify(obj) { //处理非stringdata,将其转化为form表单传输字符串的形式
    let str = ''
    for (let k in obj) str += `${k}=${obj[k]}&`
    return str.slice(0, -1)
}

function ajax(options) {
    let defaultoptions = { //设置一些默认参数,以防输入缺失
        url: "",
        method: "GET",
        async: true,
        data: {},
        headers: {"content-type":"application/x-www-form-urlencoded"},
        success: function() {},
        error: function() {}
    }

    let { url, method, async, data, headers, success, error } = { //参数传入
        ...defaultoptions,
        ...options
    }

	//对数据进行处理,如果headers是json类型且data是object类型,就用json处理data的形式
    // if ((typeof data === 'object') && (headers['content-type']?.indexOf('json') > -1)) {//?表示不确定是否存在
    if (typeof data === 'object' && headers['content-type'] ? headers['content-type'].indexOf('json') > -1 : 0) {    
        data = JSON.stringify(data)
    } else { //否则使用form表单处理数据的形式
        data = queryStringify(data)
    }

    // 如果是 get 请求,并且有参数,那么直接组装一下 url 信息
    if (/^get$/i.test(method) && data) url += '?' + data

    // 发送请求
    const xhr = new XMLHttpRequest()
    xhr.open(method, url, async)
    xhr.onload = function() {
        if (!/^2\d{2}$/.test(xhr.status)) { //状态码判断(不是200-299的就报错并结束后续执行)
            console.log('error')
            error(`错误状态码:${xhr.status}`) //回调(传进来的函数被调用)
            return
        }

        // 执行解析-使用try catch捕获错误
        try {
            let result = JSON.parse(xhr.responseText)
            console.log(result)
            success(result)
        } catch (err) {
            error(`解析失败 ! 因为后端返回的结果不是 json 格式字符串`)
        }
    }

    // 设置请求头内的信息
    for (let k in headers) xhr.setRequestHeader(k, headers[k])

    //数据发送
    if (/^get$/i.test(method)) {
        xhr.send()
    } else {
        xhr.send(data)
    }
}

使用封装的utils.js

<script src="./util.js"></script>
<script>
    ajax({
            url: "http://localhost:3000/users",
            method: "POST",
            // data: "username=xila&password=123456",
            data: {
                username: "xilala",
                password: 123456
            },
            headers: {
                // "content-type": "application/x-www-form-urlencoded"
                "content-type": "application/json"
            },
            success: function(res) {
                console.log(res, 'success')
            },
            error: function(err) {
                console.log(err, 'error')
            }
        })
</script>
            
ajax({
    url: "http://localhost:3000/users",
    method: "GET",
    async: true,
    data: {
        username: "bill",
        password: "123"
    },
    headers: {},
    success: function(res) {
        console.log(res)
    },
    error: function(err) {
        console.log(err)
    }
})

一个封装小案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="myget">get</button>
    <button id="mypost">post</button>
    <button id="myput">put</button>
    <button id="mypatch">patch</button>
    <button id="mydelete">delete</button>

    <script src="./util.js"></script>
    <script>
        myget.onclick = function() {
            ajax({
                url: "http://localhost:3000/users",
                data: {
                    username: "brisa",
                    password: "123456"
                },
                success: function(res) {
                    console.log(res, 'success')
                },
                error: function(err) {
                    console.log(err, 'error')
                }
            })
        }
        
        mypost.onclick = function() {
            ajax({
                url: "http://localhost:3000/users",
                method: "POST",
                data: {
                    username: "ximi",
                    password: "123456"
                },
                headers: {
                    "content-type": "application/json"
                },
                success: function(res) {
                    console.log(res, 'success')
                },
                error: function(err) {
                    console.log(err, 'error')
                }
            })
        }

        myput.onclick = function() {
            ajax({
                url: "http://localhost:3000/users/12",
                method: "PUT",
                data: {
                    username: "wahaha",
                    password: "abcded"
                },
                headers: {
                    "content-type": "application/json"
                },
                success: function(res) {
                    console.log(res, 'success')
                },
                error: function(err) {
                    console.log(err, 'error')
                }
            })

        }
        mypatch.onclick = function() {
            ajax({
                url: "http://localhost:3000/users/10",
                method: "PATCH",
                data: {
                    username: "ximila",
                    // password: "123456"
                },
                headers: {
                    "content-type": "application/json"
                },
                success: function(res) {
                    console.log(res, 'success')
                },
                error: function(err) {
                    console.log(err, 'error')
                }
            })
        }

        mydelete.onclick = function() {
            ajax({
                url: "http://localhost:3000/users/9",
                method: "DELETE",
                // data: {
                //     username: "ximila",
                //     // password: "123456"
                // },
                // headers: {
                //     "content-type": "application/json"
                // },
                success: function(res) {
                    console.log(res, 'success')
                },
                error: function(err) {
                    console.log(err, 'error')
                }
            })
        }
    </script>
</body>

</html>

3.2.6.前后端交互案例

总结:
1.考虑开发思维,这里选择的是面向对象——面向对象,那么就把list当做一个对象,对其的各种操作【初始化,维护】【增删查改】也封装在对象里面
2.事件绑定-用的事件委托
3.页面渲染-ajax
4.删除时,使用自定义属性,便于获取id
5.删除时,无所谓页面和数据库的删除顺序。但是,添加时,应该先加入数据库,再重新对页面进行渲染!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <input type="text" name="" id="mytext" />
    <button id="myadd">add</button>
    <ul class="list"></ul>
    <script type="module">
      import ajax from "./util.js";

      // 把整个列表封装为一个对象,对其的增删查改直接调用内部的方法
      class TodoList {
        constructor(select) {
          this.listEle = document.querySelector(select);
          this.listdata = []//列表数据

          this.init()
        }

        //初始化
        init(){
          //初始化
          this.bindEvent()
          //获取数据的方法
          this.getList()
        }

        bindEvent(){
          this.listEle.onclick = (evt)=>{
            if(evt.target.nodeName==="BUTTON"){
                this.removeItem(evt.target)
            }
          }
        }

        getList(){
          //获取数据
          ajax({
            url:"http://localhost:3000/list",
            success:(res)=>{
              this.listdata = res
              this.render()
            },
            error:function(res){
            }
          })
        }
        //渲染页面
        render(){
          this.listEle.innerHTML= this.listdata.map(item=>`
          <li>
            ${item.text}
            <button data-index=${item.id}>del</button>
          </li>
          `).join("")//增加一个自定义属性,方便后续获取id
        }

        addItem(text){
          //在数据库中添加后,成功完成回调后,再渲染页面
          ajax({
            url:`http://localhost:3000/list`,
            method:"POST",
            data:{text:text},//同名,可以直接简写为text
            success:(res)=>{
              // console.log('成功添加',res)
              // location.reload()//全局刷新页面,不合理
              this.listdata = [...this.listdata, res]//追加
              this.render()
            },
            error:function(res){
            }
          })
        }


        removeItem(target){
          //删除如何处理?可以运用事件委托,绑定到ul上,冒泡冒到该项来进行删除
          //节点删除
          target.parentNode.remove()
          // console.log(target.dataset.index)
          //数据库删除
          ajax({
            url:`http://localhost:3000/list/${target.dataset.index}`,
            method:"DELETE",
            success:(res)=>{
              console.log('成功删除')
            },
            error:function(res){
            }
          })
        }
        updateItem(){

        }
      }

      var obj = new TodoList(".list")
      // console.log(obj)
      myadd.onclick = ()=>{
        // console.log(mytext.value)
        obj.addItem(mytext.value)
      }
    </script>
  </body>
</html>

3.3.Promise

  • promise是一个ES6的语法
  • 承诺的意思
  • 是一个专门用于解决异步回调地狱的问题的方法

3.3.1.回调地狱问题

回调地狱/嵌套金字塔

  • 当一个回调函数嵌套一个回调函数的时候就会出现一个嵌套结构
  • 当嵌套的多了就会出现回调地狱的情况
  • 比如我们发送三个 ajax 请求
    • 第一个正常发送
    • 第二个请求需要第一个请求的结果中的某一个值作为参数
    • 第三个请求需要第二个请求的结果中的某一个值作为参数
  • 会导致代码混乱、不好维护等问题
//ajax里嵌入ajax,继续套娃
//tiechui已经登录
ajax({
    url:"http://localhost:3000/news",
    data:{
        author:"tiechui"
    },
    success:function(res){
        //获取对应ID
        console.log(res[0].id)
        //获取相应评论
        ajax({
            url:"http://localhost:3000/comments",
            data:{
                newsId:res[0].id
            },
            success:function(res){
                console.log(res)
            }
        })
    }
})

3.3.2.Promise的基础语法

语法:

new Promise(function(resolve, reject){
    // resolve 表示成功的回调
    // reject 表示失败的回调
}).then(function(res){
    // 成功的函数
}).catch(function(err) {
    // 失败的函数
})
  • promise 就是一个语法
    • 我们的每一个异步事件,在执行的时候。都会有三个状态,执行中/成功/失败——pending 执行中;fulfilled 成功;reject 失败
  • 因为它包含了成功的回调函数
  • 所以我们就可以使用 promise 来解决多个 ajax 发送的问题
//promise基础语法

// Promise 构造函数
var q = new Promise(function(resolve,reject){
    //异步代码
    setTimeout(()=>{
        //成功兑现承诺
        resolve(["111","222","333"])

        //失败拒绝承诺
        // reject("error")
    },2000)
})
// 3个状态
// pending 执行中
// fulfilled 成功
// reject 失败

//q是promise对象
q.then(function(res){
    //兑现承诺,这个函数被执行
    console.log("success",res)
}).catch(function(err){
    //拒绝承诺,这个函数被执行
    console.log("fail",err)
})

3.3.3.Promise封装ajax

//util.js中加入以下代码
function pajax(options){
    return new Promise((resolve,reject)=>{
        ajax({
            ...options,
            success(res){
                resolve(res)
            },
            error(err){
                reject(err)
            }
        })
    })
}
<script src="./util.js"></script>
<script>
    //tiechui已经登录
    pajax({
    url: "http://localhost:3000/news",
    data: {
        author: "tiechui",
    },
    })
    .then((res) => {
        // console.log(res)
        return pajax({
        url: "http://localhost:3000/comments",
        data: {
            newsId: res[0].id,
        },
        }).then((res) => {
        console.log("success",res);
        });
    })
    .catch((err) => {
        console.log(err);
    });
</script>

3.3.4.Promise版ajax案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <input type="text" name="" id="mytext" />
    <button id="myadd">add</button>
    <ul class="list"></ul>
   
    <script type="module">
      import {pajax} from "./util.js";

      // 把整个列表封装为一个对象,对其的增删查改直接调用内部的方法
      class TodoList {
        constructor(select) {
          this.listEle = document.querySelector(select);
          this.listdata = []//列表数据

          this.init()
        }

        //初始化
        init(){
          //初始化
          this.bindEvent()
          //获取数据的方法
          /*this.getList()*/
          this.getList().then((res)=>{
              this.listdata = res
              this.render()
          }).catch(err=>{})
        }

        bindEvent(){
          this.listEle.onclick = (evt)=>{
            if(evt.target.nodeName==="BUTTON"){
                this.removeItem(evt.target)
            }
          }
        }

        getList(){
          //获取数据
          /*pajax({
            url:"http://localhost:3000/list"
          }).then((res)=>{
              this.listdata = res
              this.render()
          }).catch(err=>{})}*/
          return pajax({
            url:"http://localhost:3000/list"
          })
        }

        //渲染页面
        render(){
          this.listEle.innerHTML= this.listdata.map(item=>`
          <li>
            ${item.text}
            <button data-index=${item.id}>del</button>
          </li>
          `).join("")//增加一个自定义属性,方便后续获取id
        }

        addItem(text){
          //在数据库中添加后,成功完成回调后,再渲染页面
          pajax({
            url:`http://localhost:3000/list`,
            method:"POST",
            data:{text:text},//同名,可以直接简写为text
          }).then((res)=>{
              // console.log('成功添加',res)
              // location.reload()//全局刷新页面,不合理
              this.listdata = [...this.listdata, res]//追加
              this.render()
            }).catch(err=>{
            })
          
        }


        removeItem(target){
          //删除如何处理?可以运用事件委托,绑定到ul上,冒泡冒到该项来进行删除
          //节点删除
          target.parentNode.remove()
          // console.log(target.dataset.index)
          //数据库删除
          pajax({
            url:`http://localhost:3000/list/${target.dataset.index}`,
            method:"DELETE",
          }).then((res)=>{
              console.log('成功删除')
          }).catch(err=>{})
        }
        updateItem(){

        }
      }

      var obj = new TodoList(".list")
      // console.log(obj)
      myadd.onclick = ()=>{
        // console.log(mytext.value)
        obj.addItem(mytext.value)
      }


      console.log("正在加载中")
      /*要求:显示looplist和datalist
        问题:当数据太多未能马上加载时,要显示"loading",数据加载完后显示数据,但是得要所有数据都加载完成后才显示数据,如何实现?
        方法:
            1.写出两个pajax及对应的then。这种方法会导致先加载完的数据先显示,不方便实现隐藏加载效果
            2.利用链式then,可以但没必要,浪费时间
            3.用Promise.all()
      */

      /*pajax({
        url:"http://localhost:3000/loopList"
      }).then(res=>{
        console.log(res)
        console.log('隐藏加载中')
      })

      pajax({
        url:"http://localhost:3000/dataList"
      }).then(res=>{
        console.log(res)
      })*/
      var q1 = pajax({
        url:"http://localhost:3000/loopList"
      })

      var q2 = pajax({
        url:"http://localhost:3000/dataList"
      })
      Promise.all([q1,q2]).then(res=>{
        console.log(res)
        console.log("隐藏加载")
      }).catch(err=>{console.log(err)})
    </script>
  </body>
</html>

3.4.async/await

3.4.1.async/await语法

  • async/await 是一个es7的语法
  • 这个语法是回调地狱的终极解决方案
  • 语法:
async function fn() 
{   
    const res = await promise对象
}
  • 这个是一个特殊的函数方式
  • 可以await一个promise 对象
  • 可以把异步代码写的看起来像同步代码
  • 只要是一个promise 对象,那么我们就可以使用 async/await 来书写
async function fn() {
    const res = new Promise(function(resolve, reject{
        ajax({
            ur1:第一个地址,
            success (res) {
                reso1ve(res)
            }
        })
    }
}
<script type="module">
    import { pajax } from "./util.js";
    async function test() {
	    //只针对内部
	    //怎么在async里实现捕获错误-try+catch
	    try {
	        // await 同步代码(没有意义)/promise对象
	        var res = await pajax({
		        url: "http://localhost:3000/news",
		        data: {
		            author: "tiechui",
		        },
	        });
	        console.log("async+await", res); //用同步实现了异步(实现顺序执行了)
	        // console.log(222)
	
	        //链式执行
	        //之前是一直then,现在可以直接这种写法
	        var res1 = await pajax({
		        url: "http://localhost:3000/comments",
		        data: {
		            newsId: res[0].id,
		        },
	        });
	        return res1;
	    } catch (err) {
	        console.log("err", err);
	    }
    }
    /*想在外面使用res只能通过then实现,否则会因为pending及异步而不能正常返回值*/
    test()
    .then((res) => {
        console.log("then", res);
    })
    // .catch((err) => {
    //   console.log(err);
    // });
    console.log(3333);
</script>

3.4.2.async/await案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <input type="text" name="" id="mytext" />
    <button id="myadd">add</button>
    <ul class="list"></ul>
   
    <script type="module">
      import {pajax} from "./util.js";

      // 把整个列表封装为一个对象,对其的增删查改直接调用内部的方法
      class TodoList {
        constructor(select) {
          this.listEle = document.querySelector(select);
          this.listdata = []//列表数据

          this.init()
        }

        //初始化
        init(){
          //初始化
          this.bindEvent()
          //获取数据的方法
          /*this.getList()*/
          this.getList().then((res)=>{
              this.listdata = res
              this.render()
          }).catch(err=>{})
        }

        bindEvent(){
          this.listEle.onclick = (evt)=>{
            if(evt.target.nodeName==="BUTTON"){
                this.removeItem(evt.target)
            }
          }
        }

        async getList(){
          //获取数据 
          var res= await pajax({
            url:"http://localhost:3000/list"
          })
          // console.log("getList",res)
          return res
        }

        //渲染页面
        render(){
          this.listEle.innerHTML= this.listdata.map(item=>`
          <li>
            ${item.text}
            <button data-index=${item.id}>del</button>
          </li>
          `).join("")//增加一个自定义属性,方便后续获取id
        }

        async addItem(text){
          //在数据库中添加后,成功完成回调后,再渲染页面
          var res = await pajax({
            url:`http://localhost:3000/list`,
            method:"POST",
            data:{text:text},//同名,可以直接简写为text
          })
          // .then((res)=>{
          //     // console.log('成功添加',res)
          //     // location.reload()//全局刷新页面,不合理
          //     this.listdata = [...this.listdata, res]//追加
          this.listdata = [...this.listdata, res]//追
          this.render()
          
          //   }).catch(err=>{
          //   })
          
        }


        async removeItem(target){
          target.parentNode.remove()
          await pajax({
            url:`http://localhost:3000/list/${target.dataset.index}`,
            method:"DELETE",
          })
          console.log('成功删除')
          // .then((res)=>{
          //     console.log('成功删除')
          // }).catch(err=>{})
        }
        updateItem(){

        }
      }

      var obj = new TodoList(".list")
      myadd.onclick = ()=>{
        obj.addItem(mytext.value)
      }

      console.log("正在加载中")      
      var q1 = pajax({
        url:"http://localhost:3000/loopList"
      })

      var q2 = pajax({
        url:"http://localhost:3000/dataList"
      })
      // Promise.all([q1,q2]).then(res=>{
      //   console.log(res)
      //   console.log("隐藏加载")
      // }).catch(err=>{console.log(err)})

      async function test(){
        var res = await Promise.all([q1,q2])
        console.log(res)
        console.log("隐藏加载")
      }
      test()
    </script>
  </body>
</html>

3.5.fetch

XMLHttpRequest是一个设计粗糙的API,配置和调用方式非常混乱,而且基于事件的异步模型写起来不友好。兼容性不好。 polyfill: https://github.com/camsong/fetch-ie8

3.5.1.fetch基础语法

fetch适用于取代XMLHttpRequest的,能够很好地解决异步和链式

  1. fetch默认get请求
  2. 中间部分可以简写 then(res=>res.json())
  3. res.json() 数组形式的json ;res.text()字符串
  4. get方式传参直接暴力拼接在后面
  5. 要先判断,错误发生不像promise中可以直接用catch
  6. headers和body的格式
   headers: {
       "content-type": "application/x-www-form-urlencoded"
   },
   body: "username=ximi&password=123456"
   }
    headers: {
        "content-type": "application/json"
    },
    body: JSON.stringify({
        username: "ximi",
        password: "123456"
    })
  1. method大小写都行

get

myget.onclick = function() {
    //fetch默认get请求
    fetch("http://localhost:3000/users").then(res=>{
        return res.json()//json格式的promise对象
    }).then(res=>{
        console.log(res)//第二个then才能拿到最终数据
    })

    //简写
    //res.json() 数组形式的json ;res.text()字符串
    fetch("http://localhost:3000/users").then(res=>res.json()).then(res=>{
        console.log(res)
    })

    // get传参直接暴力拼接在后面
    var username = 'xiaoming'
    fetch(`http://localhost:3000/users?username=${username}`).then(res=>res.json()).then(res=>{
        console.log(res)
    })

    //错误捕获
    fetch("http://localhost:3000/users111").then(res=>{
        console.log(res)
        if(res.ok){
            return res.json()
        }else{
            //拒绝,使用promise自带的方法
            return Promise.reject({
                status:res.status,
                statusText:res.statusText
            })
        }
    }).then(res=>{
        console.log("success",res)
    }).catch(err=>{
        console.log("error",err)
    })
}

POST

mypost.onclick = function() {
    fetch("http://localhost:3000/users",{
        method: "POST",
        /*headers: {
            "content-type": "application/x-www-form-urlencoded"
        },
        body: "username=ximi&password=123456"
        }*/
        headers: {
            "content-type": "application/json"
        },
        body: JSON.stringify({
            username: "ximi",
            password: "123456"
        })
        }).then(res=>{
            console.log(res)
            if(res.ok){
                return res.json()
            }else{
                return Promise.reject({
                    status:res.status,
                    statusText:res.statusText
                })
            }
        }).then(res=>{
            console.log("success",res)
        }).catch(err=>{
            console.log("error",err)
        })
}

PUT

myput.onclick = function() {
    fetch("http://localhost:3000/users/2",{
        method: "PUT",
        headers: {
            "content-type": "application/json"
        },
        body: JSON.stringify({
            username: "ALILALA",
            password: "123456"
        })
        }).then(res=>{
            console.log(res)
            if(res.ok){
                return res.json()
            }else{
                return Promise.reject({
                    status:res.status,
                    statusText:res.statusText
                })
            }
        }).then(res=>{
            console.log("success",res)
        }).catch(err=>{
            console.log("error",err)
        })

}

PATCH

mypatch.onclick = function() {
    fetch("http://localhost:3000/users/3",{
        method: "PATCH",
        headers: {
            "content-type": "application/json"
        },
        body: JSON.stringify({
            username: "ALIA",
            // password: "123456"
        })
        }).then(res=>{
            console.log(res)
            if(res.ok){
                return res.json()
            }else{
                return Promise.reject({
                    status:res.status,
                    statusText:res.statusText
                })
            }
        }).then(res=>{
            console.log("success",res)
        }).catch(err=>{
            console.log("error",err)
        })
}

DELETE

mydelete.onclick = function() {
    fetch("http://localhost:3000/users/13",{
        method: "DELETE",
        }).then(res=>{
            console.log(res)
            if(res.ok){
                return res.json()
            }else{
                return Promise.reject({
                    status:res.status,
                    statusText:res.statusText
                })
            }
        }).then(res=>{
            console.log("success",res)
        }).catch(err=>{
            console.log("error",err)
        })
}

3.5.2.fetch案例

案例一

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="text" name="" id="mytext">
    <button id="myadd">add</button>
    <ul class="list"</u1>
    <script type="module">
        async function test(){
            var author = "tiechui"
            var res = await fetch(`http://localhost:3000/news?author=${author}`).then(res=>res.json())
            console.log(res)

            var res1 = await fetch(`http://localhost:3000/news?comments=${res[0].id}`).then(res=>res.json())
            console.log(res1)
        }
        test()
    </script>
</body>
</html>

案例二

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <input type="text" name="" id="mytext" />
    <button id="myadd">add</button>
    <ul class="list"></ul>
   
    <script type="module">

      // 把整个列表封装为一个对象,对其的增删查改直接调用内部的方法
      class TodoList {
        constructor(select) {
          this.listEle = document.querySelector(select);
          this.listdata = []//列表数据

          this.init()
        }

        //初始化
        init(){
          //初始化
          this.bindEvent()
          //获取数据的方法
          /*this.getList()*/
          this.getList().then((res)=>{
              this.listdata = res
              this.render()
          }).catch(err=>{})
        }

        bindEvent(){
          this.listEle.onclick = (evt)=>{
            if(evt.target.nodeName==="BUTTON"){
                this.removeItem(evt.target)
            }
          }
        }

        async getList(){
          //获取数据 
          var res= await fetch("http://localhost:3000/list").then(res=>res.json())
          return res
        }

        //渲染页面
        render(){
          this.listEle.innerHTML= this.listdata.map(item=>`
          <li>
            ${item.text}
            <button data-index=${item.id}>del</button>
          </li>
          `).join("")//增加一个自定义属性,方便后续获取id
        }

        async addItem(text){
          //在数据库中添加后,成功完成回调后,再渲染页面
          var res = await fetch(`http://localhost:3000/list`,{
            method:"POST",
            headers:{"content-type":"application/json"},
            body:JSON.stringify({text}),//同名,可以直接简写为text
          }).then(res=>res.json())
          this.listdata = [...this.listdata, res]//追
          this.render()    
        }


        async removeItem(target){
          target.parentNode.remove()
          await fetch(`http://localhost:3000/list/${target.dataset.index}`,{
            method:"DELETE",
          }).then(res=>res.json())
          console.log('成功删除')
        }


        updateItem(){}
      }

      var obj = new TodoList(".list")
      myadd.onclick = ()=>{
        obj.addItem(mytext.value)
      }

      console.log("正在加载中")      
      var q1 = fetch("http://localhost:3000/loopList").then(res=>res.json())

      var q2 = fetch("http://localhost:3000/dataList").then(res=>res.json())
      
      async function test(){
        var res = await Promise.all([q1,q2])
        console.log(res)
        console.log("隐藏加载")
      }
      test()
    </script>
  </body>
</html>

3.6.大案例

list.js

//面向过程

//1.获取数据
var current = 0//记录当前是第几页
var isLoading = false//记录是否正在请求(防止多次请求)
var total = 0//记录总数据长度
getList()
async function getList(){
    current++
    //这种获取方法也可以
    var res = await fetch(`http://localhost:3000/goods?_page=${current}&_limit=5`)

    //获取响应头的数据
    total = res.headers.get('X-Total-Count')//获取到的是字符串

    var list = await res.json()
    // console.log(list)
    renderHTML(list)
    return list
}
//2.数据加载,将内容返回到页面
function renderHTML(arr) {
    for (let i = 0; i < arr.length; i++) {
        var oli = document.createElement('li')
        oli.innerHTML = `<li>
                <img src="http://localhost:3000${arr[i].poster}" alt="">
                <h3>${arr[i].title}</h3>
            </li>`
            //图片路径访问问题,通过json-server可以在3000接口后面直接访问db下的public下面的图片,并且可以省略public
        
        //事件绑定
        oli.onclick = function(){
            //跳转
            location.href = `detail.html?id=${arr[i].id}`
        }

        list.appendChild(oli) //append不会导致屏幕一闪一闪的
    }
}


//3.窗口滚动时进行判断
window.onscroll = function() {
    //最后一条数据拿到了,就不要再滚动了
    //怎么判断-通过响应头里的X-Total-Count
    // console.log(list.children.length,total)
    if(list.children.length===Number(total)) return


    var listHeight = list.offsetHeight
    var listTop = list.offsetTop
    var scrollTop = document.documentElement.scrollTop ||
        document.body.scrollTop
    var windowHeight = document.documentElement.clientHeight
    if (isLoading) return
    if ((listHeight + listTop) - (Math.round(windowHeight + scrollTop)) < 100) {
        console.log('到底')
        isLoading = true

        //渲染下一页数据
        getList().then(()=>{
            isLoading=false//下一次触发
        })
    }
}

list.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./css/list.css"></head>

<body>
    <h1>标题</h1>
    <ul id="list">
    </ul>
    <script src="./js/list.js"> </script>

</body>

</html>

detail.js

//拿列表传来的ID

// 1.截取字符串
// console.log(location.href.split('=')[1])

// 2.URL构造函数
var obj = new URL(location.href)
// console.log(obj)
// console.log(obj.searchParams.get('id'))
var id = obj.searchParams.get('id')

//面向对象

class Detail {
    constructor(id){
        this.id = id
        this.init()
    }
    async init(){
        //获取数据
        var info = await this.getList()
        //渲染页面
        this.renderHTML(info)
    }

    async getList(){
        var res = await fetch(`http://localhost:3000/goods/${this.id}`)
        var info = await res.json()
        return info
    }

    async renderHTML(info){
        console.log(info)
        var {title, feature, price,desc} = info

        var oh1 = document.querySelector('h1')
        var ofeature = document.querySelector('.feature')
        var oprice = document.querySelector('.price')
        var olist = document.querySelector('.list')
        oh1.innerHTML = title
        ofeature.innerHTML = feature
        oprice.innerHTML = `<span style="color:red;">价格:¥${price}</span>`
        olist.innerHTML = desc.map(item=>
            `<li><img src="${item}"/></li>`
        ).join("")
    
    }
}
new Detail(id)

detail.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./css/detail.css">
</head>
<body>
    <h1>详情页面</h1>
    <div class="feature"></div>
    <div class="price"></div>
    <ul class="list"></ul>
    <script src="./js/detail.js"></script>
</body>
</html>

3.7.cookie

cookie的特点:

  1. 只能存储文本
  2. 单条存储有大小限制4KB左右
    数量限制(一般浏览器,限制大概在50条左右)
  3. 读取有域名限制:不可跨域读取,只能由来自 写入cookie的 同一域名 的网页可进行读取。简单的讲就是,哪个服务器发给你的cookie,只有哪个服务器有权利读取
  4. 时效限制:每个cookie都有时效,默认的有效期是,会话级别:就是当浏览器关闭,那么cookie立即销毁,但是我们也可以在存储的时候手动设置cookie的过期时间
  5. 路径限制:存cookie时候可以指定路径,只允许子路径读取外层cookie,外层不能读取内层。
//怎么判断是否登录?
//http协议是无状态的协议
//localStorage--BOM中讲过,不便直接存密码

//cookie-本地存储  sessionID 钥匙===>Node.js cookie+session

//cookie 本地存储技术
//存cookie
savebtn.onclick = function(){
    //只能一条一条存
    //重名的话,后面覆盖前面的
    document.cookie = "age=18"

    //路径设置——path
    // document.cookie = "username=brisa;path=/155-cookie/aaa"

    //过期时间设置——expires
    var date = new Date()
    date.setMinutes(date.getMinutes()+1)
    document.cookie = `username=brisa;expires=${date.toUTCString()}`
}
getbtn.onclick = function(){
    //HttpOnly属性的只能由后端读取,前端直接访问不到
    console.log(getCookie('age'))
    console.log(getCookie('username'))
}

function getCookie(key){//获取cookie中相应key的value
    var str = document.cookie
    var arr = str.split(';')
    // console.log(arr)
    var obj = {}
    for(var i=0;i<arr.length;i++){
        var subArr = arr[i].split('=')
        // console.log(sub)
        obj[subArr[0]]=subArr[1]
    }
    // console.log(obj)

    return obj[key]
}
//删
delbtn.onclick = function(){
    //把expires设置为当前时间往前推
    var date = new Date()
    date.setMinutes(date.getMinutes()-1)
    document.cookie = `username=brisa;expires=${date.toUTCString()}`
    document.cookie = `age=18;expires=${date.toUTCString()}`
}

3.8.jsonp

3.8.1.jsonp基本原理

Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据

为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略

同源策略:同域名、同端口号、同协议
不符合同源策略,浏览器为了安全,会阻止请求

解决跨域问题

  1. cors 由服务端或后端设置,Access-Control-Allow-Origin
  2. jsonp 前后端协作完成

json原理:动态创建script标签,src属性指向没有跨域限制
指向一个接口,接口返回的格式一定是 ****() 函数表达式

注意
1.后端接口形式必须是 **(),需要后端配合
2.jsonp的缺点:(1)onload删除节点(2)只能get请求,不能post put patch delete

// function test(obj){
//     console.log(obj)
// }
function callbackFunction(obj){
    console.log(obj)
}
mybtn.onclick = function(){
    // var oscript = document.createElement('script')
    // oscript.src= '01.txt'//未来地址
    // document.body.appendChild(oscript)
    var oscript = document.createElement('script')
    oscript.src= "https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"//未来地址
    document.body.appendChild(oscript)

    //访问完成后就可以删掉该节点
    oscript.onload = function(){
        //删除当前节点
        oscript.remove()
    }
}

3.8.2.jsonp案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="text" id="mysearch">
    <ul id="list"></ul>
    <script>

        mysearch.oninput = function(evt){
            if(!evt.target.value){
                list.innerHTML = ''
                return}
                // console.log(evt.target.value)
            var oscript = document.createElement('script')
        
            oscript.src = `https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=36547,37558,37520,38093,38126,38116,37989,37925,26350,38099,38008,37881&wd=${evt.target.value}&req=2&csor=1&cb=brisa&_=1675842510792`

            document.body.appendChild(oscript)
            oscript.onload = function(){
                oscript.remove()
            }            
        }
        function brisa(obj){
            list.innerHTML = obj.g.map(item=>`<li>${item.q}</li>`).join('')
        }
    </script>
    
</body>
</html>

3.9.再谈函数

在这里插入图片描述

函数定义阶段会在栈区和堆区占用空间,函数调用阶段,会临时申请一片区域用于使用,调用完成后空间就会被销毁

如何实现执行空间不被销毁?

函数有返回值,而且返回值必须是复杂类型,而且要赋值给外面的变量
想收回时,直接将外面的那个变量赋值为null

//函数有返回值,而且返回值必须是复杂类型,而且要赋值给外面的变量
function test(){
    var name='brisa'//外面调用时,也仍然被直接销毁
    console.log(name)
    var obj = {
        a:1,
        b:2
    }
    return obj
}
var obj = test()
console.log(obj)

obj= null//收回空间

3.10.闭包

3.10.1.对闭包的理解

闭包:函数内部返回一一个函数(或对象中包含着一个函数),被外界引用。这个内部函数就不会被销毁。内部函数所用到的外部函数的变量也不会被销毁。

优点:让临时变量永驻内存
缺点:内存泄漏 func=null回收

<script>  
    function outer(){
    var name='brisa'
    return function(){
        return name+'111'//name不会被销毁
    }
    }
    var func = outer()
    console.log(func())
</script>

闭包-函数柯里化

<script>
    //减少变量传输
    //  fetch('http://www.a.com/aaa')
    //  fetch('http://www.a.com/bbb')
    //  fetch('http://www.b.com/bbb')
    //  fetch('http://www.b.com/bbb')

    function FetchContainer(url){
    return function(path){
        return fetch(url+path)
    }

    var fetcha = FetchContainer('http://www.a.com')
    fetcha('./aaa').then(res=>res.json()).then(res=>console.log(res))
    fetcha('./bbb').then(res=>res.json()).then(res=>console.log(res))
    fetcha = null

    var fetchb = FetchContainer('http://www.b.com')
    fetchb('./aaa').then(res=>res.json()).then(res=>console.log(res))
    fetchb('./bbb').then(res=>res.json()).then(res=>console.log(res))
    fetchb = null
    }
</script>

3.10.2.闭包的应用

1.记住列表索引

<ul>
    <li>001</li>
    <li>002</li>
    <li>003</li>
</ul>
<script>
    var oli = document.querySelectorAll('li')
    /*for(var i=0;i<oli.length;i++){
        oli[i].onclick = function(){console.log(i)}//全是3,因为for循环已经执行完了,可以将var->let
    }*/
    for(var i=0;i<oli.length;i++){
        oli[i].onclick = (function(index){
            return function(){
                console.log(index)
            }
        })(i)//构建匿名函数,直接马上调用,叫做匿名自执行函数
    }
    
</script>

2.jsonp案例优化—函数防抖

<input type="text" id="mysearch" />
<ul id="list"></ul>
<script>
    //之前的每输入一个字母就会发一次请求,这里优化为键入字母等待足够长的时间才发请求
    function brisa(obj) {
    console.log(obj)
    list.innerHTML = obj.g.map((item) => `<li>${item.q}</li>`).join("");
    }

    //使用闭包实现
    //设置一个定时器变量首先定义为null,随着字符输入,设置500毫秒延时发送请求,如果在这500毫秒内又输入了新的字符,则清除掉上一次的请求,否则则延时完成后发送请求
    //且这里定时器要一直用,要保存下来,不被清除,但不建议将其改为全局变量,因为可能会名字冲突
    //一般不建议多的去使用全局变量
    mysearch.oninput = (function () {
    var timer = null
    return function (evt) {
        if (timer) {//有值就清掉它
            clearTimeout(timer);
        }
        timer = setTimeout(function () {//timer变为了1
            // console.log("发送ajax请求");
            if(!evt.target.value){
                list.innerHTML = ''
                return
            }
            var oscript = document.createElement('script')
        
            oscript.src = `https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=36547,37558,37520,38093,38126,38116,37989,37925,26350,38099,38008,37881&wd=${evt.target.value}&req=2&csor=1&cb=brisa&_=1675842510792`

            document.body.appendChild(oscript)
            oscript.onload = function(){
                oscript.remove()
            }   
        }, 500)
    };
    })();//自执行
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值