华清远见重庆中心-JS个人总结

目录

1.Javascript简介与基础语法:

变量:

js代码的执行顺序:

第一个js代码:

2.数据类型:

BigInt:

Boolean:

 Null:

 Object:

 String:

 Symbol:

 Undefined:

 Number:

 序列化和反序列化:

数据类型的转换:

Reflect:

3.节点操作:

插入节点:

查询节点:

删除节点:

替换节点:

异步加载js文件:

通过class来设置样式:

自定义属性:

child:

window:

location:

history:

localStorage:

4.运算符和条件判断

比较运算符:

赋值运算符:

逻辑运算符:

三元运算符:

算数运算符:

运算优先级顺序:

if条件判断:

switch条件判断:

5.数组和循环

多维数组:

如何判断一个变量是否是数组:

数组的基础:

数组的操作:

流程控制-循环:

冒泡排序法:

6.函数和数组的遍历函数

变量的作用域:

参数的值传递和引用传递:

函数的使用:

节流和防抖:

内存堆栈与垃圾回收:

通过作用域理解var和let的区别:

预编译:

arguments变量:

bind call apply函数的应用:

lambda表达式:

数组的迭代操作:

数组去重:

MapReduce统计模型:

7.字符串操作

表达式语法:

第一个正则表达式:

贪婪模式和非贪婪模式:

正则表达式的全局模式:

正则表达式中的特殊字符:

字符串的基础操作:

字符串的match函数:

8.闭包和计时器

闭包:

列表显示:

添加数据:

删除单条数据:

批量删除数据:

数据编辑:

导致内存溢出的情况:

计时器:

9.js移动与渐入渐出动画

封装移动函数:

渐入渐出动画:

元素移动:

10.时间对象和数学函数

时间对象:

数学函数:

11.事件和异常处理

事件:

事件的基础:

事件分类:

事件机制——冒泡:

事件机制——捕获事件:

阻止事件冒泡:

屏蔽默认事件:

事件对象的currentTarget和target:

通过js代码来触发事件:

自定义事件对象:

给div元素增加keydown和focus事件:

异常处理:

基本吃力方法和异常的特点:

finally:

自定义异常对象:

12.面向对象编程

对象:

类和实例对象:

判断对象是否包含某个key:

声明私有属性和私有方法:

通过面向对象渲染一个表:

13.this,super和继承

继承与原型:

静态属性和方法:

严格模式:

原型链:

instanceof:

lambda表达式中的this:

super关键字:

this关键字:

14.Object类方法和访问器:

delete关键字:

Object常用静态对象:

访问器getter和setter:

包装类:


1.Javascript简介与基础语法:

变量:

<script>
    // 变量
    // 变量就是存储数据的容器
    // 变量是一个可以变化的量,数据可以发生变化

    // js 中预留了一些代表特殊含义的英文单词,这些单词我们称为关键字

    // 声明变量
    // 语法:var 变量名

    var a; // 声明后不赋初始值 默认都为 undefined

    // 声明变量并赋予初始值

    var b = 5;

    // 同时声明多个变量并赋予初始值

    var c, d = 10, e;

    // 修改变量
    // 变量在存储新值之前讲完全弃用原来的值
    c = 2

    // 还可以使用 let 来声明变量
    // let 声明变量和使用变量的方法和 var 没有区别
    let x, y, z = 88

    // let 变量不能重复定义 var 变量可以重复定义

    // 常量
    // 恒定不变的值为常量
    // 声明时必须赋初始值 且声明后无法修改
    const g = 10
    const pi = 3.1415926

    // 访问变量或常量
    // 变量值存进容器以后,我们需要从容器中取出来进行使用,这个过程就叫访问

    console.log(b);
    console.log(g);
    console.log(pi);

    // 声明json对象 (或叫做朴素对象 plain objcet)
    let obj = {
        // key: value 数据格式的键值对
        // key 我们称为 键、对象的属性名
        name: '张三',
        sex: 'male',
        age: 17
    }

    let obj2 = { a: 1, b: 2, c: 3 }

    // 访问对象和对象属性
    console.log(obj)
    // 使用 . 运算符读取对象中的属性
    console.log(obj.age)
    // 使用索引来读取对应的数据
    // 站在字典的角度来看,name 就是目录,"张三" 就是目录对应的内容
    // 目录是唯一的
    console.log(obj['name'])


    // 给对象属性赋值
    obj.name = '法外狂徒'
    // 通过索引赋值对象属性
    obj['sex'] = 'female'
    console.log(obj);


    // 变量的命名规范:
    // 	1,必须以字母开头,或者使用  $ 或者 _  开头  ,不能用数字,变量名建议不要使用中文
    // 	2,小驼峰命名法:  多个单词构成的名字,第一个单词全小写,后面的首字母大写
    // 	3,为了提高代码的可读性,命名规范,见名知意
    // 	4,不能使用关键字(有特殊功能的名字:class、var、this)

    let _a // 下划线开头的变量一般是局部变量出现在函数中作为形式参数
    let $body // $ 开头一般是jquery对象
    let bird // 用字母开头
    let blueBird // 多个单词,首个单词的首字母小写,其余单词的首字母大写
    let simpleDateFormat
    // let const // 由于const是关键字,所以不能用作变量名
</script>

js代码的执行顺序:

<!-- 若有多个script,那么从上而下依序执行 -->
    <script>
        console.log('hello world')
    </script>

<script>
    console.log('next script')

    // 在同一个script标签中,js的执行顺序

    // 以一句话为单位,基本顺序:从上到下,从左到右,每句话用分号隔开“;”

    console.log('this is a'); console.log('this is b'); console.log('this is c');
    console.log('this is d'); console.log('this is e'); console.log('this is f');

    // 若代码中每一行只有一句js,那么句尾的分号可以省略
    console.log('这句话没有打分号')

    // 赋值运算的顺序: 先执行赋值符右侧代码,再赋值给左侧
    let a = 3 + 4 * 2

    console.log('a: ', a);

    // 函数调用顺序
    let fn1 = (x, y, z) => {
        console.log(x)
        console.log(y)
        console.log(z)
        return x + y + z
    }

    let fn2 = () => {
        return 1
    }

    let fn3 = () => {
        return 2
    }

    // debugger 的作用:可以给代码加断点(breakpoint),程序运行到断点处,会停下来,开发人员可以查看此时变量的值,且可以手动一步一步运行程序进行调试
    console.log('debugger前');

    debugger

    console.log('debugger后');

    // 以下代码执行顺序:
    // 从左到右执行,若存在函数,先执行函数
    // 若在执行的函数中存在其他函数,例如下面的fn1的调用,那么会先执行内部的函数,如 fn3 fn2,最后执行 fn1
    c = fn2() + 4 + fn1(fn3(), fn2(), 11)

</script>

第一个js代码:

<!-- 使用 script 标签引入外部js文件
    src 是文件路径
-->
<script src="./main.js"></script>

<!-- script 标签写在body后面 -->
<script>
    // console.log 打印日志到控制台
    console.log('hello world')
</script>

2.数据类型:

BigInt:

<script>
    // BigInt 长整型
    // 用来描述一个位数非常长的整型数字
    let a = 1n;
    console.log(a);
    console.log(typeof a); // => bigint

    // bigint 类型的数只能和另一个 bigint 类型的数进行运算
    // 否则会抛出异常
    console.log(124n / 2n);
</script>

Boolean:

<script>
    //  boolean 布尔型数据
    // 该类型数据用于描述对错真假,只有两个值 true(真) false(假)
    let a = true

    // 判断数据类型
    console.log(typeof a); // => boolean
</script>

 Null:

<script>
    // null 空类型
    // 空类型唯一的值就是 null
    // null的含义:用来代表对象类型的空引用(空指针)

    let a = null

    console.log(a); // => null

    // 由于 null 代表的是 引用地址(指针) 又因为 js 把引用地址理解为对象,所以此处输出 object
    console.log(typeof a); // => object
</script>

 Object:

<script>
    // Object 类型
    // 对象是一组数据的集合
    // 所有能被构造的(包含constructor构造器)类型都是对象
    // 所以 函数 数组 本质上也是对象

    // 朴素对象    也叫做 json 对象
    let obj = {
        name: '张三',
        sex: 'male',
        age: 17
    }

    // 什么是 json?
    // json 是 js 的轻量化数据结构

    // json 文件特点
    // 1.json 文件中,根节点只能有一组 {} 花括号或 一组 [] 方括号
    // 2.属性 key 必须由双引号包裹 value 若是字符串,则必须是双引号
    // 3.每个属性由逗号隔开,最后一个属性后面的逗号必须省略

    // 那么如何声明一个 json 对象呢?
    // 1.直接使用json格式
    let user = {
        "name": "张三",
        "sex": "男",
        "age": 17
    }

    // 2.key 不包含空白符和其他特殊符号时 可以不加引号
    user = {
        // 字符串可以不用双引号
        name: '张三',
        sex: 'male',
        age: 17
    }

    let name = '法外狂徒男枪'
    let sex = 'other' 
    let age = 17

    // 3.变量名若和对象的属性名相同,则可以省略 如下:
    user = {
        // 完整写法
        // name: name,
        name,
        sex,
        age
    }
    console.log(user);

    // 数组和函数都属于对象类型
    let arr = [1, 2, 3]
    function fn() { }
    console.log(typeof arr);
    console.log(typeof fn);// => function 虽然 typeof 检测函数结果为 functiong 但由于函数包含constucktion

    // 学习面向对象的时候再介绍类实例
</script>

 String:

<script>
    // String 字符串类型
    // 将很多的字符串起来就是字符串

    // 可以使用单双引号来定义字符串
    let str = 'hello world'
    console.log(typeof str);
    str = ''// 空字符串
    console.log(typeof str);
    str = "hello world"
    console.log(typeof str);

    // 什么叫转义?
    // 转变字符原有的含义就成为转义
    // 转义后的字符就是转义字符
    // 常见转义字符:\n 换行  \t 制表符
    str = '你好\n世界'
    console.log(str);
    str = 'hello\tworld'
    console.log(str);
    // 单引号的字符串显示单引号的方法
    str = 'ac\'dc'
    console.log(str);
    // 双引号的字符串显示双引号的方法
    str = "ac\"dc"
    console.log(str);
    // 通常可以在双引号中直接书写单引号
    // 单引号的字符串中直接书写双引号
    str = 'abc"d'
    console.log(str);

    // 若两个值通过加法运算符连接,任意一个值为字符串类型,则加法运算符会直接进行拼接字符串
    let a = 'hello',b = 123
    console.log(a + b);

</script>

 Symbol:

<script>
    // mdn数据类型:

    // Symbol 的意思为符号,专用来创建类中的匿名属性或函数

    // Symbol.for Symbol.keyFor 可以访问全局Symbol注册表
    // 全局注册表上可以存放全局可访问的 Symbol 值

    // Symbol 作为 key 声明的对象属性 无法被枚举
    // Symbol 作为 key 声明的对象属性 只能通过相同的 Symbol 值访问

    // 定义一个Symbol类型的值
    let s = Symbol()
    let s2 = Symbol('abc')

    console.log(s);
    console.log(s2);

    class A { }

    let a = new A
    for (const key in a){}
</script>

 Undefined:

<script>
    // undefined 未定义类型
    // 一个变量声明后不赋值,则它的值为 undefined
    // 未定义类型中只有一个值叫做 undefined

    // let a = undefined
    let a
    console.log(typeof a);// => undefined

    a = 123
    console.log(typeof a);

    // 赋值 undefined
    a = undefined
    console.log(a);
    console.log(typeof a); // => undefined
</script>

 Number:

<script>
    // 数字类型用于描述数字
    let a = 234
    // 代表无限大的数字,是数字类型的值
    a = Infinity
    // 代表不是数字,是数字类型的值 NaN(Not a Number)
    a = NaN

    console.log('abc' / 123);// 运算结果不是数字 所以显示 NaN

    // 有时需要判断一个值是否是NaN
    // 可以使用以下方法
    // 如果是NaN则返回true,否则返回false
    console.log(isNaN(a));

    // 浮点数
    a = 0.618

    console.log(typeof a);

    // 科学计数法
    a = 3e3
    a = 3e-3

    console.log(a);

    console.log(typeof a);

    // 单双精度浮点数是什么意思
    // 单精度浮点数就是32位的小数
    // 双精度浮点数就是64位的小数
</script>

 序列化和反序列化:

<script>
    // 序列化和反序列化是什么?
    // 序列化就是将对象转换成字符串
    // 反序列化就是将字符串转换成对象

    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);
</script>

数据类型的转换:

<script>
    // 不同类型之间的数据是可以互相转换类型的,这个过程叫数据转换

    // 数据转换的方法分为两种:显式转换,隐式转换
    // 显式转换:在代码中有明确的代码用于转换类型
    // 隐式转换:在代码中没有明确的代码用于转换类型


    // Boolean转换成其他类型
    let b = true
    // 转换数字
    b = Number(b)
    // 除了 Number 以外,还能使用 parseInt parseFloat 进行转换
    console.log(b);// => 1
    b = false
    b = Number(b)
    console.log(b);// => 0

    // true 转换数字为 1,false 转换数字为 0

    // 转换成字符串
    b = true
    b = String(b)
    console.log(b);// => true 的字符串
    b = false
    b = b + ''// 连接字符串时,js 会自动转换数据格式为字符串,这是一个隐式转换
    console.log(b);// => false 的字符串


    // 数字类型转其他类型
    let num = 123
    // 转boolean
    num = Boolean(num)
    console.log(num);// => true
    num = 0
    num = Boolean(num)
    console.log(num);// => false

    // 数字为0 结果为false,否则为true

    // 转字符串
    num = 1314
    num = String(num)
    console.log(num);// => 1314
    console.log(typeof num);// => String


    // 字符串转其他类型
    let str = 'hello world'
    // 转 boolean 值
    str = Boolean(str)
    console.log(str);// => true
    str = ''
    str = Boolean(str)
    console.log(str);// => false

    // 转空字符串为 false 其余为 true

    // 转数字
    str = 'hello world'
    str = Number(str)
    console.log(str);// => NaN
    str = '6e+2'
    str = Number(str)
    console.log(str);// => 600
    // 加法运算符直接写在字符串前,可以产生转换成数字的效果
    str = '6e+2'
    str = +str
    console.log(str);// => 600

    // 若字符串代表的是合法数字则转换为对应数字 否则为NaN


    // 对象转其他类型
    let obj = { a: 1, b: 2 }
    // 转 boolean 值
    obj = Boolean(obj)
    console.log(obj);// => true

    obj = null // 此处赋值 undefined 效果一样
    obj = Boolean(obj)
    console.log(obj);// => false

    // 变量是 null 或 undefined 时 转换结果为false
    // 对象只要存在 结果就为 true

    // 转数字
    obj = { a: 1, b: 2 }
    obj = Number(obj)
    console.log(obj);// => NaN


    // 转字符串
    obj = { a: 1, b: 2 }
    obj = String(obj)
    console.log(obj);// => [object Object]
    // 对象强转字符串结果始终为 [object Object]

    // 若希望将对象转换成字符串后能够查看其数据,则可以使用序列化
</script>

Reflect:

<script>
    // Reflect 反射
    // 反射是用来操作对象的方法

    // 文档:

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

    // obj.c = 3
    // obj['e1_!'] = 4

    // 定义对象属性
    // 作用和 Object.defineProperty 相同
    // Object.defineProperty(obj, 'c', {
    //     // 属性值
    //     value: 3,
    //     // 是否可写
    //     writable: false
    // })

    // 第一个参数:要定义属性的对象
    // 第二个参数:要定义的属性名称
    // 第三个参数:配置对象
    // 默认情况下用此方法添加的属性值是不可修改的
    Reflect.defineProperty(obj, 'c', {
        value: 5,
        writable: true
    })

    // 删除属性
    // 等价于 delete obj['b']
    // delete obj.a
    // delete obj['b']
    // 第一个参数:要删除属性的对象
    // 第二个参数:要删除的属性名称
    Reflect.deleteProperty(obj, 'a')

    // 返回对象的所有 key 数组
    let r = Reflect.ownKeys(obj)
    console.log(r);

    // 判断对象是否存在某属性
    // 第一个参数:要判断属性是否存在的对象
    // 第二个参数:要判断是否存在的属性名称
    r = Reflect.has(obj, 'e')
    console.log(r);
    r = Reflect.has(obj, 'c')
    console.log(r);

    // 判断一个对象是否存在某属性的其他方法
    // 1. 使用if的隐式转换
    if (obj.e) {
        // obj.e 若等于以下值:null undefined '' 0 ==> false  其余情况为true
        // if 语句将隐式转换数据为 boolean 值
        // 存在e
        console.log('e存在');
    }
    // 2. 使用typeof
    console.log(typeof obj.e);
</script>

