华清远见重庆中心——JavaScript基础阶段技术总汇

JavaScript

JavaScript简介

javascript 是什么

java: 类似java计算机编程语言

script: 脚本

类似java计算机编程语言的脚本语言,缩写为js

java 和 javascript 的区别

  • java 静态编译,js 作为脚本动态编译

  • java 是强类型语言,js 是弱类型语言

javascript 的作用

提供了一个和html进行动态交互的功能

javascript安装与使用

安装

js 运行只需要一个js运行环境即可,那么最常见的js运行环境是浏览器。所以只需要安装浏览器即可。

使用

  • 在浏览器控制台上直接运行js语言片段

  • 在html文件的script标签中

  • 使用.js文件书写js代码,然后再html中通过script标签引入代码

js语法简介

js语法名称为:ECMA Script

Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)

ECMA Script 缩写为 ES

常见说法ES5、ES6,后面的数字,代表的是语法版本号

什么是API

API 是 Application Programming Interface 的缩写,翻译过来就是 “应用程序编程接口”

API 是提供一些用于编程工具,对于 js 来说,API 可以是某框架提供的变量或函数,也可以是网络上的一个可访问的url地址,称为网络接口

API 既然是编程工具,那么其作用是通过调用 API 来实现一个功能,例如:排序,查询,添加数据等操作

由于 JS API 数量众多,课程中将每日介绍一些常用的 API

js运行原理

参考文献:

深入:微任务与 Javascript 运行时环境 - Web API 接口参考 | MDN

并发模型与事件循环 - JavaScript | MDN

https://dmitripavlutin.com/javascript-promises-settimeout/

js 执行上下文(execution context)

执行js的内存区域

3种地方会创建执行上下文

  1. 全局执行上下文: js程序一开始运行就创建的上下文 (页面 script 标签内的内容就是再全局执行上下文上运行的)

  2. 函数执行上下文: 函数运行时会创建一个用于运行函数的执行上下文

  3. eval(): eval() 函数调用时,其内部字符串被当作脚本执行时,会创建执行上下文

js 运行时(runtime)

用来调度和维护任务,处理事件循环的地方,一个js运行时,包括以下几个部分

  1. 执行上下文栈

  2. 执行上下文堆

  3. 消息队列

1. 任务队列(宏任务队列)

2. 微任务队列

可视化表达为:

事件循环

由js运行时处理的事件或其他任务,都会被加入到队列中,任务不断的被加入队列,从队列取出执行,又加入队列,又被取出执行的过程称为事件循环

类似如下代码:

while(waitForMessage()) {

    processMessage()

}

注意: 队列的执行顺序始终是 先进先出

任务队列 (task queue)

也叫 宏任务队列

该队列中的任务将在一个事件循环周期开始时被调用

事件循环周期开始后,再被加入的任务,将不会在这一个周期内被调用

哪些方法可以创建宏任务呢?

例如: setTimeoutsetInterval 创建的任务将被加入该队列

微任务队列 (microtasks queue)

该队列中的微任务将在一个宏任务执行结束后,依次执行队列中的所有微任务

并且微任务队列没有执行完的情况下,若再加入微任务,该微任务依然会被执行,微任务队列的执行直到队列中所有任务全部完成为止

哪些方法可以创建微任务呢?

例如: Promiseresolve 函数调用时,会创建一个微任务

javaScript代码执行顺序

  • 有多个script,从上而下依顺序执行

  • 同一个script标签中,顺序:从上到下,从左到右,每句话用分号隔开

  • 代码中只有一句js,末尾分号可以省略

  • 赋值运算顺序,
    • 先执行赋值符右侧代码,再赋值给左侧
  • 例子

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

引入js代码

  • 使用script标签引入外部js文件 src为文件路径

  • script标签写在body后面

变量

什么是变量

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

关键字

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

变量的命名规范

  • 必须以字母开头,或者使用  $ 或者 _  开头  ,不能用数字,变量名建议不要使用中文

  • 小驼峰命名法:  多个单词构成的名字,第一个单词全小写,后面的首字母大写

  • 为了提高代码的可读性,命名规范,见名知意

  • 不能使用关键字(有特殊功能的名字:class、var、this)

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

声明变量var

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

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

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

    var b = 5;

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

    var c, d = 10, e;

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

声明变量let

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

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

常量

// 常量

// 恒定不变的值为常量

// 声明时必须赋初始值 且声明后无法修改

const g = 10

const pi = 3.1415926

// 访问变量或常量

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

json对象

// 声明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);

数据类型

数据类型是什么

js中对不同种类数据的一个类型分类

数据类型分类

7种原始类型
  • undefined

  • 0Boolean

  • Number

  • String

  • BigInt

  • Symbol

  • null

最后一种类型

Object

数据按照参数的传递方式分为

  • 值传递类型

  • 引用传递

typeof ----用于判断数据类型

let obj = {}



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

按照值类型

  • Boolean 布尔型

  • Number 数字型

  • String 字符串型

  • BigInt 长整数型

引用类型

  • Object 对象类型

  • Symbol 符号类型

undefined 未定义

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

null 空引用

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

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

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

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

数据类型详细

1- Undefined 未定义类型

一个变量声明后不赋值,则他的值为undefined

未定义类型中只有一个值为 undefined

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

    a = 123

    // 赋值 undefined
    a = undefined

    console.log(a);

    console.log(typeof a); // => undefined
2- Boolean 布尔类型

用于描述数据对错真假,只有两个值 ture 真;false假

let a = true

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

3-Number 数字类型

数字类型用于描述数字

  • 其中Infinity 无限大的数字

    •  // 代表无限大的数字
          a = Infinity
          console.log(typeof a);
      
          console.log(123 / 0);
      
  • NaN 代表不是数(not a number)

    •  a = NaN
          console.log(typeof a);
      
          console.log('abc' / 123); // 运算结果不是数字 所以显示 NaN
      
          // 有时需要判断一个值是否是NaN
          // 可以使用以下方法
          console.log(isNaN(a));
      
  • 浮点数 a=0.618

  • 科学计数法

    • a=3e+3 a=3e3 //300

    • a=3e-3 //0.03

    • 单双精度浮点数是什么意思

      • 单精度浮点数是32位的小数

      • 双精度浮点数是64位的小数

4- String 字符串类型

将很多的字符串起来就是字符串

  • 可以使用单双引号来定义字符串

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

    • 转变字符原有的含义

    • 转义字符:使用反斜杠\进行转义后的字符

    • 常见转义字符:\n 换行 \t制表符(差不多tab宽的空格)

      • 单引号字符串显示单引号的方法

          str = 'abc\'d'
        
      • 双引号字符串显示双引号的方法

         str = "abc\"d"
        
      • 通常可以在双引号中直接写单引号,单引号字符串中直接写双引号

         str = 'abc"d'
            console.log(str);
            str = "abc'd"
            console.log(str);
        
      • 两个值通过加法运算符连接,任意一个值为字符串,则进行拼接字符串

        let a = 'hello', b = 123
            console.log(a + b);
        

5- BigInt 长整数类型

用来描述一个位数特别长的整型数字

let a = 1n;
    console.log(a);
    console.log(typeof a); // => bigint
bigint 类型只能和另一个bigint类型的数进行运算

