华清远见重庆中心-JS技术总结

js的内容更多更杂,学习更多逻辑上的东西,难度也加大了许多,我将根据老师的笔记以及自己写的代码展开一下内容

数据类型

7 种原始类型

undefined, Boolean,Number, String,BigInt,Symbol, null

最后一种类型是: Object

## typeof,可以使用 `typeof` 来判断数据类型 如:

let obj = {}

console.log(typeof obj) // => 'object'

数据按照参数的传递方式,可以分为 `值传递类型` 和 `引用传递类型`

## 值类型

Boolean 布尔型,Number 数字型,String 字符串,BigInt 长整数

## 引用类型

Object 对象类型,symbol 符号类型

## 值类型和引用类型数据的区别

值类型:变量中直接存贮值本身

引用类型:变量中存储的是引用地址,而值是存在引用地址所指向的内存中的某个对应位置

## undefined 未定义

undefined 是一个单独的类型,用于给未定义的变量赋值

## null 空引用

null 值的是空引用,是 js 的一个原始数据类型,用来指代引用类型数据的空值

类型与类型之间可以相互转换,这里说两个比较特殊的转换序列化和反序列化

// 序列化就是将对象转换成字符串

 // 反序列化就是将字符串转换成对象

    let obj = {

        name: '张三的母亲',

        sex: 'other',

        age: 40

    }

    // 序列化

    let str = JSON.stringify(obj)

    console.log(str);

    // 通过json格式声明一个字符串

    str = '{"name": "英雄的母亲", "child": "张三", "score": 88, "isOk": false}'

    // 反序列化

    obj = JSON.parse(str)

    console.log(obj);

浏览器操作

## dom (document object model) 文档对象模型

dom 是 js 中的对象,用来操纵浏览器中的元素

## bom (browser object model) 浏览器对象模型

bom 是 js 中的对象,用来操纵浏览器,不同的 bom 对象 代表浏览器不同的功能

history:  // 前进  // window.history.forward()

                 // 后退 // window.history.back()

location: // 跳转网页不计入历史 // window.location.replace('https://www.bilibili.com')

                 // 刷新页面// window.location.reload()

                 // 地址栏参数// location.search

localStorage:// localStorage 数据持久化到浏览器中,关闭窗口或浏览器,都不会消失

                          // 设置数据

                    // 第一个参数:key

                    // 第二个参数:value 且必须是字符串

                    // window.localStorage.setItem('name', '张三')

                    // 通过索引方式赋值 等价于 setItem

                    // localStorage['sex'] = 'male'

navigator:用于查看设备信息

运算符和条件判断

比较运算符:

  // 大于 console.log(1 > 2);

 // 小于   console.log(1 < 2);

// 大于等于 小于等于

    console.log(3 >= 3)

    console.log(3 <= 3)

  // == 非严格相等判断

    // 非严格相等判断 在运算符左右两边不需要使用相同的数据类型

    // 当数据类型不同时 == 运算符将自动进行隐式转换

     let a = ''

    let b = 0

    console.log(a == b);

    // === 严格相等

    // 类型和值都相等才会被认为相等

    let x = ''

    let y = 0

    console.log(x == y);

    console.log(x === y);

    // 对象相等判断

    let obj1 = { name: '张三' }

    let obj2 = { name: '张三' }

    // 由于对象类型的变量保存的是对象的引用地址

    // 所以此处两对象引用地址不同 结果为false

    console.log(obj1 === obj2);

    // != 非严格不等于

    console.log('' != 0);

   // !== 严格不等于

    console.log('' !== 0);

赋值运算符

 // += 自增

    a += 2 // 等价于 a = a + 2

    // -= 自减

    a -= 5 // 等价于 a = a - 5

    // *= 自乘

    a *= -1

    // /= 自除

    a /= 2

    // %= 自模

    a = 5

    a %= 3 // 等价于 a = a % 

 // ++ 自增

    a = 0

    a++

// -- 自减

    a = 0

    a--

    // 自增自减运算符位置的区别

    // 运算符放前面,则先运算后引用

    console.log(--a);

    // 运算符放后面,则先引用后运算a--

逻辑运算符

// && 与(短路运算)

    // 语法:bool expression && bool expression

    // 两个表达式都为真,结果返回真;任一一个表达式为假,结果返回假

// || 或(短路运算)

    // 语法:bool expression || bool expression

    // 两个表达式任一一个为真,结果返回真;否则为假

    // !非 作用是取反

    // 语法: !(tool express)

三目运算符

 // 三元运算符: 用于判断一个表达式结果,为真时,返回结果1,为假时返回结果2

    // 语法:bool expression? case1: case2

    // 作用:主要用在给变量赋值

  // 嵌套三元运算符 在问号或冒号处可以换行

    console.log(obj.sex === 'male' ? '男' :

        obj.sex === 'female' ? '女' : '其他');

    // 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来

    console.log(obj.sex === 'male' ? '男': (obj.sex === 'female' ? '女' : '其他'));

算数运算符

  // 加减乘除符号

    let a = 1, b = 2

    console.log(a + b);

    console.log(a - b);

    console.log(a * b);

    console.log(a / b);

    // 加号有两种特殊使用情况

    // 1. 连接字符串

    console.log(a + 'hello world');

    // 2. 转换字符串为数字

    console.log(+'3e-2');

    // % 模运算 除以一个数字并取余

    console.log(1 % 2);

    // ** 幂运算 运算 n 的 m 次方是多少

    console.log(2 ** 3);

    // 算数运算符遵循数学的符号优先级

    console.log(1 + 2 * 3 ** 2);

    // 可以使用圆括号来分组

    console.log(1 + (2 * 3) ** 2);

运算符的优先级

++ -- > 算术运算符 > 比较运算符 > 逻辑运算符 === 三元运算符 > 赋值运算符

条件判断

if

  // if 语句:用于进行条件判断的语句

    // if(bool expression) {

    //      // code block 代码块

    // }

    // 作用:当布尔表达式值为真时,执行后面的代码块

