JavaScript高级,ES6 笔记 第四天(防抖、节流、深拷贝,事件总线)

目录

throw

try  catch  finally

浅拷贝

深拷贝

 1、递归方式

2.loadsh

3.JSON(推荐)

call

apply

bind

 节流

 防抖

视频案例

事件总线


throw

抛出自定义错误

<!DOCTYPE html>
<html>
<body>

<p>Please input a number between 5 and 10:</p>

<input id="demo" type="text">
<button type="button" onclick="myFunction()">Test Input</button>
<p id="message"></p>

<script>
function myFunction() {
    var message, x;
    message = document.getElementById("message");
    message.innerHTML = "";
    x = document.getElementById("demo").value;
    try {
        if(x == "") throw "is Empty";
        if(isNaN(x)) throw "not a number";
        if(x > 10) throw "too high";
        if(x < 5) throw "too low";
    }
    catch(err) {
        message.innerHTML = "Input " + err;
    }
}
</script>

</body>
</html>

try  catch  finally

try里面放可能会出错误的代码    catch是当发生错误时执行的,且有参数,代表错误本身,finally表示不管有没有错误都会执行

<body>
  <p>123</p>

  <script>
    try{
      let ss = document.querySelector('.p');//Cannot read properties of null (reading 'style')
      ss.style.color = 'red'
    }catch(err){
      console.log(err.message);

    }finally{
      alert('sdasda')
    }

    
  
  </script>
  
</body>

浅拷贝

浅拷贝与深拷贝都只针对  引用类型

浅拷贝拷贝的是值(其实也是地址)

    <script>
        const obj={
            age :18
        }

        let o = {...obj}
        let o1={}
        Object.assign(o1,obj)
        o1.age=20
        console.log(o1);//age =20
        console.log(obj);//age =18

    </script>
</body>

但是这样有问题:

        const obj={
            age :18,
            obj1:{
                sex:1

            }
        }

        let o = {...obj}
        let o1={}
        Object.assign(o1,obj)
        o1.obj1.sex = 2
        console.log(o1);
        console.log(obj);//sex都为2

欠拷贝只直接拷贝最外一层的值,而对于obj1来说,最外层的值是他的地址,所以就直接把地址给了o1,改变o1时也会改变obj

深拷贝

直接拷贝对象 不是地址

 1、递归方式

设置函数,当originValue里面还有引用数据类型时,再次调用自己,只是将originValue改为里面的引用数据类型

    <script>
    const obj = {
      uname: 'pink',
      age: 18,
      hobby: [{ name: 2222, age: 2222 }, '足球',new Set([123, 22, 333])],
      family: {
        baby: '小pink'
      },
      run: function () {
        console.log(1);
      },
      sete: new Set([123, 22, 333]),
      asas:Symbol('asd')
    }
    obj.self = obj

    function deepcopy1(originValue,map = new WeakMap()){
      if(typeof originValue === 'symobol'){
        return new Symbol(originValue.desciption)
      }

      if(!(originValue instanceof Object)){
        return originValue
      }
      if(typeof originValue === 'function'){//函数不需要深拷贝
        return originValue
      }

      if(originValue instanceof Set){
        const newSet = new Set()
        for(let item of originValue){
          newSet.add(deepcopy1(item,map))
        }
        return newSet
      }

      if(map.get(originValue)){//当我遍历到self,self需要我自身,也就是obj,这里直接把map中的obj拿给他
        return map.get(originValue)
      }

      let newobj = originValue instanceof Array?[]:{}  //如果originValue是单纯的对象或数组
      map.set(originValue,newobj)
      for(let key in originValue){
        newobj[key] = deepcopy1(originValue[key],map)
      }
      return newobj
    }
    let ss = deepcopy1(obj)
    console.log(ss);



      </script>

这里比较难的地方在于    obj.self = obj  ,也就是在obj中创造了self指向自己

在深拷贝时,传入map = new WeakMap()用于记录已经生成的应用对象,当拷贝到self时,这个时候又需要创造一个obj对象,这时可以将map中存储好的obj直接拿出来赋值给它

2.loadsh

先去下载库,并引入js

Lodash 简介 | Lodash 中文文档 | Lodash 中文网

调用cloneDeep()

        var oo = _.cloneDeep(obj)
        console.log(oo);

3.JSON(推荐)

JSON 语法 | 菜鸟教程

    <script>
        const obj = {
          uname: 'pink',
          age: 18,
          hobby: ['乒乓球', '足球'],
          family: {
            baby: '小pink'
          }
        }

        // JSON.stringify(obj)
        //将对象转换为JSON字符串
        let o =JSON.parse(JSON.stringify(obj))//将JSON字符串转换为对象



      </script>