6- 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 {
        text = 'hello world'
        #msg = '被你发现了'
        #symbol = Symbol()

        constructor() {
            this[this.#symbol] = function () {
                console.log('这是个匿名函数');
            }
        }

        test() {
            console.log(this.#msg);
            console.log(this[Symbol.for('add')](1, 2));
            this[this.#symbol]()
        }

        // Symbol.for Symbol.keyFor 是访问的全局Symbol注册表
        [Symbol.for('add')](x, y) {
            return x + y
        }
    }

    let a = new A()
    for (const key in a) {
        console.log(key);
    }

7- null 空类型

唯一值就是null

null的含义:用来代表对象类型的空引用(空指针)
let a = null

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

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

8-Object类型

对象是一组数据的集合

所以能够被构造的(包含constructor构造器)类型都是对象

所以函数 数组 本质也是对象

朴素对象(plain object)也叫json对象

 let obj = {
        name: '张三',
        sex: 'male',
        age: 17
    }
  • json:是js轻量化数据结构

  • json文件特点

    • json文件中,根节点只能有一组{}花括号或一组[]方括号

    • 属性key必须有一组双引号包裹 value若是字符串,必须是双引号

    • 每个属性由逗号隔开,最后一个属性后面的逗号必须省略

  • 如何声明一个json对象

    • 直接用json格式

      // 1. 直接使用json格式
          let user = {
              "name": "张三",
              "sex": "male",
              // 可以使用字符串去定义key
              'age @!*dfksjf': 17
          }
      
    • key不包含空白符和其他特殊符号时,可以不加引号

      // 2. key 不包含空白符和其他特殊符号时 可以不加引号
          user = {
              // 字符串可以不用双引号
              name: '张三',
              sex: 'male',
              age: 17
          }
      
    • 变量名若和对象的属性名相同,可以省略

       let name = '法外狂徒男枪'
          let sex = 'other'
          let age = 17
          // 3. 变量名若和对象的属性名相同,则可以省略 如下:
          user = {
              // 完整写法
              // name: name,
              name,
              sex,
              age
          }
      
  • 和函数都属于对象类型

 // 数组和函数都属于对象类型
    let arr = [1, 2, 3]
    function fn() { }

    console.log(typeof arr);
    console.log(typeof fn); // => function 虽然 typeof 检测函数结果为 function 但由于函数包含 constructor 所以函数本质上是对象类型

    // 学习面响对象的时候再介绍类实例

对象的分类

js中有多种方式可以创建对象,不同的对象从含义,称呼到作用,有略微的区别

除此之外,对象的访问和对象属性的访问方法都是相同的

json 对象

json(JavaScript Object Notation, JS 对象简谱) : 轻量化js数据结构

为什么使用json对象,作用是什么?json 对象主要用于存储数据,有着简洁的数据结构,正由于这样的特点,json 对象常用于网络信息传输中,传递数据

实例对象

实例的概念来自于面向对象编程,计算机看待整个世界时会把所有的东西,抽象成类(class),和类型中的某个个例,这个个例称为实例,例如:人可以抽象成人类,某个具体的人,例如张三,就是一个实例。

通过 class 创建出来的对象,称为实例对象(也叫类实例)

dom 和 bom 对象

dom(document object model) 和 bom (bowser object model) 对象,是浏览器中,使用js时,浏览器提供的两组内置对象,dom 对象对应 html 文档中的标签,bom 对象对应的是浏览器的一些工具

还有一些框架的特殊对象

有些框架会创建出一些特殊的对象,具有特殊功能和名称,例如 jquery 对象,vue 对象等等

数据类型的转换

  • 概念
    • 不同类型之间的数据是可以相互转换类型的
  • 数据转换的方法
    • 显示转换

      • 在代码中有明确的代码用于转换类型
    • 隐式转换

      • 在代码中没有明确的代码用于转换类型
boolean类型转换成其他类型
  • 转换成数字 —ture为1,false为0

     let b = true
        // 转换数字
        b = Number(b)
        // 除了 Number 以外 还能使用 parseInt parseFloat 进行转换
        console.log(b); // 
        b = false
        b = Number(b)
        console.log(b) // => 0
    
        // true 转换数字为 1 false 转换数字为 0=> 1
    
  • 转换成字符串 — 用+连接字符串时,js隐式转换为字符串

     b = true
        console.log(b);
        b = String(b)
        console.log(b); // => true 的字符串
        b = false
        b = b + '' // 连接字符串时,js会自动转换数据格式为字符串,这是一个隐式转换
        console.log(b); // => false 的字符串
    
Number类型转换成其他型
  • 转boolean —数字为0结果为false,否则为true

     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(1314)
        console.log(num); // => 1314
    
String字符串转其他类型
  • 转boolean -----字符串为false,其他为true

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

    • —加法运算符直接写在前面可以产生转换成数字;

    • —若字符串代表的是合法数字转换成数字,否则为NaN

 // 转数字
    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);

    // 若字符串代表的是合法数字则转换为对应数字 否则为 NaN
Object对象转其他类型
  • 转boolean类型

    • 给对象赋值为null或者undefined 转换成boolean的值都为false,对象只要存在就为ture
     let obj = { a: 1, b: 2 }
        // 转 boolean 值
        obj = Boolean(obj)
        console.log(obj); // => ture
      obj = null // 此处赋值 undefined 效果一样
        obj = Boolean(obj)
        console.log(obj); // => false
    
        // 变量是 null 或 undefined 时 转换结果为 false;对象只
     要存在 结果就为 true
    
  • 转数字 ----为NaN

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

    • 对象强转字符串结果始终为 [object Object]

    • 若希望将对象转换成字符串后能够查看其数据,则可以使

        // 转字符串
        obj = { a: 1, b: 2 }
        obj = String(obj)
        console.log(obj); // [object Object]
        // 对象强转字符串结果始终为 [object Object]
      
        // 若希望将对象转换成字符串后能够查看其数据,则可以使用 “序列化”
      

序列化与反序列化

  • 序列化 -------JSON.stringify()
    • 将对象转换成字符串 JSON.stringify()
  • 反序列化 -----JSON.parse()
    • 将字符串转换成对象

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

Reflect反射

反射是用来操作对象的方法

文档: Reflect - JavaScript | MDN

给对象添加属性

  • obj.c=3 ----给obj添加属性值为3的属性c

  • obj[‘e’]=4 —给obj添加属性值为4的属性e

给对象定义属性
Object.defineProperty(obj,‘需要定义的属性名’,{value:3})
// Object.defineProperty(obj, 'c', {
    //     // 属性值
    //     value: 3,
    //     // 是否可写
    //     writable: false
    // })
Reflect.defineProperty(obj,‘c’,{value:5})
  • 第一个参数:要定义属性的对象

  • 第二个参数:要定义的属性名称

  • 第三个参数:配置对象

      Reflect.defineProperty(obj, 'c', {
            value: 5,
            writable: true
        })
    

删除属性

  • delete obj.a

  • delete obj[‘b’]

  • Reflect.deleteProperty(obj,‘a’)
    • 第一个参数:要删除属性的对象

    • 第二个参数,要删除的属性名称

返回对象的所有key数组

Reflect.ownKeys(obj)
  • 参数是对象

判断是否存在某属性

  • Reflect.has(obj,‘c’)
    • 第一个参数:要判断属性是否存在的对象

    • 第二个参数:要判断是否存在的属性名

  • 使用if的隐式转换

     使用if的隐式转换
        if(obj.e) { // obj.e 若等于以下值: null undefined '' 0 ==> false  其余情况为 true
            // if 语句将隐式转换数据为 boolean 值
            // 存在e
            console.log('e存在');
        }
    
  • 使用typeof

    console.log(typeof obj.e)

浏览器操作

bom和dom

dom

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

bom

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

window

打开窗口

window.open(‘网址’,‘iframe名称,没有就打开新窗口’)

// 打开窗口
    // 第一个参数:网址
    // 第二个参数:iframe名称,没有的话就打开新窗口
    // window.open('./child.html', 'child')
关闭窗口

window.close()

窗口间的通信
原理:
  • 通过窗口对象(window)发送消息(postmessage)

  • 另一个窗口通过message事件来接收消息

  • 消息: 发送

location

跳转网页
  • 给location.href赋值就会跳转

  • let input = document.querySelector(‘input’)

     let button = document.querySelector('button')
     button.addEventListener('click', () => {
         // 给 location.href 赋值页面就会调转
         window.location.href = input.value
     })
    
跳转网页不计入历史
刷新页面

window.location.reload()

地址栏参数

location.search

history 浏览器浏览记录

前进

window.history.foward()

后退

window.history.back()

转到指定位置

history.go(deta)

  • // history.go(1) 等价于 history.forward()

  • // history.go(-1) 等价于 history.back()

localStorage

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

  • 设置数据
    • window.locaStorage.setItem(‘name’,‘张三’)

    • localStorage[‘sex’]=‘male’

  • 读取数据
    • localStorage.getItem(‘name’)

    • localStorage[‘sex’]

sessionStorage
用法和localStorage一样,但是关闭窗口数据消失
    // window.sessionStorage.setItem('age', '17')

navigator 用于查看设备信息

节点操作

查询节点

dom对象中可以查询其父节点和子节点

  • 查询元素:document.querySelector()

    // 获取tr
        let tr = document.querySelector('.tr')
    
  • 访问子节点使用 children 属性

    • let tr = document.querySelector(‘.tr’)

    • children 属性包含一个子节点的集合,所以访问具体子节点可以使用索引值

    • // children 属性包含一个子节点的集合

       // 所以访问具体子节点可以使用索引值
       let node = tr.children[1]
       node.remove()//删除
      
  • 访问父节点使用 parentElement 属性

插入节点

  • 步骤

    • 创建节点

      • document.createElement(‘标签名‘)

      • let img = document.createElement(‘img’)

         // 设置图片源
         img.src = '../img/head.png'
        
    • 插入节点到文档中

      • 1appendChild (追加的元素),追加一个子元素

      • // appendChild 追加一个子元素

         // 参数是要追加的元素
         document.body.appendChild(img)
        
+ 2 insertBefore (要插入的元素,插入位置的子节点)

  插入节点到另一个节点前

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

删除节点 remove()

  • 删除元素
    // 节点的remove可以删除元素
    // li.remove()
  • removeChild 删除子节点

    // removeChild 删除子节点
        // 参数是想要删除的子节点dom对象
        ul.removeChild(li)
    
  • 动态加载一次性js脚本资源

    • 下载按钮

      // 用途:

        // 动态加载一次性js脚本资源,加载完后就可以删除元素了
      
        // 例如:下载按钮
        btn.addEventListener('click', () => {
            // 动态创建a标签并点击它 来实现下载
            let a = document.createElement('a')
            // download 可以指定下载后的文件名
            a.download = '1.png'
            // 下载文件的地址
            a.href = '../img/head.png'
            // 无需插入页面即可点击
            a.click()
            // 若a标签以插入页面则需要调用 remove 来删除
            a.remove()
        })
      

替换节点parent.replaceChild(newNode,child)

  • parent.replaceChild(newNode, child)

    parent: 要替换节点的父节点

    newNode: 新的要插入文档的节点

    child: 被替换的节点

  • // 构造一个新的元素用于替换

     let td = document.createElement('td')
     td.textContent = 'hello world'
    
     tr.replaceChild(td, _td)
    

下载文件

<body>
    <!-- 点击下载按钮,动态下载文件 -->
    <button>下载</button>

    <!-- a标签中 href填入要下载的文件路径即可,可以是网络路径 -->
    <!-- <a download="1.png" href="../img/head.png">head.png</a> -->
</body>

<script>
    // 下载文件的实现方式,实际上就是使用 <a> 标签

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

        // 创建 a 标签
        let a = document.createElement('a')
        // 设置下载文件的名称
        a.download = '1.png'
        // 给 a 标签的 href 属性赋值
        a.href = '../img/head.png'
        // 使用js代码触发a标签的点击事件
        a.click()
    })