// if else: 如果。。。否则。。。

    // 语法:

    // if(bool expression) {

    //      // code block 代码块

    // } else {

    //      // code block

    // }

  // if    else if     else: 如果。。。或者。。。或者。。。否则。。。

    // 语法:

    // if (bool expression) {

    //     // code block 代码块

    // }

    // else if (bool expression) {

    //     // code block

    // }

    // else if (bool expression) {

    //     // code block

    // }

    // ... 此处 else if 可以写多个

    // ...

    // else {

    //     // code block

    // }

    // 最后的 else 是非必填的

    // if  else if  else 语句,若代码块中只有一句话 则可以省略花括号

    // 总结:

    // 1. if 语句要么执行要么不执行

    // 2. if else 语句,一定有一个会被执行,二选一

    // 3. if  else if 语句,多选一或都不执行

    // 4. if    else if    else 语句,一定有一个会被执行,多选一

switch:

    // 语法:判断value值为多少,如果为constant1则执行其后的代码,

    // 如果为constant2则执行其后的代码,

    // 如果不满足所有的case语句,则执行default

    // switch ( value ) {

    //     case constant1:

    //         // code

    //         break;

    //     case constant2:

    //         break;

    //     ... 可以有任意多个case语句

    //     default: // default 不是必须要写的

    //         break;

    // }

数组和循环

数组

    // 存储一组数据的一个容器

    // 声明数组

    // 使用脚本形式声明数组

    let arr = []

    // 声明并初始化数组

    // 数组中的每一个数据称为 数组成员

    arr = [false, 2, 'hello world', { name: '张三' }, null, undefined]

   // 对象形式的声明方法

    arr = new Array()

    // 声明并初始化

    arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)

    // 访问数组成员

    // 使用索引值访问数组中的成员

    // 索引:数组给每个成员的编号称为索引,索引值从零开始依次递增1

    // 访问数组成员时,若索引超出数组范围,则获取到的结果为 undefined

    console.log(arr['0']);

    console.log(arr['1']);

    // 数组索引可以不适用引号

    console.log(arr[2]);

    // 给数组成员赋值

    // 使用数组变量名加上索引值进行赋值,可以给指定索引位置的成员进行赋值

    arr[4] = { name: '李四' }

 // 数组长度(指的就是数组成员的个数)

    // 若赋值的时候索引若不是正整数,该值将被视为数组对象的属性值,不计入数组成员,所以不会影响数组长度

数组的操作

// push 追加数据到数组末尾

    // 参数:被添加的新数组成员

    arr.push('hello world')
 

    // pop 从尾部取出一个成员

    // 返回值是取出的成员

    let r = arr.pop()

    // unshift 在头部添加数据

    // 参数:被添加的新数组成员

    arr.unshift('my best day')
 

    // shift 从头部取出一个成员

    // 返回值是取出的成员

    r = arr.shift()

 // push 和 unshift 可以批量添加成员

    arr.push('a', 'b', 'c')

    arr.unshift('x', 'y', 'z')

    // splice 删除指定位置的成员,并用新成员替换,或插入新成员到指定成员的前面

    // 第一个参数:删除成员的起始位置

    // 第二个参数:删除成员的个数

    // 第三个参数:用于替换被删除成员的新数据,该参数可以省略

    // 删除一个成员:

    // r = arr.splice(6, 3)

    // splice的返回值 就是被删除的成员数组

    // console.log(r);

    // 在指定成员前追加新数据

    // 若第二个参数为0,则可以实现在指定位置的前面添加成员的功能

    arr.splice(6, 0, { name: 'Bob' })

    console.log(arr);

   // concat 连接数组

    // 参数:多个被追加进数组的成员,若成员是数组,该数组中每个成员将被加入原数组

    // concat 返回一个新数组

    // r = arr.concat(7, 8, 9)

    // r = arr.concat([7, 8, 9], [10, 11])

    // console.log(r);

    // console.log(arr);

    // concat 的应用场景多用于克隆数组

    let arr2 = [].concat(arr)

    console.log(arr2);

    console.log(arr === arr2);

  // join 使数组成员用一个字符连接起来

    // join 函数接收一个参数,该参数就是连接数组成员时使用的字符

    arr2 = ['abc', 'xyz', '123']

    r = arr2.join('-*-')

    // includes 判断是否包含某成员

    r = arr.includes('z')

   // indexOf 查询指定数组成员的索引

    r = arr.indexOf('b')

    console.log(r);

    // 可以使用indexOf判断是否包含某个数组成员 若不包含 返回 -1

    r = arr.indexOf('g')

    console.log(r); // => -1

 // slice 数组切片 获取子数组

    // 切片遵循“前截后不截”原理: 起始位置包含在结果内,结束位置不包含

    r = arr.slice(5, 8)

    console.log(r);

    // 参数只有一个,代表从该位置开始 一直截取到最后

    r = arr.slice(5)

  // Array.isArray 方法检测变量是否为数组

    // 是数组的话返回 true 否则为 false

多维数组

    // 多维数组:多个维度(rank)组成的数组

    let arr = [1, 2, 3]

    // 声明二维数组

    // 二维数组中每个一维数组的成员都是一个数组

    arr = [[1, 2], [3, 4], [5, 6]]

    // 读取数组

    // 可以将二维数组视为一个x轴和y轴构成的二维平面,所以任意值都可以由x,y坐标来表示

    console.log(arr[0][1]);

    console.log(arr[2][0]);


 

    // 声明多维数组

    // 多维数组是数组内嵌套多层数组的数组

    arr = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]

   

    // 访问数组

    // 可以理解成三维空间中访问一个三维坐标系

    console.log(arr[0][1][1]);

循环

// 循环就是重复执行某一段代码的行为

    // for 循环

    // 例如按次数进行循环

    let count = 5 // 循环次数

    // for (let i = 0; i < count; i++) {

    //     console.log('hello world');

    // }

    // 语法:

    // for( 初始化语句; 循环条件; code block 运行一次结束后运行的代码 ) { // code block }

    // 初始化语句:初始化变量值

    // 循环条件:循环条件是个布尔表达式,若结果为真,则执行 code block 的代码块

break:跳出循环    continue继续循环

 // do while 循环:先执行循环代码,再判断循环条件

    // 语法:

    // do {

    //     code block

    // } while (condition)

  // while 循环:当。。。时,就循环执行代码

    // 语法:

    // condition: while循环的条件,是一个布尔表达式,若成立则执行循环的 code block

    // while (condition) {

    //      code block

    // }

