本文摘要:http://www.liaoxuefeng.com/

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

我们来实现一个对Array的求和。通常情况下,求和的函数是这样定义的:

function sum(arr) {
return arr.reduce(function (x, y) {
return x + y;
}); //返回的是求和的值
} sum([1, 2, 3, 4, 5]); // 15

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!

function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum; //返回的是求和函数
}

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()

调用函数f时,才真正计算求和的结果:

f(); // 15

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。 (函数中返回函数,并且保存了相关参数和变量)

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

f1()f2()的调用结果互不影响。

闭包

注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
} var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。

你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

f1(); //
f2(); // 16
f3(); // 全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。 一开始我也是懵逼的,但是没关系,我们一步一步的输出我们的想知道的变量就知道了。 ①函数Count() 返回的是 arr[]数组
var results = count();  //results 是个数组了
console.log(results);


② f1,f2,f3实际是三个函数    function () {return i * i; }   我们输出看下就知道了
var f1 = results[0];  //function () {return i*i;}
var f2 = results[1];
var f3 = results[2];

console.log("f1:" + f1);
 console.log("f2:" + f2);
 console.log("f3:" + f3);

console.log("f1:" + f1());
console.log("f2:" + f2());
console.log("f3:" + f3());

  说明此时的i为4,那到底是不是这样的,我们再Count()里面输出i看看

 

f1,f2,f3都是一样的函数,并且i是为4的,所以结果就是16了

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。(避免发生上面的情形)

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
} var results = count();
var f1 = results[0]; //function () {return n * n;}
var f2 = results[1];
var f3 = results[2]; f1(); // 1
f2(); // 4
f3(); // 9

注意这里用了一个“创建一个匿名函数并立刻执行”的语法:

(function (x) {
return x * x;
})(3); // 9

理论上讲,创建一个匿名函数并立刻执行可以这么写:

function (x) { return x * x } (3);

但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:

(function (x) { return x * x }) (3);

通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:

(function (x) {
return x * x;
})(3);

说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?


只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:
'use strict';

function create_counter(initial) {
var x = initial || 0; //没有传值就是0
return {
inc: function () {
x += 1;
return x;
}
}
}

它用起来像这样:


var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3 var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。


闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2pow3


function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
} // 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3); pow2(5); // 25 5*5
pow3(7); // 343 7*7*7





闭包 -------JavaScript的更多相关文章

  1. 前端知识体系:JavaScript基础-作用域和闭包-JavaScript的作用域和作用域链

    JavaScript的作用域和作用域链 作用域: 变量的作用域无非两种:全局作用域和局部作用域 全局作用域: 最外层函数定义的变量拥有全局作用域.即对任何内部函数来说都是可以访问的. <scri ...

  2. [JavaScript闭包]Javascript闭包的判别,作用和示例

    闭包是JavaScript最重要的特性之一,也是全栈/前端/JS面试的考点. 那闭包究竟该如何理解呢? 如果不爱看文字,喜欢看视频.那本文配套讲解视频已发送到B站上供大家参考学习. 如果觉得有所收获, ...

  3. JavaScript闭包(Closure)

    JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...

  4. Javascript闭包——懂不懂由你,反正我是懂了

    摘要:“如果你不能向一个六岁的孩子解释清楚,那么其实你自己根本就没弄懂.”好吧,我试着向一个27岁的朋友就是JS闭包(JavaScript closure)却彻底失败了. 越来越觉得国内没有教书育人的 ...

  5. javascript从定义到执行 js引擎 闭包

    javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境 栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链 ...

  6. javascript 函数和作用域(闭包、作用域)(七)

    一.闭包 JavaScript中允许嵌套函数,允许函数用作数据(可以把函数赋值给变量,存储在对象属性中,存储在数组元素中),并且使用词法作用域,这些因素相互交互,创造了惊人的,强大的闭包效果.[upd ...

  7. JavaScript一看就懂(2)闭包

    认识闭包之前需要先了解作用域,如果你对作用域还没有足够了解,请移步JavaScript一看就懂(1)作用域 什么是闭包? 我们可以先简单认为:一个函数a定义在另一个函数b里面,这个函数a就是闭包: f ...

  8. JavaScript进阶系列01,函数的声明,函数参数,函数闭包

    本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...

  9. JavaScript 作用域和闭包——另一个角度:扩展你对作用域和闭包的认识【翻译+整理】

    原文地址 --这篇文章有点意思,可以扩展你对作用域和闭包的认识. 本文内容 背景 作用域 闭包 臭名昭著的循环问题 自调用函数(匿名函数) 其他 我认为,尝试向别人解释 JavaScript 作用域和 ...

随机推荐

  1. vue实现复制功能(项目使用)

    安装依赖 npm install --save vue-clipboard2 用法: import Vue import VueClipboard from 'vue-clipboard2' Vue. ...

  2. rgb值转换成16进制

    由于jQuery获取css中的background有时候是rgb值,所以往往需要一个转换函数. 以前觉得难,还写个博客记录,现在觉得好容易. let testColor = "rgb(20, ...

  3. Chapter13

    package scala /** * Created by EX-CHENZECHAO001 on 2018-04-04. */class Chapter13 { // 13 集合 // 所有的集合 ...

  4. $.getScript("/Scripts/js/video.min.js");

    js内引用JS: $.getScript("/Scripts/js/video.min.js");

  5. (一)Redis简介和安装

    1       Redis介绍 1.1      什么是NoSql 为了解决高并发.高可扩展.高可用.大数据存储问题而产生的数据库解决方案,就是NoSql数据库. NoSQL,泛指非关系型的数据库,N ...

  6. idea没有绑远程地址,如何提交到github的空项目

    一 有同事问我怎么提交到github的空项目....这么简单.... 二 1.创建本地的git仓库 选择自己的项目 创建成功 2.添加代码,选中左边的文件,add 3.提交 5.定义远程仓库,不定义的 ...

  7. zookeeper的简单搭建,java使用zk的例子和一些坑

    一 整合 由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面. 附上自己的github项目地址 https://github.com/247292980/spring- ...

  8. springBoot jpa uuid生成策略

    实体类 import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; @Entity @Table(na ...

  9. Garmin APP开发之入门

    Garmin开发-入门 先附上几个已经开发完成的app日历 up down 翻月 start 回到当前月(就差农历了) 秒表和定时器一体app界面比较简单,但是实用,长按菜单键可以切换秒表和定时器,有 ...

  10. linux 下源码编译环境配置

    yum install -y apr* autoconf automake bison bzip2 bzip2* compat* cpp curl curl-devel \ fontconfig fo ...