JavaScript闭包(三)

目录

1.执行环境与作用域链

2. 立即执行函数

3. 闭包知识点

3.1 什么是闭包
3.2 使用闭包的意义与注意点
3.3 闭包的具体应用

4. 小结


这是JavaScript基本语法的函数部分的第2篇文章,主要讲述了JavaScript中比较重要的知识点闭包;
在讲闭包之前,在上一篇《JavaScript函数(二)》的基础上,进一步深化执行环境和作用域链的知识点,并补充立即执行函数方面的知识;
最后重点探讨了有关闭包的相关方面;


1. 执行环境与作用域链

讲闭包之前,首先要清晰了解函数的执行环境和作用域链的原理;
1.1 执行环境
执行环境指的是变量在执行阶段所在的作用域,执行环境定义了变量或函数有权访问的其他数据,每一个执行环境都有一个与之关联的变量对象,环境中所有的变量都保存在这个对象中;

var a = 1;
function fn (args){
    console.log(a+args)
}
fn(1)//2

上述代码的存在两个执行环境,每个执行环境都有与之关联的变量对象,变量a和函数fn保存在全局变量对象window当中,保存函数的参数的agruments对象保存在局部变量对象fn当中;
值得注意的是,如果这个执行环境是函数,则将其活动对象作为变量对象,即函数调用时所生成的对象,因为函数未调用定义在里面的变量是不存在的;
1.2 作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域的用途是能够保证执行环境有权、有序访问当前作用域及其外部的变量

var a =1;
function fn(b,c){
    var d = 4;
    console.log(a+b+c+d)
}
fn(2,3)//10

以上述代码为例,去探讨执行环境和作用域链的相关知识点;

  • js引擎在解析阶段将变量a和函数fn保存在window变量对象上,此时变量a和函数fn的执行环境是window对象;
  • 在调用函数fn时,函数fn创建创建一个活动对象fn(),函数内部的变量b、c(保存在函数的arguments对象中)和变量d的执行环境是活动对象fn();
  • 此时,函数内部的代码在执行过程中会创建活动对象的作用域链,它可访问到的变量处理有保存在arguments对象的b和c,直接定义在内部的d,同时可以访问到定义在外部执行环境——window对象的a;
  • 因此,这就是一条简单的作用域链,同时,这条作用域链式单向(由内向外可访问)、有序的;

1.3 关于JavaScript的块级作用域
JavaScript是不存在块级作用域的,只用函数才能单独开辟一个作用域来;
上一段比较经典代码,来为块级作用域和接下来的闭包预预热;

//html
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
//js
var lists = document.querySelectorAll('li')
console.log(lists)
for(var i=0;i<lists.length;i++){
  lists[i].onclick = function(){
    console.log(i)
  }
}

【demo】

  • 原本的需求是点击第几个li,控制台弹出第几个li的下标,但实际弹出的都是5;
  • 出现这个问题的原因是,for这个流程控制语句是无法创建块级作用域的,如果能够创建块级作用域的话,那么每循环一次,function里面的i都是保存当时的i
  • 而实际上由于for不存在款及作用域,所以循环遍历后,i=5,当触发点击事件时,调用函数console.log(i)就变成5;
  • 如果想用实现原本的需求,那么我们就需要在每次循环时都能够创建一个能够保存当时i的块级作用域来;
  • 具体如何实现需要用到接下来的知识点,所以答案在后头;

2. 立即执行函数

所谓立即执行函数(Immediately-Invoked Function Expression,IIFE),就是函数声明后立即调用该函数,具体的写法为:

(function(){
  console.log(1)
})();
//1
可以传参
(function(a){
  console.log(a)
})(5);
//5

初学者可能对立即执行函数的写法感到奇怪,其实只要了解js背后的解析原理就很容易掌握其规则;
立即执行函数由一个匿名函数和两个圆括号构成,将匿名函数放在第一个圆括号内;

1.()();
2.function(){};
3.(function(){})()

之所以出现这样的写法,是js默认出现在行首的函数解析为语句(声明式)

//声明式
function(){};
//表达式
var f = function(){}

如果语句后面直接出现圆括号会报错,通过圆括号将语句括起来从而让js识别为表达式,然后再加一个圆括号立即调用,从而实现一个立即执行函数;

function(){}();//报错
(function(){})()//不报错