函数和数组的遍历函数

数组的迭代操作

    // 以下所有的遍历函数的参数都是相同的回调函数

    // 回调函数接收以下参数

    // el 被遍历的当前数组成员

    // index 被遍历的当前成员的索引

    // arr 被遍历的数组对象
 

    // forEach 循环遍历每一个数组成员

  // every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环

   // map 映射数组到新数组中

    // map 的回调函数将返回一个值,代表当前被遍历的数组成员在新数组中的投影

    // map 函数将返回一个新的投影数组

    // 例如将students中所有的年龄放入一个新数组

    // let r = students.map((el, index, arr) => {

    //     // return 的内容将被放到新的数组中

    //     return el.age

    // })

    // filter 过滤器

    // filter 返回过滤完后的新数组

    // let r = students.filter((el, index, arr) => {

    //     // 过滤掉年龄大于25岁的成员

    //     // if (el.age > 25) {

    //     //     // return false 代表过滤掉该数据

    //     //     return false

    //     // }

    //     // return true // return true 代表保留该数据

    //     return el.age <= 25

    // })

   // find 查找符合回调函数条件的数组成员并返回它

    // 返回值为查询结果

    let r = students.find((el, index, arr) => {

        // 查找女同学

        if (el.sex === 'female') {

            return true // return true 代表当前成员就是要查找的元素

        }

        return false

        return el.name === '隔壁老王'

    })

   // findIndex 查找对应成员所在索引

    // 使用方法和 find 相同,返回结果为查找到的成员索引

    // let r = students.findIndex((el, index, arr) => {

    //     return el.name === '隔壁老王'

    // })

  // some 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。

    // 给出一个判断条件,some 函数将对每个成员都做出相同的判断

    // 任意成员符合条件返回 true 时 some函数则返回true 否则为 false

    // 请判断 arr2 中是否有成员存在于 arr1 中

    let arr1 = ['x', 'y', 'z']

    let arr2 = ['a', 'b', 'z']

    // let r = arr2.some((el, index, arr) => {

    //     // 判断当前数组成员是否再 arr1 中存在

    //     if (arr1.includes(el)) {

    //         // 返回 true 代表 找到一个满足条件的数组成员

    //         return true

    //     }

    //     return false

    // })

 // sort 参数是一个排序规则的函数

    // el1 和 el2 代表的是排序时两两比较的两个数组成员

    // 排序原理是,将每个成员都和剩下所有成员进行比较,每一对 el1 和 el2 决定他们的先后顺序

    // 也叫“冒泡排序”

    // sort 函数执行完后将返回新数组

    students.sort((el1, el2) => {

        if (el1.age > el2.age) {

            // el1.age 若大于 el2.age 则 若从小到达排列 el1 应该放到 el2 的右侧

            // 右侧联想 右箭头 >;即大于符号,则此处应该返回一个大于零的值

            return 1

        } else if (el1.age < el2.age) {

            // el1.age 若小于 el2.age 则 若从小到大排列 el1 应该放到 el2 的左侧

            // 左侧联想到 左箭头 < ;即小于符号,所以返回一个小于 0 的值

            return -1

        } else {

            // 若 el1 和 el2 不需要交换顺序 则返回 0

            return 0

        }

    })

mapReduce统计模型

 // reduce 中的参数

    // p: 第一个数组成员或上一次运行回调函数的返回值

    // n: 下一个被遍历的成员

reduce((p,n)=>{

return p+n  //求和

})

字符串操作

正则表达式

    // \ 斜杠:转义

    console.log(/\\abc/.test('\\abc'));

    // ^ :匹配字符串的开头   regex = /^abc/

    // $ :匹配字符串的结尾  regex = /xyz$/


 

    // ------------------- 匹配字符个数的符号

    // 这些匹配字符个数的符号,代表的意思是:匹配前一个字符多少次

    // * :匹配任意次  regex = /^o*k$/

    // ? : 匹配0次或1次    regex = /^o?k$/

    // + : 匹配至少1次    regex = /^o+k$/

    // {n} : 匹配指定次数    regex = /^o{2}k$/

    // {n,} : 匹配至少n次    regex = /^o{2,}k$/

    // {n,m} : 匹配至少n次,至多m次  regex = /^o{1,3}k$/

    // ------------------- 匹配字符个数的符号 - end

    // [xyz]: 匹配字符集合,匹配一个字符,该字符在方括号内    regex = /^[xyz]$/

    // x|y : 或    regex = /^(good|bad)$/

    // [^xyz]: 匹配负值集合,匹配一个字符,该字符不在方括号内  regex = /^[^xyz]$/

     // [a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内    regex = /^[A-Z][0-9]$/

    // [^5-7]: 取范围负值,匹配一个字符,该字符不在指定范围内    regex = /^[^5-7]$/

    // ------------------- 分组 (pattern)

    // (pattern): 将pattern里面的所有字符当作一个字符处理    regex = /^abc(123)+xyz$/


 

    // 站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值

    // regex = /abc(123)+xyz/

    regex = /abc123xyz/
 

    // (?:pattern): 匹配分组内容,但不获取圆括号中的值

    regex = /abc(?:123)+xyz/

    r = '000123abc123123xyz444555'.match(regex)

    // assert 断言 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions/Assertions

    // 什么是断言? 断言就是一段下笃定的言论,例如: 今天一定会下雨

    // 先行断言

    // 语法: x(?=y)

    // x 跟随 y 时 匹配 x

    // /小明(?=小红)/ 该正则理解为: 小明后面一定跟随了一个小红

    console.log('小明小红'.match(/小明(?=小红)/));


 

    // 先行否定断言

    // 语法: x(?!y)

    // x 后没有 y 跟随时 匹配 x

    // /小明(?!小红)/ 小明后面一定没有跟随小红

    console.log('小明小芳'.match(/小明(?!小红)/));


 

    // 后行断言

    // 语法: (?<=y)x

    // x 的前有 y 则 匹配 x

    // /(?<=小红)小明/ 小明的前面一定有小红

    console.log('小红小明'.match(/(?<=小红)小明/));


 

    // 后行否定断言

    // 语法: (?<!y)x

    // x 前没有 y 则 匹配 x

    // /(?<!小红)小明/ 小明的前面一定没有小红

    console.log('小芳小明'.match(/(?<!小红)小明/));

   // 不开全局模式 只会匹配到第一个符合条件的字符串

   // 全局模式的另一个应用场景

    // 字符串的替换函数

    str = '共产党万岁,共产党伟大,共产党真牛逼'

    regex = /共产党/

    // replace 用于替换字符串

    // 第一个参数: 要替换的内容,是一个正则表达式

    // 第二个参数: 要换入的内容

    // 返回值: 替换后的字符串

    r = str.replace(regex, '***');

    console.log(r);

    // 使用全局模式进行全局替换 否则只会替换第一个

    regex = /共产党/g

    r = str.replace(regex, '***');

    console.log(r);

   // match:从字符串中匹配出符合正则的字符串

    // 参数:用于匹配的正则表达式

    // 返回值:包含了匹配结果的数组