</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>

运算符

运算符

算术运算符
算术运算符:+ - * / % **(幂运算)
加减乘除
  • // 加减乘除符号

     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);
注意
  • 算术运算符遵循数学的符合优先级

  • 可以使用圆括号分组

赋值运算符
+= 自增
  • a+=2 等价 a=a+2
-=自减
  • a -= 5 // 等价于 a = a - 5
*= 自乘
  • a *= 5 // 等价于 a = a * 5
/=自除
  • a/=2 等价于 a=a/2
%=自模

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

++自增
–自减
自增自减运算符位置的区别
  • // 运算符放前面,则先运算后引用

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

比较运算符
& 符号开头

分号结尾 的内容 我们称为 html 实体

比较运算符: >, <,>=, <=, ==, !=, =, !
比较运算符用于对运算符左右两个值进行比较
比较运算符的运算结果为boolean值,用于指示比较结果为真或者假
大于

console.log(1 > 2);

小于

console.log(1 < 2);

大于等于 小于等于

console.log(3 >= 3)

console.log(3 <= 3)

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

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

    • 参考:JavaScript 中的相等性判断 - JavaScript | MDN

    • 非严格相等判断 可以不同类型的数据拿来进行比较,所以有时可能会得到期望意外的结果,不建议使用

===严格相等 —类型和值都相等才会认为相等
对象的相等判断
  • // 由于对象类型的变量保存的是对象的引用地址

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

  •   let obj1 = { name: '张三' }
        let obj2 = { name: '张三' }
        // 由于对象类型的变量保存的是对象的引用地址
        // 所以此处两对象引用地址不同 结果为false
        console.log(obj1 === obj2);
    
!= 非严格不等于
    console.log('' != 0);
!==严格等于
     console.log('' !== 0);
逻辑运算符
  • 参考:表达式和运算符 - JavaScript | MDN

  • // 逻辑运算符:符合逻辑条件时将返回真或假

  • // bool expression 的意思是:布尔表达式

  • // 什么是布尔表达式?整个js代码运算结果是一个布尔值,这样的代码片段叫做布尔表达式

      // 举例如下:
        console.log(1 === '') // 所有比较判断都是布尔表达式
        console.log(true)
    
&&与
  • 语法:bool expression && bool expression

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

||或
  • 语法:bool expression || bool expression

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

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

     let a = 1 // => undefined
        let b = a || 5
        console.log(b);
    
  • // 短路运算的应用场景2:按条件执行语句

        let x = user.age > 20
        x && console.log('user.age 大于 20')
        user.age > 20 && console.log('user.age 大于 20')
    
!非 作用是取反
三元(目)运算符
用于判断一个表达式结果,为真时,返回结果1,为假时返回结果2
  • 语法:

    bool expression? case1: case2

  • 作用:

    主要用在给变量赋值

     let obj = {
            name: '张三',
            sex: 'other',
            age: 16
        }
    
        console.log(obj.sex === 'male' ? '男' : '女');
        // 嵌套三元运算符 在问号或冒号处可以换行
        console.log(obj.sex === 'male' ? '男' :
            obj.sex === 'female' ? '女' : '其他');
        // 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来
        console.log(obj.sex === 'male' ? '男': (obj.sex === 'female' ? '女' : '其他'));
    
运算符的优先级顺序
++ – > 算术运算符 > 比较运算符 > 逻辑运算符 === 三元运算符 > 赋值运算符

// switch ( value ) {
// case constant1:
// // code
// break;
// case constant2:
// break;
// … 可以有任意多个case语句
// default: // default 不是必须要写的
// break;
// }

let r = 6
let myCase = 4
console.log®;

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

数组和循环

数组

什么是数组?

存储一组数据的一个容器

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

    • let arr = []

    • 声明并初始化数组

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

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

  • 对象形式声明数组
    • 声明

       arr = new Array()
      
    • 声明并初始化

      arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)
      
访问数组成员
  • 使用索引值来访问数组中的成员

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

    • 访问数组成语时,若索引超出数组范围,获取的结果为undefined

      console.log(arr[‘0’]);

        console.log(arr['1']);
        // 数组索引可以不使用引号
        console.log(arr[2]);
      
给数组成员赋值

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

 arr[4] = { name: '李四' }
    console.log(arr);

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

    arr[9] = 555
    console.log(arr);
数组长度length

数组长度指数组成员的个数

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

arr[-1] = true
    console.log(arr);
    console.log(arr.length);

多维数组

什么是多维数组

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

声明二维数组

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

    arr = [[1, 2], [3, 4], [5, 6]]
读取数组

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

 console.log(arr[0][1]);
 console.log(arr[2][0]);
声明多维数组

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

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

可以理解成三位空间中访问三位坐标系

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

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

Array.isArray来检测变量是否为数组

是数组返回true 否则返回false

let a = [1, 2]
let b = 'hello world'
console.log(Array.isArray(a));
console.log(Array.isArray(b));

数组的操作

    let arr = [1, 2, true, { name: 'Amy' }, 2]
  • push追加数据到数组末尾

    • 参数被添加的新数组成员

      arr.push('hello world')
      
  • pop从尾部取出一个成员

    • 返回值是取出的成员

      let r = arr.pop()
      console.log(r);
      
  • unshift在头部添加数据

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

       arr.unshift('my best day')
       console.log(arr);
      
  • shift从头部取出一个成员

    • 返回值是取出的成员

      r = arr.shift()
      console.log(r);
      
push和unshift可以批量添加成员
 arr.push('a', 'b', 'c')
 arr.unshift('x', 'y', 'z')
  • splice 删除指定位置的成员,并使用新成员替换,或插入新成员到指定的成员的前面

    • 第一个参数:删除指定成员的起始位置

    • 第二个参数,删除成员的个数

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

    • 删除一个成员

       r = arr.splice(6, 3)
      
      • splice的返回值,就是被删除的成员数组
    • 在指定成员追加数据

      • 若第二个参数为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)
      
  • join 使数组成员用一个字符连接起来

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

    • arr2 = [‘abc’, ‘xyz’, ‘123’]
      r = arr2.join(‘-*-’)
      console.log®;
  • includes判断是否包含某成员

     r = arr.includes('z')
      console.log(r);
    
  • indexOf 查询指定数组成员的索引

    •  r = arr.indexOf('b')
      
    • lastIndexOf 查询最后一个指定数组成员的索引 (因为数组成员可能重复)

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

    • r = arr.indexOf(‘g’)

       console.log(r); // => -1
       if (arr.indexOf('g') === -1) {
           console.log('该数组成员不存在');
       }
      
  • slice 数组切片 获取子数组

    切片”遵循前截后不截“,即数学中左闭区间右开区间

    • r = arr.slice(5, 8)

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

    • r = arr.slice(5)
      console.log®;

