Skip to content

1.1 防抖

javascript
const ipt = document.querySelector('input')
ipt.addEventListener('keyup', function(){
    console.log('发送请求了')
})

// 防抖:防止抖动, 你先抖动着, 啥时候停了,再执行下一步

// 我们想要的一个效果应该是什么样子?

// 1. 当我们键盘抬起的时候,不会立马触发这个回调函数, 我们给一个延迟发送的等待时间
// 2. 应该是在等待某个延迟时间之后再触发,比如设定300ms
// 3. 如果在这个300ms之内,又触发了keyup事件,那么就重新计时,重新等待300ms

// 回城 : b ==> 10s城, 如果这中间又按了一下这个键,又重新计时

const sendMsg = function(){
    console.log('发送请求了')
}
ipt.addEventListener('keyup', sendMsg)

防抖:debounce 当持续的触发某个事件的时候,一定时间内没有再触发事件,回调函数才会执行一次。 如果我们的等待时间到来之前,又触发了这个事件,重新计时

javascript

// ==>  你先抖动着, 啥时候停了,再执行下一步
const ipt = document.querySelector('input')
const debounce = (fn, ms) => {
    console.log(2222)
    return function(){
        // 控制sendMsg什么时候执行
        console.log(333)
    }
}

// 需求:让sendMsg这个函数,等待ms的时间才执行
// 可以封装一个debounce防抖函数来操作这个sendMsg,让他500ms后执行

// 在防抖debounce里面去控制什么时候执行sendMsg

ipt.addEventListener('keyup', debounce(sendMsg, 300))

02 debounce 函数中的return

javascript
const input = document.querySelector('input')
const sendMsg = function(){
    console.log('发送请求了')
}

// 1. return function外面,绑定keyup事件的时候,就立即执行了 
    // 因为加了小括号  debounce(sendMsg, 300)
// 2. return function里面的代码, 触发事件的时候才执行,没触发一次,执行一次
const debounce = (fn, ms = 0) => {
    console.log(1111)
    return function(){
        console.log(2222)
    }
}

input.addEventListener('keyup', debounce(sendMsg, 300))
// input.addEventListener('keyup', function(){
//         console.log(2222)
//     })

03 简版的防抖

javascript
<input type="text">
<script>
  const input = document.querySelector('input')
  const sendMsg = function(){
      console.log('发送请求了')
  }
  // 需求:
  // 1. 触发keyup 延迟300ms之后,才执行回调函数
  // 2. 如果等待的300之间,又按下了键, 重新计时  setTimeout 

  const debounce = (fn, ms = 0) => {
      // 以后这里的代码还执行不? 
      // 要保证程序中,只能由一个定时器
      let timerId 
      return function(){
          // let timerId // 变量如果放到这里,每次都重新声明,每次都是undefined
          // 怎么清除定时器?
          clearTimeout(timerId) // undefined 
          // 开启一个定时器 等待300ms之后,调用fn
          timerId = setTimeout(function(){
              fn()
          }, ms)
      }
  }

  // input.addEventListener('keyup', debounce(sendMsg, 600))
  // input.addEventListener('keyup', function(){
  //         // let timerId // 变量如果放到这里,每次都重新声明,每次都是undefined
  //         // 怎么清除定时器?
  //         clearTimeout(timerId) // undefined 
  //         // 开启一个定时器 等待300ms之后,调用fn
  //         timerId = setTimeout(function(){
  //             fn()
  //         }, ms)
  //     })

  // 简版的防抖
  function debounce(fn, ms = 0){
      // 1. 先声明一个变量,接收定时器的number
      let timerId 
      return function(){
          // 2. 先清除定时器
          clearTimeout(timerId)
          // 3. 开启定时器
          timerId = setTimeout(function(){
              fn()
          }, ms)
          
      }
  }
  input.addEventListener('keyup', debounce(sendMsg, 300))

04 防抖中的this指向

javascript
<input type="text">
<script>
  const input = document.querySelector('input')
  const sendMsg = function(){
      // console.log(this)
      console.log('发送请求了')
  }
  // 1, 一开始 this指向input
  // 2. 写了防抖之后,this指向了window

  const debounce = (fn, ms = 0) => {
      let timerId 
      return function(){
          clearTimeout(timerId)

          timerId = setTimeout(() => {
              // 改变this指向 
              fn.call(this)
          }, ms)
      }
  }
  // 当我们使用了debounce之后,原来操作的回调函数,this指向改变了呀
  input.addEventListener('keyup', debounce(sendMsg, 300))