闭包和计时器

闭包

    // 闭包也叫函数闭包,通过函数产生一个封闭的内存空间,包裹一些需要被保存的数据

    // 且函数需要返回一个持续引用的对象,这就叫闭包

    // 为了方便理解,做如下的假设:

    // 1. 假设有一个函数A

    // 2. A内声明变量B

    // 3. A返回一个 包含函数的内容

    // 4. A返回的 包含的函数必须引用变量B

    // 5. 此时函数A就是闭包的

    // 应用场景

    // 闭包用于存储一些不让函数外访问的数据,或者为了避免作用域中变量名的冲突,可以使用闭包

计时器

当经过指定时间后触发一段代码的函数就是一个计时器

    // 声明一个计时器:setTimeout

    // 第一个参数:计时器计时结束后触发的函数

    // 第二个参数:计时时长,单位:毫秒

    // 返回值: 计时器id

  clearTimeout(timerId)

    // 计时器id 用于停止计时

    // setInterval 循环计时函数

    // setInterval 函数,每次经过指定时间,触发一次指定的函数

 clearInterval(timerId2)停止计时

    // 参数和返回值 与 setTimeout 相同

时间对象和数学函数

时间对象

    // 创建时间对象的方法

    // 语法:new Date(params)

   // 如何读取时间?如何设置时间?

    // 读取时间

    date = new Date()

    console.log(date.getFullYear()); // 年

    console.log(date.getMonth()); // 月  月份从0开始计算

    console.log(date.getDate()); // 日 一个月中的第几天

    console.log(date.getDay()); // 一周中的第几天 一周中的第一天是周日 值为 0

    console.log(date.getHours()); // 时

    console.log(date.getMinutes()); // 分

    console.log(date.getSeconds()); // 秒

    console.log(date.getMilliseconds()) // 毫秒

设置用set

数学函数

 // 三角函数

    // 需要注意的是,三角函数 sin()、cos()、tan()、asin()、acos()、atan() 和 atan2() 返回的值是弧度而非角度。

    // 若要转换,弧度除以 (Math.PI / 180) 即可转换为角度,同理,角度乘以这个数则能转换为弧度。

事件

  // load 加载完成

    img.addEventListener('load', () => {

        console.log('加载完成');

    })

    // error 加载失败

    img.addEventListener('error', () => {

        console.log('加载失败');

    })

    // 焦点事件

    const input = document.querySelector('input')

    const box = document.querySelector('.box')

    // focus 获取焦点

    input.addEventListener('focus', () => {

        console.log('获取焦点');

    })

    // blur 失去焦点

    input.addEventListener('blur', () => {

        console.log('失去焦点');

    })

    box.addEventListener('focus', () => {

        console.log('box获取焦点');

    })

    box.addEventListener('blur', () => {

        console.log('box失去焦点');

    })

    // 鼠标事件

    const box2 = document.querySelector('.box2')

    // 点击事件

    box2.addEventListener('click', () => {

        console.log('单击左键');

    })

    // 右键菜单

    box2.addEventListener('contextmenu', () => {

        console.log('右键菜单');

    })

    // 双击

    box2.addEventListener('dblclick', () => {

        console.log('双击');

    })

    // 鼠标点下

    box2.addEventListener('mousedown', ev => {

        console.log('点下');

        console.log(ev);

        // ev.button 用于区分点击的是哪个键

        // 0: 左键

        // 1: 中键

        // 2: 右键

        console.log(ev.button);

    })

    // 鼠标抬起

    box2.addEventListener('mouseup', ev => {

        console.log('抬起');

        console.log(ev);

        console.log(ev.button);

    })

    // 进入和离开事件

    // box2.addEventListener('mouseenter', () => {

    //     console.log('进入');

    // })

    // box2.addEventListener('mouseleave', () => {

    //     console.log('离开');

    // })

    // 悬停和出去

    // box2.addEventListener('mouseover', () => {

    //     console.log('悬停');

    // })

    // box2.addEventListener('mouseout', () => {

    //     console.log('出去');

    // })

    // 移动

    box2.addEventListener('mousemove', ev => {

        console.log('移动');

        console.log(ev);

        // offsetX offsetY 是鼠标相对于元素左上角的坐标

        console.log(ev.offsetX);

        console.log(ev.offsetY);

    })

    // 滚轮

    box2.addEventListener('wheel', ev => {

        console.log('鼠标滚轮');

        console.log(ev);

        // deltaY 纵向滚动的变化量

        // 正数向下 负数向上

        console.log(ev.deltaY);

    })
 

    // 拖动事件

    // 元素上需要添加 draggable="true"

    const box3 = document.querySelector('.box3')

   // 变化事件

    // 一般除了输入框外都使用 change 事件

    input.addEventListener('change', ev => {

        console.log('变化');

        console.log(ev);

        // 当前输入框的值

        console.log(ev.currentTarget.value);

        console.log(ev.target.value);

    })


 

    // 按键事件

    // 按下

    // 可以按住不放持续触发事件

    input.addEventListener('keydown', ev => {

        console.log('按下');

        console.log(ev);

        // 获取用户点击的哪个按键

        console.log(ev.key);

        console.log(ev.keyCode);

    })

    box.addEventListener('keydown', () => {

        console.log('box按下');

    })

    // 抬起

    input.addEventListener('keyup', ev => {

        console.log('抬起');

        console.log(ev);

        // 获取用户点击的哪个按键

        console.log(ev.key);

        console.log(ev.keyCode);

    })

    // 按压

    input.addEventListener('keypress', ev => {

        console.log('按压');

        console.log(ev);

    })

