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>
时间: 2024-10-10 01:03:10

JavaScript设计模式与开发实践-读书笔记(3)闭包和高阶函数的相关文章

javascript设计模式与开发实践阅读笔记(3)——高阶函数的其他应用

高阶函数的其他应用 1.currying 函数柯里化,又称部分求值,一个currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来.待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值. var cost = (function(){ var args = []; return function(){ if ( arguments.length === 0 ){ var money =

JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html

JavaScript 设计模式与开发实践读书笔记 最近利用碎片时间在 Kindle 上面阅读<JavaScript 设计模式与开发实践读书>这本书,刚开始阅读前两章内容,和大家分享下我觉得可以在项目中用的上的一些笔记. 我的 github 项目会不定时更新,有需要的同学可以移步到我的 github 中去查看源码: https://github.com/lichenbuliren/design-mode-notes 1.currying 函数柯里化 currying 又称 部分求值 .一个 cu

Javascript设计模式与开发实践读书笔记(1-3章)

第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用   多态最根本好处在于,你不必询问对象“你是什么类型”而后根据得到的答案调用对象的某个行为--你只管调用行为就好,剩下的一切多态会搞定 换句话说就是:多态的最根本作用就是把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 例子:假设有一个地图应用,每个地图API提供商都提供了show方法,负责在页面上显示地图,首先我们用一些分支条件语句来实现一个调用方法renderMap 此时一旦需要增加搜搜地图的应用,

JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)

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

JavaScript设计模式与开发实践---读书笔记(7) 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. JavaScript中的Array.prototype.foreach. 1.JQuery中的迭代器 $.each函数 2.自己实现一个each函数 var each = function(ary,callback){ for(var i=0,l=ary.length;i<l;i++){ callback.call(ary[i],i,ary[i]);//把下标和元素当作参数传给callback函数 }

JavaScript设计模式与开发实践---读书笔记(1)

前言 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案. 从某些角度来看,设计模式确实有可能带来代码量的增加,或许会把系统的逻辑搞的更复杂.但软件开发的成本并非全部在开发阶段,设计模式的作用是让人们写出可复用和可维护性高的程序. 所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”. 不变和稳定的部分是非常容易复用的. 分辨模式的关键是意图而不是结构 模式只有放在具体的环境下才有意义,辨别模式的关键是这个模式出现的场景,以及为我们解决的问题.

JavaScript设计模式与开发实践---读书笔记(5) 策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封转起来,并且使它们可以相互替换. JavaScript版本的策略模式: 奖金系统: var strategies = { "S": function(salary){ return salary*4; }, "A": function(salary){ return salary*3; }, "B": function(salary){ return salary*2; } }; var calc

JavaScript设计模式与开发实践---读书笔记(8) 发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案. 可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口. 自定义事件 首先要指定好谁充当发布者: 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者: 最后发布消息时,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数. 另外,我们还可以往回调函数里填入

JavaScript设计模式与开发实践---读书笔记(9) 命令模式

命令模式的用途: 命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令. 命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 命令模式的例子-菜单程序: <!DOCTYPE html> <html lang="en"> <head> <met