但是JSON有个问题,如果 对象有  函数  或者  symbol  则无法解析

call

可以改变this指向,在调用的时候用,第一个值就是设置指向

        let obj={

        }

        function fn(x,y){
            console.log(this);
        }
        fn.call(obj,1,2)//obj

apply

也是改变this指向 ,类似,传参必须为数组,这个用的比较多

        let obj={
        }
        function fn(x,y){
            console.log(this);//obj
            console.log(x+y);//3
        }
        fn.apply(obj,[1,2])

值得注意的是,当不匹配时:

        let obj={

        }

        function fn(x){
            console.log(this);//obj
            console.log(x);//1
        }
        fn.apply(obj,[1,2])

不会将数组全部传入

提供了另外一种求最大值的方式

        let aa = [1,4,5,8,9]
        //Math.max(1,4,7,98)

        console.log(Math.max(...aa));
        console.log(Math.max.apply(null,aa));

bind

前面两种都必须要调用函数,这个不需要,返回值是函数,其实就是改变原函数的this得到了一个新函数

        let aa = [1,4,5,8,9]
        function fn(){
            console.log(this);
        }
        fn.bind(null)//无输出

        let fun = fn.bind(aa);
        fun()//a

小应用,点击按钮禁用,两秒开启

    <button>点击禁用,两秒开启</button>
    <script>
        let btn = document.querySelector('button');
        btn.addEventListener('click',function(){
            this.disabled = true
            setTimeout(function(){
                this.disabled = false
            }.bind(this),2000)
        })

      </script>

这个地方把定时器的this改为了btn,因为bind(this)里面这个this还是btn

 节流

在一定的时间内只能执行一次,不管触发了多少次,在这个时间内只执行一次

例子:div滑动时,200ms内只改变span一次

        var start = 0

        document.querySelector('div').addEventListener('mousemove',function(){
            let now = Date.now()
            if(now-start>=200){
                    document.querySelector('span').innerHTML =i++
                    start = now
                }

        })

每次调用函数时都将此时的时间赋给now,当now与start差别大于200毫秒时才会改变i,然后将现在的时间赋给start以进行下次函数

上面用的是时间差值的方法,也可以采用定时器的方法

这里再加了一个immediate 变量,具体原理参照  防抖

<body>
  <input type="text">
  <button>cancel</button>

  <button class="throttle">throttle</button>

  <script>
    let ipt = document.querySelector('input')

    let mythrottle = function(fn,interval,immediate =true){
      let timeout = null
      let immediate_done = false
      let _throttle = function(){
        if (!immediate_done && immediate) {
            fn.apply(this)
            immediate_done = true
            return
          }

        if(!timeout){
          timeout = setTimeout(()=>{
          fn.apply(this)
          timeout = null
        },interval)
        }
      }
      return _throttle
    }

    ipt.addEventListener('input',mythrottle(function(){
      console.log(this.value);
    },1000))
  </script>

 防抖

触发后n秒内只执行一次,但是如果这段时间内还触发了,则会重新计算时间

也就是说,在一定时间内我不停的进行高速的事件触发,但是只会在最后一次触发结束后进行执行

应用场景:输入框,一般是等我输入完了才会去匹配

例子:在div滑动时 停下来一秒钟才会改变span

        document.querySelector('div').addEventListener('mousemove',function(){
            if(timeget) clearTimeout(timeget)
            timeget = setTimeout(function(){
                document.querySelector('span').innerHTML =i++

            },1000)
        })

例子:再次采用函数实现,需求是对输入框,输入结束一段时间后反应,这里还提供了应该取消接口,可以在这段时间结束前 结束响应

    <input type="text">
    <button>cancel</button>

    <script>
        let mydebounce = function(fn,delay){
            let timeout = null 
            let _debounce = function(){
                if(timeout) clearTimeout(timeout)
                timeout = setTimeout(()=>{
                    fn.apply(this)
                },delay)
            }
            _debounce.cancel = function(){
                if(timeout) clearTimeout(timeout)
            }
            return _debounce
        }

        let ipt = document.querySelector('input')
        let btn = document.querySelector('button')
        let debounce = mydebounce(function(){
            console.log(this.value);
        },2000)
        ipt.addEventListener('input',debounce)
        btn.addEventListener('click',debounce.cancel)
    </script>
</body>