冒泡,捕获事件

在html中触发事件的元素,将会把事件不断的向父元素传递,这个过程叫做事件冒泡

阻止事件冒泡

   document.querySelector('button').addEventListener('click', (ev) => {

        // 使用事件对象的 stopPropagation 来阻止事件冒泡

        // ev.stopPropagation()

        console.log('button');

    })

 // 事件触发后,可以由上级元素先处理事件,这个过程就是捕获事件

    // addEventListener 的第三个参数代表是否捕获事件

    // 捕获事件的元素会先处理事件,然后将事件对象还给产生事件的元素,然后正常冒泡

    document.body.addEventListener('click', () => {

        console.log('body');

    }, true)

面向对象编程

js用属性和行为来描述某个物体而产生的一种数据结构,该数据称为对象

    // 声明对象

    // 设计一个对象最重要的就是设计它的属性和行为

    // 属性: 都是一些值

    // 行为: 都是一些函数

   let duck = {

        // 设计对象属性如下

        color: '#0f0',

        age: 2,

        name: '押宝',

        // 对象行为

        // 对象的行为一般称为方法

        swim() {

            console.log(`鸭子在游泳`);

        },

        shout() {

            console.log('叫啦');

        },

        fly() {

            console.log('鸭在飞');

        }

    }

类和实例对象

<script>
    // 什么是类 class
    // 用于描述同类事物的类型叫做类,类型是抽象的

    // 构造函数: 用于构造类实例的函数

    // 声明类型
    // es5
    // 类型名大写开头
    // 该函数 Human 就是类型的构造函数
    function Human(name, age, sex){
        // 在类型中 this 关键字 代表当前实例对象
        // 属性
        this.name = name
        this.age = age
        this.sex = sex

        // 行为
        this.eat = function(food){
            // 方法中使用this访问实例对象
            console.log(`${this.name} 正在吃 ${food}`);
        }
    }
   

    // es6 语法定义类型如下:
    class Car {
        // 属性
        // 价格
        value = 0
        // 生产厂商
        producer = ''
        // 名称
        name = ''

        // 声明构造函数
        constructor(value, producer, name){
            this.value = value
            this.producer = producer
            this.name = name
        }
        
        // 行为
        run(){
            console.log(`价值 ${this.value} 的 ${this.name} 在跑`);
        }
    }


    // 实例化类型
    // 生成一个类型的个例的过程称为实例化
    // 以下代码中 new Human() 这句话就是在实例化Human类
    // 实例化的结果被存在了 zhangSan 变量中,
    // 我们称实例化的结果为:实例,实例对象,类实例

    // new 关键字用于实例化类型
    // new 关键字后面的 Human() 实际上是在调用构造函数
    let zhangSan = new Human('张三', 17, 'male')
    console.log(zhangSan);


    // 修改实例属性
    zhangSan.age = 24

    // 调用实例行为
    zhangSan.eat('小黄瓜')

    let car = new Car(10000000, '陆厂', '跑得快')
    console.log(car);
    console.log(car.value);
    car.run()
    

    // 实例和类的关系是:
    // 类是实例的模板,实例是类的成品

   
    // 访问实例属性和行为




    // 所有使用 constructor 构造函数构造而成的数据类型都是 Object 对象类型(也就是使用关键字 new 创建的对象)
    // 例如:
    // console.log(typeof Number(123))
    // console.log(typeof new Number(123))
    // console.log(typeof String('hello'))
    // console.log(typeof new String('hello'))
    // 这可以解释为什么数组是 object 类型
    // 因为数组可以使用 new Array() 来创建
</script>

声明私有属性和私有方法

// 在实例对象内能使用但是实例对象外无法使用的属性和方法,称为私有属性和方法

<script>
    // 什么是私有属性和方法?
    // 在实例对象内能使用但是实例对象外无法使用的属性和方法,称为私有属性和方法

    function Human(name, age, sex) {
        this.name = name
        this.age = age
        this.sex = sex

        // 声明一个变量来充当私有属性
        let hobbie = '败家'

        this.eat = function (food) {
            console.log(`${this.name} 正在吃 ${food}`);
            console.log(hobbie);
            fn()
        }

        // 私有方法
        const fn = function () {
            console.log('这是私有方法');
        }
    }


    class Car {
        value = 0
        producer = ''
        name = ''
        // 私有方法或属性在名称的前面使用 # 井号
        #engine = '这是发动机'

        constructor(value, producer, name) {
            this.value = value
            this.producer = producer
            this.name = name
        }

        run() {
            console.log(`价值 ${this.value} 的 ${this.name} 在跑`);
            console.log(this.#engine);
            this.#error()
        }

        #error() {
            console.error('这是私有的方法');
        }
    }

    let zs = new Human('张三', 17, 'male')
    console.log(zs.hobbie);
    // zs.fn()


    let car = new Car(1000, 'ok', 'hao')
    // console.log(car.#engine);
    car.run()
    // car.#error()

</script>

静态属性和方法

 // 静态属性和方法 指的就是一类中共有的属性和方法


    class Human {
        name
        sex
        // class 类中 用static关键字声明静态属性和方法

        // 静态属性是属于类的属性
        // 用来描述这个类
        static category = '哺乳动物'

        constructor(name, sex) {
            this.name = name
            this.sex = sex
        }
        eat(food) {
            console.log(`${this.name} 吃了 ${food}`);
        }

        // 静态方法,用来描述一个类的行为
        static greedy() {
            console.log('小孩子才做选择 我全都要');
        }
    }

    let human = new Human('张三', 'male')

    // 调用静态属性或方法,使用类名调用而不是实例对象
    console.log(Human.category);
    Human.greedy()

面向对象编程更难转变的是思维方式

