JS之作用域与闭包

 

  作用域在JS中同样也是一个重要的概念。它不复杂,因为ES5中只有全局作用域和函数作用域,我们都知道他没有块级作用域。但在ES6中多了一个let,他可以保证外层块不受内层块的影响。即内层块形成了一个块级作用域,这是let的一个特点。它不简单,因为在许多的函数嵌套的情景下,只有对它理解深刻,才能更好的去分析。今天我们着重讲的是函数作用域与全局作用域。

  同样在分析之前,我们来看一段代码。

var a=1;
function f1(){
var b=2;
function f2(){
var c=b;
b=a;
a=c;
console.log(a,b,c);
}
f2();
}
f1();//2,1,2

  上面的代码,有三个执行上下文环境(EC),全局EC,f1EC,f2EC。全局环境下有一个变量a和一个函数f1(),在f1环境中,有一个变量b和一个函数f2(),在f2环境中有一个变量c。但在f2中,可以访问到f1环境中的b,也可以访问到全局环境中的a,在f1中,可以访问到全局环境下的a,但不可以访问f2中的c,在全局中,不可以访问f1中的b也不可以访问f2中的c.。这就是一个作用域链。

  于是,我们可以知道,函数的内部环境可以通过作用域链访问到所有的外部环境,但是外部环境却不可以访问外部环境,这就是作用域的关键。但是我们要知道,作用域是在一个函数创建时就已经形成的,而不是调用时。所以有些人可能会认为按着作用域链向上查找是查找它的父作用域,就像上面的那个例子。但是这个例子只是一种特殊情况。我们要认识到并不是查找它的父作用域,而是查找创建该函数的那个作用域。看下面的这段代码。

var a=10;
function fn(){
var a=20;
return function b(){
console.log(a);
};
}
var g=fn();
g();//20

  这里我们在调用g函数,发现他的值是20,。而它所谓的父作用域应该是全局作用域,但它却不是10.所以这就说明了作用域链向上查找是寻找创建它的那个作用域。

  上面的这个例子同时引出了我下面要说明的一个问题,闭包。

  

  闭包,是函数中一个核心的概念。它的文字说明多种多样,我看过很多人对它的文字描述,虽然说得不全一样,但是中心观点都差不太多。我个人认为我最喜欢的一个文字描述就是《锋利的jquery》关于插件描述的那个章节中的一段说明。

  闭包,允许使用内部函数(即函数定义和函数表达式位于另一个函数的函数体内),而且,这些内部函数可以访问他们所在的外部函数中的声明的所有局部变量丶参数和声明的其他内部函数,当其中一个这样的内部函数在包含他们的外部函数之外被调用时,就会形成闭包。即内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量丶参数以及其他内部函数。这些局部变量丶参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

  上面这段话就是《锋利的jquery》中关于闭包的一段描述。说的很长很详细,简单来说,就是在一个函数a内部定义的另一个函数b,当b在a之外被执行时,就会形成闭包。同时b函数仍然可以访问到a函数中的局部变量与函数。

  我们在开始了解闭包时,有一个特别经典的题目。看下面代码

function fn(){
var array=[];
for(var i=0;i<10;i++){
array[i]=function(){
return i;
}
}
return array;
}
fn();//[ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]

  

  我们的本意是得到这个数组中每个函数都能返回自己的索引值,可是得到的是每个函数却都返回了10.如上面的文字说明中所讲的那样,闭包保存的是定义它的那个函数内部的局部变量丶参数和其他内部函数,也就是说保存的是这个函数执行上下文中的整个VO,而不是一个变量。上面代码中的函数作用域链中都保存着fn的活动对象,他们引用的都是一个i,当fn返回时,i的值是10,所以每个函数都引用保存i那个变量的同一个变量。我们如果想得到原先想得到的那个结果,可以加上另一个匿名函数改变他的父作用域(其实应该是创建它的作用域),将它包裹起来。

function fn(){
var array=[];
for(var i=0;i<10;i++){
array[i]=function(num){
return function(){
return num;
};
}(i);
}
return array;
}

  这个匿名函数有一个参数num,同时是返回值。在调用每个匿名函数时,传入了变量i。由于参数是按值传递的,所以i就会复制给num,而这个匿名函数的内部又创建了一个访问num的闭包,返回后能够访问到该匿名函数中的VO(包括参数),于是每个函数返回的都是num的一个副本,所以可以得到不同的值。

  其实,说了这么多,我们只要熟悉闭包的两个应用场景,就能比较好的理解闭包的意义。

一.作为函数的返回值.。作为函数返回值被执行后仍然可以访问定义它的那个函数环境的VO。

function f(){
var a=1;
return function(){
console.log(a);
}
} var g=f();
g();//1;

二.作为一个函数的参数。作为函数返回值被当做另一个函数的参数传入时,仍然是访问定义它的那个函数环境的VO

