zoukankan      html  css  js  c++  java
  • JavaScript设计模式与开发实践-读书笔记(3)闭包和高阶函数

    闭包(closure)

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

    变量的作用域,就是指变量的有效范围。

    全局变量和局部变量。

    在JavaScript中,函数可以用来创造函数作用域。

    变量的生存周期,全局变量的生命周期是永久的,除非我们主动销毁这个全局变量。

    对于在函数体内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。

    利用闭包我们可以完成许多奇妙的工作。

    闭包的作用:

    1.封转变量

     闭包可以帮助我们把一些不需要暴露在全局的变量封转成"私有变量"。

    2.延续局部变量的寿命

     img对象经常用于数据上报,我们可以把img变量用闭包封转起来,便能解决请求丢失的问题

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

    可以利用闭包实现一个完整的面向对象系统。
    用闭包实现命令模式

    闭包是一个非常强大的特性,但人们对其也有诸多误解。一种耸人听闻的说法是闭包会造成内存泄露,所以要尽量减少闭包的使用。

    使用闭包的同时比较容易形成循环引用,如果闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,这时候可能造成内存泄露。但这本身并非闭包的问题,也并非JavaScript的问题。

    高阶函数

    高阶函数是指至少满足下列条件之一的函数。

    函数可以作为参数被传递;

    函数可以作为返回值输出。

    函数作为参数传递

    1.回调函数

    在ajax异步请求的应用中,回调函数的使用非常频繁。

    回调函数的应用不仅只在异步请求中,当一个函数不适合执行一些请求时,我们也可以把这些请求封转成一个函数,并把它作为参数传递给另外一个函数,"委托"给另外一个函数来执行。

    2.Array.prototype.sort

    Array.prototype.sort接受一个函数作为参数,这个函数里面封转了数组元素的排序规则。我们的目的是对数组进行排序,这是不变的部分;而使用什么规则去排序,则是可变的部分。

    函数作为返回值传递

    1.判断数据的类型

    2.getSingle

    单例模式的例子:

    var getSingle = function(fn){
        var ret;
        return function(){
            return ret || (ret = fn.apply(this,ar;guments))
        };
    };
    //这个高阶函数的例子,既把函数当作参数传递,又让函数执行后返回了另一个函数。
    var getScript = getSingle(function(){
        return document.createElement('script');
    });
    
    var script1 = getScript();
    var script2 = getScript();
    alert(script1 === script2);//输出:true

    高阶函数实现AOP

    AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过"动态织入"的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便的复用日志统计等功能模块。

    在JavaScript这种动态语言中,AOP的实现更加简单,这是JavaScript与生俱来的能力。

    通常,在JavaScript中实现AOP,都是指把一个函数"动态织入"到另一个函数之中。

    常见的高阶函数的应用

    1.currying

    函数柯里化(function currying)。currying又称部分求值。一个curring的函数首先会接受一些参数,接受这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

     在如下的例子里,这个函数的作用遍历本月每天的开销并求出它们的总和:

    var currying = function(fn){
            var args = [];
            return function(){
                if(arguments.length === 0){
                    return fn.apply(this,args);
                }else{
                    [].push.apply(args,arguments);
                    return arguments.callee;
                }
            }
        };
    
        var cost = (function(){
            var money = 0;
            return function(){
                for(var i=0;l = arguments.length,i<l;i++){
                    money+=arguments[i];
                }
                return money;
            }
        })();
    
        var cost = currying(cost); //转化为currying函数
        cost(100);//未真正求值
        cost(200);//未真正求值
        cost(300);//未真正求值
        alert(cost());//求值并输出600

    只有当我们以不带参数的形式执行cost()时,才利用前面保存的所有参数,真正开始进行求值计算。

    2.uncurrying

    有没有办法把泛化this的过程提取出来呢,uncurrying就是用来解决这个问题的。以下代码是uncurrying的实现方式之一:

    Function.prototype.uncurrying = function(){
        var self = this;
        return function(){
            var obj = Array.prototype.shift.call(arguments);
            return self.apply(obj,arguements);
        }
    };

    另一种实现方式:

    Function.prototype.uncurrying = function(){
        var self = this;
        return function(){
            return Function.prototype.call.apply(self,arguements);
        }
    };

    3.函数节流

    (1)函数有可能被非常频繁的调用,而造成大的性能问题

         如下场景:

    1. window.onresize事件
    2. mousemove事件
    3. 上传进度

    (2)函数节流的原理

      按时间段来忽略一些事件请求。很显然,可以借用setTimeout来完成这件事情。

    (3)函数节流的代码实现

      关于函数节流的代码实现有许多种,下面的throttle函数的原理是,将即将被执行的函数用setTimeout延迟一段时间执行。

      

        var throttle = function(fn,interval){
            var _self = fn,//保存需要被延迟执行的函数调用
                timer,//定时器
                firstTime = true;//是否是第一次调用
            return function(){
                var args = arguments,
                    _me = this;
                if(firstTime){    //如果是第一次调用,不需延迟执行
                    _self.apply(_me,args);
                    return firstTime = false;
                }
                if(timer){    //如果定时器还在,说明前一次延迟执行还没有完成
                    return false;
                }
                timer = setTimeout(function(){    //延迟一段时间执行
                    clearTimeout(timer);
                    timer = null;
                    _self.apply(_me,args);
                },interval||500);
            };
        };
    
        window.onresize = throttle(function(){
            console.log(1);
        },500);

    4.分时函数

    例子是创建WebQQ的QQ好友列表。列表中通常会有成百上千个好友,如果一个好友用一个节点来表示,当我们在页面中渲染这个列表的时候,可能要一次性往页面中创建成百上千个节点。

    在短时间内往页面中大量添加DOM节点显然也会让浏览器吃不消,我们看到的结果往往就是浏览器的卡顿甚至假死。

    这个问题的解决方案之一是下面的timeChunk函数,timeChunk函数让创建节点的工作分批进行,比如1秒钟创建1000个节点,改为每隔200毫秒创建8个节点。

        var timeChunk = function(ary,fn,count){
            var obj,
                t;
            var len = ary.length;
            var start = function(){
                for(var i=0;i<Math.min(count||1,ary.length);i++){
                    var obj = ary.shift();
                    fn(obj);
                }
            };
    
            return function(){
                t = setInterval(function(){
                    if(ary.length ===0){    //如果全部节点都已经被创建好
                        return clearInterval(t);
                    }
                    start();
                },200);    //分批执行的时间间隔,也可以用参数的形式传入
            };
        };
    
        var ary = [];
        for(var i=1;i<=1000;i++){
            ary.push(i);
        };
        var renderFriendList = timeChunk(ary,function(n){
            var div = document.createElement('div');
            div.innerHTML = n;
            document.body.appendChild(div);
        },8);
    
        renderFriendList();

    5.惰性加载函数

    因为浏览器之间的实现差异,一些嗅探工作总是不可避免。惰性载入函数方案。

    在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们期望的addEvent函数,在下一次进入addEvent函数的时候,addEvent函数里不再存在条件分支语句。

    <body>
        <div id="div1">点击我绑定事件</div>
    </body>
    </html>
    <script>
        var addEvent = function(elem,type,handler){
            if(window.addEventListener){
                addEvent = function(elem,type,handler){
                    elem.addEventListener(type,handler,false);
                }
            }else if(window.attachEvent){
                addEvent = function(elem,type,handler){
                    elem.attachEvent('on'+type,handler);
                }
            }
            addEvent(elem,type,handler);
        };
    
        var div = document.getElementById('div1');
        addEvent(div,'click',function(){
            alert(1);
        });
        addEvent(div,'click',function(){
            alert(2);
        });
    </script>
  • 相关阅读:
    UVA 1386 Cellular Automaton
    ZOJ 3331 Process the Tasks
    CodeForces 650B Image Preview
    CodeForces 650A Watchmen
    CodeForces 651B Beautiful Paintings
    CodeForces 651A Joysticks
    HUST 1601 Shepherd
    HUST 1602 Substring
    HUST 1600 Lucky Numbers
    POJ 3991 Seinfeld
  • 原文地址:https://www.cnblogs.com/6489c/p/5935019.html
Copyright © 2011-2022 走看看