资讯专栏INFORMATION COLUMN

详解Javascript的作用域、作用域链以及闭包

3403771864 / 444人阅读

  一、我们先说说javascript的作用域

  ①全局变量-函数体外部进行声明

  ②局部变量-函数体内部进行声明

  1)函数级作用域

  javascript语言中局部变量不同于C#、Java等高级语言,在这些高级语言内部,采用的块级作用域中会声明新的变量,这些变量不会影响到外部作用域。

  而javascript则采用的是函数级作用域,也就是说js创建作用域的单位是函数。

  例如:

  在C#当中我们写如下代码: 

 static void Main(string[] args)
  {
  for (var x = 1; x < 10; x++)
  {
  Console.WriteLine(x.ToString());
  }
  Console.WriteLine(x.ToString());
  }

  上面代码会出现如下的编译错误:

  The name 'x' does not exist in the current context

  同样在javascript当中写如下代码:

  <script>
  function main() {
  for (var x = 1; x < 10; x++) {
  console.log(x.toString());
  }
  console.log(x.toString());
  }
  main();
  </script>

  输出结果如下:

  [Web浏览器] "1"

  [Web浏览器] "2"

  [Web浏览器] "3"

  [Web浏览器] "4"

  [Web浏览器] "5"

  [Web浏览器] "6"

  [Web浏览器] "7"

  [Web浏览器] "8"

  [Web浏览器] "9"

  [Web浏览器] "10"

  在知道“块级作用域”和“函数级作用域”的区别,块级作用域当离开作用域后,这样外部就 用不上,就比如之前C#例子,"x"离开for循环后这个就没效果,但是javascript中不一样,它还可以进行访问。

  2)变量提升

  再看javascript中作用域的一个特性,例子如下:

  function func(){
  console.log(x);
  var x = 1;
  console.log(x);
  }
  func();

  输出结果:

  [Web浏览器] "undefined"

  [Web浏览器] "1"

  其实我们可以想,为什么第一个输出是“undefined”呢?我们可以看到javascript中的“变量提升”,称为“声明提升”更好,因为这个机制就是把变量的声明提前到函数的前面。也就会造成提升至,也就是为什么第一次没有输出“1”的原因。

  上面的代码就相当于:

  function func(){
  var x;
  console.log(x);
  var x = 1;
  console.log(x);
  }
  func();

  3)函数内部用不用“var”对程序的影响

  这是个值得注意的地方:

  var x = 1;
  function addVar(){
  var x = 2;
  console.log(x);
  }
  addVar();
  console.log(x);

  输出:

  [Web浏览器] "2"

  [Web浏览器] "1"

  当在函数内部去掉var之后,再执行:

  var x = 1;
  function delVar(){
  x = 2;
  console.log(x);
  }
  delVar();
  console.log(x);

  [Web浏览器] "2"

  [Web浏览器] "2"

  上面的例子说明了,在函数内部如果在声明变量没有使用var,那么声明的变量就是全局变量。

  二、javascript的作用域链

  先看如下的代码:

  var name="Global";
  function t(){
  var name="t_Local";
  function s1(){
  var name="s1_Local";
  console.log(name);
  }
  function s2(){
  console.log(name);
  }
  s1();
  s2();
  }
  t();

  输出结果:

  [Web浏览器] "s1_Local"

  [Web浏览器] "t_Local"

  那么就有几个问题:

  1.为什么第二次输出的不是s1_Local?

  2.为什么不是Global?

  解决这个两个问题就在于作用域链…

  下面就解析一下这个过程,

1.png

  例如当上面程序创建完成的时候,会形成上图中的链状结构(假想的),所以每次调用s1()函数的时候,console.log(name);先会在他自己的作用域中寻找name这个变量,这里它找到name=“s1_Local”,所以就输出了s1_Local,而每次调用s2()的时候,它和s1()函数过程一样,只不过在自身的作用域没有找到,所以向上层查找,在t()函数中找到了,于是就输出了"t_Local"。

  我们可以验证一下,如果把t中的name删除,可以看看输出是不是“Global”

  结果如下:

  [Web浏览器] "s1_Local"

  [Web浏览器] "Global"

  当然具体每一个作用域直接是如何连接的,请参考


  https://www.jb51.net/article/28610.htm

  其中也说明了为什么JS当中应该尽量减少with关键字的使用。

  三、闭包

  了解了作用域和作用域链的概念之后,再去理解闭包就相对容易了。

  1.闭包第一个作用

  还是先看例子: 

 function s1() {
  var x = 123;
  return s2();
  }
  function s2() {
  return x;
  }
  alert(s1());

  这里我想弹出x的值,但是却发生了错误 "Uncaught ReferenceError: x is not defined",根据作用域链的知识,了解到因为s2中没有x的定义,并且向上找全局也没有x定义,所以就报错了。也就是说s1和s2不能够共享x的值。

  那么问题来了,我想要访问s1当中的x,怎么弄?

  修改代码如下:

  function s1() {
  var x = 123;
  return function(){
  return x;
  };
  }
  var test = s1();
  console.log(test());

  结果为:

  [Web浏览器] "123"