this关键字和继承

  // js 执行上下文中的 this

    console.log(this); // => window

    console.log(this === window);

    // 函数内的this

    function fn() {

        // 非严格模式下 函数内 this => window

        console.log(this);

    }

    fn()

  // 实例对象方法的this

    class A {

        name

        constructor(name) {

            this.name = name

        }

        fn() {

            // 类方法中的 this => 调用该方法的实例对象

            console.log(this);

        }

    }

    let a = new A('a')

    let b = new A('b')

    a.fn()

    b.fn()

  // 有些情况下,回调函数中的this可能被其他函数赋值,所以this不是undefined

    // 此处 事件回调函数被 addEventListener 赋值了其中的 this

    // 所以这里的this指的是绑定事件的那个dom对象

    document.querySelector('button').addEventListener('click', function () {

        console.log(this);

    })

严格模式下

  // 1. js 执行上下文 => window

    // 2. 只要是函数 this 就是 undefined (函数=>undefined)

    // 3. 方法内的this指向调用方法的对象 (方法=>实例)

    // 4. this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined,它取决于函数 addEventListener 自己

super


<script>
    // 鸟
    class Bird {
        // 名称
        name = '鸟'

        constructor(name) {
            this.name = name
        }

        // 飞
        fly() {
            console.log('鸟在飞');
        }
    }

    // 鸭子
    class Duck extends Bird {
        color

        constructor(name, color) {
            // 子类构造函数中理论上都应该使用 super 关键字来构造父类
            // 在构造函数中,super 代表父类的构造函数
            super(name)
            // 子类构造函数要使用 this 关键字的话,则必须先调用 super
            this.color = color
        }

        // 重写父类的方法
        fly() {
            console.log(`${this.name} 在飞`);
            // 可以使用super关键字调用父类方法
            // 方法中的super代表父类实例
            console.log(super.name); // super 只能调用方法不能调用属性
            super.fly() // 通过 super 调用父类方法 
        }
    }

    let duck = new Duck('卤鸭子', '#f00')
    console.log(duck);
    duck.fly()




    // function 类不能使用super
    function Phone(_price) {
        this.price = _price
    }

    function IPhone(_price, _color) {
        this.price = _price
        this.color = _color
    }

    IPhone.prototype = new Phone()

    let iphone = new IPhone(1000, 'pink')
    console.log(iphone);

</script>

 // 子类构造函数中理论上都应该使用 super 关键字来构造父类

            // 在构造函数中,super 代表父类的构造函数

            super(name)

            // 子类构造函数要使用 this 关键字的话,则必须先调用 super

lambda中的this

   // 1. lambda 表达式没有 this

    // 2. lambda 表达式中的 this 来自于声明函数时上下文中的 this

继承

 // 从父类传承下他的属性和行为,使子类具备相同的属性和行为,同时子类可以拥有自己的属性和行为

    // 父类(parent class)也叫做基类(base class),也叫超类(super class)

extends

<script>
    // 什么是继承
    // 从父类传承下他的属性和行为,使子类具备相同的属性和行为,同时子类可以拥有自己的属性和行为
    // 父类(parent class)也叫做基类(base class),也叫超类(super class)



    // 以下述三种人种为例: 人类 就是 欧洲人和非洲人的父类,非洲人和欧洲人是人类的子类

    // class 类 语法下的继承
    // 人类
    class Human {
        name
        sex
        sleep() {
            console.log('人类在睡觉');
        }
    }


    // 使用 extends 关键继承父类
    // 欧洲人
    class Eurapean extends Human {
        // 子类可以有自己的属性和行为
        luck = true
        detox() {
            console.log(`远离黄赌毒 珍爱生命`);
        }

        // 子类可以拥有和父类相同的方法,用来覆盖父类的方法
        // 这种方法称为 方法的重写 (override)
        sleep() {
            console.log(`${this.name} 在睡觉`);
        }
    }

    let h = new Eurapean()
    h.name = '张三'
    h.sex = 'male'
    console.log(h);
    h.sleep()
    console.log(h.luck);
    h.detox()


    // 非洲人
    class Afriaca extends Human {
    }




    // function 类 语法的继承
    // 汽车
    function Car() {
        this.name = '皮卡'
        this.run = function () {
            console.log('车在跑');
        }
    }

    // 声明一个皮卡继承Car

    function PickCar() {
        // 载重量
        this.weight
        this.mount = function () {
            console.log('装货');
        }
    }

    // 利用原型进行继承
    // 什么是原型? 类的模板(设计图)

    // 给 PickCar类型赋值原型属性,既能进行继承
    PickCar.prototype = new Car()

    let pk = new PickCar()
    console.log(pk);


    // 原型 __proto__ 和 prototype 的区别
    // __proto__ 实例对象中访问原型的属性
    // prototype 类访问原型的属性

    // 实例对象才能访问 __proto__ 对象
    console.log(pk.__proto__);
    // 类名才能访问 prototype
    console.log(PickCar.prototype);



    // 原型属性或原型函数
    // 例如
    // Car.prototype.wheelCount = 4
    // Car.prototype.shout = () => {
    //     console.log('喇叭叫')
    // }
    // // 原型属性或原型函数可以在类实例或子类的实例中调用
    // // 实例化对象会继承原型属性和函数
    // let c = new Car()
    // console.log(c.wheelCount);
    // c.shout()

    // pk = new PickCar()
    // console.log(pk.wheelCount);
    // pk.shout()


    // // 若不实例化类,可以通过原型,直接访问,例如:
    // console.log(Car.prototype.wheelCount)
    // Car.prototype.shout()

</script>

原型链

