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]channel实现数据交互

    package main import ( "fmt" "time" ) func main() { ch := make(chan string)//创建ch ...

  2. 在Lua中封装一个调试日志(附lua时间格式)

    --自己封装一个Debug调试日志 Debug={} Info={} local function writeMsgToFile(filepath,msg) end function Debug.Lo ...

  3. CSS中的三种基本的定位机制(普通流、定位、浮动)

    一.普通流 普通流中元素框的位置由元素在XHTML中的位置决定.块级元素从上到下依次排列,框之间的垂直距离由框的垂直margin计算得到.行内元素在一行中水平布置. 普通流就是html文档中的元素如块 ...

  4. vim的基本使用

    Vim 编辑器中设置了三种模式—命令模式.末行模式和编辑模式,每种模式分别又支持多种不同的命令快捷键,这大大提高了工作效率,而且用户在习惯之后也会觉得相当顺手.要想高效率地操作文本,就必须先搞清这三种 ...

  5. php 与java安卓客户端的查询交互

    PHP 服务器端: function getids() { $this->output->set_header('Content-Type: application/json; chars ...

  6. Replication--修改复制代理配置来查看代理运行情况

    1>在复制监视器中选中订阅右键 2>选择代理配置文件 3>将代理配置文件设置为”详细历史记录代理配置文件“,确定以保存 4>重启代理 5>代理运行一段时间后,重启代理 6 ...

  7. Spring学习(三)——集成 Velocity

    上篇文章http://www.cnblogs.com/wenjingu/p/3822989.html我们使用Gradle构建了一个简单的Spring MVC Web应用程序, 本篇将在上篇的基础上将j ...

  8. pageadmin自助建站 网站目录权限的设置方法

    在用pageadmin网页设计的时候遇到各种问题可以参考官网教程网站目录权限的设置方法 网站目录必须设置读取和写入权限,否则后台解压,删除文件,在线上传等功能都无法正常使用,下面讲解本机和服务器配置目 ...

  9. 「HNOI 2015」落忆枫音

    题目链接 戳我 \(Description\) 给一张\(n\)割点\(m\)条边的\(DAG\),保证点\(1\)不存在入边,现在需要在\(DAG\)中加入一条不在原图中的边\((x,y)\),求这 ...

  10. window.onload与$(document).ready()的对比

    一.window.onload:(1)必须等待网页中所有的内容加载完毕后(包括图片)才能执行:(2)不能同时编写多个,如: window.onload = function(){ alert(&quo ...