Javascript 闭包与高阶函数 ( 一 )

时间:2022-03-19 09:58:36

上个月,淡丶无欲 让我写一期关于 闭包 的随笔,其实惭愧,我对闭包也是略知一二 ,不能给出一个很好的解释,担心自己讲不出个所以然来。 所以带着学习的目的来写一写,如有错误,忘不吝赐教 。

为什么要有闭包?

初识闭包时,,我一直在想,为什么只有JS有闭包,c#,c++ 为什么没有 ??

1. 封装局部变量

看下面一个例子,计算 斐波那契 数。

为了能够重用数据,一个通用做法就是将计算过的数据缓存起来,但缓存的数据对外是不可见的 。

看下面的 c# 代码 :

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#了, 很撇脚,囧 ,但是在这类静态语言,这种方法很合适 。

看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 的一个属性存储,但是对外可见,无法隐藏 。

就到了闭包大显身手的时候,由于这里 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中,私有变量是一件很麻烦的事情 。这时候,将局部变量放置在一个函数作用域中,可以在内部使用,而外面无法访问。这就形成了闭包 。

2.延续对象生命周期

对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。

而对于在函数内用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"));

Javascript 闭包与高阶函数 ( 一 )

实现单例模式

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

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

Javascript 闭包与高阶函数 ( 一 )

在上面的解法中,我们将函数柯里化和数据计算放在一起,违背了单一职责原则 。现在,我们可以专门定义一个函数,用于对参数进行柯里化。

'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

Javascript 闭包与高阶函数 ( 一 )的更多相关文章

  1. Javascript 闭包与高阶函数 &lpar; 二 &rpar;

    在上一篇 Javascript 闭包与高阶函数 ( 一 )中介绍了两个闭包的作用. 两位大佬留言指点,下来我会再研究闭包的实现原理和Javascript 函数式编程 . 今天接到头条 HR 的邮件,真 ...

  2. JavaScript之闭包与高阶函数(一)

    JavaScript虽是一门面向对象的编程语言,但同时也有许多函数式编程的特性,如Lambda表达式,闭包,高阶函数等. 函数式编程是种编程范式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是 ...

  3. JavaScript ES6函数式编程(一):闭包与高阶函数

    函数式编程的历史 函数的第一原则是要小,第二原则则是要更小 -- ROBERT C. MARTIN 解释一下上面那句话,就是我们常说的一个函数只做一件事,比如:将字符串首字母和尾字母都改成大写,我们此 ...

  4. &lbrack;Node&period;js&rsqb; 闭包和高阶函数

    原文地址:http://www.moye.me/2014/12/29/closure_higher-order-function/ 引子 最近发现一个问题:一部分写JS的人,其实对于函数式编程的概念并 ...

  5. Python 进程线程协程 GIL 闭包 与高阶函数(五)

    Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 ​ 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...

  6. JavaScript进阶之高阶函数篇

    JavaScript进阶之高阶函数篇 简介:欢迎大家来到woo爷说前端:今天给你们带来的是JavaScript进阶的知识,接下来的系列都是围绕着JavaScript进阶进行阐述:首先我们第一篇讲的是高 ...

  7. javascript设计模式学习之三—闭包和高阶函数

    一.闭包 闭包某种程度上就是函数的内部函数,可以引用外部函数的局部变量.当外部函数退出后,如果内部函数依旧能被访问到,那么内部函数所引用的外部函数的局部变量就也没有消失,该局部变量的生存周期就被延续. ...

  8. 《JavaScript设计模式与开发实践》——第3章 闭包和高阶函数

    闭包 变量的作用域和生存周期密切相关 高阶函数 函数可以作为参数被传递 函数可以作为返回值输出

  9. 理解运用JS的闭包、高阶函数、柯里化

    JS的闭包,是一个谈论得比较多的话题了,不过细细想来,有些人还是理不清闭包的概念定义以及相关的特性. 这里就整理一些,做个总结. 一.闭包 1. 闭包的概念 闭包与执行上下文.环境.作用域息息相关 执 ...

随机推荐

  1. HTML初级入门内容

    常用属性: Width=宽度 Height=高度 Size=大小 Color=颜色 Align=布局方向,值包括(top,bottom,left,right,center)上,下,左,右,中. Bor ...

  2. Docker三剑客之Swarm介绍

    DockOne技术分享(二十): 我用swarm在多台物理机调度管理容器,用ovs实现跨主机的容器互联问题 [编者的话]Swarm项目是Docker公司发布三剑客中的一员,用来提供容器集群服务,目的是 ...

  3. strut1&period;X和spring整合的二种方法

    第一种集成方法 原理:在Action中取得BeanFactory对象,然后通过BeanFactory获取业务逻辑对象 缺点:产生了依赖,spring的类在action中产生了依赖查找.(注意和依赖注入 ...

  4. javascipt &colon; reduce

    $scope.totalPrice = function () { return $scope.addcartProduct.reduce(function (money, product) { re ...

  5. Struts2学习---拦截器&plus;struts的工作流程&plus;struts声明式异常处理

    这一节我们来看看拦截器,在讲这个之前我是准备先看struts的声明式异常处理的,但是我发现这个声明式异常处理就是由拦截器实现的,所以就将拦截器的内容放到了前面. 这一节的内容是这样的: 拦截器的介绍 ...

  6. SQL语句-UPDATE语句

    Update语句 update语句用于修改表中已经存在的数据 单表修改语句结构 多表修改语句结构 update语句的常规用法 update students set sname='abcd',gend ...

  7. Linux显示用户注册名

    Linux显示用户注册名 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ finger -s Login Name Tty Idle Login Time Of ...

  8. ISLR系列:&lpar;4&period;3&rpar;模型选择 PCR &amp&semi; PLS

    Linear Model Selection and Regularization 此博文是 An Introduction to Statistical Learning with Applicat ...

  9. Android 图片Bitmap,drawable,res资源图片之间转换

    一.知识介绍 ①res资源图片是放在项目res文件下的资源图片 ②BitMap位图,一般文件后缀为BMP,需要编码器编码,如RGB565,RGB8888等.一种逐像素的显示对象,其执行效率高,但缺点也 ...

  10. &lbrack;转载&rsqb;ISO 8601规则

    1.每年有52周或者53周2.周一至周日为一个完整周.3.每周的周一是该周的第1天.周日是该周的第7天4.每年的第一周 为 每年的第一个周四所在的周.比如 2017年1月5日为当年的第一个周四,那么 ...