上个月,淡丶无欲 让我写一期关于 闭包 的随笔,其实惭愧,我对闭包也是略知一二 ,不能给出一个很好的解释,担忧本身讲不出个因此然来。 因此带着学习的目的来写一写,若有错误,忘不吝赐教 。javascript
初识闭包时,,我一直在想,为何只有JS有闭包,c#,c++ 为何没有 ??html
看下面一个例子,计算 斐波那契 数。
为了可以重用数据,一个通用作法就是将计算过的数据缓存起来,但缓存的数据对外是不可见的 。
看下面的 c# 代码 :java
public static class Fibonacci{ public static Fibonacci(){ cache[0] = 1; cache[1] = 1; } private static IList<int> cache = new List<int>(1000,-1); public static int Calc(n){ if(cache[n] != -1){ return cache[n]; }else{ return cache[n] = Calc(n-1) + Calc(n-2); } } }
快两年没写c#了, 很撇脚,囧 ,可是在这类静态语言,这种方法很合适 。c++
看JS 怎么写面试
var cache = [1, 1]; var calc = function(){ return cache[n] != undefined ? cache[n]: cache[n] = calc(n-1) + calc (n-2); }
可是在JS中杜绝使用全局变量,因此下面改写编程
var calc = function(){ return calc.cache[n] != undefined ? calc.cache[n]: calc.cache[n] = calc.cache(n-1) + calc.cache (n-2); } calc.cache = [1,1];
这里将全局变量做为 calc 的一个属性存储,可是对外可见,没法隐藏 。c#
就到了闭包大显身手的时候,因为这里 cache 被外部调用,因此能够不被销毁。设计模式
var Fibonacci = (function() { var cache = [1, 1]; return { calc: function(n) { return cache[n] != undefined ? cache[n] : cache[n] = this.calc(n - 1) + this.calc(n - 2); } } })(); Fibonacci.calc(5); // 8
总结:
在 c# ,c++ 等高级语言中,存在私有变量,因此无需闭包 。可是在JS中,私有变量是一件很麻烦的事情 。这时候,将局部变量放置在一个函数做用域中,能够在内部使用,而外面没法访问。这就造成了闭包 。浏览器
对于全局变量来讲,全局变量的生存周期固然是永久的,除非咱们主动销毁这个全局变量。
而对于在函数内用var关键字声明的局部变量来讲,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁:
如今来看看下面这段代码:缓存
var func = function(){ var a = 1; return function(){ a++; alert ( a ); } }; var f = func(); f(); // 输出:2 f(); // 输出:3 f(); // 输出:4 f(); // 输出:5
跟咱们以前的推论相反,当退出函数后,局部变量a并无消失,而是彷佛一直在某个地方存活着。这是由于当执行 var f=func();时,f返回了一个匿名函数的引用,它能够访问到 func()被调用时产生的环境,而局部变量 a 一直处在这个环境里。既然局部变量所在的环境还能被外界访问,这个局部变量就有了不被销毁的理由。在这里产生了一个闭包结构,局部变量的生命看起来被延续了。
这个特性有时候会很麻烦,但有时候颇有用,用来延续局部变量的寿命 。
img 对象常常用于进行数据上报,以下所示:
var report = function( src ){ var img = new Image(); img.src = src; }; report( 'http://xxx.com/getUserInfo' );
可是经过查询后台的记录咱们得知,由于一些低版本浏览器的实现存在bug,在这些浏览器
下使用report函数进行数据上报会丢失30%左右的数据,也就是说,report函数并非每一次都成功发起了 HTTP请求。 丢失数据的缘由是 img 是 report 函数中的局部变量, 当 report 函数的调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP请求,因此这次请求就会丢失掉。
如今咱们把 img 变量用闭包封闭起来,便能解决请求丢失的问题:
var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
闭包在JS中很是普遍,经常与高阶函数做伴 。
高阶函数是指至少下面条件之一的函数
函数做为参数传递做为回调函数,应用场景很是普遍,此处再也不举例 。
函数做为返回值,这里给出两个例子 。
判断数据类型
'use strict'; var Type = {}; for (var i = 0, type; type = ['String', 'Number', 'Boolean', 'Object'][i++];) { (function(type) { Type["is" + type] = function(o) { return Object.prototype.toString.call(o) === '[object ' + type + ']'; } })(type); } console.log(Type.isString("hh"));
实现单例模式
var getSingle = function(func){ var ret = null; return function(){ return ret || ret = func.apply(this, Array.prototype.slice.call(arguments)); } } var getScript = getSingle(function(){ return document.createElement( 'script' ); }); var script1 = getScript(); var script2 = getScript(); alert ( script1 === script2 ); // 输出:true
AOP(面向切面编程)的主要做用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些
跟业务逻辑无关的功能一般包括日志统计、安全控制、异常处理等。把这些功能抽离出来以后 。再经过“动态织入”的方式掺入业务逻辑模块中。这样作的好处首先是能够保持业务逻辑模块的纯净和高内聚性,其次是能够很方便地复用日志统计等功能模块。
在传统经过on = 为事件注册处理程序中,赋值一个新的处理程序会覆盖掉原来的处理程序,咱们能够这么作
var addEvent = function(target,event ,func){ var old; target[event] = functoin(e){ if(old = target[event]){ old(); } func(); } }
一般,在 JavaScript中实现 AOP,都是指把一个函数“动态植入”到另一个函数之中,具
体的实现技术有不少,本节咱们经过扩展 Function.prototype 来作到这一点
Function.prototype.before = function(beforeFn) { var self = this; return function() { beforeFn.apply(this, Array.prototype.slice.call(arguments)); return self.apply(this, Array.prototype.slice.call(arguments)); } }; Function.prototype.after = function(afterFn) { var self = this; return function() { var ret; ret = self.apply(this, Array.prototype.slice.call(arguments)); afterFn.apply(this, Array.prototype.slice.call(arguments)); return ret; } } var func = function() { console.log(2); } func = func.before(function() { console.log(1); }).after(function() { console.log(3); }) func();
输出 1 2 3 。
return 后的函数中的this 取决于真实环境的this ,由于返回的是一个独立的函数 。
currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数以后,该函数并不会当即求值,而是继续返回另一个函数,刚才传入的参数在函数造成的闭包中被保存起来。待到函数被真正须要求值的时候,以前传入的全部参数都会被一次性用于求值。
看下面一个例子
func(1); 1 func(1)(2); 2 func(1)(2)(3); 6 ...
这个例子就是函数柯里化的典型应用,重点考察闭包和高阶函数,也是一道比较常见的面试题
看下面的解法 。
'use strict'; var curry = (function() { var data = [1]; var func = function(n) { data.push(n); return func; } func.valueOf = function() { var ret = data.reduce(function(a, b) { return a * b; }) data = [1]; return ret; } return func; })(); console.log(curry(1)); console.log(curry(1)(2)); console.log(curry(1)(2)(3));
在上面的解法中,咱们将函数柯里化和数据计算放在一块儿,违背了单一职责原则 。如今,咱们能够专门定义一个函数,用于对参数进行柯里化。
'use strict'; var curry = function(fn) { var args = []; var ret = function(n) { args.push(n); return ret; } ret.valueOf = function() { var ret = args.reduce(fn); args = []; return ret; } return ret; } var func = curry(function(a, b) { return a * b; }) console.log(func(1)); console.log(func(1)(2)); console.log(func(1)(2)(3));
若是有错误,但愿不吝赐教 ~
注: 这篇随笔的一些例子代码来自于 《JavaScript 设计模式与实践 》 ,书中很是详细并深刻讲解了JavaScript 高级和 17 种设计模式,对于JavaScript提升很是有帮助 。安利 ~ 。 想要电子版能够给我发邮件: mymeat@126.com
转载请说明原文出处:http://www.cnblogs.com/likeFlyingFish/p/6421615.html