数组的迭代操作

以下所有的遍历函数的参数都是相同的回调函数
回调函数接收以下参数
  • el 被遍历的当前数组

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

  • arr 被遍历的数组对象

  • let students = [

        { id: 0, name: '张三', sex: 'male', age: 16 },
        { id: 2, name: '隔壁老王', sex: 'other', age: 30 },
        { id: 1, name: '李四', sex: 'female', age: 20 },
    ]
    
forEach 循环遍历每一个数组成员
// forEach 循环遍历每一个数组成员
    // students.forEach((el, index, arr) => {
    //     console.log(el);
    //     console.log(index)
    //     console.log(arr);
    // })
every遍历每个数组成员,(与forEach相比)但是中途可以跳出循环
 // 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 el.age
    // })

    // console.log(r);
filter过滤器

filter返回过滤完后的新数组

    // let 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 查找符合回调函数条件的数组成员并返回它

返回值为查询结果

 // let r = students.find((el, index, arr) => {
    //     // // 查找女同学
    //     // if (el.sex === 'female') {
    //     //     return true // return true 代表当前成员就是要查找的元素
    //     // }
    //     // return false

    //     return el.name === '隔壁老王'
    // })
    // console.log(r);
findIndex 查找对应成员所在索引
    // findIndex 查找对应成员所在索引
    // 使用方法和 find 相同,返回结果为查找到的成员索引
    // let 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', 'z']
    
      // let r = arr2.some((el, index, arr) => {
      //     // 判断当前数组成员是否再 arr1 中存在
      //     if (arr1.includes(el)) {
      //         // 返回 true 代表 找到一个满足条件的数组成员
      //         return true
      //     }
      //     return false
      // })
      // console.log(r);
    
sort排序
  • 若调用sort函数不传参数时,会使用浏览器默认的排序规则(从第一位开始比大小排序)

    // 若调用 sort 函数不传参数时,会使用浏览器默认的排序规则

      let numList = [90, 12, 110, 9, 40, 214] //=> 结果顺序不是想要的
      // let numList = [90, 12, 11, 92, 40, 21]//=>结果可以
      numList.sort()
      console.log(numList);
    
  • 自定义排序(冒泡排序)

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

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

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

    • 原理:

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

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

           students.sort((el1, el2) => {
              if (el1.age > el2.age) {
                  // el1.age 若大于 el2.age 则 若从小到达排列 el1 应该放到 el2 的右侧
                  // 右侧联想 右箭头 >;即大于符号,则此处应该返回一个大于零的值
                  return 1
              } else if (el1.age < el2.age) {
                  // el1.age 若小于 el2.age 则 若从小到大排列 el1 应该放到 el2 的左侧
                  // 左侧联想到 左箭头 < ;即小于符号,所以返回一个小于 0 的值
                  return -1
              } else {
                  // 若 el1 和 el2 不需要交换顺序 则返回 0
                  return 0
              }
          })
          console.log(students);
      

数组去重

  • 缓存重复数据
    • 缓存

          // 缓存
          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去重

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

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

mapRudece 统计模型

mapReduce 运算模型是拿来做大数据统计的
要统计出结果,需要经历 map 和 reduce 两个步骤
案例
let arr = [
        {
            name: '李四666',
            score: 45,
            sex: 'female'
        },
        {
            name: '张三1',
            score: 45,
            sex: 'male'
        },
        {
            name: '李四1',
            score: 89,
            sex: 'female'
        },
        {
            name: '老王1',
            score: 48,
            sex: 'male'
        },
        {
            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:统计总分和平均分
        // 2. 统计总分和平均分
        r = arr.map(el => {
            return {
                avg: 0, // 品均分
                sum: 0, // 总分
                score: el.score, // 每个同学的分数
                count: 0 // 同学的数量
            }
        })
        // 前置插入一个空对象用来充当上一次的结果
        r.unshift({
            avg: 0,
            sum: 0,
            score: 0,
            count: 0
        })
        console.log(r);
        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:按男女统计分数最低的两个人
      // 3. 按男女统计分数最低的两个人
        r = arr.map(el => {
            return {
                // 男生分数最低的人
                male: [],
                // 女生分数最低的人
                female: [],
                stu: el
            }
        })
        r.unshift({
            male: [],
            female: [],
            stu: 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: 21 }, { score: 34 }]
                // n.stu.score = 25
    
                // 将新数据加入数组
                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,
                stu: null
            }
        })
    
        console.log(r);
    

流程控制–循环

什么是循环

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

for循环

语法:

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

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

let index = 0
    // for (index; index < count; index++) {
    //     console.log('hello world');
    // }
  • 循环的继续与跳出
    • continue; 继续循环,结束本次循环,继续下一次循环

    • break;跳出循环

      // 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: '妹纸',
            age: 18
        }
    
        // key: 对象属性名称
        // for (let key in obj) {
        //     console.log(key);
        //     console.log(obj[key]);
        // }
    
for of:用于遍历数组,且不能获取索引值,只能遍历所有成员

element:数组中每个成员

for (let element of arr) {

console.log(element);

}

  • 可以使用在所有的存在迭代器的集合对象上,如: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

冒泡排序

原理
  • 以排序出一个从小到大的序列为例

    令数组长度为 lenght

    排序过程将进行 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
              }
          }
      }
    
      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
            }
        }
    }

    console.log(students);

函数

* 函数的使用

什么是函数

站在开发人员的角度讲:函数对一段程序的封装(封装:包装)

站在生活的角度讲:函数就是帮我们完成某样任务的机器

为什么要使用函数

用于包装可以重复使用的代码

代码的复用

输入input和输出output

输入:函数完成任务时必不可少的材料

输出:函数执行完成后产出的产物

定义函数
语法:function 函数名(参数列表) { // code block 代码块 }
 function sayHello() {
        console.log('hello');
    }
调用(call)函数,通过函数名进行调用,并且后面跟上圆括号

sayHello()

有参数的函数:

参数列表中声明参数,如果有多个参数,用逗号隔开

参数列表中的参数,我们称为:形式参数

返回结果:函数的返回值结果称为‘’返回值‘’

return还会终止函数内后续代码的执行

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

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

        // 函数若没有返回值(也就是没有return)那么函数默认返回 undefined
    }
调用有参参数:通过函数名调用,且调用的同时,传入真实的参数

调用函数时,圆括号中的参数叫做:实际参数

通过变量(或常量)存储函数和调用函数
声明一个sub变量来储存减法函数
  let sub = function (x, y) {
        let result = x - y
        return result
    }

    r = sub(1, 2)
    console.log(r);
定义参数时,可以给参数设置默认值

当不给函数提供参数时,参数将采用默认值

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

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

lambda表达式

什么是lambda表达式

是一种用来定义函数的方法

也成为箭头函数

定义一个无参函数

圆括号部分是参数列表

花括号部分是代码块

 const 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());

预编译(面试)

参考:JS----预编译及变量提升详解 - 掘金
预编译是什么

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

总结
哪些东西会被预编译:

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() { }
    }

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

    // let AO = {
    //     a: function a() { },
    //     b: function (x, y) { },
    //     c: 123,
    //     d: 789
    // }

    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

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

*变量的作用域

什么是变量的作用域(scope)?

作用域指的是作用范围,范围内可以访问变量,超出范围则无法访问

可以结合debugger和浏览器的堆栈信息来查看
方法:
  1. 在需要查看变量作用的地方写上 debugger

2. 浏览器上 ctrl + shift + i 或 F12

3. 选择 sources

4. 找到当前代码对应的 Call Stack

5. 查看当前 Call Stack 下的 Scope

作用域一个分为三种
  • Global全局作用域

  • Block块级作用域

  • Function作用域

全局作用域

具备全局作用域的只有全局变量或常量

全局变量指的是作用域为全局(Global)的变量,在代码任何位置都可以使用

  • 自动全局变量

    • 给未定义的变量直接赋值,该变量会变成自动全局变量,若在浏览器中,该变量会存到window对象里

          zhangSan = { name: '法外狂徒', age: 17 }
      
  • (普通的)全局变量

    • 直接在函数外声明的变量也是全局作用域的变量

      let luoXiang = { name: ‘罗老师’ }

      var laoWang = { name: '隔壁老王' }
      
块级作用域(Block)

块级作用域就是在代码块中有效的作用域

let关键字声明的变量具备块级作用域

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

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

    // console.log(a);
    console.log(other);
函数作用域(Function)

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

    fn()
    function fn() {
        let x = 1
        var y = 2  
        z = 3

        console.log(x);
        console.log(y);
        console.log(z);
    }
    // console.log(x);
    // console.log(y);
    console.log(z);
