zoukankan      html  css  js  c++  java
  • JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)

      上部分主要介绍高阶函数的常见形式,本部分将着重介绍高阶函数的高级应用。

      1.currying

      currying指的是函数柯里化,又称部分求值。一个currying的函数会先接受一些参数,但不立即求值,而是继续返回给另一个函数,通过闭包存储起来。等到函数被真正需求要求值的时候,将之前传入的参数统一起来求值。例如,我们要计算一个月的开销,我们并不需要计算每天具体花了多少,而是需要计算月底总共花掉多少,也就是说,实际上我们只需要在月底计算一次。所以每个月的前29天,我们都只需要保存好当天的开销,到30天时进行计算。我们可以借助currying函数实现。

    var currying = function(fn){
        var args = [];
    
        return function(){
            //当参数为空时执行fn
            if(arguments.length === 0){
                return fn.apply(this, args);
            }else{ //有参数时,不计算,将参数存放至args
                [].push.apply(args, arguments);
                return arguments.callee;
            }
        }
    }
    
    var cost = (function(){
        var money = 0;
    
        return function(){
            for(var i = 0, len = arguments.length; i < len; i++){
                money += arguments[i];
            }
            return money;
        }
    })()
    
    var cost = currying(cost);
    cost(100); //第一天的花费,不计算真正的值
    cost(200); //第二天的花费,不计算真正的值
    cost(300); //第三天的花费,不计算真正的值
    cost() //输出600,输出前三天的值

      2.函数节流

        在某些场景下,函数有可能被非常频繁地调用,而造成性能问题。常见的场景有:

        1.window.onresize事件,当给浏览器绑定resize事件浏览器窗口改变时,这个事件被触发的频率非常高,如果window.onresize事件中处理一些与DOM相关的操作,将造成非常大的性能影响,有可能造成浏览器卡顿;           2.mousemove事件,如果给一个div绑定了mousemove事件,在拖拽过程中往往也会频繁触发该事件;

        3.百度搜索输入框,用户输入内容,页面做出相应的搜索,通过绑定keydown事件,如果不作处理,将请求地非常频繁。

      上面几个场景的共同问题是函数被触发的频率太高,我们可以通过忽略一部分事件请求来完成函数节流,如window.onresize中,拖动窗口一秒钟可能触发了10次,但是我们只取其中的两三次。可以通过setTimeout来完成这件事。

    var throttle = function(fn, interval){
        var timer, firstTime = true;
    
        return function(){
            var args = arguments,
                me = this;
    
            //如果是第一次,则不需要延迟
            if(firstTime){
                fn.apply(me, args);
                return firstTime = false; 
            }
    
            //如果定时器还在,说明前一次的延迟执行还未完成
            if(timer){
                return false;
            }
    
            timer = setTimeout(function(){
                clearTimeout(timer);
                timer = null;
                fn.apply(me, args);
            }, interval)
        }
    }
    
    window.onresize = throttle(function(){
        console.log('Window onresize!!!')
    }, 1000)
    //测试结果:1秒内只会弹出一次Window onresize!!!

       3.分时函数

      场景如:WebQQ中的好友列表。列表中通常有几百上千好友,如果一个好友用一个节点表示,当我们要渲染这个列表时,可能需要一次性忘页面中创建成百上千个节点,在短时间内往页面中大量添加DOM节点可能会让浏览器卡顿甚至假死。可以如下代码表示:

    var arr = [];
    
    for(var i = 0; i < 1000; i++){
        arr.push(i)
    };
    
    var renderFriendList = function(data){
        for(var i = 0, l = data.length; i < l; i++){
            var div = document.createElement('div');
            div.innerHtml = i;
            document.body.appendChild(div);
        }
    };

    renderFriendList(arr);

    这个问题的解决方案是采用分时函数,将创建节点分时进行,原来一秒钟创建1000个节点,改为每隔200ms创建10个节点,代码如下:

    var timeChunk = function(arr, fn, count, time){
        var obj, timer, len = arr.length;
    
        var start = function(){
            for(var i = 0; i < Math.min(count || 1, arr.length); i++){
                var obj = arr.shift();
                fn(obj);
            }
        };
    
        return function(){
            timer = setInterval(function(){
                if(arr.length === 0){
                    return clearInterval(timer);
                    timer = null;
                }
                start();
            }, time)
        };
    };

      假设我们依然有1000个好友,我们可以利用timeChunk函数,每200ms插入一批,一批只往页面中添加10个元素;

    var arr = [];
    
    for(var i = 0; i < 1000; i++){
        arr.push(i)
    };
    
    var renderFriendList = timeChunk(arr, function(n){
        var div = document.createElement('div');
        div.innerHtml = n;
        document.body.appendChild(div);
    }, 10, 200);
    
    renderFriendList();

      4.惰性加载函数

      在Web开发中,在IE、Chrome、FireFox中事件绑定方法不一样,我们通常会这么写:

    var addEvent = function(elem, type, handler){
        if(window.addEventListener){
            return elem.addEventListener(type, handler, false);
        }
        if(window.attachEvent){
            return elem.attachEvent('on' + type, handler);
        }
    };

      这样写的缺点是,当每次调用addEvent的时候都会去执行if条件分支,显然这样并不是最佳实践,可以使用一些方法避免这些重复的执行过程。

      第二种方案是,我们把嗅探浏览器的操作提前到代码加载的时候,在加载的时候就立刻进行一次判断,以便让addEvent返回一个包裹了正确逻辑的函数,代码如下:

    var addEvent = function(){
        if(window.addEventListener){
            return function(elem, type, handler){
                elem.addEventListener(type, handler, false);            
            }
        }
        if(window.attachEvent){
            return function(elem, type, handler){
                elem.attachEvent('on' + type, handler);
            }
        }
    }();

      目前的addEvent相比之前多次执行if有较大改善,但是仍然存在缺点:如果我们从头到尾都没使用addEvent这个函数,那么一开始多嗅探浏览器将是多余的操作,这将稍稍延长页面ready的时间。

      第三种方案是惰性载入函数方案。此时的addEvent依然被声明为一个普通函数,在函数里依然有一些分支判断。但是在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们期望的addEvent函数,在下一次进入addEvent的时候,将不存在条件分支语句:

    var addEvent = function(elem, type, handler){
        if(window.addEventListener){
            addEvent =  function(elem, type, handler){
                elem.addEventListener(type, handler, false);            
            }
        }
        if(window.attachEvent){
            addEvent =  function(elem, type, handler){
                elem.attachEvent('on' + type, handler);        
            }
        }
    
        addEvent(elem, type, handler);
    };

      小结:高阶函数和闭包结合使用使得JavaScript更灵活,掌握高阶函数将加深我们对JavaScript编程的理解。

  • 相关阅读:
    无线传感器网络 与 OMNET++学习笔记(二) NED
    无线传感器网络 与 OMNET++学习笔记(一)
    win10:未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序
    XML 命名空间“clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit”中不存在标记“Chart”
    白书动态规划例题和习题简解
    BZOJ 1266
    BZOJ 1001 (UVa1376, LA3661 )
    UVa 11178
    BZOJ 1787 裸LCA
    BZOJ 2440
  • 原文地址:https://www.cnblogs.com/ppforever/p/4737819.html
Copyright © 2011-2022 走看看