javaScript核心技术--“闭包”,不看绝对后悔!

       “闭包”,又称“定义在函数内部的函数”,闭包技术是javaScript中很关键的核心技术,很多框架的研发或者企业高端技术都需要使用到它。要理解闭包技术,必须先弄明白“变量的作用域”。

1.变量的作用域

      javaScript沿袭的java的变量规则,但稍有改进。和java一样可分为“全局变量”和“局部变量”,在javaScript中的“局部变量”又称之为函数变量。

var x = 999;

function f1() {
  var y = 888;
  console.log(x); 
  console.log(y); 
}
f1() // 999  888
console.log(y); // Uncaught ReferenceError: n is not defined

由上可知javaScript和java一样,父对象的所有变量,对子对象都是可见的,反之则不成立。而有些时候需要在外部父对象中获取子对象区域内部的变量,正常情况下是无法做到的,这时候就需要用到“闭包”技术了。

2.什么是闭包

       请先看以下的函数:

function f1() {
  var n = 999;
  function f2() {
  console.log(n); // 999
  }
}

上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。上面的代码中内部函数分f2()就是“闭包”,一个定义在函数内部的函数。
 

3.闭包的用途

3.1. 突破局域限制,读取函数内部的变量值。

逻辑思维分析:

        上面我们已经知道了函数f2()就是闭包,那么我们如果去使用它获取函数内部的变量呢?

        分析:既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在外部得到返回值,进而间接读取它的内部变量了吗!

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。

3.2.“记住”诞生的环境

       闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。它可以让这些变量始终保持在内存中,使得它诞生环境一直存在。

现在,假设我们有一个需求:每调用一次函数,都记录这个函数的被调用的次数。如何实现?用我们常规的思维,肯定是定义一个外部变量,然后每调用一次就++,如下所示:

var start = 0;
function test_01() {
  start++;
}

test_01();
start // 1
test_01();
start // 2
test_01();
start // 3

 你们会发现,上面的方式完美的实现了。但假如需求在改动一下,函数test_01()内部还有一个函数test_02(),要录test_02()函数被调用的次数,这个时候如何实现呢?我们继续按上面的套路搬砖:

var start = 0;
function test_01() {

    function test_02() {
        start++;
    }
}

test_01();
start // 0
test_01();
start // 0
test_01();
start // 0

这时候你们会发现,无论你调用多少次函数,start都不会增长,一直是0。why?

但如果你把上面的代码改一改,将函数test_02作为返回值,并且外部定义一个变量接受它,就不一样了。

var start = 0;
function test_01() {

   return function test_02() {
        start++;
    }
}

var temp = test_01();
temp ();
start // 1
temp ();
start // 2
temp ();
start // 3

 可以看到start的值又神奇般的增长了。这究竟是为什么呢?你是否感觉到了想破脑袋也想不明白是为什么?哈哈……

其实这就是闭包技术的一种体现。用比较科学的术语来技术就是:“temp始终在内存中,而temp的存在依赖于函数test_01(),函数test_01()也因此始终在内存中,不会在调用结束后,被垃圾回收机制回收。所以它才能一直记录下这个‘诞生环境’ ”。

       上面的这种解释可能过于“科学语言”,让人难以理解。因此我用比较通俗的语言来解释:因为我在外部声明了一个变量temp,它调用了函数test_01(),而test_01()又返回了函数test_02()。所以上面的代码可以等价与下面的代码:

var start = 0;
function test_01() {

   return function test_02() {
        start++;
    }
}

var temp = function test_02() {
        start++;
    };
temp ();
start // 1
temp ();
start // 2
temp ();
start // 3

上面我举的第一个例子就已经很好的说明了,在同一个作用域操作一个变量是可以成功的。这种变换操作手法更this的作用域极其相似。javaScript中this始终指向当前对象,然而this的指向却是动态的。说到这里了我就随便提一提this的作用域吧。

this的作用域


var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var B = {
  name: '李四'
};

B.describe = A.describe;
B.describe()
// "姓名:李四"

var name = '龚文学';
var tenp = function () {
    return '姓名:'+ this.name;
  };

temp(); // 姓名:龚文学

从上面可以看出在A对象和B对象调用同一个函数this的指向不同,所以输出了不能的结果。如果把这个函数提取出来,赋值给一个变量,this的指向就是最顶层的window对象,这个时候就输出了我的顶顶大名--“龚文学”。这与“闭包”的方式十分类似,我以此举例说明,希望能帮助大家理解。如果大家还是有不懂的地方,请在微信公众平台《Java深度编程》留言。

3.3.封装对象的私有属性和私有方法

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25

上面代码中,函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。因为闭包能一直记住之前的环境,所以Person的内部变量会随之永久改变,这与java的get,set方式十分类似。

 

4.闭包深度理解

        通过上面的论述,如果你已经认真的阅读过了,相信你已经对闭包有了一定的认识与理解,但这还不够。下面我将进一步的由浅入深的详加说明。

        上面我例子中的函数是无参的,如果是一个有参的呢?假如现在有一个需求调用一个函数,传入一个固定数字,每调用一次都在这个固定参数值上加1,无论后面调用参数是否改变,都以这个固定参数为准,该怎么实现呢?

function test(num) {
  return function () {
    return num++;
  };
}

var inc = test(5);

