Javascript中的闭包

前面的话:

  闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它。下面是作者从作用域链慢慢讲到闭包以及在后面提到了一些闭包的高级用法。下面大家一起来学习Javascript中的闭包。

谈一谈JavaScript作用域链

  当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。

  作用域链的作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。

看下面的例子:

//定义全局变量color,对于全局都适用,即在任何地方都可以使用全局变量color
var color = "red"; function changeColor(){
//在changeColor()函数内部定义局部变量anotherColor,只在函数changeColor()里面有效
var anotherColor = "blue"; function swapColor(){
//在swapColor()函数内部定义局部变量tempColor,只在函数swapColor()里面有效
var tempColor = anotherColor;
anotherColor = color;
color = tempColor; //这里可以访问color、anotherColor和tempColor
console.log(color); //blue
console.log(anotherColor); //red
console.log(tempColor); //blue
} swapColor();
//这里只能访问color,不能访问anotherColor、tempColor
console.log(color); //blue
console.log(anotherColor); //anotherColor is not defined
console.log(tempColor); //tempColor is not defined
} changeColor();
//这里只能访问color
console.log(color); //blue
console.log(anotherColor); //anotherColor is not defined
console.log(tempColor); //tempColor is not defined

 还有几个坑需要注意一下:

1、var和函数的提前声明

var color = "red";

function changeColor(){
var color = "yellow";
return color;
} var result = changeColor();
console.log(result);

再如:

function fn(a) {
console.log(a);
var a = ;
function a() {}
console.log(a);
}
fn();
//输出:function a() {} ,2

2、Javascript中没有块级作用域,但是有词法作用域,比如:

function f1(){var a=;f2();}
function f2(){return a;}
var result = f1();
console.log(result);
//输出结果:a is not defined

3、在函数内部不用var关键字申明变量,则默认该变量为全局变量,比如:

function add(a,b){
var sum = a+b;//次世代sum为add函数内部的变量,仅限在函数内部使用,在函数外面不可以使用
return sum;
}
var result = add(,);
console.log(result); //
console.log(sum); //sum is not defined
//不使用var关键字声明变量
function add(a,b){
sum = a+b;//此时的sum为全局变量,在函数之外也可以调用
return sum;
}
var result = add(,);
console.log(result); //
console.log(sum); //

补充:

在JavaScript中如果不创建变量,直接去使用,则报错:

console.log(xxoo);
// 报错:Uncaught ReferenceError: xxoo is not defined

JavaScript中如果创建值而不赋值,则该值为 undefined,如:

var xxoo;
console.log(xxoo);
// 输出:undefined

在函数内如果这么写:

function Foo(){
console.log(xo);
var xo = 'seven';
} Foo();
// 输出:undefined

上述代码,不报错而是输出 undefined,其原因是:JavaScript的函数在被执行之前,会将其中的变量全部声明,而不赋值。所以,相当于上述实例中,函数在“预编译”时,已经执行了var xo;所以上述代码中输出的是undefined。

注意:我们平时在声明变量时一定要注意!!!还有不要滥用全局变量(在forin循环的时候特别注意)!!!

4、词法作用域是不可逆的,我们可以从下面的例子中看到结果:

// name = undefined
var scope1 = function () {
// name = undefined
var scope2 = function () {
// name = undefined
var scope3 = function () {
var name = 'Todd'; // locally scoped
};
};
};

前面我们了解了作用域的一些基本知识,我们发现有作用域的存在能帮我们省去不少事,但是于此同时,也给我们带来了很多麻烦,比如说我们想在下面的函数A中,调用函数B,我们该怎么办呢?

function A(){
    function B(){
           //
    }
}

思路:我们给函数B设一个返回值,然后在函数A中调用,代码如下:

function A(){
function B(){
console.log("Hello foodoir!");
}
return B;
}
var c = A();
c();//Hello foodoir!

这样我们就可以得到我们想要的结果。这样,我们基本上到了一个最简单的闭包形式。我们再回过头分析代码:

(1)定义了一个普通函数A
(2)在A中定义了普通函数B
(3)在A中返回B(确切的讲,在A中返回B的引用)
(4)执行A(),把A的返回结果赋值给变量 c
(5)执行 c()

把这5步操作总结成一句话:函数A的内部函数B被函数A外的一个变量 c 引用。当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。

思考:我们还有没有其他的方法?

思路:使用匿名函数

function A(){
//匿名函数
var B = function(x,y) {
return x+y;
}
console.log(B(,));//
return B(,);
}
var c = A();
console.log(c);//