3.节点操作:

插入节点:

<script>
    // 插入节点分两个步骤
    // 1.创建节点
    // 2.插入节点到文档中

    // 创建节点
    // 参数是标签名
    // 返回一个dom对象
    let img = document.createElement('img')
    // 设置图片源
    img.src = '../img/可可.JPG'

    // 插入节点

    // document.body 就是body标签的dom对象

    // appendChild 追加一个子元素
    // 参数是要追加的元素
    document.body.appendChild(img)

    let ok = document.querySelector('.ok')

    // insertBefore 插入节点到另一个节点前
    // 第一个参数:要插入的元素
    // 第二个参数:插入位置的子节点
    document.body.insertBefore(img,ok)


    // 创建元素并插入元素的用途
    // 1.异步加载 js css 等文件

    // 例如异步加载js 
    let loadJsBtn = document.querySelector('.load-js')
    loadJsBtn.addEventListener('click', () => {
        // 异步加载js

        // 创建标签
        let script = document.createElement('script')
        script.src = './js/main.js'

        // 插入script标签到body中,这样js脚本将自动加载
        document.body.appendChild(script)
    })

    // 2.动态追加一些以html作为模板的元素
</script>

查询节点:

<body>
    <table border>
        <tbody>
            <tr>
                <td>
                    1-1
                </td>
                <td>1-2</td>
                <td>1-3</td>
            </tr>
            <tr>
                <td>2-1</td>
                <td>2-2</td>
                <td>2-3</td>
            </tr>
            <tr class="tr">
                <td>3-1</td>
                <td>3-2</td>
                <td>3-3</td>
            </tr>
            <tr>
                <td>4-1</td>
                <td>4-2</td>
                <td>4-3</td>
            </tr>
        </tbody>
    </table>
</body>
<script>
    // 查询节点
    // dom对象中可以查询其父节点和子节点

    // 获取tr
    let tr = document.querySelector('.tr')

    // 访问子节点使用 children 属性
    console.log(tr.children)

    // children 属性包含一个子节点的集合
    // 所以访问具体子节点可以使用索引值
    let node = tr.children[1]
    node.remove()


    // 访问父节点使用 parentElement 属性
    // 例如 访问 tr 节点树 tbody 如下:
    console.log(tr.parentElement);
    console.log(tr.parentElement.parentElement);
</script>

删除节点:

<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
    <button>下载</button>
</body>
<script>
    let li = document.querySelector('ul>li:nth-of-type(3)')
    let ul = document.querySelector('ul')
    let btn = document.querySelector('button')
    // 节点的remove可以删除元素
    // li.remove()

    // removeChild 删除子节点
    // 参数是想要删除的子节点dom对象
    ul.removeChild(li)

    // 用途:
    // 动态加载一次性js脚本资源,加载完后就可以删除元素了

    // 例如:下载按钮
    btn.addEventListener('click', () => {
        // 动态创建a标签并点击它 来实现下载
        let a = document.createElement('a')
        // download 可以指定下载后的文件名
        a.download = '1.png'
        // 下载文件的地址
        a.href = './img/可可.jpg'
        // 无需插入页面即可点击
        a.click()
        // 若a标签以插入页面 则需要调用 remove 来删除
        a.remove()
    })
</script>

替换节点:

<body>
    <table border>
        <tr>
            <td>1-1</td>
            <td>1-2</td>
            <td>1-3</td>
        </tr>
        <tr class="tr">
            <td>2-1</td>
            <td>2-2</td>
            <td>2-3</td>
        </tr>
        <tr>
            <td>4-1</td>
            <td>4-2</td>
            <td>4-3</td>
        </tr>
    </table>
</body>
<script>
    let tr = document.querySelector('.tr')
    let _td = document.querySelector('.tr>td:nth-of-type(2)')

    // 替换节点语法:
    // parent.replaceChild(newNode,child)
    // parent:要替换节点的父节点
    // newNode:新的要插入文档的节点
    // child:被替换的节点

    // 构造一个新的元素用于替换
    let td = document.createElement('td')
    td.textContent = 'hello world'

    tr.replaceChild(td, _td)
</script>

异步加载js文件:

<body>
    <button>异步加载js</button>
</body>
<script>
    let btn = document.querySelector('button')
    btn.addEventListener('click', () => {
        // 创建script标签
        let script = document.createElement('script')
        script.src = './js/main.js'

        // load 加载完成事件
        script.addEventListener('load',() => {
            console.log('加载完成');
            // 删除脚本
            script.remove()
        })

        // 插入页面
        document.body.appendChild(script)
    })
</script>

通过class来设置样式:

<style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #000;
        }

        .box.active {
            background: #ff0;
        }
    </style>

<body>
    <div class="box"></div>
    <button>开灯</button>
</body>
<script>
    // 查询元素
    let btn = document.querySelector('button')
    let box = document.querySelector('.box')

    btn.addEventListener('click', () => {
        // 判断 box 中是否包含 active 类名
        // contains 返回 true 代表包含 否则不包含
        let r = box.classList.contains('active')


        if (r) {
            // 包含 active 的话
            // 删除 active存在
            box.classList.remove('active')
        }
        else {
            // 若不包含 active
            // 则添加类名
            box.classList.add('active')
        }
    })

</script>

自定义属性:

<style>
        .btn {
            width: 100px;
            height: 100px;
            background-color: #f00;
        }
    </style>

<body>
    <div class="btn">设置属性</div>
    <div class="read-btn">读取属性</div>
</body>
<script>
    let btn = document.querySelector('.btn')
    let readbtn = document.querySelector('.read-btn')
    btn.addEventListener('click', () => {
        // 什么是自定义的html属性?
        // 自定义的 费浏览器故犯的一些 html 属性 就是自定义属性

        // 设置自定义属性
        // 第一个参数:属性名
        // 第二个参数:属性值,请填入字符串类型的值,否则将被强转成字符串
        // btn.setAttribute(key,value)
        // 请不要直接填对象
        // btn.setAttribute('my-attr', { a: 1, b: 2 })
        btn.setAttribute('my-attr', '你好')
        // btn.setAttribute('id', 'ok')

        // 若要设置对象就应该序列化
        btn.setAttribute('my-obj', JSON.stringify({ a: 1, b: 2 }))
    })

    readbtn.addEventListener('click', () => {
        // 读取属性
        let r = btn.getAttribute('my-attr')
        console.log(r);
        r = btn.getAttribute('my-obj')
        console.log(JSON.parse(r));
    })

    
    // 应用场景
    // 当你希望将参数保存到html标签上时,可以考虑自定义属性
</script>

child:

<body>
    <h1>hello child</h1>
    <div>
        <input type="text">
        <button>发送</button>
    </div>

</body>
<script>
    let input = document.querySelector('input')
    let button = document.querySelector('button')


    // 接收消息
    window.addEventListener('message', ev => {
        console.log(ev);
        // 读取消息参数
        console.log(ev.data);

    })

    button.addEventListener('click', () => {
        // 子窗口向父窗口发送消息
        // window.parent 当前窗口的父窗口
        window.parent.postMessage(input.value)
    })
</script>

window:

<body>
    <!-- <iframe name="child" src="https://www.bilibili.com/" style="width: 500px;height:300;"></iframe> -->
    <iframe name="child" src="./child.html" style="width: 500px;height:300px;"></iframe>
    <div>
        消息:<input type="text" name="msg">
        <button class="send1">提交</button>
    </div>
</body>
<script>
    // 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window#%E6%96%B9%E6%B3%95

    // window对象的相关属性和方法,在调用的时候可以省略window
    // 例如 window.open() 可以写作 open()

    //打开窗口
    // 第一个参数:网址
    // 第二个参数:iframe名称,没有的话就打开新窗口
    // windou.open('https://www.baidu.com/','child')

    // 关闭窗口
    // window.close()

    // 窗口间通信
    // 原理:
    // 通过窗口对象(window)发送消息(postMessage)
    // 另一个窗口需要通过 message 事件来接收消息

    // 例子:父窗口向子窗口发消息
    let msgInp = document.querySelector('input[name=msg]')
    let send1 = document.querySelector('.send1')
    let iframe = document.querySelector('iframe[name=child]')

    send1.addEventListener('click', () => {
        // 发送消息
        // 向目标窗口发送消息
        // iframe.contentWindow 代表 iframe 中的窗口对象
        iframe.contentWindow.postMessage(msgInp.value)
    })

    // 接受子窗口的消息
    window.addEventListener('message', ev => {
        console.log(ev.data);
    })

</script>

location:

<body>
    <div>
        网址:<input type="text" name="msg">
        <button>跳转</button>
    </div>
</body>
<script>
    // location 代表浏览器地址栏

    // 跳转网页
    let input = document.querySelector('input')
    let btn = document.querySelector('button')

    btn.addEventListener('click', () => {
        // 给 location.href 赋值页面就会跳转
        window.location.href = input.value
    })

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

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

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

</script>

history:

<script>
    // history 浏览器浏览记录(历史记录)

    // 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/History

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

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

    // 转到指定位置
    // history.go(delta)
    // history.go(1) 等价于 history.forward()
    // history.go(-1) 等价于 history.back()
</script>

localStorage:

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

    // 设置数据
    // localStorage.setItem('a', JSON.stringify({ x: 1, y: 2 }))
    // localStorage 只能存字符串

    // localStorage['b'] = JSON.stringify([1, 2, 3]) 
    // 等价于调用 setItem 方法

    // 读取数据
    console.log(localStorage.getItem('a'));
    console.log(localStorage['b']);// 等价于调用 getItem 方法

    // sessionStorage 用法和 localStorage 相同,但是窗口关闭数据就消失了

    sessionStorage['a'] = 1
    sessionStorage['b'] = 2
</script>
<script>
    // navigator 用于查看设备信息
    // 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator
    console.log(navigator);
</script>

4.运算符和条件判断

比较运算符:

<body>
    比较运算符: >,&lt;,>=,&lt;=,==,!=,===,!==
    &nbsp;
    &copy;
    &lt; &gt;
    <!-- & 符号开头 ; 分号结尾的内容 我们成为 html 实体 -->
</body>
<script>
    // 比较运算符用于对运算符左右两个值进行比较
    // 比较运算符的运算结果为boolean值,用于指示比较结果为真或者假

    // 大于
    console.log(1 > 2);// => false
    // 小于
    console.log(1 < 2);// => true

    // 大于等于 小于等于
    console.log(3 >= 3);
    console.log(3 <= 2);

    // == 相等判断 (不建议使用)
    // 非严格相等判断 在运算符左右两边,不需要使用相同的数据类型
    // 当数据类型不同是时 ==运算符将自动进行隐式转换
    // 参考:
    // 非严格相等判断,可以不同类型的数据拿来进行比较,所以有时可能会得到期望以外的结果,不推荐使用
    let a = '', 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);
</script>

赋值运算符:

<body>
    赋值运算符 := += -= *= /= %= ++ --
</body>
<script>
    // 赋值运算符
    let a = 1

    // += 自增
    a += 2 //等价于 a = a + 2
    console.log(a);

    // -= 自减
    a -= 5 //等价于 a = a - 5
    console.log(a);

    // *= 自乘
    a *= -1 //等价于 a = a * -1
    console.log(a);

    // /= 自除
    a /= 2 //等价于 a = a / 2
    console.log(a);

    // %= 自模
    a = 5
    a %= 3 //等价于 a = a % 3
    console.log(a);

    // ++ 自增
    a = 0
    a++
    console.log(a);

    // --自减
    a = 0
    a--
    console.log(a);

    a = 0
    // 自增自减运算符位置的区别
    // 运算符放前面,则先运算后引用
    console.log(--a);
    // 运算符放后面,则先引用后运算
    a = 0
    console.log(a++);

    a = 0
    console.log(1 + ++a);// => 2
    console.log(3 - a-- / 2);// => 2.5
    console.log(a);// => 0
</script>

逻辑运算符:

<body>
    逻辑运算符:&&与 ||或 !非 ~异或 &按位与 |按位或
</body>
<script>
    // 逻辑运算符:符合逻辑条件时返回真或假

    // bool expression 的意思是:布尔表达式
    // 什么是布尔表达式?整个js代码运算结果是一个布尔值,这样的代码片段叫做布尔表达式
    // 举例如下:
    console.log(1 === '');
    console.log(true);

    let user = {
        sex: 'other',
        age: 24
    }
    // && 与
    // 语法: bool expression && bool expression
    // 两个表达式都为真,结果返回真,任意一个表达式为假,结果返回为假
    // 例如:
    console.log(true && true);
    console.log(false && true);
    console.log(true && false);
    console.log(user.sex === 'other' && user.age > 20);

    // || 或
    // 语法:bool expression || other expression
    // 两个表达式任意一个为真,结果返回真,否则为假
    console.log(true || true);
    console.log(false || user);
    console.log(true || false);
    console.log(false || false);

    // && 和 || 开发人员称为短路运算
    // 短路运算的应用场景1: 赋值默认值

    let a// => undefined
    let b = a || 5
    console.log(b);
    // 短路运算的应用场景2:赋值默认值
    let x = user.age > 20
    // x && console.log((user.age>20))
    x && console.log('user.age 大于 20')
    user.age > 20 && console.log('user.age 大于 20');

    // !非 作用是取反
    // 语法:!(tool express)
    console.log(!true);
    console.log(!false);

    
</script>

三元运算符:

<script>
    // 三元运算符:用于判断一个表达式结果,为真时返回结果1,为假时返回结果2
    // 语法:bool expression? case1: case2
    // 作用:主要用在给变量赋值

    let obj = {
        name: '张三',
        sex: 'male',
        age: 16
    }

    console.log(obj.sex === 'male' ? '男' : '女');
    obj.sex = 'other'
    // 嵌套三元运算符 在'?'或':'处可以换行
    console.log(obj.sex === 'male' ? '男' :
        obj.sex === 'female' ? '女' : '其他');
        // 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来
    // console.log(obj.sex === 'male' ? '男' : obj.sex && 'female' ? '女' : '其他');
    console.log(obj.sex === 'male' ? '男' : (obj.sex === 'female' ? '女' : '其他'));
</script>

算数运算符:

<script>
    // 加减乘除符号
    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);
    console.log(3 % 2);
    console.log(5 % 3);

    // ** 幂运算 运算 n 的 m 次方是多少
    console.log(2 ** 3);
    console.log(4 ** 3);

    // 算数运算符遵循数学的符号优先级
    console.log(1 + 2 * 3 ** 2);
    // 可以使用圆括号来分组
    console.log(1 + (2 * 3) ** 2);
</script>

运算优先级顺序:

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

if条件判断:

<script>
    let user = {
        name: '张三',
        sex: 'male',
        age: 17,
        score: 60
    }
    // if 语句:用于进行条件判断的语句
    // if(bool expression){
    // code block 代码块
    // }
    // 作用:当布尔表达式值为真时,执行后面的代码块

    if (user.name === '张三') {
        console.log('欢迎');
    }

    // if else:如果... 否则...
    // 语法:
    // if(bool expression) {
    //      code block 代码块
    // } else {
    //      code block 代码块
    // }

    if (user.score >= 60) {
        console.log('及格');
    } else {
        console.log('不及格');
    }

    // 三元运算符替代if else
    console.log(user.score >= 60 ? '及格' : '不及格');

    // if   else if     else:如果... 或者... 或者... 否则...
    // 语法:
    // if(bool expression) {
    //      code block 代码块
    // } 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 (user.sex === 'male') {
    //     console.log('男');
    // } else if (user.sex === 'female') {
    //     console.log('女');
    // } else {
    //     console.log('其他');
    // }
    // if   else if     else 语句,若代码块中只有一句话 则可以省略花括号
    if (user.sex === 'male') console.log('男');
    else if (user.sex === 'female')console.log('女');
    else console.log('其他');

    // 总结:
    // 1. if 语句要么执行要么不执行
    // 2. if else 语句,一定有一个会被执行,二选一
    // 3. if   else if 语句,多选一或都不执行
    // 4. if    else if     else 语句,一定有一个会被执行,多选一
</script>

switch条件判断:

<script>
    // switch 语句
    // 语法:判断value值为多少,如果为constant1则执行其后的代码
    // 如果为constant2则执行其后的代码
    // 如果不满足所有的case语句,则执行default
    // switch(value){
        // case constant1:
            // code
            // break;
        // case constant2:
            // break;
        // ...可以有任意多个case语句
        // default: // default 不是必须要写的
            // break;
    // }

    let r = 6
    let myCase = 4
    console.log(r);

    switch (r) {
        // r 的值可以和 case 中的表达式进行匹配
        // 匹配成功则执行后续代码
        case myCase + 2:
            console.log('myCase');
            break;
        case 0:
            console.log('随机结果为0');
            // break 用于跳出 switch 语句
            break;
        case 1:
            console.log('随机结果为1');
            break;
        case 2:
            console.log('随机结果为2');
            break;
        case 3:
            console.log('随机结果为3');
            break;
        default:
            console.log('这是 default');
            break;
    }
