• JavaScript设计模式与开发实践 闭包和高级函数


    1、闭包

    1.1 变量的生命周期

      除了变量的作用域,另一个域闭包有关的概念是变量额生存周期。

      函数内部用var声明的局部变量,退出函数时,会随着函数调用的结束而被销毁.

    var func = function() {
        var a = 1;
        alert(a);
    }
    
    func();
        var func = function(){
            var a = 1;
            return function(){
                a++;
                alert ( a );
            }
        };
    
        var f = func();
    
        f(); // 输出:2
        f(); // 输出:3
        f(); // 输出:4
        f(); // 输出:5

      这里当执行var f = func()后,f返回了一个匿名函数的引用,它可以访问到func()被调用时产生的环境,而局部变量a一直处在这个环境里,既然局部变量所在的环境还能被外界访问,这个局部变量就有不被销毁的理由。

    var nodes = document.getElemensByTagName('div');
    
    for(var i = 0; i < node.length; i++) {
        (function(i){
            nodes[i].onclick = function() {
                alert(i)  
            }
       })(i)
    }

      使用闭包把每次循环的i封闭,当事件函数顺着作用域链从内到外查找i,会先找到在被封闭在闭包环境的i。

    1.2 闭包的更多作用

    1.封装变量

      闭包可以把不需要再全局的变量封装成“私有变量”。

        //假设有一个计算乘积的简单函数:
        var mult = function(){
            var a = 1;
            for ( var i = 0, l = arguments.length; i < l; i++ ){
                a = a * arguments[i];
            }
            return a;
        };
        //我们可以加入缓存机制来提高这个函数的性能:
        //将输入的参数和输出的结果保存到cache对象
        var cache = {};
        var mult = function(){
            var args = Array.prototype.join.call( arguments, ',' );
            //每次执行先查询cache里有没执行过使用这个参数
            if ( cache[ args ] ){
                return cache[ args ];
            }
    
            var a = 1;
            for ( var i = 0, l = arguments.length; i < l; i++ ){
                a = a * arguments[i];
            }
            return cache[ args ] = a;
        };
    
        alert ( mult( 1,2,3 ) ); // 输出:6
        alert ( mult( 1,2,3 ) ); // 输出:6
        //将cache放在mult内部,避免引发错误
        var mult = (function(){
            var cache = {};
            return function(){
                var args = Array.prototype.join.call( arguments, ',' );
                if ( args in cache ){
                    return cache[ args ];
                }
                var a = 1;
                for ( var i = 0, l = arguments.length; i < l; i++ ){
                    a = a * arguments[i];
                }
                return cache[ args ] = a;
            }
        })();
        //把能够独立出来的代码封装在独立地小函数
        var mult = (function(){
            var cache = {};
            var calculate = function(){ // 封闭calculate 函数
                var a = 1;
                for ( var i = 0, l = arguments.length; i < l; i++ ){
                    a = a * arguments[i];
                }
                return a;
            };
    
            return function(){
                var args = Array.prototype.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' );

    img是report的局部变量,report函数执行完,img局部变量会被销毁,可能没来得及发出HTTP请求。

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

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

    闭包和面向对象设计

        //下面来看看这段跟闭包相关的代码:
        var extent = function(){
            var value = 0;
            return {
                call: function(){
                    value++;
                    console.log( value );
                }
            }
        };
    
        var extent = extent();
        extent.call(); // 输出:1
        extent.call(); // 输出:2
        extent.call(); // 输出:3
        //如果换成面向对象的写法,就是:
        var extent = {
            value: 0,
            call: function(){
                this.value++;
                console.log( this.value );
            }
        };
    
        extent.call(); // 输出:1
        extent.call(); // 输出:2
        extent.call(); // 输出:3
        //或者:
    
        var Extent = function(){
            this.value = 0;
        };
    
        Extent.prototype.call = function(){
            this.value++;
            console.log( this.value );
        };
    
        var extent = new Extent();
    
        extent.call();
        extent.call();
        extent.call();

    命令模式的意图是把请求封装成对象,从而分离请求的发起者和请求的接收者(接收者)之间的耦合关系。在命令被执行之前,可以预先往命令对象中植入命令的接收者。

    但在JavaScript,函数作为一等对象,本身就可以四处传递,用函数对象而不是普通对象来封装请求显得更自然。如果需要往函数对象中预先植入 命令的接收者,可以使用闭包。

        var Tv = {
            open: function(){
                console.log( '打开电视机' );
            },
    
            close: function(){
                console.log( '关上电视机' );
            }
        };
    
        var createCommand = function( receiver ){
            var execute = function(){
                return receiver.open(); // 执行命令,打开电视机
            }
            var undo = function(){
                return receiver.close(); // 执行命令,关闭电视机
            }
            return {
                execute: execute,
                undo: undo
            }
        };
    
        var setCommand = function( command ){
            document.getElementById( 'execute' ).onclick = function(){
                command.execute(); // 输出:打开电视机
            }
            document.getElementById( 'undo' ).onclick = function(){
                command.undo(); // 输出:关闭电视机
            }
        };
    
        setCommand( createCommand( Tv ) );

    2 高阶函数

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

    1. 函数可以作为参数被传递
    2. 函数可以作为返回值输出

    2.1 函数作为参数传递

    1 回调函数

      AJAX异步请求中,回调函数应用非常频繁。我们想在AJAX请求返回都做一些事,但不知道请求返回的时间,最常用的方案是把callback函数当做参数传入AJAX请求的方法。

        var getUserInfo = function( userId, callback ){
            $.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){
                if ( typeof callback === 'function' ){
                    callback( data );
                }
            });
        }
        getUserInfo( 13157, function( data ){
            alert ( data.userName );
        });

      当一些函数不适合执行一些请求,可以把这些请求封装成一个函数,并把它作为参数传递给另一个函数,“委托”给另一个函数执行。

      例如,在页面创建100个节点并把它们设置为隐藏,但每次操作都要把它们隐藏,因此可以把隐藏操作的代码抽出。

        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';
        });

    2 Array.prototype.sort

    Array.prototype.sort接受一个函数作为参数,这个函数封装了数组的排序规则。

        //从小到大
            [ 1, 4, 3 ].sort( function( a, b ){
            return a - b;
        });
    
            //从大到小
        [ 1, 4, 3 ].sort( function( a, b ){
            return b - a;
        });

    2.2函数作为返回值输出

    1.判断数据的类型

        var isString = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object String]';
        };
        var isArray = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object Array]';
        };
        var isNumber = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object Number]';
        };

    将相同的部分提取,只有Object.prototype.toString.call(obj)返回值不同。

    var isType = function(type) {
        return function(obj) {
            return Object.prototype.toString.call( obj ) === '[object '+ type +']';
       }
    }

    可以用循环语句注册

        var Type = {};
        for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
            (function( type ){
                Type[ 'is' + type ] = function( obj ){
                    return Object.prototype.toString.call( obj ) === '[object '+ type +']';
                }
            })( type )
        };

    2.3高阶函数其他应用

    1  函数节流(throttle)

     定义

      如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。

      也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

    为什么需要函数节流

      在某些情况下,函数可能被频繁调用而造成大的性能问题。

    1. window.onresize事件。如果在window上绑定了resize事件,当拖动浏览器窗口来改变大小,这个事件触发的频率很高,如果resize事件函数里有DOM操作,可能造成浏览器卡顿。
    2. mousemove事件。如果给DOM节点绑定了拖曳事件(主要是mousemove),当div节点被拖动,也会频繁触发该事件。 
    3. 上传进度。 

    实现

    下面的throttle函数的原理是,将即将被执行的函数用setTimeout延迟一段时间执行。如果该次延迟执行完成,则忽略接下来调用该函数的请求。throttle函数接受2个参数,第一个为需要被延迟执行的函数,第二个为延迟执行的事件。  

        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 );

      

    2 分时函数

      在短时间内忘页面大量添加DOM节点会让浏览器卡顿甚至假死。例如WebQQ的QQ好友列表,如果一个好友用一个节点表示,可能会创建成百上千的节点。

      使用timeChunk函数让创建节点分批进行,如一秒创建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();

    3 惰性加载函数

    一般我们定义浏览器通用的事件绑定函数addEvent如下。它的缺点是,每次被调用都会执行if分句。

        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返回一个包裹了正确逻辑的函数。

    但它仍然有缺点。如果外面没有调用过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函数就不存在条件分支语句。

                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 );
                };
  • 相关阅读:
    GetArxPath
    动态链接库
    获取文件名称 消除前面的绝对地址路径
    arx 插入图片
    cstring to utf8
    map 用法
    异常处理
    面向对象 "一"
    configparser模块
    装饰器
  • 原文地址:https://www.cnblogs.com/surahe/p/6004463.html
Copyright © 2020-2023  润新知