博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
我是怎样理解闭包的
阅读量:6315 次
发布时间:2019-06-22

本文共 4161 字,大约阅读时间需要 13 分钟。

什么是闭包

文章首发:  

最近更新: 2014年02月28日14:39:58 ()

一、一个问题

例一:假设要写动态生成HTML的函数,HTML有一部分是固定的。

方案①

function buildHtml(args) {    var template = ['
', '', ' ', '', '
']; template[1] = args[0]; template[3] = args[1]; return template.join('');}

  

执行函数时,先定义template,执行完销毁template,多次执行就多次重复定义和销毁,这样不好,考虑把template变量放到生成Html函数的外面,改造后成了这样:

方案②

//...var template = ['
', '', ' ', '', '
'];function buildHtml(args){ template[1] = args[0]; template[3] = args[1]; return template.join('');}//...这里可以调用上面的代码

当其它页面或模块也需要这个生成Html函数时,拷贝相关代码时,要保证template变量名和buildHtml函数名不能与已有的命名空间冲突,还可以这样实现:

方案③

//...buildHtml.template = ['
', '', ' ', '', '
'];function buildHtml(args){ var template = buildHtml.template; template[1] = args[0]; template[3] = args[1]; return template.join('');}//...这里可以调用上面的代码

方案④

var buildHtml = (function(){    var template = ['
', '', ' ', '', '
']; return function(args){ template[1] = args[0]; template[3] = args[1]; return template.join(''); }})();

方案3和方案4都只用考虑其中一个变量名是否冲突。方案4 结构更紧凑,且template私有化,外面无法访问,这里就用到了闭包。

二、闭包定义

在一个函数内定义一个函数,并且这个内部函数可以在外面访问,这时候就形成了闭包。

方案④是立执行函数,函数内部变量template在函数执行完后,仍保留在内存中,等待内部函数调用,当buildHtml=null时,才被内存回收。同理,当外部函数执行完毕后,若内部函数就不能访问了,就不形成闭包。

那怎样实现访问内部函数,通过return将内部引用暴露给外部?将内部函数赋给全局变量?全局变量可以是未经var声明而即时产生的,也可能是传入的实参,如例四。

例二:

function wrapFns (){    var arr = [];    for(var i = 10;i--;){        arr[i] = function(){            return i;        }    }    return arr;}var fns = wrapFns();console.log(fns[10]()); // 值是多少?

内部函数对象,通过隐藏属性引用[[scope]],访问指向的作用域链各个活动对象。在这里,第一个活动对象就是wrapFns的上下文,所以当执行wrapFns时,访问到的i已经为0了,输出值是:0。

 

三、闭包的内部机制

为什么函数的局部变量会被维持?闭包的内部机制是怎样的?函数嵌套很多层时的闭包是怎样的?

①函数也是对象,有[[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的活动对象才被销毁。

如果B函数引用给外部,它的[[scope]]中的活动对象,比如父函数A的活动对象会被维持,当B函数再次调用时,仍然可以读写,这样可实现数据的本地存储,并且只能由你这个接口访问。

作用域链临时变更情况

  1. 如使用with(obj){},try{}catch(obj){},会将obj添加到作用域链的首部,其它活动对象相应后移一位,直到with结束。
  2. 使用Function定义的函数对象,函数对象的[[scope]]属性置为window。

四、闭包的应用

1、封装插件

(function(window,undefined){    window.ymPrompt = {}; // })(window);

2、保存私有属性  

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];    }})();

  

五、那些不经意间用了闭包

1. Demo1

var object = {    name:"My Object",    getNameFunc: function(){        var that = this;        return function(){            return that.name;        };    }};console.log(object.getNameFunc()());// My Object

  

2. Demo2

(function(w,undefined){    var temp = ['a'],        domRefer = document.getElementById('');        domRefer.addEventListener('click',function(event){            //...        },false);})(window)

  

六、使用闭包注意

1、只有需要时才使用闭包,闭包会导致一些东西常驻内存。

一个这样的构造函数

function Construt(){     var bb;     this.aa = '1';     this.fun1 = function(){};     this.fun2 = function(){}; }

  

在fun1和fun2中没用到bb,就不要用闭包。

function Construt(){    this.aa = '1';}Construt.prototype={    constructor:Construt,    fun1:function(){},    fun2:function(){}}

  

2、 使用闭包时,可以将不需继续调用局部变量在父函数末尾置为null。

七、最后一点

一个函数,能访问什么变量,看的是定义在什么地方,而不是执行的位置。

// 外部定义的函数,在一函数中被返回并执行,执行时,并无闭包功能function inside() {    return x;}function outside(){    var x = 1;    return inside;}var xx = outside()();

想知道结果,点击,打开调试器即可。

 

如有新的内容,会不断更新...

 

转载于:https://www.cnblogs.com/sprying/p/3329199.html

你可能感兴趣的文章
Python selenium 滚动条 详解
查看>>
poj1035Spell checker
查看>>
微信程序开发
查看>>
如何退出minicom【学习笔记】
查看>>
李开复教你如何给自己的简历打分
查看>>
POJ 3071 Football 【概率DP】
查看>>
打开Silverlight设计器发生了未经处理的异常
查看>>
sina微博加入到博客园
查看>>
Azure storage tool [Free]
查看>>
C++内存布局之虚拟继承
查看>>
Sqlserver 数据库基本查询
查看>>
图书馆维护系统总结
查看>>
[hadoop源码阅读][5]-counter的使用和默认counter的含义
查看>>
NOOK2刷机成功
查看>>
NSIndexPath的初始化方法
查看>>
Mac Mountain 更改mysql root 密码和无法创建用户问题
查看>>
发送一条微博
查看>>
Linux创建用户、用户组 及 删除
查看>>
简明Python3教程 14.输入输出
查看>>
【Ubuntu】Ubuntu Java 环境变量
查看>>