2.png

  解释:因为function本质也是数据,所以它和x的作用域相同,可以访问x。这样就相当于对外部开放了一个可以访问内部变量的接口。

  还可以怎么玩,稍微修改一下代码

  var func;
  function f(){
  var x='123';
  func=function(){
  return x;
  };
  }
  f();
  alert(func());

  定义一个全局的变量,然后在函数内部让其等于一个函数,这样就可以通过这个全局变量来进行访问x了。

  综上:闭包是啥?闭包就相当于函数外部和内部的桥梁,通过闭包可以在外部访问函数内部的变量。这也是闭包的第一个作用。

  2.闭包的第二个作用

  先看代码:

  function f1(){
  var n=1;
  add=function(){
  n+=1;
  };
  function f2(){
  console.log(n);
  return '输出完成';
  }
  return f2;
  }
  var res=f1();
  console.log(res());
  add();
  console.log(res());

  输出结果:

  [Web浏览器] "1"

  [Web浏览器] "输出完成"

  [Web浏览器] "2"

  [Web浏览器] "输出完成"

  问题为什么第二次输出的结果n变成了2,没有被清除?

 这很好解释,在res是一个全局变量,一直驻留在内存当中,它就相当于f2函数,f2函数又要依赖于f1函数,所以f1也驻留在内存当中,保证不被GC所回收,每一次调用add函数的时候,就相当于把f1中的n重新赋值了,这样n的值就变化了。这也是闭包的第二个作用,可以让变量的值始终保存在内存当中。

  3.闭包的应用

  ①可以做访问控制(相当于C#当中的属性)

  //定义两个变量,用于存储取值和存值
  var get,set;
  //定义一个自调用函数,设定set和get方法
  (function(){
  //设定x的默认值
  var x = 0;
  set = function(n){
  x = n;
  }
  get = function(){
  return x;
  }
  })();
  console.log(get());
  set(5);
  console.log(get());

  输出结果:

  [Web浏览器] "0"

  [Web浏览器] "5"

  ②可以用做迭代器

 

 //定义一个函数,里面使用了闭包
  function foo(myArray){
  var i=0;
  //闭包迭代器
  next=function(){
  //每次返回数组的当前值,下标+1
  return myArray[i++];
  }
  }
  //调用foo,参数为一个数组
  foo(['a','b','c','d']);
  //每次调用next都会打印数组的一个值
  console.log(next());
  console.log(next());
  console.log(next());
  console.log(next());

  输出结果:

  [Web浏览器] "a"

  [Web浏览器] "b"

  [Web浏览器] "c"

  [Web浏览器] "d"

  ③闭包在循环中的使用

  例1

 

 function f(){
  var a=[];
  var i;
  for(i=0;i<3;i++){
  a[i]=function(){
  return i;
  };
  }
  return a;
  }
  var test=f();
  console.log(test[0]());
  console.log(test[1]());
  console.log(test[2]());

  输出结果:

  [Web浏览器] "3"

  [Web浏览器] "3"

  [Web浏览器] "3"

  为什么结果不是0、1、2?

  这里我们使用了闭包,每次循环都指向了同一个局部变量i,但是闭包不会记录每一次循环的值,只保存了变量的引用地址,所以当我们在调用test[0]()、test[1]()、test[2]()的时候都返回的是for循环最后的值,也就是3的时候跳出了循环。

  例2:我想输出0,1,2怎么搞?

  function f(){
  var a=[];
  var i;
  for(i=0;i<3;i++){
  a[i]=(function(x){
  return function(){
  return x;
  }
  })(i);
  }
  return a;
  }
  var test=f();
  console.log(test[0]());
  console.log(test[1]());
  console.log(test[2]());

  结果:

  [Web浏览器] "0"

  [Web浏览器] "1"

  [Web浏览器] "2"

  欢迎大家关注更多后续精彩文章,学习更要实践。


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

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

相关文章

  • 闭包详解

    摘要:函数中的闭包闭包是指有权访问另一个函数作用域中的变量的函数。理解闭包预与变量此时返回注意观察下面的输出内容,理解函数的调用时刻和把的赋值给变量时刻这个函数会返回长度为的函数数组。 Javascript函数中的闭包 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是,在一个函数的内部创建另一个函数。 有关创建作用域链以及作用域链有什么作用的细节对于彻底理解闭包至关重...

    lunaticf 评论0 收藏0
  • 闭包全面详解

    摘要:环境由闭包创建时在作用域中的任何局部变量组成。严格来说,闭包需要满足三个条件访问所在作用域函数嵌套在所在作用域外被调用闭包的形成原理先了解的垃圾回收机制会找出不再使用的变量,不再使用意味着这个变量生命周期的结束。 什么是闭包 最原始定义 闭包(closure),是指函数变量可以保存在函数作用域内,因此看起来是函数将变量包裹了起来。 //根据定义,包含变量的函数就是闭包 function...

    qylost 评论0 收藏0
  • 详解js中闭包

    摘要:定义函数的时候,为什么的值重新从开始了因为又一次运行了函数,生成一个新的的活动对象,所以的作用域链引用的是一个新的值。 前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以《javascript高级程序设计》里面的理论为基础。用拆分的方式,深入讲解一下对于闭包的理解,如果有不对请指正。 写在闭包之前 闭包的内部细节,...

    chaosx110 评论0 收藏0
  • 详解js变量、作用及内存

    摘要:不是引用类型,无法输出简而言之,堆内存存放引用值,栈内存存放固定类型值。变量的查询在变量的查询中,访问局部变量要比全局变量来得快,因此不需要向上搜索作用域链。 赞助我以写出更好的文章,give me a cup of coffee? 2017最新最全前端面试题 基本类型值有:undefined,NUll,Boolean,Number和String,这些类型分别在内存中占有固定的大小空...

    waltr 评论0 收藏0
  • 【进阶2-1期】深入浅出图解作用链和闭包

    摘要:本期推荐文章从作用域链谈闭包,由于微信不能访问外链,点击阅读原文就可以啦。推荐理由这是一篇译文,深入浅出图解作用域链,一步步深入介绍闭包。作用域链的顶端是全局对象,在全局环境中定义的变量就会绑定到全局对象中。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周开始前端进阶的第二期,本周的主题是作用域闭包,今天是第6天。 本...

    levius 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

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