</script>

5.数组和循环

多维数组:

<script>
    // 什么是多维数组?
    // 多维数组:多个维度(rank)组成的数组

    let arr = [1, 2, 3]

    // 声明二维数组
    // 二维数组中每个一维数组的成员都是一个数组
    arr = [[1, 2], [3, 4], [5, 6]]

    // 读取数组
    // 可以将二维数组视为一个x轴和y轴构成的二维平面,所以任意值都可以由x,y坐标来表示
    console.log(arr[0][1]);// => 2
    console.log(arr[2][0]);// => 5


    // 声明多维数组
    // 多维数组是数组内嵌套多层数组的数组
    arr = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]
    console.log(arr[0][1][1]);// =>5
    console.log(arr[1][1][0]);// => 10

    // 访问数组
    // 可以理解成三维空间中访问一个三维坐标系
    
</script>

如何判断一个变量是否是数组:

<script>
    let a = [1,2]
    let b = 'hello world'

    // Array.isArray 方法检测变量是否为数组
    // 是数组的话返回 true 否则为 false
    console.log(Array.isArray(a));
    console.log(Array.isArray(b));
</script>

数组的基础:

<script>
    // 什么是数组?
    // 存储一组数据的一个容器

    // 声明数组
    // 使用脚本形式声明数组
    let arr = []
    // 声明并初始化数组
    // 数组中的每一个数据称为 数组成员
    arr = [false, 2, 'hello world', { name: '张三' }, null, undefined]
    console.log(arr);

    // 对象形式的声明方法
    arr = new Array()
    console.log(arr);
    // 声明并初始化
    arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)
    console.log(arr);



    // 访问数组成员
    // 使用索引值访问数组中的成员
    // 索引:数组给每个成员的编号成为索引,索引值从零开始一次递增1
    // 访问数组成员时,若索引超出数组范围,则获取到的结果为 undefined
    console.log(arr['0']);
    console.log(arr['1']);
    // 数组索引可以不使用引号
    console.log(arr[2]);

    // 给数组成员赋值
    // 使用数组变量名加上索引值进行赋值,可以给指定索引位置的成员进行赋值
    arr[4] = {name:'李四'}
    console.log(arr[4]);

    arr[6] = 333
    console.log(arr);

    arr[9] = 10
    console.log(arr);

    // 数组长度(指的就是数组成员的个数)
    // 若赋值的时候索引不是正整数,该值将被视为数组对象的属性值,不计入数组成员,所以不会影响数组长度
    arr[-1] = true
    console.log(arr);
    console.log(arr.length);
</script>

数组的操作:

<script>
    // 数组的操作

    let arr = [1, 2, true, { name: 'Amy' }, 2]

    // push 追加数据到数组末尾
    // 参数:被添加的新数组成员
    arr.push('hello world')
    console.log(arr);

    // pop 从尾部取出一个成员
    // 返回值是取出的成员
    let r = arr.pop()
    console.log(r);
    console.log(arr);

    // unshift 在头部添加数据
    // 参数:被添加的新数组成员
    arr.unshift('my best day')
    console.log(arr);

    // shift 从头部取出一个成员
    // 返回值是取出的成员
    r = arr.shift()
    console.log(r);
    console.log(arr);


    // push 和 unshift 可以批量添加成员
    arr.push('a', 'b', 'c')
    arr.unshift('x', 'y', 'z')
    console.log(arr);

    // 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('_')
    console.log(r);


    // includes 判断是否包含某成员
    r = arr.includes('z')
    console.log(r);


    // indexOf 查询指定数组成员的索引
    r = arr.indexOf('b')
    console.log(r);
    // 可以使用indexOf判断是否包含某个数组成员 若不包含 返回 -1
    r = arr.indexOf('g')
    console.log(r);// => -1
    if (arr.indexOf('g') === -1) {
        console.log('该数组成员不存在');
    }

    // lastIndexOf 查询最后一个指定数组成员的索引(因为数组成员可能重复)
    console.log(arr.indexOf(2));
    console.log(arr.lastIndexOf(2));

    // slice 数组切片 获取子数组
    // 切片遵循“前截后不截”原理:起始位置包含在结果内,结束位置不包含
    console.log(arr.slice(5, 8));
    // 参数只有一个,代表从该位置开始一直截取到最后
    console.log(arr.slice(5));

</script>

流程控制-循环:

<script>
    // 循环是什么?
    // 循环就是重复执行某一段代码的行为

    // for 循环
    // 例如按次数进行循环
    let count = 5 //循环次数
    // for (let i = 0; i < count; i++) {
    //     console.log('hello world')
    // }


    // 语法:
    // for (初始化语句; 循环条件; code block 运行一次结束后运行的代码) { // code block }
    // 初始化语句:初始化变量值
    // 循环条件:循环条件是个布尔表达式,若结果为真,则执行 code block 的代码块

    let index = 0
    // for (; index < count; index++) {
    //     console.log('hello world');
    // }



    // 循环的继续与跳出
    // 若打印0~100之间所有的偶数,数字大于51则停下 代码如下
    // for (let i = 0; i <= 100; i++) {
    //     if (i % 2 === 0) {
    //         console.log(i);
    // 继续循环
    // 结束本次循环,继续下一次循环
    //         continue;
    //     }
    //     if (i > 51) {
    //         console.log('break');
    //         console.log(i);
    //         // 跳出循环
    //         break;
    //     }
    //     console.log('i:' + i);
    // }

    // 使用for 循环遍历数组
    let arr = [
        { name: '张三' },
        true,
        55,
        'hello world',
        [1, 2, 3]
    ]

    // for (let i = 0; i < arr.length; i++) {
    //     // 使用 i 充当数组索引来遍历数组
    //     console.log(arr[i]);
    // }

    // for in:用于遍历数组或对象
    // 语法:
    // for (key in arr|obj) {
    // code block
    // }

    // 遍历数组
    // index:每个被遍历的数组成员的索引值
    // for (let index in arr) {
    //     console.log(index);
    //     console.log(arr[index]);
    // }

    // 遍历对象
    let obj = {
        name: '小红',
        sex: 'female',
        age: 18
    }

    // key:对象属性名称
    // for (let key in obj) {
    //     console.log(key)
    //     console.log(obj[key])
    // }

    // let keys = Reflect.ownKeys(obj)
    // console.log(keys);
    // for (let i = 0; i < keys.length; i++) {
    //     const key = keys[i]
    //     console.log(obj[key]);
    // }

    // for of:用于遍历数组,且不能获取索引值,只能遍历所有成员
    // element:数组中每个成员
    // for(let element of arr){
    //     console.log(element);
    // }


    // for of 可以用在所有存在迭代器的集合对象上,如:Set 和 Map


    // while 循环:当...时,就循环执行代码
    // 语法:
    // condition:while循环的条件,是一个布尔表达式,若成立则执行循环的 code block
    // while(condition){
    //    // code block
    // }

    // let a = 0
    // while (a < 5) {
    //     console.log('hello world');

    //     // while 循环通常在代码块的末尾 应该修改循环条件
    //     a++
    // }

    // do while 循环:限制性循环代码,再判断循环条件
    // 语法:
    // do {
    //    // code block
    // }while(condition)
    a = 0
    do {
        console.log('hello world');
        a++
    } while (a < 5)

    // 所有的循环语句,都可以使用 break 和 continue
</script>

冒泡排序法:

<script>
    // 冒泡排序
    // 原理:此处以排序出一个从小到大的序列为例
    // 令数组长度为 length
    // 排序过程将进行 length - 1 次
    // 每一次排序过程中,将对数组中每个成员从左到右进行两两比较,较大的数字将被挤到后面去
    // 每执行完一次循环,将在数组末尾确定一个数字的位置
    // 最后直到数组的第一个成员和第二个成员进行排序为止,排序结束
    let arr = [5, 2, 1, 3, 7, 0, 9, 4]

    for (let i = arr.length - 1; i > 0; i--) {
        for (let j = 0; j < i; j++) {
            // 判断前一个数和后一个数的大小
            if (arr[j] > arr[j + 1]) {
                // 交换
                let temp = arr[j]
                arr[j] = arr[j + 1]
                arr[j + 1] = temp
            }
        }
        alert(arr)
        console.log(arr);
    }

    // 基于冒泡排序的自定义排序规则
    // 例如:下列数组按分数排序
    let students = [
        {
            name: '张三',
            score: 60
        },
        {
            name: '李四',
            score: 40
        },
        {
            name: '老王',
            score: 80
        }
    ]

    for (let i = students.length - 1; i > 0; i--) {
        for (let j = 0; j < i; j++) {
            if (students[j].score > students[j + 1].score) {
                let temp = students[j]
                students[j] = students[j + 1]
                students[j+1] = temp
            }
        }
    }

</script>

6.函数和数组的遍历函数

变量的作用域:

<script>
    // 什么是变量的作用域(Scope)?作用域指的是作用范围,范围内可以访问变量,超出范围则无法访问

    // 为了更好理解作用域,可以结合 debugger 和 浏览器的堆栈信息来查看
    // 方法:
    // 1.在需要查看变量作用的地方写上 debugger
    // 2.浏览器上 ctrl + shift + i 或 F12
    // 3.选择 sources
    // 4.找到当前代码对应的 Call Stack
    // 5.查看当前 Call Stack 下的 Scope



    // 作用域一共只有三种
    // Global 全局作用域
    // Block 块级作用域
    // Function 函数作用域


    // 全局作用域(Global)

    // 具备全局作用域的只有全局变量或常量
    // 全局变量指的是作用域为全局(Global)的变量,代码中任何位置都能使用
    // 全局变量有两种

    // 1.自动全局变量
    // 给未定义的变量直接赋值,该变量会变成 自动全局变量,若在浏览器中,该变量会被存到window对象里
    zhangSan = { name: '法外狂徒', age: 17 }

    // 2.(普通的)全局变量
    // 直接在函数外声明的变量也是全局作用域的变量
    let luoXiang = { name: '罗老师' }


    // 块级作用域(Block)

    if (true) {
        // 块级作用域就是在代码块中有效的作用域
        // let 关键字声明的变量具备块级作用域
        // var 关键字声明的变量具备全局作用域
        let a = 1
        var other = 2

        console.log(a);
        console.log(other);
    }

    // for (let i = 0; i < 5; i++) {
    //     // 经过一段时间后执行里面的回调函数
    //     setTimeout(() => {
    //         console.log(i);
    //     }, 1000)
    // }

    // 函数作用域(Function)
    // 函数中的变量,无论使用var还是let定义的,都是函数作用域,在函数外无法访问

    function fn(){
        let x = 1
        var y = 2
        console.log(x);
        console.log(y);
    }
    // console.log(x);
    // console.log(y);



    // 总结:
    // 函数外声明的变量 作用域是全局
    // 函数内用关键字声明的变量 作用域是函数内
    // let 声明的变量具备块级作用域

</script>

参数的值传递和引用传递:

<script>
    let x = 1
    let y = { name: '张三', sex: 'male' }

    function modify(a, b) {
        a++
        b.name = '李四'
        b.sex = 'female'
        console.log('函数内');
        console.log(a);
        console.log(b);
    }

    // 传入两个参数
    // x 是值类型传参
    // y 是引用类型传参

    // 值类型传参,将x变量中的值 直接赋予了函数内的形参
    // boolean number string 都是值传递的

    // 引用类型传参,将y中的引用地址,传递给函数内的形参
    // object function 都是引用传递的

    modify(x, y)
    
    console.log('函数调用完后');
    console.log(x);
    console.log(y);


</script>

函数的使用:

<script>
    // 函数

    // 什么是函数?
    // 站在开发人员的角度讲:函数式对一段程序的封装(封装:包装)
    // 站在生活的角度来讲,函数就是帮我们完成某样任务的机器

    // 为什么要使用函数?
    // 用于包装可以重复使用的代码
    // 代码的复用

    // 输入input 和 输出ouput
    // 输入:函数完成任务时必不可少的材料
    // 输出:函数执行完后产出的产物




    // 定义函数:
    // 语法:fuction 函数名 (参数列表){// code block 代码块}

    function sayHello() {
        console.log('hello');
    }

    // 调用(call)函数:通过函数名进行调用,并且后面跟上圆括号
    sayHello()


    // 有参数的函数:
    // 参数列表中声明参数,如有多个参数,用逗号隔开
    // 参数列表中的参数,我们称为:形式参数

    // 返回结果,函数的返回结果称为“返回值”
    // return 还会终止函数内后续代码的运行

    // 定义一个加法函数
    function add(x, y) {
        // 做函数的运算
        let result = x + y
        // return 终止函数运行,并返回一个输出
        return result

        // 函数若没有返回值(也就是没有return) 那么函数默认返回 undefined
    }

    // 调用有参函数:通过函数名调用,且调用的同时,传入真实的参数
    // 调用函数时,圆括号中的参数叫做:实际参数
    let r = add(1, 2)
    console.log(r);


    // 通过变量(或常量)存储函数和调用函数

    // 声明一个sub变量来储存减法函数
    // 必须先声明后调用
    let sub = function (x, y) {
        let result = x - y
        return result
    }

    r = sub(1, 2)
    console.log(r);

    // sayHow()
    // function 定义的函数可以先调用再声明
    // function sayHow() {
    //     console.log('how');
    // }
    // let sayHow = function () {
    //     console.log('how');
    // }


    // 定义参数时,可以给参数设置默认值
    function sayMessage(msg = 'hello message') {
        console.log(msg);
    }

    sayMessage('this is my param')
    // 当不给函数提供参数时,参数msg将采用默认值
    sayMessage()


    // 自调用函数
    // 定义后立即调用的匿名函数
    console.log(((x, y) => x + y)(2, 3));
    // 定义调用函数的步骤
    // 1. 先打两个圆括号
    // 2. 再第一个圆括号中声明函数
    // 3. 第二个圆括号代表调用函数,圆括号中填入实际参数

</script>

节流和防抖:

<script>
    // 防抖
    // 函数将在一个固定时间后被调用,若计时未完成又执行该函数,则取消上次计时,重新开始计时
    // 用于限制频繁的网络请求,例如:搜索功能,用户停止输入的一段时间后才会执行搜索任务

    // 防抖
    // 步骤:
    // 1. 取消计时
    // 2. 重新计时

    const fd = (() => {
        // 计时器id
        let timerId
        return () => {
            // 取消计时
            clearTimeout(timerId)
            // 重新计时
            timerId = setTimeout(() => {
                // 计时完成后要执行的代码
                console.log('hello world');
            }, 3000)
        }
    })()

    console.log(fd);

    // 封装防抖函数
    // 这样任何函数都能被添加防抖功能

    function add(x, y) {
        console.log('ok');
        return x + y
    }

    // 定义一个函数来封装防抖
    // fn:要添加防抖的函数
    // delay:防抖延迟多久
    // that:防抖函数内的 this 指代
    function fdPlus(fn, delay, that) {
        let timerId
        return () => {
            clearTimeout(timerId)
            timerId = setTimeout(() => {
                // 调用参数 fn 函数
                // 将当前函数的参数 arguments 作为 fn 的参数传入进去
                fn.apply(that, arguments)
            }, delay)
        }
    }

    add = fdPlus(add, 3000)
    add(1, 2)


    // 节流
    // 固定时间内只能调用一次的函数,可以使用时间戳或计时器的方式实现
    // 作用同样是限制用户频繁的网络请求,例如:发送验证码

    // 时间戳
    function jlTimespan() {
        // 记录上一次成功运行节流函数的时间
        let lastTime
        // 函数调用冷却时间
        let cd = 3000
        return () => {
            // 一次都没调用过该函数
            if (!lastTime) {

                // 执行节流函数的内容
                console.log('hello 节流')

                lastTime = Date.now()
            } else {
                // 计算上一次调用该函数和本次调用该函数的时间间隔
                // 当前时间
                let now = Date.now()
                // 获取间隔时间
                let timespan = now - lastTime
                // 当间隔时间大于cd,则认为可以再次调用该函数
                if (timespan >= cd) {
                    console.log('hellow 节流');
                    // 记录本次调用的时间
                    lastTime = now
                }

            }
        }
    }

    let fn = jlTimespan()
    console.log(fn);

    // 计时器  
    // 1. 判断是否可以运行节流代码
    // 2. 执行节流的内容
    // 3. 计时
    function jlTimer() {
        // 计时器id
        let timerId
        let cd = 3000
        return () => {
            // 若已经开始计时,则 timerId 存在
            if (timerId) return

            // 执行被节流的函数内容
            console.log('hello 节流');

            // 开始计时
            timerId = setTimeout(() => {
                // 清空计时器id 允许再次调用
                timerId = undefined
            }, cd)
        }
    }

    fn = jlTimer()



    // 封装节流函数
    function sub(x, y) {
        console.log(this);
        console.log('sub');
        console.log(x, y);
        return x - y
    }

    function jlPlus(fn, delay, that) {
        // 计时器id
        let timerId
        return function () {
            if (timerId) return

            // 执行节流的代码
            fn.apply(that, arguments)

            // 计时
            timerId = setTimeout(() => {
                timerId = undefined
            }, delay)
        }
    }

    fn = jlPlus(sub, 5000, 'hello this')
