javascript闭包,高阶函数及防抖节流实现

闭包与高阶函数

  • 本文描述了作用域,闭包,内存管理的理解及高阶函数面向切面编程,节流防抖函数多种实现方案

作用域

理解作用域:var a = 2,引擎干了那些活

  • 遇见 var a,编译器会询问当前作用域是否存在该变量,存在,忽略;不存在,在当前作用域下,声明该变量,并命名为 a;

  • 运行处理 a=2 时,引擎会询问当前作用域集合是否存在 a 的变量;存在就使用该变量,不存在继续向外层作用域查找该变量;找到了,将 2 赋值给 a,没找到,则抛出异常;
    总结: 变量赋值会有两个动作,当前作用域声明该变量,查找变量,找到变量进行赋值;

  • LHS 和 RHS 查询:LHS 可以简单说是左侧查询,RHS 右侧查询;LHS 查询,没找到变量会创建变量,RHS 查询,没找到变量抛出异常;

      function(a){
          var b = a;
          return a+b
      }
      var  c = foo(2);
    
      LHS:3
      RHS:4
    

变量作用域:

  • 变量有效范围。

函数作用域:

  • 函数可以用来创造函数作用域;函数可以获取外部变量,外部无法获取函数内部变量;如果函数内部没有该变量,函数会根据执行环境创造的作用域链,逐次向外层去寻找该变量。
  • 下面代码,也会帮大家理解函数作用域
          var a = 123;
          var b = 'b1'
          var fn = function () {
              var b = 1;
              var fn1 = function(){
                  var c = 'c'
                  console.log(c) // c
                  console.log(b) // 1
              }
              fn1();
              console.log(c) // c is not defined
          }
          fn()
    

变量生存周期

  • 在函数内部,函数调用结束就会被销毁;函数外部全局变量,需要手动销毁,不销毁会一直存在内存中,销毁的方案:var a = null;

闭包

  • 上面将作用域和变量生存周期简单的讲述了一下,接下来将会谈谈闭包,闭包跟这些概念非常紧密。

  • 定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行;
    实例:不是闭包的例子

    function foo() { var a = 2;
        function bar() {
            console.log( a ); // 2
        }
        bar();
     }
    foo();
    

    实例:闭包效果的实例

     function foo() {
       var a = 2;
        function bar() {
            console.log( a ); // 2
        }
       return  bar;
     }
    var baz = foo();
    baz();
    

    分析: 闭包函数外部作用域,可以访问函数内部词法作用域的变量。baz()对该作用域进行引用,这个引用就叫做闭包

  • 为了更好的理解闭包,下面在写两个例子

    实例 1:

     function fo (){
         var a = 2
         function ba(){
             console.log(a)
         }
         bg(ba)
     }
     function bg(fn){
         fn() // 闭包
     }
     fo()
    

    实例 2:

    var bg;
    function fo (){
       var a = 2
       function ba(){
           console.log(a)
       }
      bg = ba;
    }
    function bar(){
       bg() //闭包
    }
     fo();
     bar();
    

    分析: 总而言之,闭包能将函数内部的作用域,传递到函数外部作用域,进行引用,都会使用闭包。

闭包与内存管理

  • 经常也会听说闭包,会造成内存泄漏,这个说法并不正确。内存泄漏,得看根对象里有多少引用,没有被根对象引用得变量或者引用类型,都会被标记-清除算法给回收。其实大量得闭包引用被滥用,才是导致内存得元凶。函数内部得局部变量在函数调用完就会被回收。对于全局得引用变量,则是可以通过引用计数进行垃圾回收。循环引用,造成的内存泄漏,只要将循环引用类型的变量设置为null,浏览器就会通过标记清除算法,进行回收内存。记住一点,闭包并不会造成内存泄漏。
闭包封装函数
 var fn4 = (function(){
        var cache = {};
        return function(){
            var args = Array.prototype.join.call(arguments, ',')
            if(cache[args]){
                return cache[args]
            }
            var a = 1;
            for(var i = 0;i<arguments.length; i++){
                a = a*arguments[i]
            }
            return cache[args] = a;
        }
    })()

高阶函数

  • 高阶函数可以说是日常开发中经常用的,其实可以说高阶函数是闭包的一种运用。那咱们先来看看高阶函数的定义吧;
  • 定义: 函数可以作为参数被传递; 函数可以作为返回值输出;这两个条件满足其中一个就可以称为高阶函数。
  • 函数作为参数,像 Array.prototype.sortArray.prototype.filter等等,都属于高阶函数
  • 函数作为返回值:示例,数据类型判断
    var isType = function(type){
          return function(obj){
           
              return Object.prototype.toString.call(obj) === '[object '+type+']'
          }
        }
    
        var isArray = function(obj){
            return isType('Array').call(null, obj);
        }
        var isNumber = function(obj){
            return isType('Number').call(null, obj)
        }
    