05 debounce传参

javascript
const input = document.querySelector('input')
const sendMsg = function(x, y){
    console.log(this)
    console.log(x + y)
    console.log('发送请求了')
}

// 正常调用回调函数如何传参?
// 函数名加括号表示立即执行,调用函数
// input.addEventListener('keyup', sendMsg(1,2))
input.addEventListener('keyup', sendMsg.bind(input, 1,2))
// input.addEventListener('keyup', function(x, y){
//     console.log(this)
//     console.log(x + y)
//     console.log('发送请求了')
// })
// input.addEventListener('keyup', undefined)

// 2. 如果要用防抖,如何传参
const debounce = (fn, ms = 0) => {
    let timerId 
    return function(...args){ // 剩余参数,真数组
        // console.log(x, y, z)
        clearTimeout(timerId)
        // 1. setTimeout外 this指向的this input绑定事件的元素
        timerId = setTimeout(() => {
            // 2. setTimeout内,如果改成箭头函数了,this也指向input
            fn.call(this, ...args)
        }, ms)
    }
}

input.addEventListener('keyup', debounce(sendMsg, 300).bind(input, 1, 2, 3))

完整版

javascript
const debounce = (fn, ms = 0) => {
    let timerId 
    return function(...args){
        clearTimeout(timerId)
        timerId = setTimeout(() => {
            fn.apply(this, args)
        }, ms)
    }
}


// 1. 掌握function的return 函数的返回值
// 2. 一般情况的事件绑定,如何传参 ==> sendMsg.bind(input, 1, 2)
// 3. 防抖,如何传参 ==> debounce(sendMsg, 300).bind(input, 1, 2, 3)

// 4. 防抖传参进入函数之后,如何接收

1.2 节流 throttle

持续触发的事件,在一段事件内只允许函数执行一次

javascript
.box{
    width: 500px;
    height: 500px;
    background-color: #ccc;
    color: #fff;
    text-align: center;
    line-height: 500px;
    font-size: 100px;
}
javascript
const box = document.querySelector('.box')
let i = 0

const move = (x, y) => {
    i++
    box.innerHTML = i
}
// box.addEventListener('mousemove', move)

// 节流:throttle
// 持续触发的事件,在一段事件内只允许函数执行一次

// ===> 减少事件在一段时间内的执行频率 

// - 用场景
// 1. 浏览器窗口缩放 resize事件
// 2. scroll滚动事件, mousemove事件
// 3. 拖拽事件等

// 需求:想要每隔500ms,执行一次move函数, 用throttle节流函数,控制它的变化

// 可以声明一个开始时间,用当前时间 减去 开始时间 , 如果 >= 500 ms 就执行一次

const throttle = (fn, ms = 0) => {
    let start = 0
    return function(...args){
        let now = Date.now()
        // let now = Date.now() // +new Date()  new Date().getTime()
        if (now - start >= ms){
            // fn.call(this, ...args)
            fn.apply(this, args)
            // 当函数执行完之后,让start等于当前的时间戳
            start = Date.now()
        }

    }
}


// box.addEventListener('mousemove', throttle(move, 300).bind(box, 1, 2))

定时器的写法- 节流 throttle

javascript
<div class="box"></div>
<script>
    const box = document.querySelector('.box')
    let i = 0

    const move = () => {
        i++
        box.innerHTML = i
    }
    // box.addEventListener('mousemove', move)

    // 每个300ms 执行一次move函数

    // 定时器的方式实现?==> setTimeout

    const throttle = (fn, ms =0) => {
        let timerId  // undefined
        return function(...args){
            // 只让有一个定时器存在, 不要再开启一个新的了
            if (timerId) return
            // 1 
            timerId = setTimeout(() => {
                fn.call(this, ...args)
                timerId = null
            }, ms);
        }
    }

    box.addEventListener('mousemove', throttle(move, 600))
javascript
Something went wrong checking access: getaddrinfo ENOTFOUND api.github.com

