乐趣区

闭包和高阶函数

许多模式都可以使用闭包和高阶函数来实现

3.1 闭包

闭包的形成与变量的作用域以及变量的生命周期密切相关

3.1.1 变量的作用域

变量的作用域,就是指变量的有效范围。我们最常谈到的是在函数中声明的变量作用域。

  1. 在 JavaScript 中,函数可以用来创造函数作用域。此时的函数像一层半透明的玻璃,在函数里面可以看到外面的变量,而在函数外面则无法看到函数里面的变量。

    • 这是因为当在函数中搜索一个变量的时候,如果该函数内并没有声明这个变量,那么此次搜索的过程会随着代码执行环境创建的作用域链往外层逐层搜索,一直搜索到全局对象为止。
    • 变量的搜索是从内到外而非从外到内的。

3.1.2 变量的生存周期

  1. 全局变量生存周期是永久的,除非我们主动销毁
  2. 而对于 var 声明的局部变量,当退出函数时,它们会随着函数调用的结束而被销毁。

example 1: 遍历绑定点击事件

for(var i = 0;i<;i++){nodeArr[i].oncick = function(){alert(i)
    }
}

点击事件为异步事件,在点击时循环已完毕, 故每一个点击事件都会得到 i = 5

method: 使用闭包把每次循环的 i 值封闭起来

for (var i =0;i<5;i++){(nodeArr[i].onclick = funcioton(){alert(i)
     })(i)
}

也可使用 es6 let。let 声明封闭作用域

3.1.3 闭包的更多作用

  1. 封装变量:闭包可以帮助把一些不需要暴露在全局的变量封装成”私有变量“

    example 可缓存计算函数

    var mult = function(){
        var a = 1
        for (var i = 0, l=arguments.length;i<;i++){a = a*arguments[s]
        }
        return a 
    }

    上面为最简易进行参数乘积的函数,接下来进行对代码提炼封装,以及闭包封装缓存

    var mult  = (function(){var cahe = {}
        // 封装功能函数,有助于代码复用性,用户计算不定个数入参乘积
        var calculate = function(){
            var a = 1
            for (var i = 0,l=arguments.length;i<l;i++){a = a*arguments[i]
            }
            return a 
        }
        return function(){
            // 将入参通过 join 连接成字符串作为 object key 存入缓存中,key 部分可以扩展如 1,3,5 与 3,1,5 为同一结果
            var args = [].join.call(arguments,',')
            // 如果缓存过直接返回值
            if(args in cache){return cache[args]
            }
            // 将未缓存加入缓存并返回
            return cache[args] = calculate.apply(null,arguments)
        }
        
    })()
    
    
  2. 延续局部变量的寿命

img 对象经常用于进行数据上报,如下所示:

var report = function(src){var img = new Image(); 
 img.src = src; 
}; 
report('http://xxx.com/getUserInfo');

在这些浏览器下使用 report 函数进行数据上报会丢失 30% 左右的数据,也就是说,report 函数并不是每一次都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的调用结束后,img 局部变量随即被销毁 ,而此时或许还没来得及 发出 HTTP 请求,所以此次请求就会丢失掉。

现在我们把 img 变量用闭包封闭起来,便能解决请求丢失的问题:

 var report = (function(){var imgs = []; 
 return function(src){var img = new Image(); 
 imgs.push(img); 
 img.src = src; 
 } 
})();

3.1.4 闭包和面向对象设计

  1. 过程与数据的结合是形容面向对象中的“对象”时经常使用的表达。
  2. 对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。

3.1.5 闭包与内存管理

  1. 闭包导致局部变量一直生存下去,使一些数据无法被及时销毁
  2. 如果需要回收变量,可以手动把这些变量设为 null
  3. 使用闭包的同时容易形成循环引用
  4. 在 ie 中 BOM 和 DOM 中的对象是使用 C++ 以 COM 对象的方式实现,而 com 对象的垃圾回收机制采用的使引用计数策略。如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。
  5. 垃圾收集器会将值为 null 的变量删除并回收他们占用的内存
  6. 将变量设置为 null 意味着切断变量与它此前引用的值之间的连接

3.2 高阶函数

高阶函数至少满足以下一个条件

  • 函数可以作为参数被传递
  • 函数可以作为返回值输出

3.2.1 函数作为参数传递

  1. 回调函数

    • 在 ajax 异步请求中,回调函数使用频繁。当我们想在 ajax 请求返回后做一些事情,最常见的方法使把 callback 函数当作参数传入发起 ajax 请求的方法中
  2. 用于将逻辑代码抽离

    var appendDiv = function(callback){for ( var i = 0; i < 100; i++){var div = document.createElement( 'div'); 
     div.innerHTML = i; 
     document.body.appendChild(div); 
     if (typeof callback === 'function'){callback( div); 
     } 
     } 
    }; 
    appendDiv(function( node){node.style.display = 'none';});

    可以看到,隐藏节点的请求实际上是由客户发起的,但是客户并不知道节点什么时候会创建好,于是把隐藏节点的逻辑放在回调函数中,“委托”给 appendDiv 方法。appendDiv 方法当然知道节点什么时候创建好,所以在节点创建好的时候,appendDiv 会执行之前客户传入的回调函数。

    3.Array.prototype.sort 方法传入一个回调作为排序的规则,使 sort 成为一个非常灵活的方法

3.2.2 函数作为返回值输出

将函数作为返回值出,更能体现函数式编程的巧妙。让函数返回一个可执行的函数,意味着运算过程使可延续的

  1. 判断数据类型

    用 Object.prototype.toString 来计算。Object.prototype.toString.call(obj)返回一个字符串,比如 Object.prototype.toString.call([1,2,3] ) 总是返回 “[object Array]”,而 Object.prototype.toString.call(“str”)总是返回 ”[object String]”。

     // 通过 return 一个函数接收外部函数传入的参数,进行参数的嵌套,var isType = function(type){return function( obj){return Object.prototype.toString.call( obj) === '[object'+ type +']'; 
     } 
    };
    // 接收第一个参数赋值不同类别的判断器
    var isString = isType('String');
    var isArray = isType('Array'); 
    var isNumber = isType('Number'); 
    // 接收第二个参数进行运算
    console.log(isArray( [ 1, 2, 3] ) );

3.2.3 高阶函数实现 AOP

  1. AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
  2. 把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。
  3. 这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,
  4. 其次是可以很方便地复用日志统计等功能模块。
  5. 通常 js 通过把一个函数”动态织入“另一个函数中
example
Function.prototype.before = function(beforefn){ 
 var __self = this; // 保存原函数的引用
 return function(){ // 返回包含了原函数和新函数的 "代理" 函数
 beforefn.apply(this, arguments); // 执行新函数,修正 this 
 return __self.apply(this, arguments); // 执行原函数
 } 
}; 
Function.prototype.after = function(afterfn){ 
 var __self = this; 
 return function(){var ret = __self.apply( this, arguments); 
 afterfn.apply(this, arguments); 
 return ret; 
 } 
}; 
var func = function(){console.log( 2); 
}; 
func = func.before(function(){console.log( 1); 
}).after(function(){console.log( 3); 
}); 
func();
退出移动版