【javascript基础】8、闭包
前言
函数和作用域啥的我们前面已经了解了,现在就要学习闭包了,这是一个挺晦涩的知识点,初学者可能会感觉不好理解,但是高手都不不以为然了,高手就给我提点意见吧,我和新手一起来学习什么是闭包。
例子
先不说定义,先看一个题,看看大家能得出正确的结果不,
function test(){var arr = [];for(var i = 0;i<10;i++){arr[i] = function(){return i;}}return arr; }var fns = test(); console.log(fns[9]()); // 值是多少? console.log(fns[0]());//值是多少?
结果就是
10 10
View Code
你做对了吗?
什么是闭包
我们知道,javascript中的变量作用域分为全局变量和局部变量,全局的变量我们在什么地方都可以使用,但是局部变量就不是这样的了,我们只能在该变量的作用域中得到,换句话说就是我们在函数的内部可以使用函数外部的变量,但是我们在函数的外部却不能使用函数内部定义的局部变量,但是在实际中我们就是想要在函数的外部使用函数内部定义的变量那该怎么办呢?例子来了
function test(){var inner = 10; } alert(inner);//error?咋办
咋办呢?我们知道,在内部我们可以访问到这个变量,我们还知道有一个操作符return可以返回想要的值,那我就在内部定义一个函数来访问这个变量,然后在返回这个函数不就行了,实践一下
function test(){var inner = 10;function inFun(){alert(inner);// };return inFun; } var outter = test(); outter();//10;
我们做到了,为自己鼓鼓掌,有时候我们就该不断鼓励自己一下,不要给自己太大的压力,我们不是富二代,在不鼓励一下自己怎么能成为富二代他爹呢。
这就是闭包了,官方没有给出闭包一个完整的准确的定义,民间流传的是在一个函数内定义一个函数,并且这个内部函数可以在外面访问,这时候就形成了闭包。看看上面函数的结构,一个函数返回了一个内部函数,我们知道在正常情况下,一个函数执行结束之后,里面的变量会被释放,也就是说,在test()这句执行之后,里面的inner应该被释放了才对,但是我们发现,outter()时我们拿到了inner的值,这就是闭包的特性:如果闭包中使用了局部的变量,那么这个变量会一直贮存在内存中,闭包会一直保持这个值,一直到外部的函数没有被引用为止,看例子
function closure(){var num = 0;function add(){console.log(++num);}return add; } var test1 = closure();//形成一个闭包,保持着自己的一个num变量 test1 ();//1 test1 ();//2 var test2 = closure();//又一个闭包,保持了一个自己的num变量 test2 ();//1 test2 ();//2
好玩不?这就是闭包的神奇的地方,也是让身为初学者的我们感到彷徨的地方,相信我,我会让你们理解明白的。要想释放num占用的内存,就该这样
test1 = null; test2 = null;
简单解析下这个例子:在执行 var test1 = closure()时,由于closure()返回到是一个函数,这里就相当于test1变量指向了一个函数add,但是这个add函数有自己的作用域和活动对象,都存在了test1中,执行test1()时,会寻找num变量,由于闭包存储了该变量就可以直接取到,并且自加1,再一次执行test1()时会继续在test1执行的add函数的执行环境和作用域中查找,发现num为1了,就找到了这个num;在执行var test2 = closure()时,会重新创建一个闭包,重新存储执行环境和活动对象,所以这是和第一次完全没有关系的。
闭包的机制
函数也是对象,有[[scope]]属性(只能通过JavaScript引擎访问),指向函数定义时的执行环境上下文。
假如A是全局的函数,B是A的内部函数。执行A函数时,当前执行环境的上下文指向一个作用域链。作用域链的第一个对象是当前函数的活动对象(this、参数、局部变量),第二个对象是全局window。
当执行代码运行到B定义地方, 设置函数B的[[scope]]属性指向执行环境的上下文作用域链。
执行A函数完毕后,若内部函数B的引用没外暴,A函数活动对象将被Js垃圾回收处理;反之,则维持,形成闭包。
调用函数B时,JavaScript引擎将当前执行环境入栈,生成新的执行环境,新的执行环境的上下文指向一个作用域链,由当前活动对象+函数B的[[scope]]组成,链的第一个对象是当前函数的活动对象(this、参数、局部变量组成),第二个活动对象是A函数产生的,第三个window。
B函数里面访问一个变量,要进行标志符解析(JavaScript原型也有标识符解析),它从当前上下文指向的作用域链的第一个对象开始查找,找不到就查找第二个对象,直到找到相关值就立即返回,如果还没找到,报undefined错误。
当有关A函数的外暴的内部引用全部被消除时,A的活动对象才被销毁。
这段是其他的地方的,就是说了执行环境和作用域的理解闭包怎么维持变量的。
闭包的应用
一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,这既是函数也是弊端。我们可以利用闭包封装一些私有的属性,例如
var factorial = (function () {var cache = [];return function (num) {if (!cache[num]) {if (num == 0) {cache[num] = 1;}cache[num] = num * factorial(num - 1);}return cache[num];} })();
封装了一个内部私有的属性来缓存结果。
下面流行的模块模式,它允许你模拟公共,私有以及特权成员
var Module = (function(){var privateProperty = 'foo';function privateMethod(args){//do something }return {publicProperty: "",publicMethod: function(args){//do something },privilegedMethod: function(args){privateMethod(args);}} })();
另一个类型的闭包叫做立即执行函数表达式,是一个在window上下文中自我调用的匿名函数:
(function(window){var a = 'foo';function private(){// do something }window.Module = {public: function(){// do something }};})(this);
闭包的弊端
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
解释例子
回到开始的例子,这是闭包的经典的例子,这个和其他的例子和有些不一样,我们分析一下,这里用了一个数组,其实这里我们执行一次var fns = test(),形成了10个闭包,数组的每一个项存了一个闭包,这与其他的例子是不一样的,其他的例子是函数执行一次形成了一个闭包,所以这个10个闭包的初始的执行环境是一样的,每一个闭包使用了i这个变量,这个变量在函数var fns = test()执行之后变为了退出循环的那个i的值10,JavaScript是解释型的语言,所以在执行数组中的闭包的时,会找到此时i的值10;看看arr的结果
现在想怎样解决这个问题呢?我们想想,这10个闭包形成时的执行环境和活动对象是一样的,现在考虑的就是要在初始时就不一样,我们知道函数的作用域是一层一层的,那我们就需要在这之间家一层作用域,这层作用域要有不同的i的值,我们想到了自执行匿名函数,(funciton(){})(),我们把i的值穿进去,按值传参就是相当于复制了一份变量嘛,在(funciton(){})()外部的作用域中的i的值的改变不会改变内部的i的值,试一下
function test(){var arr = [];for(var i = 0;i<10;i++){(function(i){ arr[i] = function(){return i;}})(i);}return arr; }var fns = test(); console.log(fns[9]()); // 值是9 console.log(fns[0]());//值是0
当然也可以这样
function test(){var arr = [];for(var i = 0;i<10;i++){arr[i] = (function(i){return function(){return i}})(i);}return arr; }var fns = test(); console.log(fns[9]()); // 值是9 console.log(fns[0]());//值是0
这两个的实质都是在闭包形成之前,给每一个闭包包上一层作用域,在这个作用域中传一个参数,是每一个闭包上一级的作用域中都有不同的i。当然还有其他的办法这里不说了。
小结
闭包的应用场景挺多的,在模块化编程中很重要的,有些地方说函数也是闭包,还是那就话,概念不重要,理解会用才是最现实的。
转载于:https://www.cnblogs.com/allenxing/p/3578914.html
【javascript基础】8、闭包相关推荐
- JavaScript基础系列---闭包及其应用
闭包(closure)是JavaScript中一个"神秘"的概念,许多人都对它难以理解,我也一直处于似懂非懂的状态,前几天深入了解了一下执行环境以及作用域链,可戳查看详情,而闭包与 ...
- 2017/5 JavaScript基础9 --- 闭包、作用域
2019独角兽企业重金招聘Python工程师标准>>> 一.理解闭包 1.闭包的例子 //一般函数 function outer(){var localVal = 30; //局部变 ...
- JavaScript基础之闭包
文章目录 一.闭包(closure) 从作用域链理解闭包 面试中的闭包 解决方法 一.闭包(closure) 来自红宝书: 闭包是指有权访问另外一个函数作用域中的变量的函数.关键在于下面两点: 是一个 ...
- Web前端-JavaScript基础教程上
Web前端-JavaScript基础教程 将放入菜单栏中,便于阅读! JavaScript是web前端开发的编程语言,大多数网站都使用到了JavaScript,所以我们要进行学习,JavaScript ...
- 常见JavaScript基础面试题上(附答案)
常见JavaScript基础面试题上(附答案) 1.JavaScript有哪些垃圾回收机制? 有以下垃圾回收机制. 标记清除( mark and sweep) 这是 JavaScript最常见的垃圾回 ...
- 深入理解javascript原型和闭包(16)——完结
之前一共用15篇文章,把javascript的原型和闭包. 首先,javascript本来就"不容易学".不是说它有多难,而是学习它的人,往往都是在学会了其他语言之后,又学java ...
- 前端---JavaScript基础1
文章目录 前端---JavaScript基础1 数据类型 面向对象 对象创建方式 对象类型判断 前端-JavaScript基础1 JS是 解释型语言:跨平台 慢 编译型语言:不能跨平台 快 数据类型 ...
- javascript 基础 转
最近面试过一些人, 发现即使经验丰富的开发人员, 对于一些基础的理论和细节也常常会模糊. 写本文是因为就我自己而言第一次学习下面的内容时发现自己确实有所收获和感悟. 其实我们容易忽视的javascri ...
- Javascript基础回顾 之(二) 作用域
本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者 ...
- JavaScript基础教程新手入门必看
对前端稍微有点了解的初学者都知道,JavaScript是必不可少的工具.毫不夸张的说,大部分网页都使用了JavaScript,想要成为一个优秀的前端工程师,做出漂亮令用户满意的网页,熟练掌握JavaS ...
最新文章
- __cplusplus的用处
- oracle insert两个关联表
- java jax-rs_在Java EE 6中将Bean验证与JAX-RS集成
- [精品]CSAPP Bomb Lab 解题报告(二)
- freemarker 对null 的处理
- Python面向对象编程案例:封装数据库增删改查操作
- Bug(二)——error LNK1104:无法打开“opengl32.lib”
- 在Mybatis中处理sql中的大于号小于号
- 为什么找不到解决方案?--答案就是:转个弯 这里以“解决表示图左边缺失线条、边缘线、分割线问题”为例...
- 项目周例会会议纪要模板
- mysql数据库丢失还原_MySQL数据库丢失后如何自动恢复呢?
- JAVA基础知识点总结
- 杭州城市交通拥堵综合治理实践
- 专科三年的教训,写给自己,也给正在学习路上的你
- 模电和数电复习资料//2021-2-18
- 计算机学的痛苦可以换专业,在大学里选错了专业,是一种怎样的“痛苦”体验?...
- Android 调用地图导航
- 摩尔定律,梅特卡夫定律和科斯定律
- 什么是适合中小企业的ERP
- eclipse资源管理器直接打开文件目录方法