<script>
    // 什么是原型链? 对象中的__proto__,如果存在上级,
    // 那么当前对象的__proto__和原型中的__proto__ 形成的一种链式结构就是原型链
    // Object 类型是所有类型的父类

    class A {
        name = 'A'
    }

    class B extends A {
        sex = 'other'
    }

    class C extends B {
        age = 17
    }

    let c = new C()

    console.log(c)
    console.log(c.name);

    // 可以通过链式调用获取到不同级的原型对象
    // console.log(c.__proto__)
    // console.log(c.__proto__.__proto__)
    // console.log(c.__proto__.__proto__.__proto__)
    // console.log(c.__proto__.__proto__.__proto__.__proto__)
    // console.log(c.__proto__.__proto__.__proto__.__proto__.__proto__)





    function D() {
        this.name = '我是D'
    }

    // 原型链的运行顺序:
    // 当访问run函数时,浏览器会先从f对象中寻找run函数,若不存在
    // 就会在父类中寻找,若父类中找不到,就会到父类的父类中寻找
    // 直到找到为止
    D.prototype.run = () => {
        console.log('D在跑')
    }

    function E() {
        this.sex = '男'
        this.run = () => {
            console.log('E在跑')
        }
    }

    E.prototype = new D()

    function F() {
        this.age = 0
    }

    F.prototype = new E()

    let f = new F()
    console.log(f)
    console.log(f.age)
    console.log(f.sex)
    console.log(f.name)
    f.run()

    // 通过上述例子,可以看出在F中并不存在sex和name,但依然可以访问他们
    // 因为浏览器在访问属性时的顺序如下:
    // 先从自身类中寻找属性,若不存在就查找他的原型是否存在该属性,若还不存在,就查询原型的原型中是否存在,依次类推
</script>

Object类方法和访问器

常用静态方法

<script>
    // Object 的常用静态方法



    // Object.assign : 拓展对象
    // 将一个对象的属性复制到另一个对象中,且返回一个新对象

    let x = {
        a: 1,
        b: 2
    }

    let y = {
        c: 3,
        d: 4
    }


    // 第一个参数:要拓展属性的对象
    // 第二个参数:拓展的属性源对象
    // Object.assign(x, y) 这句话的含义就是,将y中的属性拷贝到x中,且返回x对象
    let r = Object.assign(x, y)
    console.log(r);


    // Object.assign 该函数常用于浅拷贝对象

    // 拷贝对象
    let obj = { name: '张三' }
    r = Object.assign({}, obj)
    console.log(r);

    // 拷贝数组
    let arr = [1, 2, 3, 4]
    r = [].concat(arr)
    console.log(r);

    // 浅拷贝例子
    let user = {
        name: '金牌猎人'
    }

    obj = {
        isOk: true,
        msg: 'hello',
        num: 123,
        user: user // 引用地址
    }

    r = Object.assign({}, obj)

    // r.user 引用地址 拷贝自 obj.user

    console.log(r === obj);
    // 修改拷贝后的数据
    // r.user.name = '亡命之徒'
    // 原始数据 obj 也被修改了
    console.log(obj.user.name);
    // 结论: 浅拷贝的结果,引用地址会被直接复制


    // 深度拷贝: 数据中所有的对象将被复制,而不是复制引用地址
    // 使用序列化和反序列化实现深拷贝
    r = JSON.parse(JSON.stringify(obj))
    console.log(r === obj);
    r.user.name = '亡命之徒'
    console.log(obj.user.name);
    console.log(r.user.name);



    // 参考地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
    // Object.defineProperty : 定义属性

    obj = {}
    obj.x = 1
    obj['y'] = 2
    console.log(obj)

    // 通过 Object.defineProperty 定义属性
    // 第一个参数:要定义属性的对象
    // 第二个参数:要定义的属性名称
    // 第三个参数:关于指定属性的配置,是一个json对象
    Object.defineProperty(obj, 'z', {
        // 属性值
        // value: 3,
        // 是否可写
        // writable: true,
        // 书写访问器时 不允许添加 value 和 writable 属性
        get() {
            // 访问中通过this访问当前对象
            return this.x
        },
        set(value) {
            this.x = value
        }
    })

    console.log(obj)


    // Object 对象上的 toString 方法
    // 所有类型都是Object类型的子类
    // toString 方法 将用在转换字符串时
    // 所有隐式转换对象为字符串的情况下,js会自动调用对象的 toString 方法

    console.log(new Number(123).toString());
    function Car() {
        this.name = '车'
    }
    console.log(new Car().toString());

    // 重写 toString
    function Bird() {
        this.name = '鸟'
        // 重写 toString
        this.toString = function () {
            return JSON.stringify(this)
        }
    }
    let b = new Bird()
    console.log(String(b));
    console.log(b + '123');
</script>

包装类

<script>
    // 什么是包装类?
    // 将我们熟知的基本的数据类型用类来进行包装,这种类就是包装类

    // 作用,多用于类型转换

    // 包装类有以下几种
    Boolean
    Number
    String
    Object
    Array
    Function
    BigInt
    Symbol

    // 包装类还包含一些可以调用的函数
    // 例如 用 Array.isArray 来判断一个变量是否是数组
    let arr = []
    console.log(Array.isArray(arr)) // ---> true

    console.log(Array.from(new Set(arr)));

    // 将变量转换为整数
    console.log(Number.parseInt(0.5)) // ---> 0

    // 将变量转化为浮点数(小数)
    console.log(Number.parseFloat('2.9')) // ---> 2.9

    // 保留数字小数点后指定位数
    console.log(Number(0).toFixed(2))

    // Number.isNaN
    // Number.isFinite

    // 包装类在参与运算的时候会被自动解包成对应的数据类型
    console.log(typeof String('hello world'))
    console.log(typeof Number(123))
</script>

delete

    // delete 删除 对象属性,对象行为

    let obj = {

        x: 1,

        y: 2,

        sayGreet: () => {

            console.log('greeting')

        }

    }

    // delete 语法:

    // delete object.attrName

    // 删除属性如下

    delete obj.y

    delete obj.sayGreet

    console.log(obj);

    // delete 关键字的功能 等价于 Reflect.deleteProperty

    Reflect.deleteProperty(obj, 'x')

    console.log(obj);

访问器get和set

访问属性用的函数,访问有两重含义,读值和赋值

    // 作用:可以控制读取属性的结果,可以控制赋值属性的内容(控制访问)

    // 具体的作用

    // getter:

    // 1. 格式化数据显示

    // 2. 简化路径访问

    // setter:

    // 1. 验证赋值参数的合法性