1.3 防抖节流总结

https://www.30secondsofcode.org/js/s/debounce 目前看见最简写法,best!

防抖和节流的区别

  1. 防抖:减少执行次数,多次密集的触发只执行一次
  2. 节流:减少执行频率,有节奏的执行
  3. 防抖关注结果,节流关注过程
javascript
// 面试 ? 什么是防抖 什么是节流, 有实际使用过吗?自己封装过防抖或者节流函数吗?

// 1. 
// 防抖 
// 你先抖动着,什么时候停了,我再执行 
//  ==> 将多次密集的执行,合并为一次

// 节流
// => 减少事件触发的频率

const sendMsg = function(){
    console.log('发送请求')
}


// 2. 应用场景
// 防抖的应用场景: 搜索框,不断的输入文字,如果每次输入一个文字就发一个请求,相当消耗性能

// 节流的应用场景:
        // 1. scroll滚动事件
        // 2. mousemove鼠标移动事件
        // 3. 窗口的resize事件等等

// 3. 有没有自己封装或者写过? ==> 好一点的公司会问,大厂才会手写

const debounce = (fn, delay) => {
    // 1. return外面 ,立即执行,只执行一次
    let timerId
    return function(...args){
        // 2. return里面,每次触发就执行
        clearTimeout(timerId)
        // 这里的this指向input
        timerId = setTimeout(() => {
            console.log(this)
            fn.call(this, ...args)
        })
    }
}

// 4. let timerId 为什么要放到return的外面, 如果放到里面
// 5. 为什么改成箭头函数了?箭头函数没有this,箭头函数中的this,就是function里面的this

// 6. bind后面传的参数,给了谁?  fn.bind(obj, 1, 2, 3)  ==> 给了return后面的哪个function
// 7. 接收参数的方式,1. 剩余参数, 2. arguments
        // 7.1 如果用rest剩余参数接收 (...args)  ==> 内部如果使用call改变this指向
        // 需要把args这个真数组,展开成参数列表
       
        // 7.2 如果使用arguments来接收参数, 那么也是可以的,注意arguments是一个伪数组
        const debounce2 = (fn, delay) => {
            let timerId
            return function(){
                clearTimeout(timerId)
                timerId = setTimeout(() => {
                    console.log(this)
                    fn.call(this, ...arguments)
                })
            }
        }

// 8. 改变this指向,还可以使用apply改变
const debounce3 = (fn, delay) => {
            let timerId
            return function(...args){
                clearTimeout(timerId)
                timerId = setTimeout(() => {
                    fn.apply(this, args)
                })
            }
        }

const debounce4 = (fn, delay) => {
    let timerId
    return function(){
        clearTimeout(timerId)
        timerId = setTimeout(() => {
            fn.apply(this, arguments)
        })
    }
}



// 防抖,如果不改变this,不传参数,就这么调用,就欧克了
input.addEventListener('keyup', debounce(sendMsg, 300))
// 防抖,如果要改变this,并且传参数,使用bind来调用,改变this指向,并且传参数,
//  ===> 为什么使用bind,bind不是立即执行的,而是返回一个函数
input.addEventListener('keyup', debounce(sendMsg, 300).bind(input, 1, 2))

节流

javascript
// 节流 减少执行的频率

// 定时器的写法
const throttle = (fn, ms = 0) => {
    let timerId 
    return function(...args){
        // 保证当前只有一个定时器,如果触发事件,不要执行后面代码
        if (timerId) return 
        timerId = setTimeout(() => {
            fn.apply(this, args)
            // 什么时候让可以重新开启定时器
            timerId = null
        })
    }
} 

// 节流, 时间戳的写法
const throttle2 = (fn, ms = 0) => {
    let start = 0
    return function(...args){
        // 判断当前时间 - 开始时间 >= ms ,就执行一次函数
        let now = Date.now()
        if (now - start >= ms) {
            fn.call(this, ...args)
            start = Date.now()
        }
    }
}

09 lodash实现节流防抖

