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>
  • 相关阅读:
    深入浅出数据库索引原理
    Mysql读写分离原理及主众同步延时如何解决
    数据库连接池实现原理
    MySQL 大表优化方案(长文)
    js-ajax-03
    js-ajax-04
    js-ajax-02
    js-ajax-01
    获取html对象方式
    js-事件总结
  • 原文地址:https://www.cnblogs.com/6489c/p/5935019.html
Copyright © 2011-2022 走看看