inc() // 5
inc() // 6
inc() // 7

 从上面的代码可以看出闭包内部函数记住了第一次传的参数5,并且每次调用都是在这个5的基础上+1,所以连续三次后num的值变成了7,为什么会这样呢?按我们常规的思维,我每次调用函数传的参数都是5,那么每次都应该是5++,应该都返回6才对啊。而事实却让我们难以理解。或许我将上面的代码改造一下会让人容易理解些:

function test(num) {

  var temp = num
  return function () {
    return temp ++;
  };
}

var inc = test(5);

inc() // 5
inc() // 6
inc() // 7

其实一个函数接受一个参数后,它的作用域就当拥有了这个值,也就是说相当于我上面的var temp = 参数。我们知道“子对象会一级一级的向上寻找变量,如果找不到就报错。如果找到了就取最近的返回”。所以上面的闭包中,第一次调用的时候函数test接受了参数5,内部也就保存了这个参数5,当继续执行内部闭包函数的时候,因为这个函数内部没有声明temp这个变量,所以它会去上面找,也就找到了test函数中的temp,也就是第一次传参数的num = 5。当找到这个参数temp后,内部的闭包函数就又会保存在自己的作用域中,所以在闭包中就存在了 temp = 5这个变量,随后执行了++,temp就变成了6;当第二次调用函数test(5)的时候,test函数的内部参数的确又重新变成了5,但是当它继续执行闭包函数的时候,这时候闭包函数内部已经有这个值了,所以它不会再向父级对象继续寻找变量了,所以也就 导致了第二次传参的时候,不管你传的参数是多少都没有关系了,它已经记录了第一次传的值。

function test(num) {
  return function () {
    return num++;
  };
}

var inc = test(5);

inc(5) // 5
inc(500) // 6
inc(1000) // 7

以上的代码就足以证明了我的推论。第二次已经调用函数,闭包中的函数已经不再接受入参了,而是取第一次的入参。

总结:

         所谓的闭包,就是一个定义在函数内部的函数。每个函数都有它的作用域,并且将内部的变量“封存”起来,所以称之为"闭包"。在这内部函数(闭包)中,它能记录第一次执行的“诞生环境”,进而能使得外部的作用域能跨域读取到这个内部函数的变量值。

 

5.闭包的弊端

       注意,因为外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,外层函数多次运行后会导致内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

 

亲爱的同学们,如果您对本次“闭包”技术还有不理解的,或者有更多,更深入的研究,欢迎前来微信公众平台《Java深度编程》探讨,如果您有不错的原创技术文章,欢迎前来投稿。感谢您本次的阅读。

 

 

JavaScript核心技术 简介: 《JavaScript核心技术》对于各种浏览器、浏览器版本、JavaScript版本、DOM版本的介绍,有助于我们理解所遇到的各种新旧代码,使我们能够对这些代码做出正确的取舍。《JavaScript核心技术》还提供了一些使用JavaScript的最佳实践。无论是新手还是老手,这些如何正确使用JavaScript的经验都能帮助他们养成良好的编程习惯。《JavaScript核心技术》还介绍了一些调试和开发JavaScript的工具,这些工具无疑能够提高我们的开发效率。 《JavaScript核心技术》最后对于Ajax和几个流行的JavaScript库的介绍,无疑会开阔我们在JavaScript使用上的思路。 《JavaScript核心技术》是一本真正意义上的“新书”,不仅介绍了最新的JavaScript知识和方向,还完全覆盖了当今Web开发中关于JavaScript的所有重要话题,它使用了大量实例代码,图文并茂地讲解了使用JavaScript的各个层次和领域的内容。它不是一本参考手册,但却是一本值得拥有的教程。 JavaScript核心技术 目录: 前言 第1章JavaScript初探 1.1规范和实现相互交织的历史 1.2跨浏览器的不兼容性和其他常见的JavaScript传说 1.3你能用JavaScript来做什么 1.4JavaScript初探:“HelloWorld!” 1.5JavaScript沙箱 1.6可访问性和JavaScript的最佳实践 第2章JavaScript数据类型与变量 2.1变量的标识 2.2作用域 2.3简单类型 2.4常量:有名称但不改变 2.5习题 第3章运算符和语句 3.1JavaScript语句的格式 3.2简单语句 3.3条件语句和程序流 3.4条件运算符 3.5逻辑运算符 3.6高级语句:循环语句 3.7习题 第4章JavaScript对象 4.1对象构造函数 4.2Number对象 4.3String对象 4.4正则表达式与RegExp 4.5有专门用途的对象:Date和Math 4.6JavaScript数组 4.7关联数组:不是数组的数组 4.8习题 第5章函数 5.1定义函数:细数所有方式 5.2回调函数 5.3函数和递归 5.4嵌套函数函数闭包和内存泄漏 5.5作为对象的函数 5.6习题 第6章捕捉事件 6.1O级DOM上的事件句柄 6.22级DOM上的事件句柄 6.3产生事件 6.4习题 第7章表单与即时验证 7.1访问表单 7.2把事件附加在表单上:不同的方法 7.3选择列表 7.4单选按钮和复选框 7.5输入字段和JiT正则表达式 7.6习题 第8章沙箱及之上的cookie、连通性和隐私 第9章基础浏览器对象 第10章DOM:文档对象模型 第11章创建定制的JavaScript对象 第12章构建动态网页:在脚本中加入样式 第13章使用Ajax 第14章好消息:生动的程序库!令人惊异的Web服务!有趣的API! 附录习题答案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值