javascript
.box{
    width: 500px;
    height: 500px;
    background-color: #ccc;
    color: #fff;
    text-align: center;
    line-height: 500px;
    font-size: 100px;
}
javascript
<div class="box"></div>
<script src="./js/lodash.min.js"></script>
<script>
    const box = document.querySelector('.box')
    let i = 0

    const move = (x, y) => {
        i++
        box.innerHTML = i
    }

    // box.addEventListener('mousemove', move)
    box.addEventListener('mousemove', _.debounce(move, 300))
    box.addEventListener('mousemove', _.throttle(move, 300))
</script>

2. 类Class

2.1 创建对象

javascript
// 1. 字面量
// 2. new Object
// 3. 构造函数  ===> class 类

// 类:抽象的,泛指的,一个大类, 描述了一类对象的属性和方法

// 实例/对象 : 具体的某一个

const obj = { name:'练练' }

const obj2 = new Object({ name:'练练' })

function Star(name, age){
    this.name = name 
    this.age = age 
}
const ll =  new Star('练练', 18)

console.log(ll)

11 使用类创建对象

javascript
// 使用类创建对象 创建了一个明星类
class Star {
    // 类的公共属性,放到constructor方法里面
    constructor(name, age){
        this.name = name 
        this.age = age 
    }
}

// 类 class就是一个语法糖,是构造函数的语法糖

const ll =  new Star('练练', 18)
console.log(ll)

// ==> 通过class关键字创建类,类名默认首字母大写
// 1. constructor()是类的默认方法,只要通过new创建一个实例,就会执行里面的代码
// 2. 一个类必须有constructor(),如果不写,一个空的constructor会默认添加
// 3. constructor可以接收传递过来的参数,默认返回实例对象this
// 4. 类必须使用new调用,否则会报错

12. 类添加公共方法

javascript
// function Star(name,age){
//     this.name = name
//     this.age = age
// }
// Star.prototype.sing = function(){}
// Star.prototype.dance = function(){}

class Star {
    // 1.类的公共属性,放到constructor方法里面
    // constructor => 名字叫做构造函数
    constructor(name, age){
        this.name = name 
        this.age = age 
    }

    // 2. 类面的所有函数(方法)都不需要写function
    sing(song){
        console.log(song)
    }

    // 3. 多个方法之间,不需要用逗号隔开
    dance(){
        console.log('跳舞')
    }
}

const ldh = new Star('刘德华', 20)
// 
// sing这些公共方法,实际上定义在原型上,
//    相当于是 ==>  Star.prototype.sing  上面定义的方法

13. 类里面的this指向

javascript
class Star {
    constructor(name, age){
        this.name = name 
        this.age = age 
        // 1. constructor中,this指向 new出来的实例对象
        console.log(this)
    }

    sing(song){
        // 2. 类的方法里面,this指向 new出来的实例对象
        console.log(song)
        // console.log(song) ==> 这个方法,调用后,没有返回值的,表达式的值就是undefined
    }

    dance(){
        console.log('跳舞')
    }
}

const ldh = new Star('刘德华', 20)
console.log(ldh.sing('哈哈哈'))

14. 类的本质

javascript
function Person(){

}

console.log(typeof Person)

// class类是构造函数的语法糖
class Star {
    dance(){
        console.log('跳舞')
    }
}

console.log(typeof Star)

// 1. 类的本质就是一个函数,我们可以简单的认为,类就是构造函数的语法糖

// 2. 类有原型,(原型对象)
console.log(Star.prototype)

// 3. 既然类有原型,我们也可以在原型上添加公共方法,但是呢,不推荐
Star.prototype.sing = function(){
    console.log(123)
}

// 4. 实例的隐式原型,指向构造函数(class类)的显示原型
const ll = new Star()
console.log(ll.__proto__ === Star.prototype)

15. 类的继承

javascript
// 继承: 子类拥有或者说继承了父类的属性和方法
// ES6 ==> 类的继承  ==> 关键字 extends继承

// ===> extends 是 寄生式组合继承的语法糖

// 定义一个父类
class Animal {
    constructor(name, age){
        this.name = name 
        this.age = age 
    }

    run(){
        console.log('奔跑')
    }
}
// 定义一个子类,继承自父类

class Dog extends Animal {
    // 子类中没有些父类的constructor中的属性
    // 子类中也没有些方法(继承了父类的方法)
}

const dog = new Dog('wangcai', 3)
console.log(dog)
dog.run()

16. super关键字

