《JavaScript Ninja》之闭包
闭包
闭包是什么,它们是如何工作的
闭包 是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域。
即:闭包可以让函数访问所有的变量和函数,只要这些变量和函数存在于该函数声明时的作用域内就行。
一个简单的闭包:
/* 分析:在同一个作用域内声明一个变量和函数,`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()的时候,不仅是声明了函数,还创建了一个闭包。该闭包不仅包含函数声明,还包含了函数声明的那一时刻点上该作用域中的所有变量。针对在函数声明那一时刻点的作用域内的所有函数和变量,闭包创建了一个“安全气泡”,因此函数获得了执行操作所需的所有东西。

闭包的三个有趣的概念:
- 内部函数的参数是包含在闭包中的。
- 作用域之外的所有变量,即便是函数声明之后的那些声明,也都包含在闭包中。
- 相同的作用域内,尚未声明的变量不能进行提前引用。
例子:
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》之闭包的更多相关文章
- JavaScript函数、闭包、原型、面向对象
JavaScript函数.闭包.原型.面向对象 断言 单元测试框架的核心是断言方法,通常叫assert(). 该方法通常接收一个值--需要断言的值,以及一个表示该断言目的的描述. 如果该值执行的结果为 ...
- 深入理解javascript原型和闭包 (转)
该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...
- JavaScript葵花宝典之闭包
闭包,写过JS脚本的人对这个词一定不陌生,都说闭包是JS中最奇幻的一个知识点, 虽然在工作中,项目里经常都会用到~ 但是是不是你已经真正的对它足够的了解~~ 又或者是你代码中出现的闭包,并不是你刻 ...
- 深入理解javascript原型和闭包系列
从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...
- 让你分分钟学会Javascript中的闭包
Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...
- 深入理解javascript原型和闭包(1)——一切都是对象
“一切都是对象”这句话的重点在于如何去理解“对象”这个概念. ——当然,也不是所有的都是对象,值类型就不是对象. 首先咱们还是先看看javascript中一个常用的函数——typeof().typeo ...
- 深入理解javascript原型和闭包(2)——函数和对象的关系
上文(理解javascript原型和作用域系列(1)——一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; co ...
- 深入理解javascript原型和闭包(3)——prototype原型
既typeof之后的另一位老朋友! prototype也是我们的老朋友,即使不了解的人,也应该都听过它的大名.如果它还是您的新朋友,我估计您也是javascript的新朋友. 在咱们的第一节(深入理解 ...
- 深入理解javascript原型和闭包(4)——隐式原型
注意:本文不是javascript基础教程,如果你没有接触过原型的基本知识,应该先去了解一下,推荐看<javascript高级程序设计(第三版)>第6章:面向对象的程序设计. 上节已经提到 ...
- 深入理解javascript原型和闭包(5)——instanceof
又介绍一个老朋友——instanceof. 对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/ ...
随机推荐
- 位运算取第一个非0的位 r & (~(r-1))
Single Number III Given an array of numbers nums, in which exactly two elements appear only once and ...
- Oracle 权限查询
查看当前用户权限:SQL> select * from session_privs; 查询某个用户被赋予的系统权限. Select * from user_sys_privs; 或者: sele ...
- 怎么设置 mysql 多主复制
更新 其实本文主要来自www.digitalocean.com ,但是我没有买他们家的 VPS 用来 demo 了.只是用vagrant 来模拟了. 介绍 说说关于通过两台 vps 来扩展 mysql ...
- 一步一步配置NLB
废话不说,配置NLB需要准备以下环境: 1. 至少两个服务器,我的是windows server 2008 R2; 我的两个服务器名分别为NLB3和NLB2,其中NLB3是主,为什么呢?后面会谈到,在 ...
- UDPsocket编程
socket编程UDP模式, package com.wtd.socket.udp; import java.io.IOException; import java.net.DatagramPacke ...
- 【Noip2012】解题报告【字符】【贪心】【高精度】【倍增】【set】
目录:1:vigenere密码[字符]2:国王游戏[贪心][高精度]3:开车旅行[倍增][set] 题目: VJ P1778 vigenere密码 Accepted 标签:NOIP提高组2012 ...
- C++-多重继承的注意点
1, 钻石型多重继承如果不想要底部的类有重复的变量,则需要声明为virtual继承 class File{...}; class InputFile: virtual public File{..}; ...
- JSP初识
JSP最终会变成一个完整的servlet在web应用中运行.它与其他的servlet非常相似,只不过这个servlet类会由容器编写. 1.JSP的生命周期 如果一个web应用包含JSP,部署这个应用 ...
- C# 轉義字符
转义字符 意义 ASCII码值(十进制) \a 响铃(BEL) 007 \b 退格(BS) ,将当前位置移到前一列 008 \f 换页(FF),将当前位置移到下页开头 012 \n 换行(LF) ,将 ...
- 建议入门-用ArcMap进行空间查询与空间连接
1.打开arcmap并导入数据(如本图导入美国地图(usa.mxd)): 2.空间查询操作,在地图上的某片区域点击右键,得到下图,点击identify,此时我在阿拉斯加上面点击的 地图会闪现一下被查询 ...