总结
  • 函数外声明的变量 作用域是全局

  • 函数内用关键字声明的变量 作用域是函数内

  • let 声明的变量具备块级作用域

深入理解变量的作用域

参考:JavaScript Scope

JavaScript 作用域

总结
  • 作用域分三类

- Block scope (代码块作用域)

- Function scope (函数作用域)

- Global scope (全局作用域)

  • 一个用var或let定义在函数外的变量就是全局变量,全局变量作用域是全局作用域

  • js中对象名和函数名也做变量使用

  • 若你给一个没有声明的变量赋值,它会自动变成一个全局变量。(但在js严格模式下不会)

  • 在 HTML 中,全局作用域是 window 对象。所有不是使用let定义的全局变量都属于 window 对象(例如有个全局变量 a,那么可以使用 window.a 来访问它)

内存堆栈与垃圾回收

什么是内存堆栈

调用函数时,函数中的各种变量将存储在内存中,以堆栈的形式进行保存

** 堆 **用于存储对象数据的内存区域

** 栈 **指的是一种先进后出的数据格式 用于存储每次函数调用时产生的数据

栈中的每一次调用内容,称为一个栈帧

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

    let b = (x) => {
        let num1 = 1
        let num2 = 2
        let obj = {name: 'abc'}
        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中查看

  • 栈将在调用函数时创建数据并进入栈,这个过程称为入栈

  • 函数调用结束后将函数中的数据将从栈中移除,这个过程称为出栈

  • 出栈时候,释放内存中已经无用的数据的过程称为垃圾回收

  • 函数调用时栈的存放顺序始终是先进后出

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

 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(
 modify(x, y)

    console.log('函数调用完后');
    console.log(x);
    console.log(y);b);
    }
传入两个参数:

x是值类型传参

y是引用类型传参

值类型传参:
  • 将x变量中的值 直接赋予了函数内的形参

  • boolean number string 都是值传递的

引用类型传参:
  • 将y中的引用地址,传递给函数内的形参

  • object function 都是引用传递的

bind call apply函数的应用

函数内可以使用 this 关键字,不同的地方 this 关键字的指代不一样
 function fn(a, b) {
        console.log(this);
        return a + b
    }

fn()

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

bind 绑定函数的 this 指代

语法 function.bind(thisArg, x, y, …)

thisArg: 要修改的this的指代

x, y, … : 函数的参数,选填

返回值: 一个被修改了this指代后的新函数

 let fn2 = fn.bind('hello bind', 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]))

arguments变量

什么是arguments

用function定义的函数中arguments代表参数数组,是浏览器隐式声明的变量

lambda表达式中不能使用arguments

  • arguments返回的是一个数组

  • arguments.callee 返回函数自身

      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)
    
通过arguments.callee时点击事件点击移除事件监听程序
    let btn = document.querySelector('button')
    btn.addEventListener('click', function () {
        console.log('click');

        // 移除事件监听程序
        btn.removeEventListener('click', arguments.callee)
    })
通过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()
    })

节流和防抖

防抖
  • 函数将在一个固定时间后调用,若计时未完成又执行该函数,则取消上次计时,重新开始计时

  • 用于限制频繁的网络请求,例如:搜索功能,用户停止输入的一段时间后才会执行搜索任务

防抖步骤
  • 1 取消计时

  • 2 重新计时

     const fd = (() => {
            // 计时器id
            let timerId
            return () => {
                // 取消计时
                clearTimeout(timerId)
                    // 重新计时
                timerId = setTimeout(() => {
                    // 计时完成后要执行的代码 写在此处
                    console.log('hello world');
                }, 3000)
            }
        })()
    
        console.log(fd);
    
封装一个防抖函数

这样任意函数都可以被添加防抖功能

参数
  • fn:要添加防抖的函数

  • delay:防抖延迟多久

  • that:防抖函数内的this指代

        // 封装防抖函数
        // 这样任何函数都能被添加防抖功能
    
        function add(x, y) {
            console.log('ok');
            console.log(x, y);
            return x + y
        }
    
        // 定义一个函数来封装防抖
        // fn: 要添加防抖的函数
        // delay: 防抖延迟多久
        // that: 防抖函数内的 this 指代
        function fdPlus(fn, delay, that) {
            let timerId
            return function() {
                // 清除计时器
                clearTimeout(timerId)
                    // 重新计时
                timerId = setTimeout(() => {
                    // 调用参数fn函数
                    // 将当前函数的参数 arguments 作为 fn 的参数传入进去
                    fn.apply(that, arguments)
                }, delay)
            }
        }
    
        add = fdPlus(add, 3000, 'helloworld')
    
        add(1, 2)
    
节流

固定时间内只能调用一次的函数,可以使用时间戳或计时器的方式实现

时间戳的方式(了解):
 function jlTimespan() {
        // 记录最后一次调用该函数的时间
        let lastTime
            // 函数调用cd
        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('hello 节流')
                        // 记录本次调用的时间
                    lastTime = now
                }

            }
        }
    }

    let fn = jlTimespan()
    console.log(fn);
计时器的方式节流
    // 计时器
    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
    }

    // 定义一个函数来封装节流
    // fn: 要添加防抖的函数
    // delay: 节流的cd
    // that: 节流函数内的 this 指代
    function jlPlus(fn, delay, that) {
        // 计时器id
        let timerId
        return function() {
            if (timerId) return

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

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

字符串操作

字符串的基本操作

字符串可以视为字符数组

  • 查看字符串长度 str.length

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

        console.log(str[4]);
    
  • 获取指定索引处的字符charAt() 等价于str()

        console.log(str.charAt(4));
    
  • 分割字符串split

    • 参数用于分割字符串的字符

    • 返回值:字符串数组

        let r = str.split(' ')
        console.log(r);
      
  • split+join 替换字符

        // 例如:替换字符 *|& 为 _
        str = 'hello*|&world*|&!!!'
        r = str.split('*|&')
        r = r.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)
        console.log(r);
      
    • 第二个参数可以省略,如果只写一个参数,substring将从该参数位置一直截取到字符串末尾

      r = str.substring(6)
      
  • indexOf:查询字符串中指定字符再字符串中的索引位置

    • 参数:要查询的字符串

    • 返回值:被查询字符串的索引

          console.log(str.indexOf('o'));
      
  • lastIndexOf

        console.log(str.lastIndexOf('o'))
    
  • startWith:用于判断字符串是否以指定字符串开头

    • 参数:指定开头的字符串

    • 返回值:Boolean值,true代表是以指定字符串开头,false代表不是

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

  • toUpperCase toLowerCase 将字符串的英文转成全为大写或小写

    • 例如: 统计一个字符串中出现了多少个a字符,忽略大小写

      str = 'alhdAkdjfalKHgladhfdjAhg'
          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);

字符串的match函数

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

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

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

    let str = 'goods_gods_goooods'
    let regex = /go{1,3}ds/
    let r = str.match(regex)
    console.log(r);

正则表达式

正则表达式是用于匹配字符串的表达式
声明正则表达式的方法:
  • 方法一

      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方法 检测字符串是否符合正则表达式

返回值true代表 受测字符串符合正则表达式描述的特征

    console.log(regex.test('xyz@gmail.com'));
正则表达式语法
参考:正则表达式_百度百科
  • \反斜杠 :转义

  • ^:匹配字符串的开头

  • $:匹配字符串的结尾

匹配字符个数的符号

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

  • *:任意次

  • ?: 匹配0次或者1次

  • +:匹配至少1次

  • {n}:匹配指定次数

  • {n,}:匹配至少n次

  • {n,m}:匹配至少n次,至多m次

匹配字符个数的符合
  • x|y : 或

  • [a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内

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

分组 (pattern): 将pattern里面的所有字符当作一个字符处理
  • 站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值

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

预查询

- 预查询:正则表达式会去匹配预查询中的内容,但不会进行取值

- 预查询分方向:前后

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

  • (?=pattern ) :后置预查询

    • 先行否定断言

      • 语法:x(?=y)

      • x跟随y时 匹配x

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

  • (?!pattern): 负后置预查询

    • 后行否定断言

      • 语法:x(?!y)

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

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

  • (?<=pattern) : 前置预查询

    • 后行断言

      • 语法: (?<=y)x

      • x 的前有 y 则 匹配 x

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

  • (?<!pattern) 负前置预查询

    • 后行否定断言

      • 语法: (?<!y)x

      • x 前没有 y 则 匹配 x

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

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

  • \d :十进制数

  • \D :非十进制数

  • \r :回车

  • \n :换行

  • \s :所有不可见字符,字符表 回车换行 空格等

  • \S :所有可见字符

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

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

  • \t :制表符

正则表达式的全局模式

不开全局模式:

只会匹配到第一个符合条件的字符串

全局模式

全局模式的正则表达式将匹配所有符合条件的字符串

在正则表达式的后面使用 g flag来开启全局模式

另外一个flag 是i代表 忽略大小写

matchAll 匹配所有符合正则描述的字符串
参数必须是一个全局模式的正则表达式
返回值:是一个iterator(迭代器)

什么是迭代器?用于循环遍历一个集合的工具就是迭代器

迭代器一定包含一个 next 函数 代表取出下一个成员

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

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

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

贪婪模式和非贪婪模式

贪婪模式(默认)

字符串在匹配正则表达式时,将尽可能多的匹配满足正则规则的字符

非贪婪模式

字符串在匹配正则表达式时,将尽可能少的匹配满足正则规则的字符

开启非贪婪模式 在匹配个数的符合后加上’?‘问哈
    // 如何开启非贪婪模式
    // 方法:在表示匹配个数的符号后面加上'?'问号
    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));