然而,在Javascript高级程序设计中是这样描述闭包的“闭包是指有权访问另一个函数作用域中的变量的函数”,但是我们看匿名函数的例子,很明显,这种方法不可取!
通过这个例子,能让我们更好的理解闭包。

下面我们再来看下面的几种闭包

demo1:

function fn(){
var b = "foodoir";
return function(){
console.log(b);//foodoir
return b;
}
}
//console.log(b);//b is not defined
var result = fn();
console.log(result());//foodoir

demo2:

var n;
function f(){
var b = "foodoir";
n = function(){
return b;
}
}
f();
console.log(n());//foodoir

demo3:

//相关定义与闭包
function f(arg){
var n = function(){
return arg;
};
arg++;
return n;
}
var m = f();
console.log(m());//124
//注意,当我们返回函数被调用时,arg++已经执行过一次递增操作了,所以m()返回的是更新后的值。

demo4:闭包中的读取与修改

//闭包中的设置与修改
var getValue,setValue;
(function(){
var n = ;
getValue = function(){
return n;
};
setValue = function(x){
n = x;
}
})();
//console.log(n);
console.log(getValue());//
console.log(setValue());//undefined setValue();
console.log(getValue());//

demo5:用闭包实现迭代效果

//用闭包实现迭代器效果
function test(x){
//得到一个数组内部指针的函数
var i=;
return function(){
return x[i++];
};
}
var next = test(["a","b","c","d"]);
console.log(next());//a
console.log(next());//b
console.log(next());//c
console.log(next());//d

demo6:循环中的闭包

//循环中的闭包
function fn(){
var a = [];
for(var i=;i<;i++){
a[i] = function(){
return i;
}
}
return a;
}
var a = fn();
console.log(a[]());//
console.log(a[]());//
console.log(a[]());// /*
* 我们这里创建的三个闭包,结果都指向一个共同的局部变量i。
* 但是闭包并不会记录它们的值,它们所拥有的只是一个i的连接,因此只能返回i的当前值。
* 由于循环结束时i的值为3,所以这三个函数都指向了3这一个共同值。
* */

思考:如何使结果输出分别为0、1、2呢?

思路一:我们可以尝试使用自调用函数

function fn(){
var a = [];
for(var i=;i<;i++){
a[i] = (function(x){
return function(){
return x;
}
})(i);
}
return a;
}
var a = fn();
console.log(a[]());//
console.log(a[]());//
console.log(a[]());//

思路二:我们将i值本地化

function fa(){
function fb(x){
return function(){
return x;
}
}
var a = [];
for(var i=;i<;i++){
a[i] = fb(i)
}
return a;
}
console.log(a[]());//
console.log(a[]());//
console.log(a[]());//

------------------------------------------------------分界线-------------------------------------------------------

在这里,我们来对闭包进行更深一步的操作

我们再将demo1的例子进行扩展

代码示例如下:

function funcTest(){
var tmpNum=; //私有变量
//在函数funcTest内
//定义另外的函数作为funcTest的方法函数
function innerFuncTest(
{
alert(tmpNum);
//引用外层函数funcTest的临时变量tmpNum
} return innerFuncTest; //返回内部函数
} //调用函数
var myFuncTest=funcTest();
myFuncTest();//弹出100

到样,我们对闭包的概念和用法有更加熟悉

闭包和this相关

闭包应用举例,模拟类的私有属性,利用闭包的性质,局部变量只有在sayAge方法中才可以访问,而name在外部也访问,从而实现了类的私有属性。

function User(){
this.name = "foodoir"; //共有属性
var age = ; //私有属性
this.sayAge=function(){
console.log("my age is " + age);
}
}
var user = new User();
console.log(user.name); //"foodoir"
console.log(user.age); //"undefined"
user.sayAge(); //"my age is 21"

关于闭包更深入的了解
前面在demo6中,我们了解了用自调用方法来实现闭包,下面我们用这种方法来进行更复杂的操作(写一个简单的组件)。

(function(document){
var viewport;
var obj = {
init:function(id){
viewport = document.querySelector("#"+id);
},
addChild:function(child){
viewport.appendChild(child);
},
removeChild:function(child){
viewport.removeChild(child);
}
}
window.jView = obj;
})(document);

这个组件的作用是:初始化一个容器,然后可以给这个容器添加子容器,也可以移除一个容器。功能很简单,但这里涉及到了另外一个概念:立即执行函数。 简单了解一下就行。主要是要理解这种写法是怎么实现闭包功能的。

闭包并不是万能的,它也有它的缺点

  1、闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题。另外在IE下有可能引发内存泄漏 (内存泄漏指当你的页面跳转的时候 内存不会释放 一直占用你的CPU 只有当你关闭了浏览器才会被释放);

  2、闭包会在父函数外部改变父函数内部的变量的值,所以不要随便改动父函数内部的值。

更多参考资料:

  《Javascript高级程序设计(第三版)》第四章、第七章

  《Javascript面向对象编程指南》第三章

作者的话:

  这篇文章主要先是通过几个简单的例子介绍作用域链(顺便补充了几个和作用域链相关的易出错的小知识),然后通过提问慢慢过渡到闭包(在闭包这部分介绍了几种常见闭包的例子),后面又进一步讲到了关于闭包的更高级的用法。后面遇到关于闭包的较好的用法会继续更新。

让你分分钟学会Javascript中的闭包的更多相关文章

  1. 难道这就是JavaScript中的"闭包"

    其实对于JavaScript中的"闭包"还没真正理解,这次在实际Coding中似乎遇到了"闭包"的问题,仅此摘录,以待深究. 表现为jQuery的post方法回 ...

  2. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  3. javascript中的闭包解析

    学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去 ...

  4. JavaScript中的闭包理解

    原创文章,转载请注明:JavaScript中的闭包理解  By Lucio.Yang 1.JavaScript闭包 在小学期开发项目的时候,用node.js开发了服务器,过程中遇到了node.js的第 ...

  5. 【JS】JavaScript中的闭包

    在JavaScript中,闭包指的是有权访问另一个函数作用域中的变量的函数:创建闭包最常见的方式就是在一个函数内创建另一个函数.如下例子: function A(propertyName){ retu ...

  6. Javascript中的闭包(转载)

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

  7. 狗日的Javascript中的闭包

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

  8. [译]Javascript中的闭包(closures)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  9. javaScript中的闭包原理 (译)

    这篇文章通过javaScript代码解释了闭包的原理,来让编程人员理解闭包.它不是写给大牛或使用功能性语言进行编程的程序员的.一旦意会了其核心概念,闭包理解起来并不难.然而,你不可能通过阅读任何有关闭 ...

随机推荐

  1. android——handler机制原理

    在android版本4.0及之后的版本中多线程有明确的分工,子线程可以写所有耗时的代码(数据库.蓝牙.网络服务),但是绝对不能碰UI,想碰UI跟着主线程走,那么我们如何才能让主线程知道我们要对 UI进 ...

  2. 微软开源 WCF 分布式服务框架,并入 .NET 基金会项目

    微软北京时间2015.5.20 在其 .NET Foundation GitHub 开源项目页中开放了 WCF 分布式服务框架的代码.WCF突然之间成为一个热门话题,在各大网站上都有不同的报道:dot ...

  3. SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因

    原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...

  4. 探索c#之一致性Hash详解

    阅读目录: 使用场景 算法原理 虚拟节点 代码示例 使用场景 以Redis为例,当系统需要缓存的内容超过单机内存大小时,例如要缓存100G数据,单机内存仅有16G时.这时候就需要考虑进行缓存数据分片, ...

  5. Net作业调度(五)—quartz.net动态添加job设计

    介绍 在实际项目使用中quartz.net中,都希望有一个管理界面可以动态添加job,而避免每次都要上线发布. 也看到有园子的同学问过.这里就介绍下实现动态添加job的几种方式, 也是二次开发的核心模 ...

  6. ubuntu下rhythmbox歌名显示乱码问题解决

    问题描述:本人装有双系统,一个是win7,另一个是ubuntu12.04LTS版本,所有的歌曲都在windows磁盘下KuGou目录中,这个时候,使用ubuntu的rhythmbox播放的歌曲的时候, ...

  7. 《Entity Framework 6 Recipes》中文翻译系列 (45) ------ 第八章 POCO之获取原始对象与手工同步对象图和变化跟踪器

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-6  获取原始对象 问题 你正在使用POCO,想从数据库获取原始对象. 解决方案 ...

  8. Atitit 教育与培训学校 的计划策划 v4 qc18

    Atitit 教育与培训学校 的计划策划 v4 qc18 1.1. 版本历史12. 教育历史的前世今生12.1. 自学vs 家庭学校vs 私立学校vs 公立学校模式 vs 企业内部学校 vs 其他商业 ...

  9. JSP的基本语法

    JSP的基本语法 一.JSP页面中的JAVA代码 二.JSP页面中的指令 三.JSP页面中的隐含对象(九大内置对象) 目录 一.JSP页面中的JAVA代码 JSP表达式(方便输出) JSP小脚本(完成 ...

  10. c#设计模式-适配器模式

    一. 适配器(Adapter)模式 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作. 名称由来 这很像变压器(Adapter),变压 ...