立即执行函数的优点在于:

  • 具有函数独立开辟一个作用域的功能,实现私有变量封装;
  • 定义好后即可执行内部的代码;
  • 匿名特性不必担心污染同级或上级的变量;

3. 闭包知识点

3.1什么是闭包

前面已经讲到执行环境和作用域链,我们知道执行环境定义了变量有权访问其他数据,函数能够创建一个作用域。如果我们现在有这么一个需求:想要在外部环境下也能访问内部环境的变量,那么究竟如何实现呢?
这里接需要引出闭包的概念:闭包(closure)指的是能够访问另一个函数内部变量的函数;

function outer(){
    var a = 1;
    function inner(){
      return a
    }
    return inner
}
var result = outer();
result();//1
  • 上述代码中,变量a是在函数outer内声明,所以只有函数outer内部才可访问,外部全局环境无法访问;
  • 通过在outer内部定义一个函数inner,因为作用域链的作用,这个inner可以访问变量a;
  • 最后暴露一个inner接口,使得在调用outer时,获得inner这个接口,在调用这个接口,从而达到访问函数内部变量的目的;
  • 通过上述分析,我们可以知道,函数inner就是闭包,即能够访问另一个函数内部变量的函数;
3.2 使用闭包的意义和注意点

使用闭包的意义
闭包的意义在于:实现函数的封装,将变量全部封装在函数内部而不必担心污染全局环境,只暴露出接口,而不必关心内部的代码逻辑,例如将对象的私有属性和方法进行封装:

function Animal(name){
    var _age;
    function setAge(age){
      _age = age
    };
    function getAge(){
      return _age
    }  
    return {
      name:name,
      setAge:setAge,
      getAge:getAge
    }
}
var cat = Animal('cat');
cat.setAge(12)
cat.getAge()//12
  • 上述代码封装了对象的私有属性_age和私有方法setAgegetAge,暴露出一个对象作为接口;
  • 函数Animal内部定义的变量外部是直接无法访问,只能通过对象接口间接访问;
  • 我们只需要调用对象相关的属性和方法,而不必关心方法具体是如何实现的,也不必担心内部定义的变量会对全局变量有污染;
    使用闭包的注意点
    使用闭包会产生内存泄露的问题。通常函数在调用完后,js内部的垃圾回收机制会自动销毁局部活动对象(函数调用时生成的对象),但是以上述代码为例:
  • var cat = Animal('cat')这段代码执行完后,按道理会销毁函数Animal这个活动对象,实际上这个活动对象仍存在内存中;
  • 原因是return {...}的这个对象的私有方法的作用域链仍在引用这个Animal活动对象;
  • 只有return {...}的这个对象被销毁后,Animal活动对象才能被销毁,内存才能释放;
    可通过添加以下代码释放内存:
var cat = Animal('cat');
cat.setAge(12)
cat.getAge()//12
cat = null
3.3 闭包的具体应用

回到前面的代码中来:

//html
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
//js
var lists = document.querySelectorAll('li')
console.log(lists)
for(var i=0;i<lists.length;i++){
  lists[i].onclick = function(){
    console.log(i)
  }
}
  • 实现点击哪个list,就在控制台输出i
  • 通过立即执行函数形成独立的作用域;
  • 传递参数进IIFE,从而每循环一次就形成函数内部的变量;
  • 最后return出一个函数,这个函数的返回值就是当时的i;
var lists = document.querySelectorAll('li')
console.log(lists)
for(var i=0;i<lists.length;i++){
  lists[i].onclick = (function(num){
    return function(){
      return num+1
    }
  })(i);
}

【demo】

4.小结

通过整篇文章,我们知道了:

  • 执行环境指的是变量在执行阶段所在的作用域,定义了变量有权访问的其他数据,每个执行环境都有一个与之关联的变量对象,所有的变量都保存在其中,函数在执行阶段会创建一个活动对象,这活动对象可以包含以下变量:arguments,函数内部使用var定义的变量和外部变量;
  • 变量在执行阶段会创建变量对象的一个作用域链,作用域链的用途是保证执行环境有权有序访问当前作用域和其他外部变量;
  • JavaScript不存在块级作用域;
  • 闭包是能够访问函数内部变量的函数,闭包的意义在于能够封装变量在函数内部而实现间接访问函数内部变量;使用闭包要注意内存泄露问题,解决办法是在调用完接口后,可以使用赋值null进行销毁;

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容