再加一个立即执行功能,即在输入时,第一次输入马上响应,后面输入则等待一段时间,然后再次输入又马上响应

        let mydebounce = function(fn,delay,immediate=false){
            let timeout = null 
            let immediate_done = false
            let _debounce = function(){
                if(timeout) clearTimeout(timeout)
                if(!immediate_done && immediate){
                    fn.apply(this)
                    immediate_done = true
                    return
                }
                timeout = setTimeout(()=>{
                    fn.apply(this)
                    immediate_done = false
                },delay)
            }
            _debounce.cancel = function(){
                if(timeout) clearTimeout(timeout)
                immediate_done = false
            }
            return _debounce
        }

定义了 immediate表示是否需要这个功能,immediate_done判断是否完成,当输入时,immediate为1,immediate_done为0时,会马上执行fn函数,然后使得immediate_done为1。在执行完时间间隔后,又将immediate_done为0

还可以将_debounce函数返回为promise,好处是在fn传入时有返回值的情况下,可以拿到这个返回值

      let res = null
      let _debounce = function () {
        return new Promise((resolve, reject)=>{
          if (timeout) clearTimeout(timeout)
          if (!immediate_done && immediate) {
            res = fn.apply(this)
            resolve(res)
            immediate_done = true
            return
          }

          timeout = setTimeout(() => {
            res = fn.apply(this)
            resolve(res)
            immediate_done = false
          }, delay)
        })
      }

 若fn有返回值,就可以通过.then得到

    let ipt = document.querySelector('input')
    let btn = document.querySelector('button')

    let debounce = mydebounce(function () {
      console.log(this.value);
      return(111)
    }, 1000)


    ipt.addEventListener('input', debounce)
    btn.addEventListener('click', debounce.cancel)

    debounce().then(res=>{
      console.log(res);
    })

 还可以使用lodash

节流

_.throttle(fn,300)

在300ms内只能执行一次fn函数

        function fn(){
            document.querySelector('span').innerHTML =i++
        }
        document.querySelector('div').addEventListener('mousemove',_.throttle(fn,300))

        

 防抖

 _.debounce(fn,300)

停下来300ms后才执行fn函数

        function fn(){
            document.querySelector('span').innerHTML =i++
        }
        document.querySelector('div').addEventListener('mousemove',_.debounce(fn,300))

视频案例

    const video = document.querySelector('video')
    video.onloadeddata = function(){
        video.currentTime  = localStorage.getItem('time')||0
    }
    video.ontimeupdate = _.throttle(function(){
        localStorage.setItem('time',video.currentTime)
    },1000)

得到video视频

每次打开页面都把存储中的time拿出来,赋值给视频的currentTime,即跳转到对应的时间

视频的时间发生改变时,添加防抖,每隔1s更新存储中的time值

实现每次页面打开可以返回上次视频播放的地方

事件总线

当项目中有很多组件,很多文件,当一个文件中的某一个事件触发,希望另外一个文件得到响应,就需要用到事件总线

这里我们自己用js实现

一般来说这个事件总线是一个对象,里面有emit函数,用于发送时间,也有on函数,用于接受事件

  <button>aaaa</button>
  <script>
    class EventBus{
      constructor(){
        this.eventMap = {}//这个map用于存储on绑定的函数
      }
      on(eventname,fn){
        let fns = this.eventMap[eventname]
        if(!fns){//第一次如果没有函数数组时,自己创造一个
          fns=[]
          this.eventMap[eventname] = fns
        }
        fns.push(fn)//将回调函数放入对应的函数数组
      }

      emit(eventname,...args){
        let fns = this.eventMap[eventname]
        if(!fns) return
        fns.forEach(fn => {
          fn(...args)
        });
      }
    }
    let eventBus = new EventBus()

    eventBus.on('btnclick',()=>{
      console.log(1111);
    })

    eventBus.on('btnclick',()=>{
      console.log(2222);
    })

    let btn = document.querySelector('button')
    btn.addEventListener('click',function(){
      console.log('自己得到');
      eventBus.emit('btnclick')
    })
  </script>

因为on函数,对于一种事件的响应函数可能不止一个,所以需要eventMap来存储函数,其数据结构为{ eventname:[fn1,fn2]}

在emit时,先取得事件对应的函数数组,再以此执行

还可以写一个删除函数函数

      off(eventname,fn){
        let fns = this.eventMap[eventname]
        if (!fns) return
        for(let i=0;i<fns.length;i++){
          if(fn === fns[i]){
            fns.splice(i,1)
            break 
          }
        }
        //如果fns已经被清空了,那直接将事件删除
        if(fns.length==0){
          delete this.eventMap[eventname]
        }
      }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值