function f(){
var a=1;
return function(){
console.log(a);
}
}
var g=f();
g();//1; function F(fn){
var a=2;
fn();
}
F(g);//1

  上面两个小例子也正好说明了闭包可以访问定义它的那个函数作用域下的内部变量和内部函数。其实是整个VO,所以还包含参数。

  闭包的理解差不多就是这样,遇到比较复杂的情况我们只要按着定义慢慢的一步步的寻找,一切问题都能迎刃而解

JS之作用域与闭包的更多相关文章

  1. 解析js中作用域、闭包——从一道经典的面试题开始

    如何理解js中的作用域,闭包,私有变量,this对象概念呢? 就从一道经典的面试题开始吧! 题目:创建10个<a>标签,点击时候弹出相应的序号 先思考一下,再打开看看 //先思考一下你会怎 ...

  2. 你不知道的JS之作用域和闭包 附录

     原文:你不知道的js系列 A 动态作用域 动态作用域 是和 JavaScript中的词法作用域 对立的概念. 动态作用域和 JavaScript 中的另外一个机制 (this)很相似. 词法作用域是 ...

  3. 你不知道的JS之作用域和闭包(五)作用域闭包

    原文:你不知道的js系列 一个简单粗暴的定义 闭包就是即使一个函数在它所在的词法作用域外部被执行,这个函数依然可以访问这个作用域. 比如: function foo() { var a = 2; fu ...

  4. JS的作用域和闭包

    1.作用域 作用域是根据名称找变量的一套规则. 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它 ...

  5. JS(作用域和闭包)

    1.对变量提升的理解 1.变量定义(上下文) 2.函数声明 2.说明 this 几种不同的使用场景 常见用法 1.作为构造函数执行 2.作为对象属性执行 3.作为普通函数执行(this === win ...

  6. 你不知道的JS之作用域和闭包(三)函数 vs. 块级作用域

      原文:你不知道的js系列 在第(二)节中提到的,标识符在作用域中声明,这些作用域就像是一个容器,一个嵌套一个,这个嵌套关系是在代码编写时定义的. 那么到底是什么产生了一个新的作用域,只有函数能做到 ...

  7. js中作用域和闭包

    作用域链实例   (1) function example() { var age = 23; alert(age) } var age = 25; example(); alert(age); // ...

  8. js变量作用域和闭包的示例

    <script> /* js是函数级作用域,在函数内部的变量,内部都能访问, 外部不能访问内部的,但是内部可以访问外部的变量 闭包就是拿到本不该属于他的东西,闭包会造成内存泄漏,你不知道什 ...

  9. 你不知道的JS之作用域和闭包(四)(声明)提升

    原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...

随机推荐

  1. [GO]结构体成员的使用:指针变量

    package main import "fmt" func main() { type student struct { id int name string sex byte ...

  2. 【转载】rabbitmq的发布确认和事务

    地址:https://my.oschina.net/lzhaoqiang/blog/670749 摘要: 介绍confirm的工作机制.使用spring-amqp介绍事务以及发布确认的使用方式.因为事 ...

  3. ubuntu同时安装Qt4.8和Qt5.7[认真写每篇博客^-^]

    这是ubuntu默认安装(从apt安装)的路径和相关文件,建议编译安装到/opt目录下. 从APT安装的默认文件夹 以下是我的安装情况,配置为qt4.8为默认. 安装包或源码下载地址: qt4:htt ...

  4. PatternLayoutEncoder 输出格式

    ch.qos.logback.classic.encoder.PatternLayoutEncoder ch.qos.logback.classic.PatternLayout ch.qos.logb ...

  5. 设计模式13:Template Method 模板方法模式(行为型模式)

    Template Method 模板方法模式(行为型模式) 变与不变 变化——是软件永恒的主题,如何管理变化带来的复杂性?设计模式的艺术性和复杂度就在于如何分析,并发现体系中的变化点和稳定点,并使用特 ...

  6. 慎用WSACleanup()

    中止Windows Sockets DLL的使用.         #include <winsock.h>         int PASCAL FAR WSACleanup ( voi ...

  7. Reactor模式和NIO(转载二)

    本文可看成是对Doug Lea Scalable IO in Java一文的翻译. 当前分布式计算 Web Services盛行天下,这些网络服务的底层都离不开对socket的操作.他们都有一个共同的 ...

  8. win10登录不上sql2005

    因需求安装了一个SQL Server 2005. 登录的时候,用户Sa模式可以登录,Windows身份验证方式却无法登录. 经测试.解决方法如下: SQL Server Management Stud ...

  9. Android-Observer(内容观察者)

    内容提供者应用暴露的数据,是被多个其他应用访问(insert,update,delete,query),但如果L应用要查询(内容提供者应用暴露的数据),难道要开启子线程一直循环去查询 ? 答:开启子线 ...

  10. docker swarm 命令

    初始化swarm manager并制定网卡地址 docker swarm init --advertise-addr 192.168.10.117 强制删除集群,如果是manager,需要加–forc ...