<script>
    // 访问器是什么?访问属性用的函数,访问有两重含义,读值和赋值

    // 作用:可以控制读取属性的结果,可以控制赋值属性的内容(控制访问)

    // 具体的作用
    // getter: 
    // 1. 格式化数据显示
    // 2. 简化路径访问
    // setter:
    // 1. 验证赋值参数的合法性



    // 对象中声明访问器
    let obj = {
        _name: '张三',

        // 读值访问器 称为 getter
        // get 关键字 后面跟上一个访问器的名称
        get name() {
            // getter 访问器需要返回一个值
            return `姓名: ${this._name}`
        },

        // 赋值访问器 称为 setter
        // set 关键字 后面跟上访问器名称
        set name(value) {
            // value: 赋值属性时的参数
            this._name = value === '张三' ? '法外狂徒' : value
        }

        // getter 和 setter 是一对,所以名称可以相同
    }

    // 使用访问器
    // 访问器被当作属性使用
    console.log(obj.name);
    obj.name = '李四'



    // 在类中声明访问器
    class Human {
        #sex
        constructor(sex) {
            this.#sex = sex
        }

        // 声明访问器
        get sex() {
            return this.#sex === 'male' ? '男' :
                this.#sex === 'female' ? '女' : '其他'
        }

        set sex(value) {
            this.#sex = value === '男' ? 'male' :
                value === '女' ? 'female' : 'other'
        }
    }

    let h = new Human('female')
    console.log(h.sex);
    h.sex = '其他'

</script>

ES6语法

<script>
    // let 关键字
    let a = 11
    // const 定义常量 常量是readonly只读的属性
    // 一旦初始化后 就不能修改其值
    const c = 15
    console.log(c)

    const g = 9.8


    // ... 扩展
    let obj = { x: 1, y: 2 }
    // 复制对象
    console.log({ z: 3, ...obj });
    let arr = [1, 2, 3]
    // 复制数组
    console.log([...arr, 4]);


    // rest parameters
    function fn(a, b, ...c) {
        console.log(a);
        console.log(b);
        console.log(c);
    }

    fn(1, 2, 3, 4, 5, 6)


    // lamda 表达式 也称 箭头函数
    let fn2 = () => ({})


    // 模板字符串
    let year = 2020
    let month = 8
    let day = 19
    console.log(`${year}/${month}/${day}`);


    // 解构复制
    // 对象
    let obj2 = { aa: 1, b: 2 }
    let { aa, b } = obj2
    console.log(aa, b);
    // 数组
    let arr2 = [1, 2, 3]
    let [x, y, z] = arr2
    console.log(x, y, z);
    // 函数参数的解构
    function fn3(a, { b, c }) {
        console.log(a);
        console.log(b);
        console.log(c);
    }
    fn3('hello', { b: 2, c: 5, a: 16 })



    // Promise语法
    let promise = new Promise((resolve, reject) => {
    })

    // 语法
    // Promise.resolve(value);
    // Promise.reject(value);
    // 参数
    // value
    // 将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。
    // 将不是thenable的对象转换为thenable的对象
    Promise.resolve()
    // Promise.all()
    // Promise.race()


    // 定义对象属性和方法的简写

    let p = 'hello'
    let obj5 = {
        // 传统定义方法,需要声明key和value
        // p: p
        // 但当 key 和 value 的名称相同时,可以省略冒号后的内容
        p,
        // 传统定义函数
        // fn: function(){}
        // 可以简写成
        fn() { }
    }


    // ? 问好语法
    // 判断是否存在,存在就执行,否则不执行
    let o = { a: 1, fn() { console.log('hello') } }
    console.log(o?.a);
    o?.fn()
    o = undefined
    o?.fn()
</script>

Map和Set


<script>
    // Map 的使用类似 json 对象
    // map 实际上就是 key: value 键值对
    // 声明 Map
    let map = new Map([
        ['a', true],
        ['b', 123],
        ['c', 'string']
    ])

    console.log(map);

    // 读值
    console.log(map.get('a'));
    console.log(map.get('b'));
    console.log(map.get('c'));
    console.log(map.get('d')); // => undefined 不存在的key读出来是 undefined

    // 赋值
    map.set('d', { name: '张三' })
    map.set('e', function () { console.log('hello') })
    console.log(map);

    // 迭代map
    let keys = map.keys()
    console.log(keys);
    let key
    // keys.next() 方法的作用是迭代下一个成员 返回一个对象
    // 返回对象包括 value 属性和 done 属性 done 代表是否迭代完成
    while (!(key = keys.next()).done) {
        // value 就是每一个key
        console.log(key.value);
        console.log(map.get(key.value));
    }

    console.log('=====================================================');

    // 可以使用 forEach 迭代
    map.forEach((value, key, _map) => {
        // value map对应key 的值
        // key 迭代的键
        console.log(value);
        console.log(key);
        // _map 当前正在迭代的map
        console.log(_map);
    })


    // has 判断是否存在某个 key
    console.log(map.has('d'));
    console.log(map.has('f'));

    // for of 遍历 Map
    for (const entry of map) {
        // entry 是包含了一组 key value 的数组
        console.log(entry[0]); // => 0 号成员就是key
        console.log(entry[1]); // => 1 号成员就是value
    }

    // Set 是不重复的数据集,若存入多个重复数据会自动去重
    let set = new Set([1, 1, 2, 2, 3, 3])
    console.log(set);

    // 添加
    set.add(true)
    set.add(12)
    set.add('hello')
    let o = { name: '李四' }
    set.add(o)

    console.log(set);

    // 删除
    set.delete('hello')
    set.delete(o)
    console.log(set);


    // 判断是否存在某个值
    console.log(set.has(12));
    console.log(set.has('world'));


    // 迭代 set
    keys = set.keys()
    while (!(key = keys.next()).done) {
        console.log(key.value);
    }


    // foreach 迭代 set
    set.forEach((value1, value2, _set) => {
        console.log(value1);
        console.log(value2);
        console.log(_set);
    })


    // 通过 set 转换成数组
    // 可以通过这种方法来让数组去重
    let arr = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
    console.log(Array.from(new Set(arr)));


    // for of 遍历 Set
    for (const value of set) {
        console.log(value);
    }

    // map 和 set 的size属性代表数据长度
    console.log(set);
    console.log(map);
    console.log(set.size);
    console.log(map.size);

    // 但凡包含迭代器的对象都可以使用 forof 来迭代

</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值