闭包

闭包是什么,它们是如何工作的

闭包 是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域

即:闭包可以让函数访问所有的变量和函数,只要这些变量和函数存在于该函数声明时的作用域内就行。

一个简单的闭包:

/* 分析:在同一个作用域内声明一个变量和函数,`outerFunction`可以看到并访问`outerValue`。
* 此处是全局作用域,该作用域实际上就是一个闭包,从未消失过(因为页面已经被加载了)。
*/ var outerValue = "ninja"; function outerFunction() {
alert(outerValue);
} outerFunction(); // ninja

更深入的闭包:

var outerValue = "ninja";
var later; // 声明一个全局变量 function outerFunction() {
// 在函数内部声明一个值。该变量的作用域是限制在该函数内部,并且在函数外部访问不到
var innerValue = "samurai"; // 在外部函数内,声明一个内部函数。注意,声明该函数时,innerValue是在作用域内的。
function innerFunction() {
alert(outerValue);
alert(innerValue);
} later = innerFunction; // 将内部函数引用到later变量上
} /* 调用外部函数,将会声明内部函数,并将内部函数赋值给later变量
* 但并不输出
*/
outerFunction(); /* 通过later调用内部函数
*不能直接调用内部函数,因为它的作用域(和innerValue一起)被限制在outerFunction()内
*/
later(); // ninja,samurai
* 分析:
  • 当调用 later 执行内部函数时,外部函数的作用域早已不复存在,但是 innerValue 变量仍然“活着”,这要归功于闭包。

  • 在外部函数中声明 innerFunction() 的时候,不仅是声明了函数,还创建了一个闭包。该闭包不仅包含函数声明,还包含了函数声明的那一时刻点上该作用域中的所有变量。

  • 针对在函数声明那一时刻点的作用域内的所有函数和变量,闭包创建了一个“安全气泡”,因此函数获得了执行操作所需的所有东西。

闭包的三个有趣的概念:

  1. 内部函数的参数是包含在闭包中的。
  2. 作用域之外的所有变量,即便是函数声明之后的那些声明,也都包含在闭包中。
  3. 相同的作用域内,尚未声明的变量不能进行提前引用。

例子:

var outerValue = "ninja";
var later; function outerFunction() {
var innerValue = "samurai"; function innerFunction(paramValue) {
console.log(outerValue);
console.log(innerValue);
console.log(paramValue);
console.log(tooLate);
} later = innerFunction;
} console.log(tooLate); // undefined var tooLate = "ronin"; outerFunction(); later('wakizashi'); // ninja,samurai,wakizashi,ronin

利用闭包简化开发

闭包的常见用法:

  • 私有变量 :封装一些信息作为“私有变量”,即,限制这些变量的作用域。

      function Ninja() {
    var age = 10; this.getAge = function() {
    return age;
    } this.setAge = function() {
    age++;
    }
    } var ninja = new Ninja();
    ninja.setAge();
    ninja.getAge(); // 11
  • 回调与计时器 : 在这种情况下,函数都是在后期未指定的时间进行异步调用,在这种函数内部,我们经常需要访问外部数据,闭包可以作为一种访问这些数据的很直观的方式,特别是当我们希望避免创建全局变量来存储这些信息时。

      // 示例1:在 Ajax 请求的 callback 里使用闭包
    jQuery('#testButton').click(function() {
    var elem$ = jQuery('#testSubject'); elem$.html("Loading..."); jQuery.ajax({
    url: "test.html",
    success: function(html) { // 通过闭包引用了elem$变量
    console.log(elem$);
    elem$.html(html);
    }
    });
    }); // 示例2:在计时器间隔回调中使用闭包
    function animateIt(elementId) { var elem = document.getElementById(elementId);
    var tick = 0; var timer = setInterval(function() {
    if (tick < 100) {
    elem.style.left = elem.style.top = tick + "px";
    tick++;
    } else {
    clearInterval(timer);
    console.log(tick == 100);
    console.log(elem);
    console.log(timer);
    }
    }, 10);
    } animateIt('box');

没有闭包,同时做多件事情的时候,无论是事件处理,还是动画,甚至是 Ajax 请求,都将是及其困难的。

函数在闭包里执行的时候,不仅可以在闭包创建的时刻点上看到这些变量的值,我们还可以对其进行更新。换句话说,闭包不仅是在创建那一时刻点的状态的快照,而且是一个真实的状态封装,只要闭包存在,就可以对其进行修改。

利用闭包提高性能&&解决常见的作用域问题

1.绑定函数上下文

bind

Prototype 的 bind()(或者是我们自己实现的),并不意味着它是 apply()call() 的一个替代方法,该方法的潜在目的 是通过匿名函数和闭包控制后续执行的上下文。这个重要的区别使 apply()call() 对事件处理程序和定时器的回调进行延迟执行特别有帮助。

2.偏应用函数

柯里化:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术。

3.函数重载

(1)缓存记忆

(2)函数包装

4.即时函数

即时函数(立即执行函数):依赖于对闭包的充分利用。

(function() {
statement..;
})();
  • 创建一个函数实例。
  • 执行该函数。
  • 销毁该函数(因为语句结束以后,,没有任何引用了)。

即时函数的用处:

- 临时作用域和私有变量

(1)创建一个独立作用域:

利用即时函数,我们可以利用其内部作用域来创建一个临时的作用域,用于存储数据状态。

记住,JavaScript 中的作用域依赖于定义变量的函数。在很多编程语言中,作用域是依赖于代码块的,但在 JavaScript 中,变量的作用域依赖于变量所在的闭包。