</script>

内存堆栈与垃圾回收:

<script>
    // 内存堆栈:
    // 调用函数时,函数中的各种变量将存储在内存中,以对战的形式进行保存
    // 堆 用于存储对象数据的内存区域
    // 栈 指的是一种先进后出的数据格式 用于存储每次函数调用时产生的数据
    // 栈中的每一次调用内容,称为一个栈帧

    let a = (x, y) => {
        let s = 0
        return b(x)
    }

    let b = (x) => {
        let num1 = 1
        let num2 = 2
        return c(num1, num2)
    }

    let c = (x, y) => {
        let sum = x + y
        return sum
    }

    let result = a(2, 3)
    console.log(result);


    // 总结:
    // 查看堆栈的方法:可以在代码中加入debugger,然后在浏览器控制台中的Call Stack中查看
    // 栈将在调用函数时创建数据并进入栈,这个过程就是入栈
    // 函数调用结束后将函数中的数据将从栈中移除,这个过程称为出栈
    // 出栈时,释放内存中已经无用的数据的过程称为垃圾回收
    // 函数调用时栈的存放顺序始终是先进后出
</script>

通过作用域理解var和let的区别:

<script>
    // 此处结合 debugger 查看以下 for 循环中 i 的作用域

    // for (let i = 0; i < 5; i++) {
    //     // setTimeout 将在指定的一段时间后 执行回调函数
    //     setTimeout(() => {
    //         //    debugger 
    //         console.log(i);
    //     });
    // }

    for(var i = 0;i<5;i++){
        setTimeout(() => {
        //    debugger
           console.log(i);
        });
    }


    
</script>

预编译:

<script>
    // 参考:https://juejin.cn/post/6844903575571677198

    // 预编译是什么?
    // 在浏览器执行js脚本前需要进行编译,有些代码在编译前将会率先被翻译执行并存入内存,这个过程称为预编译

    // console.log(x + 1)
    // 变量无法在声明之前访问它
    // let x = 1

    console.log(sub(2, 1));
    // 声明函数在调用之后
    function sub(x, y) {
        let a = 1
        return x - y
    }


    // 上述调用函数不会报错,因为使用了 function 关键字声明的函数,将会被预编译

    function fn(a) {
        console.log(a);
        var a = 123;
        console.log(a);

        function a() { };
        console.log(a);

        var b = function () { };
        console.log(b);

        function d() { };
    }

    console.log('fn 运行开始');

    // 调用函数
    fn(1)



    // 总结:
    // 哪些东西会被预编译?
    // 1.调用函数时,在函数执行前会进行函数的预编译
    // 2.预编译时,函数内的 形参和函数内声明的变量和函数会被预编译
    // 预编译的顺序:
    // 1.寻找函数内的形参和变量,为其赋值undefined
    // 2.将实参值传给形参
    // 3.寻找函数声明


    method(1, 2, 3)

    function method(x, y, z) {
        console.log(x);// => function x(){}
        console.log(y);// => 2
        console.log(z);// => function z(){}

        var y = 0
        console.log(y);// => 0

        function z() { }
        console.log(z);// => function z(){}
        z = 10
        console.log(z);// => 10

        function x() { }
        console.log(x);// => function x(){}
    }
    console.log('method 运行结束');



    method2(true, function (x, y) {
        console.log(x);
        console.log(y);
    }, 'hello')

    function method2(a, b, c) {
        console.log(a);// => function a(){}
        function a() {
            console.log('this is a');
        }
        console.log(b(4, 5));   // => 4
                                // => 5
                                // => undefined     //因为b()无返回值,所以默认返回值是 undefined

        console.log(c);// => hello
        var c =123
        console.log(d);// => undefined
        var d = 789
        console.log(c);// => 123
        console.log(d);// => 789
    }
</script>

arguments变量:

<body>
    <button>只调用一次的按钮</button>
    <button class="other">递归调用</button>
</body>
<script>
    // 什么是 arguments ?
    // 用 function 定义的函数中 arguments 代表参数数组,是浏览器隐式声明的变量
    // lambda 表达式中不能使用 arguments

    function add(a, b) {
        console.log(arguments);
        // 第一个参数
        console.log(arguments[0]);
        // 第二个参数
        console.log(arguments[1]);

        // 当前函数自身
        console.log(arguments.callee);

        return a + b
    }

    add(1, 2)

    let btn = document.querySelector('button')
    btn.addEventListener('click', function () {
        console.log('click');

        // 移除事件监听程序
        btn.removeEventListener('click', arguments.callee)
    })

    let other = document.querySelector('.other')
    let count = 0
    other.addEventListener('click', function () {
        console.log(count);
        count++
        // arguments.callee 在匿名函数中递归时使用
        if (count < 10) arguments.callee()
    })

</script>

bind call apply函数的应用:

<script>
    // 函数内可以使用 this 关键字,不同的地方 this 关键字

    function fn() {
        console.log(this);
    }

    // fn()

    // 函数内的 this 所指代的内容可以通过 bind call apply 来修改

    // bind 绑定函数的 this 指代
    // 语法 function.bind(thisArg,x,y,...)
    // thisArg:要修改的this的指代
    // x,y,...:函数的参数,选填
    // 返回值:一个被修改了this指代后的新函数
    let fn2 = fn.bind('hello world', 1, 2)

    console.log(fn2(4, 5));


    // call 调用函数并指定 this 指代
    // 语法:function.call(thisArg,x,y,...)
    // thisArg:要修改的this的指代
    // x,y,...:函数的参数,选填
    // 返回值:函数本身的返回值
    console.log(fn.call('hello call', 5, 6));


    // apply 调用函数并指代 this 指代
    // 语法:function.apply(thisArg,[x,y,...])
    // thisArg:要修改的this的指代
    // x,y,...:函数的参数,选填
    // 返回值:函数本身的返回值
    console.log(fn.apply('hello apply', [8, 9]));

</script>

lambda表达式:

<script>
    // lambda(郎木达)表达式
    // 什么是 lambda 表达式?是一种用来定义函数的方法
    // lambda 表达式也称为箭头函数

    // 定义一个无参函数
    // 圆括号部分是参数列表
    // 花括号部分是代码块
    let sayHello = () => {
        console.log('hello');
    }

    sayHello()


    // 定义只有一个参数的函数
    let sayMessage = (msg) => {
        console.log(msg);
    }

    // 参数只有一个时,圆括号可以省略
    sayMessage = msg => {
        console.log(msg);
    }

    sayMessage('hello message')


    // lambda 的返回值
    let add = (x, y) => {
        return x + y
    }

    // 在箭头后不写花括号 代表直接返回箭头后的内容
    add = (x, y) => x + y

    console.log(add(1, 2));

    // 返回对象的情况
    // 在箭头后使用圆括号包裹想要返回的对象
    let getUser = () => ({ name: '张三', sex: 'male' })
    console.log(getUser);
</script>

数组的迭代操作:

<script>
    let students = [
        { id: 0, name: '张三', sex: 'male', age: 16 },
        { id: 1, name: '李四', sex: 'female', age: 20 },
        { id: 2, name: '隔壁老王', sex: 'other', age: 30 }
    ]

    // 以下所有的遍历函数的参数都是相同的回调函数
    // 回调函数接受以下参数
    // el 被遍历的当前数组成员
    // index 被遍历的当前成员的索引
    // arr 被遍历的数组对象



    // forEach 循环遍历每一个数组成员
    students.forEach((el, index, arr) => {
        console.log(el);
        console.log(index);
        console.log(arr);
    })


    // every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环
    students.every((el, index, arr) => {
        console.log(el);
        console.log(index);
        console.log(arr);

        if (el.sex === 'female') {
            // every 的回调函数中需要返回一个bool值 false 类似于循环语句中的 break ,用于跳出循环
            return false
        }
        // 类似于 continue 继续循环
        return true
    })



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

    // 例如将students中所有的年龄放入一个新数组
    let r = students.map((el, index, arr) => {
        console.log(el);
        console.log(index);
        console.log(arr);

        // return 的内容将被放到新的数组中
        // return { age: el.age }
        return el.age
    })

    console.log(r);

    // filter 过滤器
    // filter 返回过滤完后的新数组
    r = students.filter((el, index, arr) => {
        console.log(el);
        console.log(index);
        console.log(arr);

        // // 过滤掉年龄大于25岁的成员
        // if(el.age>25){
        //     // return false 代表过滤掉该数据
        //     return false
        // }
        // // return true 代表保留该数据
        // return true

        return el.age <= 25
    })
    console.log(r);

    // find 查找

    // some 查询是否存在满足条件的成员

    // find 查找符合回调函数条件的数组成员并返回它
    // 返回值是查找的结果
    r = students.find((el, index, arr) => {
        // // 查找女同学
        // if(el.sex === 'female'){
        //     // return true 代表当前成员就是要查找的元素
        //     return true
        // }
        // return false

        return el.name === '隔壁老王'
    })
    console.log(r);

    // findeIndex 查找对应成员所在索引
    // 使用方法和 find 相同,返回结果为查找到的成员索引
    r = students.findIndex((el, index, arr) => {
        return el.name === '隔壁老王'
    })
    console.log(r);


    // some 方法测试数组中是不是至少有1个元素通过了被提供的函数测试,它返回的是一个Boolean类型的值
    // 给出一个判断条件,some 函数将对每个成员都作出相同的判断
    // 任意成员符合条件返回 true 时 some函数则返回true 否则为 false

    // 请判断 arr2 中是否有成员存在于 arr1 中
    let arr1 = ['x', 'y', 'z']
    let arr2 = ['a', 'b', 'c']

    r = arr2.some((el, index, arr) => {
        // 判断当前数组成员是否在 arr1 中存在
        if (arr1.includes(el)) {
            // 返回 true 代表 找到一个满足条件的数组成员
            return true
        }
        return false
    })
    console.log(r);


    // sort 排序

    // 若调用 sort 函数不传参数时,会使用浏览器默认的排序规则
    let numList = [90, 12, 110, 9, 40, 214]
    numList.sort()
    console.log(numList);

    // 自定义排序

    // 排序规则:按年龄从小到大排序

    // sort 参数是一个排序规则的函数
    // 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 的左侧
            // 左侧联想 左箭头 <:即小于符号,则此处应该返回一个小于零的值
            return -1
        } else {
            // 若 el1 和 el2 不需要交换顺序 则返回 0
            return 0
        }
    })
    console.log(students);
</script>

数组去重:

<script>
    // 数组去重

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

    // 有以下两种方法供参考

    // 1.缓存重复数据
    // 缓存
    let temp = {}
    // 结果数组
    let result = []
    for (let i = 0; i < arr.length; i++) {
        const item = arr[i]
        // 判断 item 充当 key 是否存在于 temp 中
        // if(temp[item]){
        if (!Reflect.has(temp, item)) {
            // item 不存在于 temp 的 key 中 
            result.push(item)
            // 添加缓存
            temp[item] = true
        }
    }
    console.log(result);

    // 使用 filter 去重
    temp = {}
    result = arr.filter(el => {
        // 若缓存中没有el
        if(!temp[el]){
            // 加入缓存
            temp[el] = true
            return true
        }
        return false
    })
    console.log(result);

    // 2.使用 set 集

    // 构造set 对象,set 对象会自动去重
    let set = new Set(arr)
    console.log(set);
    // 将set转换为数组
    result = Array.from(set)
    console.log(result);
    // 合成一句话
    console.log(Array.from(new Set(arr)));
    
</script>

MapReduce统计模型:

<script>
    // mapReduce 运算模型是拿来做大数据统计的
    // 要统计出结果,要经理 map 和 reduce 两个步骤
    // 1. map 的作用 用于返回统计结果所需的数据结构
    // 2. reduce 的作用就是具体的统计逻辑

    let arr = [
        {
            name: '李四666',
            score: 45,
            sex: 'female'
        },
        {
            name: '张三1',
            score: 45,
            sex: 'female'
        },
        {
            name: '李四1',
            score: 89,
            sex: 'female'
        },
        {
            name: '老王1',
            score: 48,
            sex: 'female'
        },
        {
            name: '张三2',
            score: 65,
            sex: 'female'
        },
        {
            name: '李四2',
            score: 32,
            sex: 'female'
        },
        {
            name: '老王2',
            score: 17,
            sex: 'male'
        }
    ]

    // 1. 统计上述数组中不及格的人数
    let r = arr.map(el => el.score)
    r.unshift(0)
    console.log(r);
    // reduce 中的参数
    // p:第一个数组成员或上一次运行回调函数的返回值
    // n:下一个被遍历的成员
    r = r.reduce((p, n) => {
        // 使用 p 充当当前不及格的人数
        if (n < 60) {
            p++
        }

        // 返回值的数据结构应该和 map 返回的结构相同
        return p
    })
    console.log(r + '个人未及格');


    // 2. 统计总分和平均分
    r = arr.map(el => {
        return {
            avg: 0, //平均分
            sum: 0, //总分
            score: el.score,    //每个同学的分数
            count: 0    //同学的数量
        }
    })
    console.log(r);
    // 前置插入一个空对象,用来充当上一次的结果
    r.unshift({
        avg: 0, //平均分
        sum: 0, //总分
        score: 0,    //每个同学的分数
        count: 0    //同学的数量
    })
    r = r.reduce((p, n) => {
        // 总分 = 上一次的总分 + 本次的分数
        let sum = p.sum + n.score
        // 人数 = 上一次的人数 + 1
        let count = p.count + 1
        // 平均分
        let avg = sum / count

        // 返回值的数据结构应给和 map 返回的结构相同
        return {
            avg,
            sum,
            score: 0,
            count
        }
    })
    console.log(r);


    // 3. 按男女统计分数最低的两个人
    r = arr.map(el => {
        return {
            // 男生分数最低的人
            male: [],
            // 女生分数最低的人
            female: [],
            stu: el
        }
    })

    r.unshift({
        male: [],
        female: [],
        stn: null
    })
    console.log(r);
    r = r.reduce((p, n) => {
        // 获取对应 n 性别的数组
        let temp = p[n.stu.sex]

        // 数组长度小于 2 就无条件添加
        if (temp.length < 2) {
            temp.push(n.stu)
        } else {
            // let male = [{score:17},{score:19}]
            // n.stu.score = 18

            // 将新数据加入数组
            temp.push(n.stu)
            // 从小到大排序
            temp.sort((el1, el2) => el1.score - el2.score)

            // 把最后一个删除
            temp.pop()

        }

        return {
            male: n.stu.sex === 'male' ? temp : p.male ,
            female: n.stu.sex === 'female' ? temp: p.female , 
            stn: null
        }
    })

    console.log(r);
</script>

7.字符串操作

表达式语法:

<script>
    // 参考
    'abc@qq.com'
    let regex = /^\\dcom$/

    // \ 斜杠:转义
    console.log(/\\abc/.test('\\abc'));

    // ^:匹配字符串的开头
    regex = /^abc/
    console.log(regex.test('abcd'));// => true
    console.log(regex.test('abcefg'));// => true
    console.log(regex.test('aabcd'));// => false

    // $:匹配字符串的结尾
    regex = /xyz$/
    console.log(regex.test('123xyz'));// => true
    console.log(regex.test('666abcxyz'));// => true
    console.log(regex.test('666abcxyz.'));// => false

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

    // *:匹配任意次
    regex = /o*k/
    console.log(regex.test('k'));// => true
    console.log(regex.test('ok'));// => true
    console.log(regex.test('ooooook'));// => true

    // ?:匹配0次或1次
    regex = /^o?k$/
    console.log(regex.test('k'));// => true
    console.log(regex.test('ok'));// => true
    console.log(regex.test('ooooook'));// => false

    // +:匹配至少1次
    regex = /^o+k$/
    console.log(regex.test('k'));// => false
    console.log(regex.test('ok'));// => true
    console.log(regex.test('ooooook'));// => true

    // {n}:匹配指定次数
    regex = /^o{2}k$/
    console.log(regex.test('k'));// => false
    console.log(regex.test('ok'));// => false
    console.log(regex.test('ook'));// => true

    // {n,}:匹配至少n次
    regex = /^o{2,}k$/
    console.log(regex.test('k'));// => false
    console.log(regex.test('ok'));// => false
    console.log(regex.test('ook'));// => true
    console.log(regex.test('oook'));// => true

    // {n,m}:匹配至少n次,至多m次
    regex = /^o{1,3}k$/
    console.log(regex.test('k'));// => false
    console.log(regex.test('ok'));// => true
    console.log(regex.test('ook'));// => true
    console.log(regex.test('oook'));// => true
    console.log(regex.test('ooook'));// => false

    // ----------------------------匹配字符个数的符号   -   end
    // [xyz]:匹配字符集合,匹配一个字符,该字符在方括号内
    regex = /^[xyz]$/
    console.log(regex.test('z'));// => true
    console.log(regex.test('yy'));// => false

    // x|y:或
    regex = /^(good|bad)$/
    console.log(regex.test('good'));    // => true
    console.log(regex.test('bad')); // => true
    console.log(regex.test('ok'));  // => false


    // [^xyz]:匹配负值集合,匹配一个字符,该字符不在方括号内
    regex = /^[^xyz]$/
    console.log(regex.test('z'));// => false
    console.log(regex.test('a'));// => true
    console.log(regex.test('abc'));// => false


    // [a-z] [0-9]:取范围值,匹配一个字符,该字符在指定范围内
    regex = /^[A-Z][0-9]$/
    console.log(regex.test('E7'));// => true
    console.log(regex.test('G8'));// => true
    console.log(regex.test('c4'));// => false

    // [^5-7]:取范围负值,匹配一个字符,该字符不在指定范围内
    regex = /^[^5-7]$/
    console.log(regex.test('6'));// => false
    console.log(regex.test('x'));// => true
    console.log(regex.test('a'));// => true


    // ----------------------------- 分组(pattern)
    // (pattern):将pattern里面的所有字符当做一个字符处理
    regex = /^abc(123)+xyz$/
    console.log(regex.test('abc123xyz'));// => true
    console.log(regex.test('abc123123xyz'));// => true
    console.log(regex.test('abc112233xyz'));// => false

    // 站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值
    regex = /abc(123)+xyz/
    let r = '000123abc123xyz444555'.match(regex)
    console.log(r);

    // (?:pattern):匹配分组内容,但不获取圆括号中的值
    regex = /abc(?:123)+xyz/
    r = '000123abc123123xyz444555'.match(regex)
    console.log(r);

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

    // 先行断言
    // 语法: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(/(?<!小红)小明/));

</script>

第一个正则表达式:

<script>
    // 正则表达式:用于匹配字符串的表达式
    // 例如:可以写一个正则表达手,用于鉴定一个字符串是否是邮箱格式的字符串

    // 声明一个正则表达式
    // 方法一:
    // abc@sdkf.com
    let regex = new RegExp('^[0-9a-zA-Z]+@[0-9a-zA-Z]+\.(com|cn)$')

    // 方法二:
    // 使用两根 / 斜线代表正则表达式的边界
    regex = /^[0-9a-zA-Z]+@[0-9a-zA-Z]+\.(com|cn)$/
    


    // 调用正则表达式的test方法 用于检测字符串是否符合正则表达式的描述
    // test 返回 true 代表 受测字符串符合正则表达式描述的特征
    console.log(regex.test('abc@666.com'));
</script>

贪婪模式和非贪婪模式:

<script>
    let str = '1234444'
    let regex = /^1234+/

    // match:从字符串中匹配出符合正则的字符串
    let result = str.match(regex)
    console.log(result);

    // 贪婪模式:字符串在匹配正则表达式时,将尽可能多的匹配满足正则规则的字符
    // 非贪婪模式:字符串在匹配正则表达式时,将尽可能少的匹配满足正则规则的字符

    // 默认情况下字符串将执行贪婪模式的匹配

    // 如何开启非贪婪模式
    // 方法:在表示匹配个数的符号后面加上'?'问号
    regex = /^1234+?/
    console.log(str.match(regex));
    regex = /^1234*?/
    console.log(str.match(regex));
    regex = /^1234??/
    console.log(str.match(regex));
    regex = /^1234{1,4}?/
    console.log(str.match(regex));
</script>

正则表达式的全局模式:

<script>
    let str = 'goods gods __goooods'
    let regex = /go+ds/

    // 不开全局模式 只会匹配到第一个符合条件的字符串
    console.log(str.match(regex));

    // 全局模式的正则表达式将匹配所有符合条件的字符串
    // 在正则表达式的后面使用 g flag 来开启全局模式
    // 另外一个 flag 是 i 代表 忽略大小写

    // matchAll 匹配所有符合正则描述的字符串
    // 参数必须是一个全局模式的正则表达式
    regex = /go+ds/g
    // 或 使用 RegExp
    // 第二个参数就是 flag
    regex = new RegExp('go+ds', 'gi')
    let r = str.matchAll(regex)
    console.log(r);

    // 此处返回值 r 是一个 iterator(迭代器)
    // 什么是迭代器?用于循环遍历一个集合的工具就是迭代器
    // 迭代器一定包含一个 next 函数 代表取出下一个成员

    // 所有的迭代器都可以使用forof来遍历
    for (const item of r) {
        console.log(item);
    }



    // 全局模式的另一个应用场景
    // 字符串的替换函数
    str = '共产党1 共产党2 共产党3'
    regex = /共产党/

    // replace 用于替换字符串
    // 第一个参数:要替换的内容,是一个正则表达式
    // 第二个参数:要换入的内容
    // 返回值:替换后的字符串
    r = str.replace(regex, '***'); 
    console.log(r);
    // 使用全局模式进行全局替换,否则只会替换第一个
    regex = /共产党/g
    r = str.replace(regex,'***')
    console.log(r);
</script>

正则表达式中的特殊字符:

<script>
    let regex = /^$/

    // \d :10进制数

    // \D :非10进制数



    // \r :回车    \n :换行
    // \s :所有不可见字符,制表符 回车换行 空格等
    // \S : 所有可见字符

    // . :基本等于任意字符 但不包括 \r\n

    // 匹配任意字符任意次数的写法如下:
    regex = /[\s\S]*/

    // \t :制表符
</script>

字符串的基础操作:

<script>
    let str = 'hello world !!!'

    // 字符串可以被视为字符数组
    // 查看字符串长度
    console.log(str.length);


    // 通过索引访问字符串中的字符
    console.log(str[4]);

    // charAt 函数可以获取指定索引出的字符
    console.log(str.charAt(4));     // 等价于 str[4]


    // split:分割字符串
    // 参数:用于分割字符串的字符
    // 返回值:字符串数组
    let r = str.split(' ')
    console.log(r);

    // split + join 替换字符

    // 例如:替换字符 *|& 为 _
    str = 'hello*|&world*|&!!!'
    r = str.split('*|&').join('_')
    console.log(r);


    // trim:去掉字符串首尾空格
    str = '                 hello world !!!                  '
    console.log(str);
    r = str.trim()
    console.log(r);



    // substring:截取子字符串
    // 第一个参数:截取字符串的起始索引位置
    // 第二个参数:截取字符串的结束索引位置
    // 口诀:前截后不截
    // 返回值:截取出来的子字符串
    str = 'hello world !!!'
    r = str.substring(4, 9)  // 从第一个 o 截取到 r
    console.log(r);

    // 第二个参数可以省略,如果只写一个参数,substring将从该参数位置一直截取到字符串末尾
    r = str.substring(6)
    console.log(r);


    // indexOf:查询字符串中指定字符在字符串中的索引位置
    // 参数:要查询的字符串
    // 返回值:被查询字符串的索引  
    console.log(str.indexOf('o'));

    // lastIndexOf
    console.log(str.lastIndexOf('o'));


    // 举例:截取字符串从 o ~ r
    console.log(str.substring(str.indexOf('o'), str.lastIndexOf('r') + 1));


    // startsWith:用于判断字符串是否以指定字符串开头
    // 参数:指定开头的字符串
    // 返回值:bool值 true代表是以指定字符串开头的,false代表不是
    console.log(str.startsWith('hello'));

    // endsWith:用于判断字符串是否以指定字符串结尾
    console.log(str.endsWith('hello'));



    // toUpperCase toLowerCase 将字符串中的英文转成全为大写或小写
    str = str.toUpperCase()
    console.log(str);

    str = str.toLowerCase()
    console.log(str);

    // 例如:统计一个字符串中出现了多少个a字符,忽略大小写
    str = 'djaLHFiuahkawengjlkaergiysadjAHda'
    str = str.toLowerCase()
    let count = 0
    for (let i = 0; i < str.length; i++) {
        const char = str[i]
        if (char === 'a') count++
    }
    console.log(count);


    // 补充:
    // 数字操作:
    // toFixed 保留小数点后多少位的函数
    // 参数:指定小数点后保留几位
    // 返回值:是一个保留了指定小数点位数的字符串
    let num = 3.1415926
    r = num.toFixed(3)
    console.log(r);

</script>

字符串的match函数:

<script>
let str = 'goods_gods_gooooods'
let regex = /go{1,3}ds/

// match:从字符串中匹配出符合正则的字符串
// 参数:用于匹配的正则表达式
// 返回值:包含了匹配结果的数组
let r = str.match(regex)
console.log(r);
</script>

8.闭包和计时器

闭包:

index.html

<script src="./sum.js"></script>
<script src="./avg.js"></script>
<script>
    // 什么是闭包
    // 闭包也叫函数闭包,一个函数被另一个函数包裹并返回,包裹一些需要被保存的数据
    // 且函数需要返回一个持续引用的对象,这就叫闭包
    // 为了方便理解,做如下的假设:
    // 1.假设有一个函数A
    // 2.A内声明变量B
    // 3.A返回一个 包含函数的内容
    // 4.A返回的 包含的函数必须引用变量B
    // 5.此时函数A就是闭包的

    const demo = (function A() {
        let B = { name: '张三' }
        return function () {
            // 函数内引用变量B 则函数A是闭包的
            console.log(B);
        }
        // 当返回一个值时 函数A将返回 A中没被使用的变量将被垃圾回收掉,则A不是闭包的
        // return 'hello world' 

        // 当返回的是直接声明的对象或数组的时候,声明时将B作为对象属性的值或数组成员传入对象或数组
        // 当A返回后,变量B则没有再被引用了,则被垃圾回收掉,A不是闭包的
        // return { name: B }
    })()

    // 自己写的例子
    const aa = (function as(){
        let bb = 0
        return function(){
            console.log(bb);
        }
    })()
    aa()

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



    // 使用自调用函数,返回一个函数
    const sayMessage = (() => {

        // 声明一个用于记录调用次数的变量
        // 闭包的函数空间内声明的变量,在内存中将持续存在
        // 垃圾回收不会回收该变量
        // 该变量在外部无法访问
        let count = 0

        // 在自调用函数中返回一个函数
        // 返回的函数就是一个闭包后的函数
        return (msg) => {
            console.log(msg);
            count++
            console.log(`sayMessage 调用次数为 ${count}`);
        }
    })()

    sayMessage('hello world')

    console.log(sum([1, 2, 3]));
    console.log(avg([4, 5, 6]));

    const obj = (function () {
        let name = '张三'

        // 在函数空间内 可以返回任意的
        return {
            // 获取名称
            getName() {
                return name
            },
            // 改名
            setName(newName) {
                name = newName
            }
        }
    })()

    console.log(obj);
    console.log(obj.getName());
    obj.setName('李四')
    console.log(obj.getName());

</script>

avg.js

// var _sum = 0

// // 求平均数
// // 参数是个数组
// function avg(arr) {
//     _sum = arr.reduce(p, n => p + n)
//     return _sum / arr.length
// }

// 求平均数
// 参数是个数组
const avg = (function () {
    let _sum
    return (arr) => {
        _sum = arr.reduce((p, n) => p + n)
        return _sum / arr.length
    }
})()

sum.js

// var _sum = 0

// 求和
// 参数是个数组
// function sum(arr) {
//     _sum = arr.reduce((p, n) => p + n)
//     return _sum
// }

const sum = (()=>{
    let _sum
    return(arr) => {
        _sum = arr.reduce((p, n) => p + n)
        return _sum
    }
})()

列表显示:

<body>
    <div>
        <table border="5px solid #000">
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>学号</th>
                    <th>班级</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <!-- 编写一个待会被js使用的html模板 -->
                <tr>
                    <td>1</td>
                    <td>张三</td>
                    <td>1班</td>
                    <td>
                        <button>编辑</button>
                        <button>删除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script src="./api/db.js"></script>
<script>
    // 数据增删改查的js练习
    // 增删改查也叫做crud(Create,Read,Update,Delete)
    // 学会数据的增删改查流程
    // 此处使用localStorage来充当数据库

    // 思路:
    // 页面加载完成后,从服务器拉取数据(也就是在页面的 load 事件后 做一些初始化页面的操作)
    // 创建一个 js 文件,充当发起网络请求的 api 文件
    // 该例子,用到一个思路:用数据驱动页面——页面最终长什么样子,取决于数据


    // 构造测试数据
    // 假设该数据是从服务器得到
    // let data = [
    //     {
    //         name: '张三',    //姓名
    //         no: '0', //学号
    //         class: '1班' //班级
    //     },
    //     {
    //         name: '李四',
    //         no: '1',
    //         class: '2班'
    //     },
    //     {
    //         name: '王麻子',
    //         no: '2',
    //         class: '3班'
    //     }
    // ]
    let data = []

    // 给 localStorage 填充数据
    // localStorage['students'] = JSON.stringify(data)

    const tbody = document.querySelector('tbody')

    // 渲染函数,用于显示渲染页面内容
    // 此处 render 中需要将 data 转换成 html 的内容
    function render() {
        // let html = ''
        // data.forEach(item => {
        //     html +=
        //         `
        // <tr>
        //     <td>${item.no}</td>
        //     <td>${item.name}</td>
        //     <td>${item.class}</td>
        //     <td>
        //         <button>编辑</button>
        //         <button>删除</button>
        //     </td>
        // </tr>
        //     `
        // })
        // console.log(html);

        //     let html = data.map(item => `
        //         <tr>
        //             <td>${item.no}</td>
        //             <td>${item.name}</td>
        //             <td>${item.class}</td>
        //             <td>
        //                 <button>编辑</button>
        //                 <button>删除</button>
        //             </td>
        //         </tr>
        //     `)
        //     console.log(html);
        //     // 再使用 join 函数拼接
        //     html = html.join('')
        //     console.log(html);
        // }

        let html = data.map(item => `
            <tr>
                <td>${item.no}</td>
                <td>${item.name}</td>
                <td>${item.class}</td>
                <td>
                    <button>编辑</button>
                    <button>删除</button>
                </td>
            </tr>
        `).join('')

        console.log(html);

        // 插入文档
        tbody.innerHTML = html
    }

    // render()

    // 给窗口对象绑定load事件
    // 当窗口加载完成后,执行事件回调
    window.addEventListener('load',() => {
        let r = query()
        // r 存在就赋值 data
        r && (data = r)
        render()
    })
</script>

添加数据:

<body>
    <!-- 工具栏 -->
    <div><button class="add-btn">添加</button></div>
    <div>
        <table border="5px solid #000">
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>学号</th>
                    <th>班级</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <!-- 编写一个待会被js使用的html模板 -->
                <tr>
                    <td>1</td>
                    <td>张三</td>
                    <td>1班</td>
                    <td>
                        <button>编辑</button>
                        <button>删除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
    // 总结:
    // 通过点击按钮,跳转到一个表单页,进行数据添加
    // 添加成功后,返回列表页,获取新数据
    // 表单中,通常要做表单验证,验证用户输入的合法性
    

    let data = []

    // 给 localStorage 填充数据
    // localStorage['students'] = JSON.stringify(data)

    const tbody = document.querySelector('tbody')

    // 渲染函数,用于显示渲染页面内容
    // 此处 render 中需要将 data 转换成 html 的内容
    function render() {
        // let html = ''
        // data.forEach(item => {
        //     html +=
        //         `
        // <tr>
        //     <td>${item.no}</td>
        //     <td>${item.name}</td>
        //     <td>${item.class}</td>
        //     <td>
        //         <button>编辑</button>
        //         <button>删除</button>
        //     </td>
        // </tr>
        //     `
        // })
        // console.log(html);

        //     let html = data.map(item => `
        //         <tr>
        //             <td>${item.no}</td>
        //             <td>${item.name}</td>
        //             <td>${item.class}</td>
        //             <td>
        //                 <button>编辑</button>
        //                 <button>删除</button>
        //             </td>
        //         </tr>
        //     `)
        //     console.log(html);
        //     // 再使用 join 函数拼接
        //     html = html.join('')
        //     console.log(html);
        // }

        let html = data.map(item => `
            <tr>
                <td>${item.no}</td>
                <td>${item.name}</td>
                <td>${item.class}</td>
                <td>
                    <button>编辑</button>
                    <button>删除</button>
                </td>
            </tr>
        `).join('')

        console.log(html);

        // 插入文档
        tbody.innerHTML = html
    }

    // 添加数据
    $('.add-btn').addEventListener('click', () => {
        // 跳转到新增数据页
        location.href = './add.html'
    })

    // 给窗口对象绑定load事件
    // 当窗口加载完成后,执行事件回调
    window.addEventListener('load', () => {
        let r = query()
        // r 存在就赋值 data
        r && (data = r)
        render()
    })
