资讯专栏INFORMATION COLUMN

由两道题扩展的对作用域,作用域链,闭包,立即执行函数,匿名函数的认识总结

piapia / 1391人阅读

摘要:前言最近在学前几天看到两道题刚开始看懵懵懂懂这几天通过各种查资料慢慢的理解顿悟了对匿名函数闭包立即执行函数的理解也更深了一点在此分享给大家我的理解与总结希望能帮助大家理解因为这篇文章是我用心总结的查阅了很多的资料所以总结的比较细篇幅较长如果

前言

最近在学JS,前几天看到两道题,刚开始看懵懵懂懂,这几天通过各种查资料,慢慢的理解,顿悟了,对匿名函数,闭包,立即执行函数的理解也更深了一点,在此分享给大家我的理解与总结,希望能帮助大家理解.因为这篇文章是我用心总结的,查阅了很多的资料,所以总结的比较细,篇幅较长,如果没耐心,建议跳出,点个收藏,以后如果要用到,有耐心想看时,方便查阅.另外如果有啥错误,还望指正

题目一
function fn() {
        for (var i = 0; i < 2; i++) {
            var variate = i;
            setTimeout(function () {
                console.log("setTimeout执行后:" + variate);
            }, 1000);
        }
        console.log(i);
    }
    fn();

最后结果是啥呢?

结果是,先打印2,再打印2个1
为什么呢?
先来梳理下函数执行过程:

首先for循环遍历i,(0,1)的时候分别将遍历值传给variate变量,variate变量最后保存的值为1

当i值为2时,指针跳出循环,执行到打印i值这步,此时i=2

执行函数fn(),执行完毕后,触发setTimeout事件,因为循环2次,而且最后保存在这个作用域中变量的值为1,所以最后输出2个1

所以最后的打印的值为2,1,1

分析完了,先不急,我们先来了解下setTimeout事件

setTimeout事件

setTimeout事件有两个参数:事件,时间开始执行时间

setTimeout事件是异步的

当调用setTimeout事件时,会把函数参数,放到事件队列中。等主程序运行完,再调用

理解这个后,答案就很容易得出了

题目二
function fn() {
           for (var i = 0; i < 2; i++) {
               (function () {
                   var variate = i;
                   setTimeout(function () {
                       alert(variate);
                   }, 1000);
               })();
                            
           }
          console.log(i);
          console.log(variate);
       }
       fn(); 

先分析下整体结构:
函数体内包含一个for循环体,循环体内又包含一个匿名函数,形成闭包,加上两个小括号-->(匿名函数)()形成立即执行函数

再思考下函数执行过程

i=0时,进入函数体内,因为是立即执行,所以i值进入匿名函数,通过作用域链,变量variate获得i值,匿名函数体内的setTimeout中的变量variate获得i值,第一轮循环结束;

i=1时,执行与1同样的过程;

i=2,跳出循环,打印i,variate;

结果是啥呢?

Excuse me?竟然有错误?

好,那就让我们来解决错误,错误显示variate is not defined,原来是这样,没定义,那分析一波,为什么会显示未定义呢?
首先我们看函数内部,内部已经定义了,所以我们想到作用域的问题

作用域和作用域链

作用域

变量和函数的访问区域,分全局作用域函数作用域,在es6中添加let关键字后有了块级作用域概念.

变量提升: JS在解析代码前会先将所有函数体内的变量,提升至函数体顶端,来看个例子

var Gscope = "global";
        function t() {
            var Gscope;
            console.log("这是全局变量:"+Gscope);//这是全局变量:undefined
            Lscope = "local";
            console.log("这是局部变量"+Lscope);//这是局部变量local
        }  
    t();
    

为什么第一个值为undefined?因为函数体内的Gscope变量被提升至函数体顶端,但是未赋值,so,undefined.

let关键字:let用于声明变量,但是let声明的变量只在let所在的代码块(块级作用域)有用,OK,show code

for (let i = 0; i < 2; i++) {
            let i = "a";
            console.log(i);//a a
        }
    console.log(i);//i is not defined

作用域链

什么是作用域链?有什么用途?怎么创建起来的?

先引用一句高级程序设计里的话:

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象

我的理解是:

作用域链就相当于是沟通执行环境内的各个变量与函数的桥梁,通过作用域链,同一执行环境里面的变量和函数都有权利访问对方;

不同的执行环境间是怎样的呢?

不同执行环境间的交流还是通过桥梁(作用域链),但是现在桥梁变成单行道了,只能允许内部环境访问外部环境,但外部环境不能访问内部环境.内部环境通过桥梁能够向上搜索查询变量和函数,但外部却不能向下搜索进入另一个执行环境.理解这个后,出现题目二的问题,variate is not defined,就很容易理解了:

因为他们两个压根不在同一个执行环境,而且,里面的变量对象通过闭包能够访问外部环境变量,但外部环境变量无权访问内部的变量variate.

这时可能又蹦出一个问题了,"桥梁"(执行环境的作用域链)怎么搭建起来的呢?

先创建一个预先包含全局变量对象的作用域链,保存在内部的[scope]属性中

调用函数时,为函数搭建一个执行环境

复制函数的[scope]中的对象构建起执行环境的作用域链

创建活动对象,并将活动对象推入执行环境的前端

分析完后,再重新阅读下作用域的概念,会发现很有道理!

闭包

首先提出几个问题:什么是闭包? 为什么要用它?它有啥缺点?怎么创建?

什么是闭包?