闭包和计时器

闭包

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

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

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

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

// 2. A内声明变量B

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

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

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

闭包的应用场景

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

 const demo = (function A() {
        let B = '张三'
        return () => {
            // 函数内引用变量B 则函数A是闭包的
            console.log(B)
        }
        // 当返回一个值时 函数A将返回 A中没被使用的变量将被垃圾回收掉,则A不是闭包的
        // return 'hello world'
        // 当返回的是直接声明的对象或数组的时候,声明时将B作为对象属性的值或数组成员传入对象或数组
        // 当A返回后,变量B则没有再被引用了,则被垃圾回收掉,A不是闭包的
        // return { name: B }
    })()


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

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

导致内存溢出的情况

内存溢出

当内存中堆栈内容已经装不下了,再给内存增加内容则会导致溢出

栈溢出

无限的函数递归会引发栈溢出

    function fn1() {
        fn1()
    }

    // fn1()
堆溢出

// 每次循环都声明一个新的对象

// 对象保存在堆上

// 当堆上的内容过多 则会导致堆溢出

 // 堆溢出
    let count = 0
    while (true) {
        // 每次循环都声明一个新的对象
        // 对象保存在堆上
        // 当堆上的内容过多 则会导致堆溢出
        let a = { name: Date.now() }
        if (count > 5) {
            console.log(count);
        }
        count++
    }

计时器

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

声明一个计时器:setTimeout
  • 参数1:计时器计时结束后触发的函数

  • 参数2:计时时长,单位毫秒

  • 返回值:计时器id

  • 计时器id 用于停止计时

    // let timerId = setTimeout(() => {
        //     console.log('hello setTimeout')
        // }, 3000)
    
清空计时器:clearTimeout(timerId)
 // document.querySelector('.btn1').addEventListener('click', () => {
    //     // clearTimeout 清空计时器
    //     // 参数是 计时器id
    //     // 清空后计时器将取消掉
    //     clearTimeout(timerId)
    // })

循环计时函数 setInterval

每次经过指定时间,触发一次指定的函数
  • 参数和返回值与setTimeout相同

      // let count = 0
        // let timerId2 = setInterval(() => {
        //     count++
        //     console.log(count);
        // }, 1000)
    
清空循环计时函数 clearInterval()
// clearInterval
    // document.querySelector('.btn2').addEventListener('click', () => {
    //     // 清空循环计时器
    //     clearInterval(timerId2)
    // })
注意:请避免出现死循环
注意事项:
  1. 尽量不使用 setInterval

理由:setInterval 可能由于人为原因忘了关闭,或者内部出现异常,导致代码死循环

  1. 若要做大量循环调用甚至是无限循环调用时(例如轮询死循环调用),请使用 setInterval 而不是使用 setTimeout 递归进行循环

    理由:setTimeout 会占用大量内存堆栈

js的移动和渐入渐出动画

移动元素的思路

使用计时器 每隔一段时间 让元素位置产生一个增量的变化

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

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

        // 先运行逻辑运算

        // 设一个速度,一帧移动15像素
        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)

渐入渐出动画

使用setInterval 使透明度有序变化

 const img = document.querySelector('img')

    let timerId

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

    // 进入事件
    img.addEventListener('mouseenter', () => {
        console.log('enter');
        clearInterval(timerId)
        timerId = setOpacity(img, Number(img.style.opacity), -0.01, 100)
    })

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


    // 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.opactiy = 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
    }

封装一个移动函数

思路

使用计时器播放动画

步骤
  • 声明移动函数

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

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

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

封装函数
 // 函数用来封装不变的内容,变化的内容就成为参数
    // 参数:
    // 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)

时间对象和数学函数

时间对象

参考

Date - JavaScript | MDN

时间字符串的参考

Date and Time Formats

时间对象Date
表示时间的字符串

字符串需要满足w3c联盟要求的时间字符串格式

例如1990-01-01

表示时间的数字

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

创建时间对象的方法 new Date()
  • 当前系统时间let date = new Date()
    • Date 时间对象默认修改了对象的 toString 方法 所以转字符串时,会显示成时间字符串

    • 所有对象都有 toString 方法,转换字符串时,js回调用该方法

  • 创建指定时间字符串的时间

    date = new Date(‘1997-07-07’)

    // 不推荐使用,因为这是个非标准时间字符串,不同浏览器解析方式可能不同

  • 通过格林威治毫秒时创建时间
    • date = new Date(1000 * 60 * 60 * 24 * 365)
  • 通过年月日时分秒创建时间
    • date = new Date(2000, 5, 6, 18, 44, 22)

    • 参数分别代表 年月日时分秒

    • 注意:月份是从0开始计算的

    • 参数至少写前两个参数,后续参数可以省略

读取时间
  • 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()) // 毫秒

设置时间

date.setFullYear(2023)

  • date.setMonth(0)

  • date.setMonth(0)

  • date.setDate(5)

  • date.setHours(20)

  • date.setMinutes(66)

  • date.setSeconds(66)

  • date.setMilliseconds(1000)