</script>

删除单条数据:

<body>
    <!-- 工具栏 -->
    <div><button class="add-btn">添加</button></div>
    <div>
        <table border="5px solid #000">
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>学号</th>
                    <th>班级</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <!-- 编写一个待会被js使用的html模板 -->
                <tr>
                    <td>1</td>
                    <td>张三</td>
                    <td>1班</td>
                    <td>
                        <button>编辑</button>
                        <button>删除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
    // 总结:
    // 删除时,为了让程序知道用户要删除哪一个数据,那么需要给按钮添加一个 数据唯一标识符 参数
    // 点击按钮时,可以获取对应的数据唯一标识符
    // 删除是危险操作,所以需要询问用户 甚至要做验证


    let data = []

    const tbody = document.querySelector('tbody')

    function render() {
        // 添加删除事件的一种方法 <button onclick="removeOne('${item.no}')">删除</button>
        let html = data.map(item => `
            <tr>
                <td>${item.no}</td>
                <td>${item.name}</td>
                <td>${item.clazz}</td>
                <td>
                    <button>编辑</button>
                    <button class="remove-btn" no="${item.no}">删除</button>
                </td>
            </tr>
        `).join('')

        console.log(html);

        // 插入文档
        tbody.innerHTML = html

    }

    // 给页面函数添加事件
    function addEvents() {
        // 查询所有删除按钮
        const removeBtns = document.querySelectorAll('.remove-btn')
        for (let i = 0; i < removeBtns.length; i++) {
            const removeBtn = removeBtns[i];
            // 给每个按钮绑定点击事件
            removeBtn.addEventListener('click', function () {
                // 使用 function 定义的此处的事件处理程序 则 this 代表被点击的按钮
                let no = this.getAttribute('no')

                // 由于删除是一个危险操作,所以需要提示确认
                confirm('确定删除吗') && removeOne(no)

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

        }
    }

    // 添加数据
    $('.add-btn').addEventListener('click', () => {
        // 跳转到新增数据页
        location.href = './add.html'
    })

    // 给窗口对象绑定load事件
    // 当窗口加载完成后,执行事件回调
    window.addEventListener('load', () => {
        let r = query()
        // r 存在就赋值 data
        r && (data = r)
        render()
        addEvents()
    })

</script>

批量删除数据:

<body>
    <div>
        <button class="add-btn">添加</button>
        <button class="removeAll">批量删除</button>
    </div>
    <div>
        <table border="5px solid #000">
            <thead>
                <tr>
                    <th><label><input class="select-all" type="checkbox">全选</label></th>
                    <th>姓名</th>
                    <th>学号</th>
                    <th>班级</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><input class="checkbox" type="checkbox"></td>
                    <td>1</td>
                    <td>张三</td>
                    <td>1班</td>
                    <td>
                        <button>编辑</button>
                        <button>删除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>

    let data = []
    // 用于存储当前被勾选的学号值
    let selection = []

    const tbody = document.querySelector('tbody')

    function render() {
        let html = data.map(item => `
            <tr>
                <td><input class="checkbox" type="checkbox" no="${item.no}"></td>
                <td>${item.no}</td>
                <td>${item.name}</td>
                <td>${item.clazz}</td>
                <td>
                    <button>编辑</button>
                    <button class="remove-btn" no="${item.no}">删除</button>
                </td>
            </tr>
        `).join('')

        tbody.innerHTML = html

    }

    function addEvents() {
        const removeBtns = document.querySelectorAll('.remove-btn')
        for (let i = 0; i < removeBtns.length; i++) {
            const removeBtn = removeBtns[i];
            removeBtn.addEventListener('click', function () {
                let no = this.getAttribute('no')

                confirm('确定删除吗') && removeOne(no)

                location.reload()
            })

        }



        // checkbox 点击事件
        const checkboxs = document.querySelectorAll('.checkbox')
        for (let i = 0; i < checkboxs.length; i++) {
            const checkbox = checkboxs[i];
            // 绑定元素的变化事件
            // 当 checkbox 的选择状态发生变化时触发该事件
            checkbox.addEventListener('change', function () {
                // 获取学号
                let no = this.getAttribute('no')
                // 获取当前勾选状态
                let checked = this.checked

                if (checked) {
                    // 判断数组中不存在学号,则添加数据
                    !selection.includes(no) && selection.push(no)
                } else {
                    // 判断数组中已存在学号,则删除学号
                    let i = selection.findIndex(item => item === no)
                    // 当索引不为 -1 说明 findIndex 查找到了数据 则删除该数据
                    i !== -1 && selection.splice(i, 1)
                }

            })
        }


        // 全选按钮
        $('.select-all').addEventListener('change', function () {
            // 获取当前的勾选状态
            let checked = this.checked
            // if (checked) {
            //     // 执行全选逻辑
            //     for (let i = 0; i < checkboxs.length; i++) {
            //         const checkbox = checkboxs[i];
            //         // 判断checkbox是否勾选,没勾选的就勾选
            //         !checkbox.checked && checkbox.click()
            //     }
            // } else {
            //     for (let i = 0; i < checkboxs.length; i++) {
            //         const checkbox = checkboxs[i];
            //         // 判断checkbox是否勾选,已勾选的就取消勾选
            //         checkbox.checked && checkbox.click()
            //     }
            // }

            // 优化代码
            for (let i = 0; i < checkboxs.length; i++) {
                const checkbox = checkboxs[i];
                // 当全选按钮的状态不等于当前 checkbox 的状态
                // 则点击当前 checkbox
                (checked !== checkbox.checked) && checkbox.click()
            }
        })
    }


    // 工具栏事件
    $('.add-btn').addEventListener('click', () => {
        location.href = './add.html'
    })

    // 全部删除
    $('.removeAll').addEventListener('click', () => {
        // 先判断 selection 中存在选项吗,不存在则不执行
        if (selection.length === 0) return
        // 询问成功后删除
        confirm('确定删除吗?') && removeAll(selection)
        location.reload()
    })

    window.addEventListener('load', () => {
        let r = query()
        r && (data = r)
        render()
        addEvents()
    })

</script>

数据编辑:

<body>
    <div>
        <button class="add-btn">添加</button>
        <button class="removeAll">批量删除</button>
    </div>
    <div>
        <table border="5px solid #000">
            <thead>
                <tr>
                    <th><label><input class="select-all" type="checkbox">全选</label></th>
                    <th>姓名</th>
                    <th>学号</th>
                    <th>班级</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><input class="checkbox" type="checkbox"></td>
                    <td>1</td>
                    <td>张三</td>
                    <td>1班</td>
                    <td>
                        <button>编辑</button>
                        <button>删除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
    let data = []
    let selection = []

    const tbody = $('tbody')

    function render() {
        let html = data.map(item => `
            <tr>
                <td><input class="checkbox" type="checkbox" no="${item.no}"></td>
                <td>${item.no}</td>
                <td>${item.name}</td>
                <td>${item.clazz}</td>
                <td>
                    <button class="edit-btn" no="${item.no}">编辑</button>
                    <button class="remove-btn" no="${item.no}">删除</button>
                </td>
            </tr>
        `).join('')
        tbody.innerHTML = html
    }

    function addEvents() {
        const removeBtns = document.querySelectorAll('.remove-btn')
        for (let i = 0; i < removeBtns.length; i++) {
            const removeBtn = removeBtns[i];
            removeBtn.addEventListener('click', function () {
                let no = this.getAttribute('no')
                confirm('确定删除吗') && removeOne(no)
                location.reload()
            })
        }

        const checkboxs = document.querySelectorAll('.checkbox')
        for (let i = 0; i < checkboxs.length; i++) {
            const checkbox = checkboxs[i];
            checkbox.addEventListener('change', function () {
                let no = this.getAttribute('no')
                let checked = this.checked

                if (checked) {
                    !selection.includes(no) && selection.push(no)
                } else {
                    let i = selection.findIndex(item => item === no)
                    i !== -1 && selection.splice(i, 1)
                }
            })
        }

        $('.select-all').addEventListener('change', function () {
            let checked = this.checked
            for (let i = 0; i < checkboxs.length; i++) {
                const checkbox = checkboxs[i];
                (checked !== checkbox.checked) && checkbox.click()
            }
        })
        
        // 编辑按钮
        const editBtns = document.querySelectorAll('.edit-btn')
        for (let i = 0; i < editBtns.length; i++) {
            const editBtn = editBtns[i];
            editBtn.addEventListener('click', function () {
                let no = this.getAttribute('no')
                // 跳转到编辑页 并将学号作为参数
                // 参数用问号追加到地址的末尾
                // ?a=1&b=2 这种格式的参数名为 queryString (查询字符串)
                location.href = './edit.html?no=' + no
            })

        }
    }



    $('.add-btn').addEventListener('click', () => {
        location.href = './add.html'
    })

    $('.removeAll').addEventListener('click', () => {
        if (selection.length === 0) return
        confirm('确定删除吗?') && removeAll(selection)
        location.reload()
    })

    window.addEventListener('load', () => {
        let r = query()
        r && (data = r)
        render()
        addEvents()
    })

</script>

导致内存溢出的情况:

<script>
    // 内存溢出
    // 当内存中堆栈内容已经装不下了,再给内存增加内容则溢出了

    // 栈溢出

    // 无限的函数递归会引发栈溢出
    function fn1() {
        fn1()
    }
    // fn1()

    // 堆溢出
    // let obj = {name:'zhangsan'}
    let count = 0
    // while (true) {
    //     // 每次循环都声明一个新的对象
    //     // 对象保存在堆上
    //     // 当堆上内容过多 则会导致堆溢出
    //     let a = { name: Date() }
    // }

</script>

计时器:

<body>
    <button class="btn1">停止 timeout 计时</button>
    <button class="btn2">停止 interval 计时</button>
</body>
<script>
    // 计时器

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

    // 声明一个计时器:setTimeout
    // 第一个参数:计时器计时结束后触发的函数
    // 第二个参数:计时时长,单位:毫秒
    // 返回值:计时器id
    // 计时器id 用于停止计时
    let timerId = setTimeout(() => {
        console.log('hello setTimeout');
    }, 3000)

    // clearTimeout
    document.querySelector('.btn1').addEventListener('click', () => {
        // clearTimeout 清空计时器
        // 参数是 计时器id
        // 清空后计时器将被取消掉
        clearTimeout(timerId)
    })



    // setInterval 循环计时函数
    // setInterval 函数,每次经过指定时间,触发一次指定的函数
    // 参数和返回值 与 setTimeout 相同
    // let count = 0
    // let timerId2 = setInterval(() => {
    //     count++
    //     console.log(count);
    // }, 1000)

    // clearInterval
    // document.querySelector('.btn2').addEventListener('click', () => {
    //     // 清空循环计时器
    //     clearInterval(timerId2)
    // })

    // 请避免以下死循环
    // setTimeout 死循环
    // let count = 0
    // function countTime() {
    //     setTimeout(() => {
    //         count++
    //         console.log(count);
    //         // 递归调用自身 形成死循环
    //         countTime()
    //         // 添加条件来跳出循环 则不会导致内存溢出
    //         count < 10 && countTime()
    //     }, 5000);
    // }
    // countTime()


    // setInterval 死循环
    // setInterval(() => {
    //     count++
    //     console.log(count);
    // })


    // 关于 setTimeout 和 setInterval 中的异常
    // function countTime() {
    //     setTimeout(() => {
    //         if (count === 5) console.log(abc);
    //         count++
    //         console.log(count);
    //         count < 10 && countTime()
    //     }, 1000);
    // }
    // countTime()
    // setTimeout 计时器内出现异常 停止递归

    // setInterval(() => {
    //     if (count === 5) console.log(abc.obj());
    //     count++
    //     console.log(count);
    // }, 1000)
    // setInterval 计时器内出现异常 程序继续执行


    // 注意事项:
    // 1.尽量不使用 setInterval
    // 理由:setInterval 可能由于人为原因忘了关闭,或者内部出现异常,导致代码死循环
    // 2. 若要做大量循环调用甚至是无限循环调用时(例如轮询死循环调用),请使用 setInterval 而不是使用 setTimeout 递归进行循环
    // 理由:setTimeout 会占用大量内存堆栈
</script>

9.js移动与渐入渐出动画

封装移动函数:

<style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #f00;
            position: relative;
            left: 500px;
            top: 200px;
        }
    </style>

<body>
    <div class="box"></div>
</body>
<script>
    // 思路

    // 一定是使用计时器来播放动画

    // 步骤如下:

    // 声明移动速度

    // 使用计时器,每帧让元素移动指定速度的距离

    // 在每一帧移动后,判断是否到达了终点位置,若已经到达了终点位置,应该停止计时器

    // 若最后一帧位置超出了终点位置,应该将元素设置到终点位置


    // 封装移动函数
    // 函数用来封装不变的内容,变化的内容就成为参数
    // 参数:
    // el:需要移动的元素
    // v:速度
    // axis:坐标轴 接收参数 'x'|'y' 代表 x轴 和 y轴
    // distance:移动距离 填入一个正数
    // duration:每一帧经过的时长
    // offset:移动距离的初始偏移量
    function move(el, v, axis, distance, duration, offset) {

        // 当前移动距离
        let current = 0

        let timerId = setInterval(() => {
            // 一个动画帧

            // 先运行逻辑运算

            // 当前的距离值
            current += v

            // 判断当前距离是否已经大于等于预设的目标距离
            // 通过 Math.abs 取 current 的绝对值 和 distance 进行判断
            if (Math.abs(current) >= distance) {
                clearInterval(timerId)
                // current 可能已经大于目标距离
                // 所以赋值 distance
                // 由于 distance 的值永远是正数,所以需要乘以速度的符号
                // Math.sign(v) 作用是获取 v 的符号 返回 1 或 -1
                current = Math.sign(v) * distance
            }

            // 再渲染到元素上
            if (axis === 'x')
                el.style.left = `${current + offset}px`
            else
                el.style.top = `${current + offset}px`
        }, duration);
    }


    const box = document.querySelector('.box')
    move(box, -10, 'x', 1000, 200, 500)
</script>

渐入渐出动画:

<style>
        img {
            width: 400px;
            height: 300px;
        }
    </style>

<body>
    <img style="opacity: 1;" src="./滚动相片练习/img/an1.jpg">
</body>
<script>
    const img = document.querySelector('img')

    let timerId
    // 绑定鼠标移入移出事件

    // 进入事件
    img.addEventListener('mouseenter', () => {
        console.log('enter');

        timerId = setOpacity(img, Number(img.style.opacity), -0.01, 10)
    })
    // 离开事件
    img.addEventListener('mouseleave', () => {
        console.log('leave');
        clearTimeout(timerId)
        timerId = setOpacity(img, Number(img.style.opacity), 0.01, 10)
    })

    // img.addEventListener('mouseover', () => {
    //     console.log('over');
    // })
    // img.addEventListener('mouseout', () => {
    //     console.log('out');
    // })

    // 设置透明度
    // el:用来修改透明度的元素
    // start:透明动画播放时的初始值
    // v:透明度的变化速度
    // duration:计时器的间隔时间
    function setOpacity(el, start, v, duration) {
        // 当前透明度
        let current = start
        // 计时器运行前先赋值一个初始透明度
        el.style.opacity = current

        let timerId = setInterval(() => {
            // 先计算动画逻辑
            current += v

            // 判断透明度是否已经到了极限值
            // if (current <= 0) {
            if ((v < 0 && current <= 0) || (v > 0 && current >= 1)) {
                // 停止计时器
                clearInterval(timerId)
                // 赋值current为极限值
                current = v < 0 ? 0 : 1
            }

            // 后赋值
            el.style.opacity = current
        }, duration)

        // 返回一个计时器id 方便随时停止计时器
        return timerId
    }
</script>

元素移动:

<style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #f00;
            position: relative;
            left: 0;
        }
    </style>

<body>
    <div class="box"></div>
</body>
<script>
    // 移动元素的思路
    // 使用计时器 每隔一段时间 让元素位置产生一个增量的变化

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

    let timerId = setInterval(() => {
        // 一个动画帧

        // 先运行逻辑运算
        let v = 15
        // 当前的距离值
        let distance = getComputedStyle(box).left
        distance = Number(distance.substring(0, distance.length - 2))
        // 叠加速度后的距离
        distance += v

        if(distance >= 500){
            // 停止计时器
            clearInterval(timerId)
        }

        // 再渲染到元素上
        box.style.left = `${distance}px`
    }, 200);