高阶函数实现AOP编程:

AOP就是面向切面编程,把核心功能无关写逻辑抽离出来,通过动态植入方式,掺进逻辑模块中;达到模块的纯净与高内聚。

1. 示例:

 Function.prototype.before = function(fn){
      var that = this; // function
      return function(){
          fn.apply(this, arguments);
          return that.apply(this,arguments)
      }
  }
  Function.prototype.after = function(fn){
      var that = this;
      return function(){
         var self = that.apply(this, arguments)// 执行原函数,形成链式调用
          fn.apply(this, arguments) // 执行掺入函数,修正this
          return self
      }
  }
  var fng1 = function(){
      console.log(1)
  }
  var fng2 = fng1.before(function(){
      console.log(0)
  }).after(function(){
      console.log(2)
  })

高阶函数实现柯里化函数:currerying

   var cont = (function(){
      var money = [];
      return function(){
          if(arguments.length === 0){
              var mon = 0;
              for(var i = 0; i<money.length; i++){
                  mon +=money[i]
              }
              return mon
          }else{
              Array.prototype.push.apply(money, arguments)
          }
      }
  })()
  cont(100);
  cont(200);
  console.log(cont())

uncurrying函数实现

Function.prototype.unCurrying = function(){
     var that =this;
     return function(){
         var o = Array.prototype.shift.call(arguments);
         
         return that.apply(o, arguments)
     }
 }
  var push = Array.prototype.push.unCurrying();
  var num =  (function(){
     push(arguments, 4)
     console.log(arguments)
 })(1,2,3)

分析: 上面的代码是由javascript的之父 Brendan Eich写的一段代码,其实上面的一段代码的效果也可以通过Array.prototype.push.call(arguments, 4)是一样的结果,关键在这种思维方法。
push方法接受两个参数,num函数的argument和4,Array.prototype.push.unCurrying();表示unCurrying方法复制了数组的push方法;而unCurrerying方法的返回值就是Function对象中的unCurreying方法并且实现了arguments继承数组的push方法,并且把4中对应argumentsd的值,通过apply的显性绑定,给添加到第一个arguments的值中去。这个时候闭包中nums对应的arguments
的长度就是4,参数就是1234;

节流与防抖

1. setTimeout节流

var setThrottle =  (function(){
    var throFlag = true; //节流判断
    return function(fn, time){
        var that = this;
        if(!throFlag){
            return false;
        }
        throFlag = false;
        setTimeout(()=>{
            fn.apply(this, arguments)
            throFlag = true;
        }, time)
    }
})();

2. date时间值节流

const setDateThro = (function(){
    let old = Date.now();
    return function(fn, time){
        if(Date.now()- old >= time){
            fn.apply(this, arguments);
            old = Date.now();
        }
    }
})()

3. 混合节流,最后一次也需要执行该函数

 const componentThro = (function(){
        let old = Date.now();
        let timeout;
        return function(fn, time){
            const args = arguments;
            clearTimeout(timeout)
            if(Date.now()- old > time){
                fn.apply(this, args);
                old = Date.now();
            }else{ // 事件完结后再执行一次
                
                timeout = setTimeout(()=>{
                    fn.apply(this, args);
                }, time)
            }
        }
    })()
        div1.onclick = function(){
            componentThro(()=>{
                console.log('节流')
            }, 1000)
        }

4. 防抖,第一次执行

  var deben = (function(){
        let timeout = undefined;
        let preFlag = true;
        return function(fn, time){
            const args = arguments;
            if(preFlag){ // 第一次执行一次;
                fn.apply(this, args)
            }
            preFlag = false
            clearTimeout(timeout)
            timeout = setTimeout(()=>{
                preFlag = true;
            }, time)
          
        }
    })()
        div1.onclick = function(){
            deben(()=>{
                console.log('防抖')
            }, 1000)
        }

5. 防抖,最后一次点击执行

 var deben = (function(){
    let timeout = undefined;
    return function(fn, time){
        const args = arguments;
        
        preFlag = false
        clearTimeout(timeout)
        timeout = setTimeout(()=>{
            
            fn.apply(this, args)
            
        }, time)
        
    }
})()
div1.onclick = function(){
        deben(()=>{
            console.log('防抖')
        }, 1000)
}

6. 防抖,第一次和最后一次都需要执行

var deben = (function(){
    let timeout = undefined;
    let preFlag = true;
    return function(fn, time){
        const args = arguments;
        if(preFlag){ // 第一次执行一次;
            fn.apply(this, args)
        }
        preFlag = false
        clearTimeout(timeout)
        timeout = setTimeout(()=>{
            
            fn.apply(this, args)
            preFlag = true;
        }, time)
        
    }
})()
div1.onclick = function(){
    deben(()=>{
        console.log('防抖')
    }, 1000)
}

参考文档

  • 节流
  • javascript 设计模式与开发实践
  • 你不知道的javascript
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章