Date.now() // 获取当前系统时间的格林威治毫秒时
date.getTime() // 获取date对象代表的格林威治毫秒时
通常来说日期对象需要转换成字符串显示,否则用户看不懂
 function format(date) {
        return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`
    }

    console.log(format(new Date()));

数学函数

专门处理数学运算的函数

数学函数可以通过浏览器内置对象Math进行直接调用

参考:
Math - JavaScript | MDN
三角函数

需要注意的是,三角函数 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:获取数字的绝对值
ceil:小数向上取整
floor:向下取整
round:四舍五入
max:取参数中较大数
min:取参数中较小数
通过max和min写一个定界函数

value:要定界的数字

max,min 最大最小值

返回值,value < min时返回min value > max时返回max value在min和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)
    }
pow:返回x的y次方 Math.pow(x,y)
sqrt:返回一个数的平方根
random : 取随机数,范围在(0-1)之间,能取0取不到1
sign:取符号

事件和异常处理

事件

在在js中,事件就是:当某种情况发生的时候,能够触发一段代码,这个发生的情况就是事件

简单的理解,事件就是:(当 。。。。时;就。。。。样)

事件在js中以对象形式存在
什么是绑定事件?

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

绑定事件的方法

使用元素对象的addEventListener函数进行事件绑定

EventTarget.addEventListener() - Web API 接口参考 | 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('按钮被点击了')
    }
事件的分类
  • 资源事件

    • load 加载完成

    • error 加载失败

  • 焦点事件

    • focus 获取焦点

    • blur 失去焦点

  • 鼠标事件

    • click 点击事件

    • contextmenu 右键菜单

    • dblclick 双击

    • mousedown 鼠标点下

    • mouseup 抬起

    • mouseenter 进入

    • mouseleave 离开

    • mouseover 悬停

    • mouseout 出去

    • mousemove 移动

    • wheel 滚轮

    • drag拖动事件

      • 元素上要添加 draggable = ‘true’

      • dragstart 开始拖动

      • dragend 结束拖动

  • 媒体事件:和媒体播放器相关事件,详细查文档

  • 表单元素事件

    • input 输入事件

    • change 变化事件

  • 按键事件

    • keydown 按下

    • keyup 抬起

    • keypress 按压

事件机制------冒泡
在html中触发事件的元素,将会把事件不断的向父元素传递,这个过程叫做事件冒泡
 document.querySelector('button').addEventListener('click', () => {
        console.log('button');
    })

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

    document.body.addEventListener('click', () => {
        console.log('body');
    })
捕获事件(事件的捕获)
事件触发后,可以由上级元素先处理事件,这个过程就是捕获事件
addEventListener 的第三个参数代表是否捕获事件
捕获事件的元素会先处理事件,然后将事件对象还给产生事件的元素,然后正常冒泡
document.body.addEventListener('click', () => {
        console.log('body');
    }, true)
阻止事件冒泡-----使用事件对象的 stopPropagation 来阻止事件冒泡
ev.stopPropagation()
屏蔽默认事件
方法一:使用事件对象屏蔽元素事件的默认行为
document.querySelector('button').addEventListener('click', ev => {
        // preventDefault 屏蔽当前元素默认的事件行为
        ev.preventDefault()
        console.log('提交');
    })

    // document.querySelector('.box').addEventListener('contextmenu', ev => {
    //     ev.preventDefault()
    //     console.log('右键');
    // })
方法二:对应事件属性 retrun false 也能屏蔽默认行为
<div class="box" oncontextmenu="return false"></div>
事件对象的currentTarget 和target
targrt:代表产生事件的元素
currentTarget:当前处理事件的元素

注意:currentTarget是变化的

button.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);
    })
通过js代码来触发元素事件的方法
方法一: 通过对应事件名的函数进行触发
缺点: 不是所有事件都有对应的方法来触发
 document.querySelector('.btn1').addEventListener('click', () => {
        console.dir(box)
        // 调用 click 来触发点击事件
        box.click()
    }) 
方法二:发出事件对象

步骤:

// 1. 创建事件对象

// 2. 调用要接收该事件的dom的dispatchEvent方法

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

        // 事件对象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 用来派发一个事件
        // 返回值是个布尔值,true 代表事件没被取消 false 代表事件被取消(调用了 preventDefault)
        let r = box.dispatchEvent(event)
        console.log(r);
    })
自定义事件对象

发出自定义事件的方法:

  1. 给需要接收自定义事件的元素,绑定自定义事件

  2. 创建一个自定义事件

  3. 调用需要接收自定义事件的dom的dispatchEvent

    // 1. 给需要接收自定义事件的元素,绑定自定义事件

    const box = document.querySelector('.box')
    
    document.body.addEventListener('open-my-eyes', ev => {
       console.log('body my eyes open');
       console.log(ev);
    })
    
    box.addEventListener('open-my-eyes', ev => {
       ev.preventDefault()
       console.log('box my eyes open');
       console.log(ev);
    })
    
    document.querySelector('button').addEventListener('click', () => {
        // 2. 创建一个自定义事件
        // CustomEvent 参数和 Event 的参数基本相同
        const ce = new CustomEvent('open-my-eyes', {
            bubbles: false,
            cancelable: false,
            // 自定义的事件参数
            // 该参数可以传递任意内容
            detail: { name: '张三' }
        })
给div添加tableindex,使div成为一个表单元素,实现获取焦点

在div上加入一个tabindex,可以让div成为一个表单元素

tabindex 可以指定使用tab按键切换焦点元素时的顺序,顺序由大到小排列

<div tabindex="0"></div>
    <input tabindex="1"/>
 let div = document.querySelector('div')
    div.addEventListener('keydown', ev => {
        console.log('keydown')
    }, true)

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

异常处理

异常处理的基本处理方法和异常的特点
异常:

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

异常处理

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

只有运行时异常(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');
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, 1)
        console.log(r);
    } catch (e) {
        console.error(e);
    } finally {
        // try catch 后 最终一定会执行的程序
        console.log('无论try执行成功还是抛出异常,都会执行此处的代码');
    }
自定义异常对象

开发人员自己创建的异常类就是自定义异常

作用

给异常分类,告诉开发人员哪些异常是需要处理的,哪些是系统异常不需要处理

 // 除法
    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 ArgZeroError()
        }
        return x / y
    }
自定义异常类
 // 自定义异常类
    // 参数不是数字异常
    class NaNError extends Error {
        // 使用 new 关键字创建类实例时,调用 constructor 构造函数
        constructor() {
            // 父类构造函数
            super('参数不是数字')
        }
    }

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

    try {
        div('ab7c', 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);
        }
    }

面向对象编程

面向过程编程和面向对象编程

面向过程编程:

根据功能逻辑,按照功能和代码的执行顺序编写代码,且以计算机的运行逻辑按照1234的步骤书写的代码,这就是面向过程的编程方法

面向对象编程:

面向对象编程(OOP:Object Oriented Programming):站在现实生活的角度,将计算机中的抽象的物体当作一个具体的东西(这个东西就叫做对象Object)来处理,这就是面向对象编程的思想

对象

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

对象的静态属性和方法

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

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

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

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

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

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

    // 调用静态属性或方法,使用类名调用而不是实例对象
    console.log(Human.category);
    Human.greedy()
function声明的类
不能使用 static 关键字声明静态属性和方法的
只能通过给构造函数添加属性来充当静态属性和方法
// function 类 是不能使用 static 关键字声明静态属性和方法的
    // function 类 只能通过给构造函数添加属性来充当静态属性和方法
    function Car() { }

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

    console.log(Car.category);
    Car.jiaYou()
JSON对象

json 对象是js中的对象,且该对象的格式采用json的格式(js中也称为朴素对象 plain object)

json JavaScript Object Notation (js 对象简谱)

json是一种js的轻量化交互式数据

json的格式:

  • 根节点只能有一个

  • json中用 “key”: value 的方式定义属性,多个属性用逗号隔开

  • 属性名必须用双引号包裹

  • 属性值若为字符串,字符串必须用双引号包裹

json 对象

json 对象采用json格式,但可以有一些省略的写法

写法:
let human = {

    "name": "张三",

    "age": 16,

    "是否已婚": false,

    "company": {

        "name": "hqyj"

    }

}

js中可以的写法:

let human = {

    'name': '张三', // 字符串可以用单引号

    age: 16, // 若属性名不包含特数字符则可以省略引号

    "是否已婚": false,

    "company": {

        "name": "hqyj"

    }

}

若属性名和属性值的变量名相同则可以省略冒号后面的内容,如:

let name = '张三'

let zhangSan = {

    // name: name // 完整写法

    name // 省略后的写法

}

若在json对象中声明函数,那么可以省略function关键字或lamda表达式的箭头

let a = {

    // 完整写法如下

    // fn: ()=>{

    //     console.log('fn被调用了')

    // }



    // 省略写法

    fn() {

        console.log('fn被调用了')

    }

}

类和实例对象

用于描述同类事物的类型叫做类,类型是抽象的

构造函数

用于构造类实例的函数

  • es5

  • // 声明类型
        // 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

    // es6 语法定义类型如下:
        class Car {
            // 属性
            // 价格
            value = 0
            // 生产厂商
            producer = ''
            // 名称
            name = ''
    
            // 声明构造函数
            constructor(value, producer, name){
                this.value = value
                this.producer = producer
                this.name = name
            }
    
            // 行为
            run(){
                console.log(`价值 ${this.value}${this.name} 在跑`);
            }
        }
    
实例化类型

生成一个类型的个例的过程称为实例化

以下代码中 new Human() 这句话就是在实例化Human类

实例化的结果被存在了 zhangSan 变量中,

我们称实例化的结果为:实例,实例对象,类实例

new 关键字用于实例化类型

new 关键字后面的 Human() 实际上是在调用构造函数

let zhangSan = new Human('张三', 17, 'male')
    console.log(zhangSan);
实例和类的关系

类是实例的模板,实例是类的成品

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

判断对象是否包含某个key

  • let obj = { a: 1, b: 2, c: ‘’ };

     // 方法一: 使用 boolean 值的隐式转换
     // if (obj.c) {
     //     // 若存在就执行if里的代码块
     //     console.log('ok');
     // }
    
     // 甚至可以用来判断对象是否存在
     // if (obj) {
     //     console.log('存在');
     // } 
      // 上述方法的缺点是: 若 c 的值是空字符串,0或者false 都会被认为 “c 不存在”,然而 c 这个 key 是存在的
    
  • // 方法二: 使用 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 存在');
     }
    
     // 判断不是 null 和 undefined
     if (typeof obj.c !== 'null' && typeof obj.c !== 'undefined') {
         console.log('c 存在');
     }
    
     // 上述方法的缺点是: 必须要预先知道 c 的类型才可以进行检测
    
  • // 总结:

    // 1. 绝对不会错的用法是 方法二 和 方法三

    // 2. 快速开发使用 方法一 但要注意其缺点

声明私有属性和私有方法

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

私有方法或属性在名称的前面使用 # 井号

this,super,严格模式

this

this关键字不同的场景,this关键字代表的东西不同

  • js执行上下文中的js =>window

    console.log(this); // => window
    console.log(this === window);
    
  • 函数内的this,非严格模式下 函数内 this => window

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

    fn()
  • json对象方法中的this=>对象自己

     let obj = {
            name: '张三',
            fn() {
                console.log(this); // => 对象自己
            }
        }
    
        obj.fn()
    
  • 实例对象方法的this

    // 类方法中的 this => 调用该方

    class A {
            name
            constructor(name) {
                this.name = name
            }
            fn() {
                // 类方法中的 this => 调用该方法的实例对象
                console.log(this);
            }
        }
    
        let a = new A('a')
        let b = new A('b')
        a.fn()
        b.fn()
    
  • 有些情况下,回调函数中的this可能被其他函数赋值,所以this不是undefined

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

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

document.querySelector('button').addEventListener('click', function () {
        console.log(this);
    })
总结:
  • js 执行上下文 => window

  • 函数内 this => window (函数=>window)

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

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

lambda表达式的this

结论
  • lambda 表达式没有 this

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

  • 作用:

  • 利用好 lambda 表达式没有 this 的特点,灵活的使用 this 获取想要的上下文中的内容

lambda表达式让回调函数访问实例对象
// 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()

instanceof 检测对象是否是某个类型的实例

  • 语法: n instanceof M

  • 含义: n 是否是 M 类的实例,如果是 则表达式返回 true

继承与原型

继承:

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

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

extends继承
  • 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语法下的继承

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

    // 声明一个皮卡继承Car

    function PickCar() {
        // 载重量
        this.weight
        this.mount = function () {
            console.log('装货');
        }
    }
  • 利用原型进行继承
    // 利用原型进行继承
        // 什么是原型? 类的模板(设计图)
    
        // 给 PickCar类型赋值原型属性,既能进行继承
        PickCar.prototype = new Car()
    
        let pk = new PickCar()
        console.log(pk);
    
  // 原型 __proto__ 和 prototype 的区别
  // __proto__ 实例对象中访问原型的属性
  // prototype 类访问原型的属性

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



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

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


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

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

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

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



可以使用super关键字调用父类方法

方法中的super代表父类实例

```js
 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);