</script>

10.时间对象和数学函数

时间对象:

<script>
    // 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date

    // 时间字符串的参考:https://www.w3.org/TR/NOTE-datetime




    // 时间对象 Date

    // 时间数据如何表示?如何创建一个时间数据

    // 表示时间的字符串
    // 字符串需要满足 w3c 联盟要求的时间字符串
    // 例如:1990-01-01

    // 表示时间的数字
    // 含义:表示格林威治1970-1-1开始以后,经过的毫秒时间



    // 创建时间对象的方法
    // 语法:new Date(params)

    // 1.当前系统时间
    let date = new Date()
    console.log(date);
    // Date 时间对象默认修改了对象的 toString 方法 所以转字符串时,会显示成时间字符串
    // 所有对象都有 toString 方法,转换字符串时,js会调用该方法

    // 2.创建指定时间字符串
    // 不推荐使用,因为这是个非标准时间字符串,不同浏览器解析时间字符串的方式不同
    date = new Date('1999-02-01')
    console.log(date);

    // 3.通过格林威治毫秒时创建时间
    date = new Date(1000 * 60 * 60 * 24 * 365)
    console.log(date);

    // 4.通过年月日时分秒创建时间
    // 参数分别代表 年月日时分秒
    // 注意:月份是从0开始计算的
    date = new Date(1999, 2, 28, 18, 23, 58)
    console.log(date);


    // 参数至少写前两个参数,后续参数可以省略
    date = new Date(1999, 2, 28, 18)
    console.log(date);
    date = new Date(1999, 2, 28)
    console.log(date);
    date = new Date(1999, 2)
    console.log(date);

    // 如何读取时间?如何设置时间?
    // 读取时间
    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());   // 毫秒 值不会大于1000

    date = new Date(2021, 12, 2)
    console.log(date);
    console.log(date.getDay());

    // 设置时间
    date.setFullYear(2023)
    date.setMonth(0)
    date.setDate(5)
    date.setHours(20)
    date.setMinutes(66)
    date.setSeconds(67)
    date.setMilliseconds(1127)
    console.log(date);

    // 其他常用时间函数
    // Date.now()   // 获取当前系统时间的格林威治毫秒时
    console.log(Date.now());
    // date.getTime()   // 获取date对象代表的格林威治毫秒时
    console.log(date.getTime());


    // 通常来说日期对象需要转换成字符串显示,否则用户看不懂
    function format(date) {
        return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`
    }

    console.log(format(new Date()));
</script>

数学函数:

<script>
    // 数学函数:专门用于处理数学运算的函数
    // 数学函数可以通过浏览器内置对象Math进行直接调用

    // 参考:

    // 三角函数
    // 需要注意的是,三角函数 sin() cos() tan() asin() acos() atan() 和 atan2() 返回的值是弧度
    // 若要转换,弧度除以 (Math.PI / 180) 即可转换为角度,同理,角度乘以这个数则能转换为弧度

    // 三角函数中所有和角度相关的值都是弧度值
    // 例如:计算30度的正弦值,如下:
    console.log(Math.sin((Math.PI / 180) * 30));

    // 反三角函数就在三角函数前加上a就可以了
    console.log(Math.asin(0.5) / (Math.PI / 180));

    // abs:获取数字的绝对值
    console.log(Math.abs(100));
    console.log(Math.abs(-100));

    // ceil:一个小数向上取整
    console.log(Math.ceil(5.1));
    console.log(Math.ceil(5.2));
    console.log(Math.ceil(5.5));
    console.log(Math.ceil(5.9));

    // floor:一个小数向下取整
    console.log(Math.floor(5.1));
    console.log(Math.floor(5.2));
    console.log(Math.floor(5.5));
    console.log(Math.floor(5.9));

    // round:四舍五入
    console.log(Math.round(5.1));
    console.log(Math.round(5.2));
    console.log(Math.round(5.5));
    console.log(Math.round(5.9));

    // max:取参数中较大数
    console.log(Math.max(1, 2, 3, 4, 5, 6, 7));

    // min:取参数中较小数
    console.log(Math.min(1, 2, 3, 4, 5, 6, 7));

    // 通过max和min写一个定界函数
    // value:要定界的数字
    // min,max 最小和最大值
    // 返回值:value < min 时返回 min , value > max 时返回 max , min < value < max 时 返回 value
    function clamp(value, min, max) {
        // let temp = Math.min(value, max)
        // temp = Math.max(temp,min)
        return Math.max(Math.min(value, max), min)
    }

    console.log(clamp(1, 0, 2));
    console.log(clamp(-1, 0, 2));
    console.log(clamp(3, 0, 2));


    // pow:返回x的y次方
    // 语法:Math.pow(x,y)
    console.log(Math.pow(2, 3));

    // sqrt:返回一个数的平方根
    console.log(Math.sqrt(25));

    // random:取随机数,范围在[0~1)之间,能取到0但取不到1
    console.log(Math.random());
    // 假设随机一个 50 ~ 100 的数
    console.log(Math.random() * 50 + 50);   // => 取不到100
    console.log(Math.round(Math.random() * 50) + 50);   // => 能取到100
    // 随机一个 n ~ m 的数
    // 则公式为:n + Math.random() * (m - n)

    // sign:取符号
    console.log(Math.sign(-135));
    console.log(Math.sign(-51));
    console.log(Math.sign(45));
    console.log(Math.sign(29));

</script>

11.事件和异常处理

事件:

事件的基础:

<script>
    // 什么是事件?
    // 事件就是某种事情
    // 在js中,事件就是:当某种情况发生的时候,能够触发一段代码,这个发生的情况就是事件
    // 简单的理解,事件就是:(当。。。时,就。。。)


    // 事件在js中以对象形式存在

    // 什么是绑定事件?
    // 将自定义的函数和某个事件进行捆绑,绑定后的效果就是:(当。。。时,发生的时候,就会触发我们绑定的函数)

    // 绑定事件的方法:
    // 使用元素对象的addEventListener函数进行事件绑定
    // mdn文档

    // addEventListener绑定事件
    // 第一个参数:要绑定的事件
    // 第二个参数:当事件发生时,触发的函数
    // 第二个参数的函数,不是有开发人员调用的,当事件发生时由浏览器调用的
    const btn1 = document.querySelector('.btn1')
    // 声明一个事件处理程序
    function btn1Clicked(event) {
        // event 是触发的事件对象
        console.log(event);
        // this 关键字 代表被添加该事件处理程序的 dom 对象
        console.log(this);
    }
    // 给 btn1 添加事件监听器
    btn1.addEventListener('click', btn1Clicked)

    // 解绑事件
    const btn2 = document.querySelector('.btn2')
    // 声明一个点击处理程序
    btn2.addEventListener('click', () => {
        // 移除事件监听器
        // 调用removeEventListener来解绑事件
        // 第一个参数:要解绑的事件名称
        // 第二个参数:要解绑的事件处理程序
        btn1.removeEventListener('click', btn1Clicked)
    })



    // 补充:
    // 我们可以使用元素上带有on开头的属性进行事件绑定,例如onclick:
    // btn1.onclick = () => { console.log('按钮被点击了') }
    // 解绑onclick函数,如下:
    // btn1.onclick = null

    function onClick() {
        console.log('按钮被点击了')
    }
</script>

事件分类:

<style>
        .box {
            background-color: #f00;
            width: 100px;
            height: 100px;
        }

        .box2 {
            background-color: #ff0;
            width: 100px;
            height: 100px;
            position: absolute;
            top: 100px;
            left: 100px;
        }

        .boxContainer {
            width: 500px;
            height: 500px;
            background-color: #0f0;
            position: relative;
        }

        .box3 {
            background-color: #00f;
            width: 100px;
            height: 100px;
        }

        .frame {
            border: 5px solid #f00;
            overflow: hidden;

        }
    </style>

<body>
    <img src="./img/an15631.jpg">
    <input type="text">
    <input type="text">
    <!-- 给一个非标单元素添加 tabindex 让他获得获取焦点的能力 从而可以触发 focus 和 blur 事件 -->
    <div class="box" tabindex="0"></div>
    <div class="boxContainer">
        <div class="box2"></div>
    </div>
    <div class="box3" draggable="true"></div>

    <div class="frame">
        <div style="height: 3000px;"></div>
    </div>
</body>
<script>
    // 资源事件
    const img = document.querySelector('img')

    // 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('获取焦点');
    })

    box.addEventListener('focus', () => {
        console.log('获取焦点');
    })

    // blur 失去焦点
    input.addEventListener('blur', () => {
        console.log('失去焦点');
    })

    box.addEventListener('blur', () => {
        console.log('失去焦点');
    })

    // 鼠标事件
    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')

    // 拖动
    box3.addEventListener('drag', ev => {
        console.log('drag');
        console.log(ev);
    })

    // 开始拖动时触发
    box3.addEventListener('dragstart', ev => {
        console.log('drag start');
        console.log(ev);
    })

    // 结束拖动时触发
    box3.addEventListener('dragend', ev => {
        console.log('drag end');
        console.log(ev);
    })

    // 媒体事件:和多媒体播放相关事件

    // 表单元素事件
    // 输入事件
    // input.addEventListener('input', ev => {
    //     console.log('输入');
    //     console.log(ev);
    //     // 本次输入的内容
    //     console.log(ev.data);
    //     // 当前输入框的值
    //     console.log(ev.currentTarget.value);
    //     console.log(ev.target.value);
    // })

    // 变化事件
    // 一般除了输入框外都使用 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);
    })


    // 补充:
    // 窗口重置大小
    window.addEventListener('resize', () => {
        console.log('resize');
        console.log(window.innerWidth);
        console.log(window.innerHeight);
    })

    // 滚动条滚动事件
    // 要监听滚动条的滚动事件,要先找到产生滚动条的元素
    // window.addEventListener('scroll', ev => {
    //     console.log('scroll');
    //     console.log(ev);
    // })

    document.querySelector('.frame').addEventListener('scroll', function (ev) {
        console.log('scroll');
        console.log(ev);
        // this 代表当前触发事件的对象
        // this.scrollTop 代表纵向滚动的高度
        console.log(this.scrollTop);
    })
</script>

事件机制——冒泡:

<body>
    <div>
        <button>点击</button>
    </div>
</body>

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

    document.querySelector('button').addEventListener('click', () => {
        console.log('button');
    })
    document.querySelector('div').addEventListener('click', () => {
        console.log('div');
    })
    document.querySelector('body').addEventListener('click', () => {
        console.log('body');
    })
</script>

事件机制——捕获事件:

<body>
    <div>
        <button>点击</button>
    </div>
</body>
<script>
    // 事件捕获
    // 事件触发后,可以由上级元素先处理事件,这个过程就是捕获事件

    document.querySelector('button').addEventListener('click', () => {
        console.log('button');
    })

    document.querySelector('div').addEventListener('click', () => {
        console.log('div');
    })

    // addEventListener 的第三个参数代表是否捕获事件
    // 捕获事件的元素会先处理事件,然后将事件对象还给产生事件的元素,然后正常冒泡
    document.body.addEventListener('click', () => {
        console.log('body');
    }, true)

</script>

阻止事件冒泡:

<body>
    <div>
        <button>点击</button>
    </div>
</body>
<script>

    document.querySelector('button').addEventListener('click', ev => {
        // 使用事件对象的 stopPropagation 来阻止事件冒泡
        // ev.stopPropagation()
        console.log('button');
    })
    document.querySelector('div').addEventListener('click', () => {
        console.log('div');
    })
    document.body.addEventListener('click', () => {
        console.log('body');
    })
</script>

屏蔽默认事件:

<style>
        .box{
            width: 100px;
            height: 100px;
            background-color: #f00;
        }
    </style>

<body>
    <!-- <form onsubmit="return false"> -->
    <form>
        <button>提交</button>
    </form>
    <!-- <div class="box" oncontextmenu="return false"></div> -->
    <div class="box"></div>
</body>
<script>

    // 方法一:使用事件对象屏蔽元素事件的默认行为
    document.querySelector('form').addEventListener('click', ev => {
        // preventDefault 屏蔽当前元素默认的事件行为
        ev.preventDefault()
        console.log('提交');
    })

    document.querySelector('.box').addEventListener('contextmenu', ev => {
        ev.preventDefault()
        console.log('右键');
    })

    // 方法二:对应事件属性 return false 也能屏蔽默认行为
</script>

事件对象的currentTarget和target:

<body>
    <div>
        <button>点击</button>
    </div>
</body>
<script>
    // 
    const div = document.querySelector('div')
    const btn = document.querySelector('button')

    // target:代表产生事件的元素
    // currentTarget:当前处理事件的元素
    // 注意 currentTarget 是变化的 可以改动
    btn.addEventListener('click', function (ev) {
        console.log('button');
        console.log(ev.target);
        console.log(ev.currentTarget);
        // this 代表当前处理该事件的元素
        console.log(this === ev.currentTarget);
    })

    div.addEventListener('click', function (ev) {
        console.log('div');
        console.log(ev.target);
        console.log(ev.currentTarget);
        // this 代表当前处理该事件的元素
        console.log(this === ev.currentTarget);
    })
</script>

通过js代码来触发事件:

<style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #f00;
        }
    </style>

<body>
    <div class="box"></div>
    <button class="btn1">调用函数触发事件</button>
    <button class="btn2">发出事件对象</button>
</body>
<script>
    // 通过js 代码来触发元素事件的方法有两个

    const box = document.querySelector('.box')
    box.addEventListener('click', ev => {
        console.log('click');
    })
    box.addEventListener('contextmenu', ev => {
        // 用 preventDefault 取消事件
        ev.preventDefault()
        console.log('menu');
    })
    document.body.addEventListener('contextmenu', ev => {
        console.log('body contextmenu');
    })

    // 方法一:通过对应事件名的函数进行触发
    // 缺点:不是所有事件都有对应的方法来触发
    document.querySelector('.btn1').addEventListener('click', ev => {
        console.dir(box)
        // 调用 click 来触发点击事件
        box.click()
    })



    // 方法二:发出事件对象
    // 步骤:
    // 1.创建事件对象
    // 2.调用要触发该事件的 dom 的 dispatchEvent 方法
    document.querySelector('.btn2').addEventListener('click', ev => {
        // 事件对象Event的文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Event/Event
        // 1.创建事件对象
        // 第一个参数:事件名
        // 第二个参数:配置对象
        let event = new Event('contextmenu', {
            // 配置项如下
            // 是否允许事件冒泡
            bubbles: false,
            // 是否可以取消事件
            cancelable: true
        })

        // 2.调用要接受该事件的 dom 的 dispatchEvent 方法
        // dispatchEvent 用来派发一个事件
        // 返回值是个bool值,true 代表事件没被取消,false 代表事件被取消(调用了 preventDefault)
        let r = box.dispatchEvent(event)
        console.log(r);
    })
</script>

自定义事件对象:

<style>
        .box {
            width: 100px;
            height: 100px;
            background-color: #f00;
        }
    </style>

<body>
    <div class="box"></div>
    <button>发出自定义事件</button>
</body>
<script>
    // 发出自定义事件的方法:
    // 1.给需要接受自定义事件的元素绑定自定义事件
    // 2.创建一个自定义事件
    // 3.调用需要接收自定义事件的 dom 的 dispatchEvent


    // 1.给需要接受自定义事件的元素绑定自定义事件
    document.body.addEventListener('open-my-eyes', ev => {
        console.log('body my eyes open');
        console.log(ev);
    })
    const box = document.querySelector('.box')
    box.addEventListener('open-my-eyes', ev => {
        ev.preventDefault()
        console.log('box my eyes open');
        console.log(ev);
    })

    document.querySelector('button').addEventListener('click', ev => {
        // 2.创建一个自定义事件
        // CustomEvent 参数和 Event 的参数基本相同
        const ce = new CustomEvent('open-my-eyes', {
            bubbles: false,
            cancelable: false,
            // 自定义的事件参数
            // 该参数可以传递任意内容
            detail: { name: '张三' }
        })


        // 3.调用需要接收自定义事件的 dom 的 dispatchEvent
        let r = box.dispatchEvent(ce)
        console.log(r);
    })
</script>

给div元素增加keydown和focus事件:

<style>
        div {
            width: 500px;
            height: 500px;
            background-color: gold;
        }
    </style>

<body>
    <!-- 在div上加入一个tabindex,可以让div成为一个表单元素 -->
    <!-- tabindex 可以指定使用tab按键切换焦点元素时的顺序,顺序由大到小排列 -->
    <div tabindex="0"></div>
    <input tabindex="1">
</body>

<script>
    let div = document.querySelector('div')
    div.addEventListener('keydown', ev => {
        console.log('keydown')
    }, true)

    div.addEventListener('focus', ev => {
        console.log('focus')
    })
</script>

异常处理:

基本吃力方法和异常的特点:

<script>
    // 异常处理

    // 什么是异常?当程序运行时,运行不下去了,碰到了问题,该问题就是一个异常
    // 异常的特点:当程序抛出异常后,代码将停止运行

    // 什么是异常处理?当程序出现异常时,开发人员进行的一个手动处理
    // 异常处理的方法叫做捕获异常

    // 只有运行时异常(runtime error)需要进行捕获
    // 运行时异常:程序稳定运行时,可能出现的异常(例如用户输入)


    // 异常演示如下:

    function fn1() {
        fn2()
    }

    function fn2() {
        // 当程序遇到问题时,由js引擎自动创建一个异常对象,从函数内向外抛出
        // 异常出现在函数中
        console.log(obj);
    }

    // 异常抛出到调用栈的顶部之后,还没人处理,则异常将跑到控制台,程序将终止运行
    // fn1()


    // 捕获异常如下:

    // 通过 try catch 捕获异常,手动排除异常,并让程序能够继续运行
    // 语法如下:
    // try {
    //     // 代码块:该代码块中可以写入你想要尝试运行的代码
    // } catch (e) { // e:该参数是一个 Error 对象
    // // 当 try 代码块中的荣抛出异常时,将触发 catch 代码块的内容
    //     // 代码块:对异常进行处理的逻辑写在这里
    // }
    // 注意:try catch 代码块 必须同时存在

    // try {
    //     // 在try中执行可能会有异常的代码
    //     fn1()
    // } catch (e) {
    //     // Error 对象包含两个常用属性
    //     // message:异常消息
    //     console.log(e.message);

    //     // stack:调用栈的信息
    //     // 调用栈可以用来查看异常程序整个调用的流程,便于找到异常位置
    //     console.log(e.stack);
    // }

    // 被处理后的异常不会导致程序终止
    // 程序会继续执行
    // console.log('hello world');

    // Error : 异常对象
    // 当程序出现了系统异常时,程序会自动抛出一个 Error 对象
    // 可以手动创建异常对象

    // 这是个除法函数
    function div(x, y) {
        if (y === 0) {
            // // 除数不能为零,所以此处我们手动抛出异常
            // // 1.创建异常对象
            // let error = new Error('除数不能为0')
            // // 2.抛出异常
            // // 抛出异常后程序将终止运行
            // throw error

            throw new Error('除数不能为0')
        }
        return x / y
    }

    try {
        div(2, 0)
    } catch (e) {
        console.error(e);
    }

    console.log('hello world');
</script>

finally:

<script>
    // 什么是finally?
    // finally 是 try catch 之后的一个代码块,作用是 无论 try 中是否出现异常,finally 中的代码都会执行


    function div(x, y) {
        if (y === 0) {
            // 除数不能为零,所以此处我们手动抛出异常
            // 1.创建异常对象
            let error = new Error('除数不能为0')
            // 2.抛出异常
            // 抛出异常后程序将终止运行
            throw error
        }
        return x / y
    }

    try {
        let r = div(3, 0)
        console.log(r);
    } catch (e) {
        console.error(e);
    } finally {
        // try catch 后 最终一定会执行的程序
        console.log('无论try执行成功还是抛出异常,都会执行此处的代码');
    }
</script>

自定义异常对象:

<script>
    // 什么是自定义异常?
    // 开发人员自己创建的异常类就是自定义异常
    // 作用:给异常分类,告诉开发人员哪些异常是需要处理的,哪些是系统异常不需要处理

    function div(x, y) {
        // 参数验证
        // if (isNaN(x) || isNaN(y)) throw new Error('参数不是数字')
        if (isNaN(x) || isNaN(y)) throw new NaNError()
        // if (y === 0) throw new Error('除数不能为0')
        if (y === 0) throw new ArgZeroError()
        return x / y
    }

    // 自定义异常类
    // 参数不是数字异常
    class NaNError extends Error {
        // 使用 new 关键字创建类实例时,调用 constructor 构造函数
        constructor() {
            // 父类构造函数
            super('参数不是数字')
        }
    }

    class ArgZeroError extends Error {
        constructor() {
            super('除数不能为0')
        }
    }

    try {
        div('abc', 0)
    } catch (e) {
        // 处理异常

        // 判断异常类型 分别进行处理
        // n instanceof m  含义为:n 是否是 m 类型的实例
        if (e instanceof NaNError) {
            console.error('参数异常')
        } else if (e instanceof ArgZeroError) {
            console.error('参数为0');
        } else {
            console.error(e);
        }
    }
</script>

12.面向对象编程

对象:

<script>
    // 什么是对象,js用属性和行为来描述某个物体而产生的一种数据结构,该数据成为对象
    // 声明对象

    // 设计一个对象最重要的就是设计它的属性和行为
    // 属性:都是一些值
    // 行为:都是一些函数
    let duck = {
        // 设计对象属性如下
        color: '#0f0',
        age: 2,
        name: '和昊',

        // 对象行为
        // 对象的行为一般称为方法
        swim() {
            console.log(`鸭桑${this.name}在游泳`);
        },
        shout() {
            console.log('笑啦');
        },
        fly() {
            console.log('极巨飞冲');
        }
    }

    // 调用对象属性和行为
    console.log(duck.name);
    duck.fly()
</script>

类和实例对象:

<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
        }

        // 行为
        drive() {
            console.log(`${this.producer}产的 价值 ${this.value} 的 ${this.name} 在飞驰`);
        }
    }




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

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

    // 修改实例属性
    zhangSan.age = 24
    console.log(zhangSan);

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

    let car = new Car(2100000, 'Nissan', 'GTR')
    console.log(car);
    console.log(car.value);
    car.drive()


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


    // 访问实例属性和行为


</script>

判断对象是否包含某个key:

<script>
    let obj = { a: 1, b: 2, c: '' }

    // 方法一:使用 boolean 值的隐式转换
    if (obj.a) {
        // 若存在就执行if里的代码块
        console.log('ok');
    }

    // 甚至可以用来判断对象是否存在
    if (obj) {
        console.log('存在');
    }
    // obj = null
    // obj = undefined
    // if (obj) {
    //     console.log('存在');
    // }

    // 上述方法的缺点是:若 c 的值是空字符串,0或者 false 都会被认为“c 不存在”,然而 c 这个 key 是存在的
    if (obj.c) {
        console.log('c 存在');
    }

    // 方法二:使用 in 关键字判断

    // 判断 c 这个 key 是否存在于 obj 对象中
    if ('c' in obj) {
        console.log('c 存在');
    }

    // 方法三:使用 Reflect 反射

    if (Reflect.has(obj, 'c')) {
        console.log('c 存在');
    }


    // 方法四:直接通过类型判断是否存在
    if (typeof obj.c === 'string') {
        console.log('c 存在');
    }

    // 方法四的缺点是:必须要预先知道 c 的类型才可以进行检测

    // 直接判断不是 null 和 undefined
    if (typeof obj.c !== 'null' && typeof obj.c !== 'undefined') {
        console.log('c 存在');
    }



    // 总结:
    // 1.绝对不会错的用法是 方法二 和 方法三
    // 2.快速开发使用 方法一 但要注意其缺点
</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);
        }

        // 私有方法
        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
        }

        drive() {
            console.log(`${this.producer}产的 价值 ${this.value} 的 ${this.name} 在飞驰`);
            console.log(this.#engine);
            this.#error()
        }

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

    let zhangSan = new Human('张三', 17, 'male')
    console.log(zhangSan.hobbie);
    // zhangSan.eat('东西')
    // zhangSan.fn()

    let car = new Car(1000, 'ok', 'good')
    // console.log(car.#engine);
    car.drive()
    // car.#error() // 编译未通过

</script>

通过面向对象渲染一个表:

<style>
        .clock {
            border: 5px solid #000;
            border-radius: 10px;
            padding: 8px;
            display: inline-block;
        }

        .red {
            color: #f00;
        }

        .green {
            color: #0f0;
        }

        .blue {
            color: #00f;
        }
    </style>

<body>
    <!-- html 模板 -->
    <!-- <div class="clock">
        <span class="red">23</span>:<span class="green">59</span>:<span class="blue">46</span>
    </div> -->
</body>
<script>
    class Clock {
        hour    // 小时
        minute  // 分钟
        second  // 秒
        #timer  // 计时器id
        #el     // clock 代表的页面元素

        // 构造函数
        constructor(h = 0, m = 0, s = 0) {
            this.hour = h
            this.minute = m
            this.second = s
            // 生成页面元素
            this.#el = document.createElement('div')
            this.#el.className = 'clock'
            document.body.appendChild(this.#el)
            // 初始化渲染
            this.render()
        }

        // 修改时间
        setTime(h, m, s) {
            this.hour = h
            this.minute = m
            this.second = s
        }

        // 开始计时
        start() {
            if (this.#timer) return
            // 计时的时候进行初始渲染
            this.render()
            this.#timer = setInterval(() => {
                // 执行计时逻辑
                this.second++
                // 进位判断
                if (this.second >= 60) {
                    this.minute++
                    this.second = 0
                    if (this.minute >= 60) {
                        this.hour++
                        this.minute = 0
                        if (this.hour >= 24) {
                            this.hour = 0
                        }
                    }
                }
                // 渲染
                this.render()
            }, 1000)
        }

        // 停止计时
        stop() {
            clearInterval(this.#timer)
            this.#timer = undefined
        }

        // 渲染函数
        // 用于显示该实例对象
        render() {
            // 构造表内部的html代码

            let html = `<span class="red">${this.hour < 10 ? '0' + this.hour : this.hour}</span>:<span class="green">${this.minute < 10 ? '0' + this.minute : this.minute}</span>:<span class="blue">${this.second < 10 ? '0' + this.second : this.second}</span>`

            // 插入页面
            this.#el.innerHTML = html
        }
    }

    let clock = new Clock(23, 59, 50)
    clock.start()

    let clock2 = new Clock(2, 15, 30)
    clock2.start()
</script>

13.this,super和继承

继承与原型:

<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()
    pk.name = '皮卡'
    pk.weight = 3
    console.log(pk);

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

    // 实例对象才能访问 __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>
    // 什么是静态属性和方法
    // 静态属性和方法 指的就是一类中共有的属性和方法

    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')
    human.eat('火锅')
    // 调用静态属性或方法,使用类名调用,而不是实例对象
    console.log(Human.category);
    Human.greedy()

    // function 是不能使用 static 关键字声明静态属性和方法的
    // function 类 只能通过给构造函数添加属性来充当静态属性和方法
    function Car() { }

    Car.category = '工业制品'
    Car.jiaYou = function () {
        console.log('汽车要加油');
    }
    console.log(Car.category);
    Car.jiaYou()

</script>

严格模式:

<!-- js 文件顶部开启严格模式 -->
<script src="./main.js"></script>
<script>
    // script 标签顶部开启严格模式
    'use strict'
    function fn2() {
        console.log(this);
    }
    fn2()

    // x = 12
</script>

<script>
    let pi = 3.14
    // with (Math) {
    //     console.log(floor(pi));
    // }

    function fn3() {
        console.log(this);
    }
    function fn4() {
        // 函数的第一行开启严格模式
        'use strict'
        console.log(this);
    }
</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>

instanceof:

<script>
    // instanceof 用于检测某个对象是否是某个类型的实例
    // 语法:n instanceof M
    // 含义:n 是否是 M 类的实例,如果是 则表达式返回 true


     // 鸟
     class Bird {
        name = '鸟'

        constructor(name) {
            this.name = name
        }

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

    // 鸭子
    class Duck extends Bird {
        color

        constructor(name, color) {
            super(name)
            this.color = color
        }

        fly(){
            console.log(`${this.name} 在飞`);
            console.log(super.name);
            super.fly()
        }
    }

    let bird = new Bird()
    console.log(bird instanceof Bird);
    let duck = new Duck()
    console.log(duck instanceof Bird);
    console.log(bird instanceof Duck);

    // 结论:
    // 实例对象可以是其类型和父类类型的实例
</script>

lambda表达式中的this:

<body>
    <button my-data="这是我的自定义属性">点击</button>
</body>
<script>
    // 结论
    // 1.lambda 表达式没有 this
    // 2.lambda 表达式中的 this 来自于声明函数时上下文中的 this
    // 作用:
    // 利用好 lambda 表达式没有 this 的特点,灵活的使用 this 获取想要的上下文中的内容
    // 后面有例子

    console.log(this);

    let fn = () => {
        'use strict'
        console.log(this);
    }
    fn()

    let obj = {
        fn: () => {
            console.log(this);
        }
    }
    obj.fn()

    class A {
        name = '张三 '
        fn() {
            console.log(this);

            const fn2 = () => {
                console.log(this);
            }
            fn2()
        }
    }
    let a = new A()
    a.fn()

    // lambda 表达式中 this 用处:
    // 让回调函数能够访问实例对象

    class B {
        fn() {
            // 有时在实例对象的方法中可能会调用一个需要传入回调函数的函数
            // 例如此处的 custom

            // 假设希望在 custom 的回调函数中 调用 play 方法 该怎么办?

            // 方法一:
            // 使用 function 声明回调函数
            // 在调用 custom 之前用变量 that 存储B实例 this

            // let that = this

            // custom(function () {
            //     that.play()
            // })

            // 方法二:
            // 使用 lambda 表达式声明回调函数,则 lambda 表达式内 this 就是B实例
            custom(() => {
                this.play()
            })
        }

        play() {
            console.log('B在玩');
        }
    }
    function custom(callback) {
        callback()
    }

    let b = new B()
    b.fn()

</script>

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>

this关键字:

<body>
    <button class="btn">click me</button>
</body>
<!-- <script>
    // 参考

    // this 关键字代表什么?

    // 根据不同的场景,this 关键字代表东西不同,有以下几种情况

    // js 执行上下文中的 this
    console.log(this);  // => window
    console.log(this === window);

    // 函数内的 this
    function fn() {
        // 非严格模式下 函数内 this => window
        console.log(this);
    }
    fn()

    // json 对象方法的 this
    let obj = {
        fn() {
            console.log(this);  // => 对象自己
        }
    }
    obj.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 始终是自己


    // 声明变量存储 A 类的 fn 方法,再次调用并观察 this
    // 给变量赋值对象方法,默认情况下 this 为 undefined
    const fnVar = a.fn
    fnVar() // => undefined

    // 给变量赋值函数值,默认情况下 this 为 undefined
    const fn2 = function () {
        console.log(this);
    }


    // 有些情况下,回调函数中的 this 可能被其他函数赋值,所以 this 不是 undefined
    // 此处 事件回调函数被 addEventListener 赋值了其中的 this
    // 所以这里的 this 指的是绑定事件的那个 dom 对象
    document.querySelector('.btn').addEventListener('click', function () {
        console.log(this);
    })

    // 自定义函数也可以修改回调函数内的 this 指代
    function custom(callback) {
        // call apply bind
        callback.call('hello world')
    }

    custom(function () { console.log(this); })

    // 总结:
    // 1.js 执行上下文 => window
    // 2.函数内 this => window(函数 => window)
    // 3.方法内的 this 指向调用方法的对象 (方法 => 实例)
    // 4.this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined
</script> -->

<script>
    'use strict'

    // 结论:
    // 1.js 执行上下文 => window
    // 2.只要是函数 this 就是 undefined (函数 => undefined)
    // 3.方法内的 this 指向调用方法的对象 (方法 => 实例)
    // 4.this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined

    // js 执行上下文中的 this
    console.log(this);  // => window
    console.log(this === window);

    // 函数内的 this
    function fn() {
        // 严格模式下 函数内 this => undefined
        console.log(this);
    }
    fn()

    // json 对象方法的 this
    let obj = {
        fn() {
            console.log(this);  // => 对象自己
        }
    }
    obj.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 始终是自己


    // 声明变量存储 A 类的 fn 方法,再次调用并观察 this
    // 给变量赋值对象方法,默认情况下 this 为 undefined
    const fnVar = a.fn
    fnVar() // => undefined

    // 给变量赋值函数值,默认情况下 this 为 undefined
    const fn2 = function () {
        console.log(this);
    }


    // 有些情况下,回调函数中的 this 可能被其他函数赋值,所以 this 不是 undefined
    // 此处 事件回调函数被 addEventListener 赋值了其中的 this
    // 所以这里的 this 指的是绑定事件的那个 dom 对象
    document.querySelector('.btn').addEventListener('click', function () {
        console.log(this);
    })

    // 自定义函数也可以修改回调函数内的 this 指代
    function custom(callback) {
        // call apply bind
        callback.call('hello world')
    }

    custom(function () { console.log(this); })
</script>

14.Object类方法和访问器:

delete关键字:

<script>
    // 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);

</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>

访问器getter和setter:

<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>

包装类:

<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>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值