// method 1
(function() {
var numClicks = 0;
document.addEventListener("click", function(){
alert( ++numClicks );
},false);
})(); // method 2
document.addEventListener("click", (function() {
var numClicks = 0;
return function() {
alert( ++numClicks );
}
})(), false);
  • 这是一种最常见的即时函数使用方式:简单,自包装功能。各功能所需的变量都保存在闭包内,但对其他地方却都不可见。

(2)通过参数限制作用域内的名称

(function(what) {
alert(what);
})('Hi there!');

(3)使用简洁名称让代码保持可读性

(function(v) {
Object.extend(v, {
href: v._getAttr,
src: v._getAttr,
...
});
})(Element.attributeTranslations.read.values);

这种在作用域内创建临时变量的技巧,对没有延迟调用的循环遍历来说尤其有用。

- 循环

闭包记住的是变量的引用,而不是闭包创建时刻该变量的值。

var div = document.getElementsByTagName("div");

for (var i=0; i < div.length; i++) {
(function(n) {
div[n].addEventListener("click", function() {
alert("div #" + n + " was clicked.");
}, false);
})(i);
}
- 类库包装

闭包和即时函数可以帮助我们让类库尽可能的保持私有,并且可以选择性的让一些变量暴露到全局命名空间内。

// method 1
(function() {
var jQuery = window.jQuery = function() {
// Initialize
}; // ...
})(); // method 2
var jQuery = (function() {
function jQuery() {
// Initialize
} // ... return jQuery;
})();

《JavaScript Ninja》之闭包的更多相关文章

  1. JavaScript函数、闭包、原型、面向对象

    JavaScript函数.闭包.原型.面向对象 断言 单元测试框架的核心是断言方法,通常叫assert(). 该方法通常接收一个值--需要断言的值,以及一个表示该断言目的的描述. 如果该值执行的结果为 ...

  2. 深入理解javascript原型和闭包 (转)

    该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...

  3. JavaScript葵花宝典之闭包

    闭包,写过JS脚本的人对这个词一定不陌生,都说闭包是JS中最奇幻的一个知识点,  虽然在工作中,项目里经常都会用到~  但是是不是你已经真正的对它足够的了解~~ 又或者是你代码中出现的闭包,并不是你刻 ...

  4. 深入理解javascript原型和闭包系列

    从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...

  5. 让你分分钟学会Javascript中的闭包

    Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...

  6. 深入理解javascript原型和闭包(1)——一切都是对象

    “一切都是对象”这句话的重点在于如何去理解“对象”这个概念. ——当然,也不是所有的都是对象,值类型就不是对象. 首先咱们还是先看看javascript中一个常用的函数——typeof().typeo ...

  7. 深入理解javascript原型和闭包(2)——函数和对象的关系

    上文(理解javascript原型和作用域系列(1)——一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; co ...

  8. 深入理解javascript原型和闭包(3)——prototype原型

    既typeof之后的另一位老朋友! prototype也是我们的老朋友,即使不了解的人,也应该都听过它的大名.如果它还是您的新朋友,我估计您也是javascript的新朋友. 在咱们的第一节(深入理解 ...

  9. 深入理解javascript原型和闭包(4)——隐式原型

    注意:本文不是javascript基础教程,如果你没有接触过原型的基本知识,应该先去了解一下,推荐看<javascript高级程序设计(第三版)>第6章:面向对象的程序设计. 上节已经提到 ...

  10. 深入理解javascript原型和闭包(5)——instanceof

    又介绍一个老朋友——instanceof. 对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/ ...

随机推荐

  1. 解决ACTIVITI流程图设置字体不生效的问题

    在ACTIVITI 5.15的版本中,有一个设置流程图的字体配置. 配置如下: <bean id="processEngineConfiguration" class=&qu ...

  2. 查看Nginx、apache、MySQL和PHP的编译参数

    1.nginx编译参数:#/usr/local/nginx/sbin/nginx -V2.apache编译参数:# cat /usr/local/apache/build/config.nice3.p ...

  3. eclipse常用的字体

    1.consolas 2.Segoe Script 3.Segoe Print 4.Courier New

  4. DatagridView自动充满屏幕,并能指定某列宽度

    1.要使datagridview正好充满屏幕,设置其AutoSizeColumnsMode属性为fill 2. 同时,我们想要某列宽点,某列窄点,在AutoSizeColumnsMode属性为fill ...

  5. redis Ok2

    Redis::__construct 描述: 创建一个Redis客户端 范例: $redis = new Redis();   connect, open 描述: 实例连接到一个Redis. 参数:h ...

  6. IT公司100题-4-在二元树中找出和为某一值的所有路径

    问题描述: 输入一个整数和一棵二元树.从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径.打印出和与输入整数相等的所有路径. 例如输入整数30和如下二元树   14 / \ 5 16 / ...

  7. 宜家的幸福生活,源于K2 BPM的支撑

    很久很久以前,有一篇很火的文章在各大网站被疯狂转载<一个在北欧生活10年的MM,告诉你为什么北欧全球幸福指数第一>,开头第一段就已经让人羡慕嫉妒恨了. "下午的四.五点钟,北欧人 ...

  8. 从协议VersionedProtocol开始

    VersionedProtocol协议是Hadoop的最顶层协议接口的抽象:5--3--3共11个协议,嘿嘿 1)HDFS相关 ClientDatanodeProtocol:client与datano ...

  9. typedef定义函数类型或函数指针

    转载请标明出处: 最近在看redis的代码,发现了有关函数指针的部分,想把它记下来. 在redis中有类似下面的定义,利用typedef 定义了一个新的类型,这种类型是一个函数: typedef vo ...

  10. Vm下 linux与windowsxp文件共享的方法

    我的PC Operating System Host是Windows XP,Guest是Linux,virtualPC是VMware workstation.方法介绍大全请参见:Windows与Vmw ...