闭包是指有权访问另一个函数作用域中变量的函数

先贴上刚刚那一段代码

function fn() {
           for (var i = 0; i < 2; i++) {
               (function () {
                   var variate = i;
                   setTimeout(function () {
                       console.log("setTimeout执行后:"+variate);
                   }, 1000);
               })();//闭包,立即执行函数,匿名函数
                            
           }
          console.log(i);//2
          console.log(variate);//variate is not defined
       }
       fn(); 

通过定义可以知道,闭包本质还是作用域链的问题.
那为什么内部环境能访问外部环境呢?
那就先探讨下,函数调用时会发生什么吧!

先创建执行环境和作用域链;

初始化函数的活动对象(命名参数值,arguments);

在作用链中搜索具有相应名字的变量,实现对变量的读取和写入;

调用执行完毕,销毁局部活动对象,仅保存全局作用域.

所以关键还是内部函数作用域链将外部的活动对象添加到自己作用域中了

这个例子中函数fn()内部嵌套了一个匿名函数形成闭包,内部的variate变量变为私有成员变量,所以外部无法访问,因而会报错variate is not defined

为什么用闭包?

因为在闭包内部保持了对外部活动对象的访问,但外部的变量却无法直接访问内部,避免了全局污染;

可以当做私有成员,弥补了因js语法带来的面向对象编程的不足;

可以长久的在内存中保存一个自己想要保存的变量.

闭包有啥缺点呢?

可能导致内存占用过多,因为闭包携带了自身的函数作用域

闭包只能取得外部包含函数中得最后一个值

怎么创建闭包?
在函数内部嵌套使用函数

匿名函数

什么是匿名函数?
顾名思义,就是没有名字的函数
如例子中的代码就是一个匿名函数

function () {
                   var variate = i;
                   setTimeout(function () {
                       console.log("setTimeout执行后:"+variate);
                   }, 1000);
               }

匿名函数优缺点?
优点:可以通过var关键字创建函数表达式,函数表达式不会出现变量提升的情况,只有在真正被解释执行的时候才会执行到函数表达式所在的代码行,有效避免了全局污染;

缺点:匿名函数绑定的事件不能解绑

立即执行函数

什么是立即执行函数?有什么作用?

什么是立即执行函数?

声明一个匿名函数,并且马上调用它{通过加()的形式}

立即执行函数的形式

(匿名函数)();

 (function () {
                   var variate = i;
                   setTimeout(function () {
                       console.log("setTimeout执行后:"+variate);
                   }, 1000);
               })()

为什么要用小括号将匿名函数包裹起来?

为了通过浏览器的语法检查

作用?
创建一个独立的作用域,避免全局污染

小结

通过两道题扩展出来知识点,并且总结出来,现在对知识点的基础概念,以及一些实现原理有了很清晰的认识,这种感觉很棒

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94539.html

相关文章

  • 从这两套题,重新认识JSthis、作用闭包、对象

    摘要:也就是说,普通情况下,指向调用函数时的对象。在全局执行时,则是全局对象。故而的方法因为构造函数闭包的关系,指向了构造函数作用域内的。 日常开发中,我们经常用到this。例如用Jquery绑定事件时,this指向触发事件的DOM元素;编写Vue、React组件时,this指向组件本身。对于新手来说,常会用一种意会的感觉去判断this的指向。以至于当遇到复杂的函数调用时,就分不清this的...

    罗志环 评论0 收藏0
  • [ JS 进阶 ] 闭包作用链,垃圾回收,内存泄露

    摘要:执行返回的内部函数,依然能访问变量输出闭包中的作用域链理解作用域链对理解闭包也很有帮助。早期的版本里采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。 关于闭包,我翻了几遍书,看了几遍视频,查了一些资料,可是还是迷迷糊糊的,干脆自己动手来个总结吧 !欢迎指正... (~ o ~)~zZ 1. 什么是闭包? 来看一些关于闭包的定义: 闭包是指有权...

    clasnake 评论0 收藏0
  • 面试官问我:什么是JavaScript闭包,我该如何回答

    摘要:到底什么是闭包这个问题在面试是时候经常都会被问,很多小白一听就懵逼了,不知道如何回答好。上面这么说闭包是一种特殊的对象。闭包的注意事项通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。从而使用闭包模块化代码,减少全局变量的污染。 闭包,有人说它是一种设计理念,有人说所有的函数都是闭包。到底什么是闭包?这个问题在面试是时候经常都会被问,很多小白一听就懵逼了,不知道如何回答好。这个...

    BenCHou 评论0 收藏0
  • JavaScript——闭包理解

    摘要:闭包占用大量内存通常,函数的作用域及其所有的变量都会在函数执行结束后被销毁。也就是说,可以通过闭包创建私有作用域将某些变量作为局部变量,避免使用全局变量而占用过多的内存。 JavaScript——闭包理解 1、闭包是什么,如何使用? 闭包指的是函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,也就是说闭包有权访问另一个函数作用域中的变量的函数。 下面是一...

    AprilJ 评论0 收藏0
  • js闭包理解

    摘要:一般来讲,函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域,但是闭包的情况有所不同理解闭包的前提先理解另外两个内容作用域链垃圾回收作用域链当代码在执行过程中,会创建变量对象的一个作用域链。 闭包是javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包来实现。个人的理解是:函数中嵌套函数。 闭包的定义及其优缺点 闭包是指有权访问另一个函数作用域中的变量的...

    EasonTyler 评论0 收藏0

发表评论

0条评论

piapia

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<