原型链

  • 对象中的__proto__,如果存在上级,那么当前对象的__proto__和原型中的__proto__ 形成的一种链式结构就是原型链

  • Object 类型是所有类型的父类

原型链的运行顺序

当访问run函数时,浏览器会先从f对象中寻找run函数,若不存在,就会在父类中寻找,若父类中找不到,就会到父类的父类中寻找直到找到为止

严格模式

w3school 文章: JavaScript “use strict”

为什么要使用严格模式

为了代码更规范,避免一些错误与 js 历史问题所产生的歧义

严格模式中不准做的事情
  • 未声明变量就直接赋值

- x = 12

- 对象也一样 x = {a: 1, b: 2}

  • 直接删除变量

- delete x

  • 重复定义参数名

- function fn(a, a) {}

  • 不允许使用 8 进制数

- let x = 010

  • 不允许使用八进制转义字符

- let x = '\010'

  • 不允许赋值只读属性

- ```js

“use strict”;

const obj = {};

Object.defineProperty(obj, “x”, { value: 0, writable: false });

obj.x = 3.14;

```

- ```js

“use strict”;

const obj = {};

const obj = {

get x() {

return 0;

},

};

obj.x = 3.14;

```

  • 删除不允许删除的属性

- delete Object.prototype

  • eval arguments 不能重复定义变量

- let eval = 3.14

  • 不能使用 with 语法

- with (Math){x = cos(2)};

  • 不能用 eval() 函数创建变量

- eval ("let x = 2")

  • *this 关键字在函数内,默认为 undefined

- ```js

function fn(){

console.log(this) // => undefined

}

```

开启严格模式的方法

在 script 标签或 js 文件的顶部,在函数内的第一行,均可以输入 'use strict' 来开启严格模式

Object类方法和访问器

object的常用静态方法

Object.assign :扩展对象
作用:将一个对象的属性复制到另一个对象中,且返回一个新对象
第一个参数:要扩展属性的对象
第二个参数:扩展的属性源对象
Object.assign(x, y) 这句话的含义就是,将y中的属性拷贝到x中,且返回x对象
Object.assign 该函数常用于浅拷贝对象
    // 浅拷贝例子
    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);
给对象定义属性Object.defineProperty
参考:
Object.defineProperty() - JavaScript | MDN
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 方法

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

delete 删除对象属性,对象行为 等价于Reflect.deleteProperty

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

包装类

将我们熟知的基本的数据类型用类来进行包装,这种类就是包装类

作用

多用于类型转换

包装类有以下几种

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

访问器getter和setter

访问器

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

作用:

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

具体作用
getter:格式化数据显示。简化路径访问
setter:验证赋值参数的合法性
声明访问器
在对象中声明访问器
 // 对象中声明访问器
    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 = '其他'

ES6语法

…扩展

…iterable  扩展运算符只能用于可迭代的对象!

复制对象
let obj = { x: 1, y: 2 }
    // 复制对象
    console.log({ z: 3, ...obj });
复制数组
 let arr = [1, 2, 3]
    // 复制数组
    console.log([...arr, 4]);
快速合并两个数组
// let arr1 = ['a','b']
        // let arr2 = [1,2,3]
        // let arr3 = [...arr1,...arr2]
        // console.log(arr3)
扩展字符串
let str = 'helloworld'
console.log(...str)

var let const区别

  • 1.var所声明的变量具备变量提升,let,const所声明的变量也有类似效果但是不属于变量提升,应该是‘暂时性死区’

    • 变量提升将所有var所修饰的变量隐式的提前到当前作用域的顶端
  • 2.var可以重复声明同名变量,let和const不允许在同一作用域下重复声明同名变量

  • 3.var和let在声明变量时可以不用对变量进行初始化,const声明必须初始化

  • 4.var不具备块级作用域 ,let和const具备块级作用域  { let }

解构赋值

对数据类型进行解构,然后进行赋值操作

  • 对数组进行解构赋值:

    var arr = [1,2,3]
    
    var [x,y,z] = arr
    
  • 对 对象进行结构赋值

    //   var obj = {
          //     name: '张三',
          //     age: 18,
          //   } 
    // var { age:y,name:x } = obj   //   var {age:x,name:y} = obj
    

rest参数

arguments对象,函数的参数列表

总结:arguments对象是一种类似于数组对象的一种数据结构!但它不是真正的数组类型!可以看作是一个类数组对象!

//     类数组的特点:

//     1.可以使用下标值进行元素值的访问

//     2.可以查看改对象的长度通过 length属性

arguments对象是存在于函数内部中!
arguments的作用是收集函数被调用时所传入的所有实参数据!以类数组的形式进行存储!
rest参数,用于函数的形参列表  …变量名
 function fun(a,b,c,...args){
            console.log(a,b,c)
            console.log(args)
            console.log(Array.isArray(args)) // true  真数组

            // rest参数作用:
            // 1.想替代传统的arguments对象
            // 2.rest参数是收集多余的参数值,以数组的形式进行存储
            // 3.rest参数只能放在形参列表的最后
        }


        fun(100,200,300,400,500)

Map和Set

Map的使用类似 json 对象
声明map
let map = new Map([
        ['a', true],
        ['b', 123],
        ['c', 'string']
    ])
读值 map.get()
赋值 map.set()
迭代map
 // 迭代map
    let keys = map.keys()
    console.log(keys);
    let key
    // keys.next() 方法的作用是迭代下一个成员 返回一个对象
    // 返回对象包括 value 属性和 done 属性 done 代表是否迭代完成
    while (!(key = keys.next()).done) {
        // value 就是每一个key
        console.log(key.value);
        console.log(map.get(key.value));
    }
 // 可以使用 forEach 迭代
    map.forEach((value, key, _map) => {
        // value map对应key 的值
        // key 迭代的键
        console.log(value);
        console.log(key);
        // _map 当前正在迭代的map
        console.log(_map);
    }) 
 // for of 遍历 Map
    for (const entry of map) {
        // entry 是包含了一组 key value 的数组
        console.log(entry[0]); // => 0 号成员就是key
        console.log(entry[1]); // => 1 号成员就是value
    }
has 判断是否存在某个key值
set 不重复的数据集,若存入多个重复数据会自动去重
添加 set.add()
删除 set.delete()
判断是否存在某个值 has
迭代set
// for of 遍历 Set
    for (const value of set) {
        console.log(value);
    }
keys = set.keys()
    while (!(key = keys.next()).done) {
        console.log(key.value);
    }
  // foreach 迭代 set
    set.forEach((value1, value2, _set) => {
        console.log(value1);
        console.log(value2);
        console.log(_set);
    })
  • 给数组去重
// 通过 set 转换成数组
    // 可以通过这种方法来让数组去重
    let arr = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
    console.log(Array.from(new Set(arr)));

map和set的size属性代表数据长度

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值