javascript
// 定义一个父类
class Father {
    constructor(){}
}

// 定义一个子类继承自父类
class Son extends Father {
    constructor(){
        // 1.  如果子类里面写了constructor方法, 那么这个方法里面必须还要调用super()
        super()
    }
}

// 2. 如果子类没有写constructor方法,那么子类会默认添加constructor方法,
// 并且内部还会自动调用super()
class Son extends Father {
    // constructor(){
    //     super()
    // }
}

const p = new Son()

17. super

javascript
// super关键字用于访问和调用父类上的函数
// 可以调用父类的构造函数constructor , 也可以调用父类的普通函数

class Father {
    constructor(x, y){
        this.x = x 
        this.y = y
        console.log(x, y)
    }

    // 如果想要在类的方法中,访问x,y这些属性,必须加this!
    sum(){
        console.log(this.x + this.y)
    }
}

// 定义一个子类继承父类Father
class Son extends Father {
    // son本身是一个类
    // 1. super() 代表的就是父类的constructor()构造函数
    constructor(x, y){
        console.log(x, y)
        super(x, y)   // 表示调用父类的constructor(x, y)
    }
}

const p = new Son(1, 2)
console.log(p)

18 .super 必须在this之前调用

javascript
  // super关键字用于访问和调用父类上的函数
  // 可以调用父类的构造函数constructor , 也可以调用父类的普通函数

  class Father {
      constructor(x, y){
          this.x = x 
          this.y = y
          console.log(this)
          console.log(x, y)
      }

      // 如果想要在类的方法中,访问x,y这些属性,必须加this!
      sum(){
          console.log(this.x + this.y)
      }
  }

  // 定义一个子类继承父类Father
  class Son extends Father {
      // son本身是一个类
      // 1. super() 代表的就是父类的constructor()构造函数
      constructor(x, y, color){
          // 2. super() 必须放在this之前,先继承父类的一些属性,再写自己的属性
          // 3. Q:super中的this指向谁?指向的是子类的实例
          super(x, y)   // constructor()

         
          this.color = color 
      }

      // 减法
      minusFake(){
          // 想要在这里调用父类的那个sum方法

          // 4.super还可以作为对象来使用,调用父类的方法
          super.sum()
          // console.log(1123)
      }
  }

  const p = new Son(1, 2, 'orange')
  console.log(p)
  p.minusFake()


  // 1. super() 作为方法直接使用,表示的是父类的constructor()这个方法
  // 2. super作为对象来使用,可以调用父类的方法 super.sum()
  // 3. super必须写在this前面

19 面试准备

javascript
// 1. CSS面试题
// 1. 盒模型
// 2. BFC  ==> 掘金上搜一搜   Block Format Context 块级格式化上下文, 
///      就是一个独立的空间,内外互不影响 
// 3. 回流/重排(宽高结构变化了)  另一个叫重绘(颜色等一些)
// 4. position sticky ==> 粘性定位

// 5. 水平垂直居中  6个 
// 6. flex 0 1 auto; 

// JS / API  面试题
// 1. DOM事件流
// 2. addEventListener('click', cbFn, useCapture)
// 3. 事件委托
// 4. 定时器 ==> 事件循环(还没讲)


// JS高级 
// 1. 作用域链
// 2. 垃圾回收 ==> GC , 回收策略 
// 3. 闭包 什么是闭包,它的应用,实际开发中用过了 防抖节流(return)
// 4. this指向! this在定义的时候不能确定指向,执行的时候才确定,但是箭头函数例外
// 5. 创建对象的三种方式
// 6. 数组、 字符串、Number等方法
// 7. 原型 / 原型链的理解 / 熟悉画图
// 8. 继承的实现方式 7 / 原型链  ===> 寄生式组合继承 ES6 ==> extends
// 9. 防抖,节流是什么?  应用场景, 可不可以手写
// 10. ==> 数据类型、堆栈存储,深浅拷贝 
// 11. 改变this的指向三种方式,区别?
// 12. var let const 区别
// 13. 检测数据类型的方式
// 14. new的执行过程
// 15. 伪数组转真数组
// 16. 判断是否是数组


// 1. Github / Gitee 注册
// 2. 防抖节流
// 3. 面试题,准备面试了~~~