在这篇文章中,我们会了解到一些闭包的知识。在了解闭包之前,我们先了解一下
javascript
作用域的概念。
作用域链
我们知道,在javascript中,是以function作为域的界限,而不是像其他很多语言一样,是以大括号作为边界的。一个定义在函数内部的变量,在函数外部是访问不了的,但是在代码块里面定义的变量,比如一个for循环中定义的变量,在代码块外面是可以被访问到的。
var a = 1;
function f(){
var b = 1;
return a;
}
f(); // 1
b; // b is not defined
变量a是一个全局域的,然而b则是在函数f()内部作用域的,所以:
在f()中,a和b都可以被访问到。
在f()外,a可以被访问到,但是b不能。
如果你在f()内部定义一个function n(),那么n()将有访问他自己域和父函数f()域的权限。这就是作用域链,并且链可以任意深。
词法作用域
在javascript中,函数具有词法作用域。意思是,在函数被定义的时候创建他的作用域,而不是在被调用时才创建。例如:
function f1(){
var a = 1;
f2();
}
function f2(){
return a;
}
f1(); // a is not defined
在函数f1()内部调用了函数f2()。因为变量a也在f1()内部,所以你的第一反应可能会觉得在f2()中可以访问a,但是并不是这样的。在f2被定义的时候,并没有a这个变量。f2()就像f1()一样,只能访问它自己的作用域和全局作用域。他们并没有共享出他们的本地作用域。
当一个函数被定义时,它会记住它自己的环境、作用域链。这并不意味着他能够知道他作用域里的每一个变量。相反的,我们可以添加、删除、更新函数作用域里面的变量,并且函数可以得到最新的变量的状态。如何在上面的例子中加一个全局变量a,那么f2()就可以访问得到。
var a = 5;
f1(); // 5
a = 55;
f1(); // 55
delete a; // true
f1(); // a is not defined
这种特性给了javascript很大的灵活性。你可以添加和删除变量,并且重新添加他们之后,一样可以继续运行。下面我们试一下把f2()删除以后重新定义他。
delete f2; // true
f1(); // f2 is not defined
var f2 = function(){
return a * 2;
}
var a = 5;
f1(); // 10
用闭包切断链
首先,我们用一些图来描述一下闭包。
这是一个全局作用域,就像一个宇宙,包含了所有东西。
它包含了变量a和函数F。
函数有它自己的私有空间来存储它内部的变量和函数,在某些情况下,会像下面的图一样。
如果你在a点,那么你在全局空间中。如果你在函数F中的b点,那么你即在全局空间中,又在函数F的空间中。如果你在函数N中的c点,那么你即在全局空间中,又在函数F的空间中,还在函数N的空间中。但是反过来,b不在函数N的空间里,a不在函数F和函数N的空间里。因此,在a点,不能访问b和c的东西,在b点,不能访问c点的东西。但是在c点可以访问b和a的东西,b点可以访问a点的东西。但是,当函数N从F里面出来到全局空间的时候,这就形成了闭包。
这时,N已经像a一样在全局空间中,并且函数N记住了他自己的状态,N仍然可以访问F的空间中的b。
如何让N像上面这样切断链呢?下面来看一下具体的代码是怎么实现的。
第一种可以把N声明成为一个全局的函数。
function f(){
var b = "b";
n = function(){
return b;
}
}
在函数中定义了一个变量n,由于没有加var,所以它成为了一个全局函数,但是为了养成好的习惯,建议还是在全局加上一个变量声明。
var n;
function f(){
var b = "b";
n = function(){
return b;
}
}
f();
当我们执行了函数f以后,函数n有了他的方法体,由于n在函数f里面,所以可以访问到f的变量b。就是出了函数f的作用域成为一个全局函数,它一样可以访问到f的变量b。
n(); // "b"
第二种可以在F中,把N返回到全局空间中。
仍然使用上面的函数,但是这次的函数和前面的有点区别。
function f(){
var b = "b";
return function(){
return b;
}
}
函数包含一个局部变量b,因此,从全局是无法访问到的。
b; b is not defined
但是函数返回了另一个函数,我们可以把他想象成上面的N。这个新的函数可以访问到他的私有空间,f的空间和全局的空间。所以,他可以访问到b。由于在全局区域可以访问函数f,因此,可以把他的返回值重新赋值给一个新的全局变量。这就成为一个可以访问函数f私有空间的一个全局函数。
var n = f();
n(); // "b"
因此,你可以认为闭包就是创建一个能够在其父函数return以后仍然在父作用域中保持关联的一个函数。
当你传入参数到一个函数,你也可以创建一个函数来return他的父函数的参数。
function f(arg) {
var n = function(){
return arg;
};
arg++;
return n;
}
var m = f(123);
m(); // 124
循环中的闭包
首先来看一下这个程序。
function f() {
var a = [];
var i;
for(i = 0; i < 3; i++) {
a[i] = function(){
return i;
}
}
return a;
}
运行此函数以后赋值给数组a
var a = f();
现在,我们运行数组a中的各个元素。
for(var j=0;j<a.length;j++){
alert(a[j]());
}
可以发现,浏览器弹出3次3。其实,我们在创建了3个指向局部变量i的闭包。闭包并没有记住i的值,而是指向i并返回它当前的值。所以在循环之后,i的值是3。所以就会有我们看到的结果。
要是我们想得到结果是弹出0,1,2的话,应该怎么改呢?其实我们可以利用自执行函数来实现。
function f() {
var a = [];
var i;
for(i = 0; i < 3; i++) {
a[i] = (function(x){
return function(){
return x;
}
})(i);
}
return a;
}
var a = f();
for(var j=0;j<a.length;j++){
alert(a[j]());
}
另外,我们也可以用一个内部函数来解决这样的问题。用这个函数在每次循环中都得到i的值。
function f() {
function makeClosure(x) {
return function(){
return x;
}
}
var a = [];
var i;
for(i = 0; i < 3; i++) {
a[i] = makeClosure(i);
}
return a;
}
Getter/Setter
在Getter/Setter中,我们同样可能会用到闭包。比如:
var getValue, setValue;
(function() {
var secret = 0;
getValue = function(){
return secret;
};
setValue = function(v){
secret = v;
};
})()
这样,当我们想得到一个比较保密的变量,但是又不想让外部能够直接得到这个变量的时候,我们可以用这样的方式来隐藏这些比较保密的变量。
setValue(123);
getValue(); // 123
Iterator
在有的时候,由于一个数组里的元素的结构太过于复杂。要循环这个数组就显得比较麻烦。那么,我们可以把next这个逻辑包含在一个函数内。这样我们就可以通过连续调用这个函数来得到数组里面各个值。
function setup(x) {
var i = 0;
return function(){
return x[i++];
};
}
var next = setup(['a', 'b', 'c']);
next(); // "a"
next(); // "b"
next(); // "c"
利用闭包这样可以把函数内部的变量的生命周期延长,可以把局部变量暴露的特性,我们可以用javascript实现很多复杂的功能。
分享到:
相关推荐
面向对象JavaScript开发是现代Web开发中不可或缺的一部分,它使得JavaScript能够构建复杂、可维护的应用程序。 面向对象编程是一种基于对象和类的编程范式,它强调数据和操作数据的方法的封装。在JavaScript中,...
“基于闭包的JavaScript面向对象编程框架” 本文总结了基于闭包的JavaScript面向对象编程框架的设计和实现。通过使用闭包,实现了基于类的面向对象编程的封装、继承和多态特征。 闭包(Closure)是JavaScript中的...
在JavaScript中,面向对象主要通过以下三种方式实现: 1. **构造函数(Constructor)**:构造函数是一种特殊的函数,用于创建和初始化对象。我们可以通过`new`关键字来调用构造函数,创建一个新的对象实例。例如: ...
JavaScript作为一门浏览器语言的核心思想;面向对象编程的基础知识及其在... 《JavaScript面向对象编程指南》着重介绍JavaScript在面向对象方面的特性,展示如何构建强健的、可维护的、功能强大的应用程序及程序库
《JavaScript面向对象编程指南》内容包括:JavaScript作为一门浏览器语言的核心思想;面向对象编程的基础知识及其在JavaScript中的运用;数据类型、操作符以及流程控制语句;函数、闭包、对象和原型等概念,以代码...
### JavaScript面向对象编程精要 #### 一、引言 JavaScript是一种灵活且强大的脚本语言,它虽然起源于一种简单的浏览器脚本语言,但随着时间的发展,JavaScript已经成为了一种功能全面的编程语言,尤其是在Web开发...
面向对象编程的基础知识及其在JavaScript中的运用;数据类型、操作符以及流程控制语句;函数、闭包、对象和原型等概念,以代码重用为目的的继承模式;BOM、DOM、浏览器事件、AJAX和JSON;如何实现JavaScript中缺失的...
### JavaScript经典面向对象设计 #### 标题与描述解析 标题“JavaScript经典面向对象设计”指出...通过学习本书中的概念和技术,开发者可以更好地理解如何利用面向对象编程的优势,构建出更加健壮和灵活的应用程序。
JavaScript是一种广泛...通过深入学习这本《JavaScript面向对象编程指南(第2版)》,开发者不仅能掌握JavaScript的面向对象编程基础,还能了解到实际项目中如何有效地运用这些知识,提升编程技巧和解决问题的能力。
### JavaScript面向对象精要 #### 一、概述 ...通过学习本书,开发者能够更好地理解JavaScript的底层工作原理,掌握高效的编码技巧,并能灵活运用面向对象的设计原则来构建可维护性强的应用程序。
JavaScript的面向对象主要基于以下三个核心概念: 1. **对象**:对象是JavaScript中的基本单位,它们由属性(key-value对)和方法(可执行的函数)组成。你可以通过创建一个`{}`字面量或`new Object()`来创建一个空...
面向对象的JavaScript编程是JavaScript开发中的重要概念,它允许我们以类和对象的...以上就是对"面向对象javascript笔记"所涵盖知识点的详细解析。理解并掌握这些概念对于深入理解和高效地编写JavaScript代码至关重要。
JavaScript是一种广泛应用于Web开发的脚本语言,它虽然支持面向对象编程,但其面向对象的实现方式与其他传统面向对象语言(如Java或C++)有所不同。JavaScript中的面向对象特性主要体现在以下几个方面: 1. **基于...
JavaScript函数闭包是模拟面向对象编程的一种技术,它允许函数记住并访问其词法作用域,即使在函数执行完毕后也能保持对其的访问。这种特性使得JavaScript能够在没有内置类和继承等传统面向对象特性的情况下实现类似...
在本文中,我们将介绍JavaScript面向对象编程中的经典案例,包括对象、类、继承、原型链和闭包等概念。 一、对象和类 在JavaScript中,对象是指一个实体,可以拥有自己的属性和方法。对象可以使用工厂函数或构造...