博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于js中的作用域
阅读量:5108 次
发布时间:2019-06-13

本文共 4558 字,大约阅读时间需要 15 分钟。

萌新出行,闲人让道,以免误伤。

我是一个js的初学者,在es6出来之后才开始学习js,所以接触到的东西大多都已经es6化了,比如函数已经习惯于箭头函数、习惯于使用const和let等等。

据说在es6之前,js是没有块级作用域的,因为当时的所有变量定义都是var,会有变量提升的问题。

 

什么是块级作用域呢,我所理解的就是“{}”中间的就是块级作用域(object对象除外)。

函数的执行体内部就是一个块级作用域,for循环的每一轮都是一个单独的块级作用域,if{...}else{...}中间都是一个块级作用域。

作用域的作用是干嘛的呢?我浅显的理解就是:隔离变量,不同作用域下同名变量不会有冲突。

 

在学习作用域之前我先去学习了一下js遗留的问题:变量提升。什么是变量提升呢?就是js执行时会先预加载一遍变量,将使用var、function定义的变量提取出来,先赋予一个初始值。

  例如:

var a = 1;var b = 2;function c(){  console.log(111)  }

  上面这段代码预加载的情况下是这样的:

  先将所有的使用var和function定义的变量取出来赋予一个初始值undefined:

var a,b,c;

  然后再执行代码,将变量的值一一赋予:

a = 1;b = 2;c = function c(){  console.log(111)}

  这就是预加载。预加载有一个遗留的问题,就是可以在变量的定义之前就可以读取到变量: 

console.log(a)var a = 1console.log(a)

  这里打印出来的分别是 undefined 和 1。因为预加载的时候先把a提取出来赋予了undefined,然后再执行代码,所以第一个打印出来的是undefined,第二个打印在a赋值之后,所以打印出来是1。

  预加载会产生一些初学者很懵逼的问题(我当时懵逼很久,死活不懂),比如:

for(var i=0;i<3;i++){  setTimeout(function(){    console.log(i)  },1000)  } console.log(i)

  最下面打印的这个i居然有值!!不可思议!!后来我才知道,这里的i使用var定义,在循环结束后会成为全局变量,因为这里没有块级作用域,不会隔离变量,三次循环使用的i是同一个i。

  在这里会打印出来三次3,为什么呢?因为setTimeout是异步加载的,在三次循环执行结束之后,i已经变成3了,并且,这时候三次方法从异步队列中出来继续执行,在自己的作用域中找不到变量i,只能找到全局的变量i,此时的i为3。

  这个问题要怎么解决呢?很简单,以前的方法是在每一次循环体里面重新定义一个方法,将定时器封装,把每一次的i作为参数传进去:

for(var i=0;i<3;i++){  function a(i){
setTimeout(function(){ console.log(i) },1000) } a(i)}

  这样的话就解决了这个问题。

  当然更简单的就是把定义i的方式从 var 变成 let,这样就产生了独自的作用域,有独自的变量i,不会产生变量混淆了。

变量提升的问题说到这,开始学习作用域了。

首先了解一个概念:执行上下文环境

  函数每调用一次,会产生一个全新的执行上下文环境。

   执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程——执行上下文栈。

  有点抽象,我来捋一捋。

  使用一个例子:

var a = 1,fn,  bar = function(x){    var b =  5    fn(x+b)  }fn = function (y){  var c = 5  console.log(c+y)}bar(a)

  代码执行过程:

  首先预加载:

var a,fn,bar

  然后执行代码,赋值:

a = 1;bar = function(x){...}fn = function(y){...}

  接着创建执行上下文环境:

执行代码最初始先创建一个全局上下文环境。在执行bar(a)的时候,创建一个bar上下文环境。在bar(a)的时候会执行fn(x+b),此时创建一个fn上下文环境。fn执行结束之后,销毁fn上下文环境。bar执行结束之后,销毁bar上下文环境。所以这段代码执行过程中,执行上下文环境的变化为:全局上下文环境 => 全局上下文环境 + bar上下文环境 => 全局上下文环境 + bar上下文环境 + fn上下文环境 => 全局上下文环境 + bar上下文环境 =>全局上下文环境

   在上面过程中,创建新的执行上下文环境称为上下文环境压栈,销毁执行上下文环境称为上下文环境出栈

看完上面不知道观众懂没懂,我也写的很乱,如果不懂的话继续往下看,后面还要用到执行上下文环境,还会介绍。

再了解一个东西:

  函数在定义的时候(不是调用的时候)就已经确定了函数体内部的变量的作用域。

再说一个概念:静态作用域

  创建函数时,函数所处的作用域为该函数的静态作用域。

这两个作用域都是依赖于函数的,但是一个是函数体内部所有变量所处的作用域,一个是函数所处的作用域。

    什么意思呢?看代码

var a = 1function fn1(){  console.log(a)}function  fn2(){  var a = 2  fn1()}fn2()

  答案是多少呢?是1。

 

  为什么呢?因为在fn1定义的时候就已经确定了其所在的作用域,其内部需要用到一个变量a,但是其作用域内部没有变量a,只能在其父级作用域中找,最后找到了全局的a,是1。

  这就是函数在定义的时候就已经确定了其内部变量的作用域,谨记。

  关于作用域记住以下三点:

    ① 作用域只是一个“地盘”,一个抽象的概念,其中没有变量,但是变量却都有一个所处的作用域,要通过作用域对应的执行上下文环境来获取变量的值。
    ② 函数体中变量的值是在
执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
    ③ 如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
  上面三点中,第一点比较好理解,因为作用域是一个抽象的东西,并不具体。但是我一直在说 “XX函数的作用域内部的变量” 这种话只是为了便于理解,其实是有问题的,毕竟作用域内部没有变量,但是变量都有一个自己所处的作用域,用来隔离不同变量。
  第二点中这个变量的值执行过程中产生并确定的。联系一下上面说到的执行上下文环境,把上面这段代码剖析一下:
首先预加载:var a,fn1,fn2然后创建全局上下文环境。接着赋值:a = 1;fn1 = function fn1(){  console.log(a)}fn2 = function fn2(){  var a = 2;  fn1()}然后执行方法fn2。执行fn2方法时预加载:var a创建fn2上下文环境然后赋值: a = 2执行方法 fn1fn1执行预加载: 没有变量。创建fn1上下文环境。执行 console.log(a)在fn1上下文环境中找不到变量a,去函数所处的作用域继续找,找到全局的变量a=1。fn1执行结束,销毁fn1上下文环境。fn2执行结束,销毁fn2上下文环境。

  现在知道为什么会打印出来1了吧。在上面这段代码剖析中也解释了第三点,在fn1作用域内部找a,需要找到fn1上下文环境,然后在该上下文环境中找变量a。

  再看一个栗子:  

var a = 10function A(){  var a = 1000  return function B(){    console.log(a)  }  }var b = A()b()

  答案是多少呢?是1000。因为B方法通过执行A赋值给b,执行方法b也就是执行方法B,其执行上下文环境中没有变量a,只能在其静态作用域中继续查找,找到了变量a=1000。

  这里也扯出来一个新的概念:作用域链

  作用域链,顾名思义,就是一串作用域连在一起。举个栗子:
var aa = 1function a(){  function b(){    function c(){      function d(){        console.log(aa)      }      d()    }    c()  }    b()}a()

  在这里打印出来的自然是1,新手都看得出来,因为整段代码只有1个全局的变量 aa = 1。但是为什么能在方法d中获取到全局的变量呢?这就是作用域链的作用。

a() --> b() --> c() --> d()在执行方法d的时候需要获取变量aa -->但是在 d上下文环境中没有变量 aa,那么就在 d 的静态作用域中继续找 -->d的静态作用域就是c的内部作用域,c上下文环境中也没有变量 aa -->那么就去 c 的静态作用域中找,也没有,继续向上到 方法b中 -->再到方法a中, 方法a的内部作用域也没有,找到了a的静态作用域,也就是全局作用域,在全局上下文环境中找到了 变量 aa = 1

 

   这就是作用域链,从你执行的方法的内部作用域开始寻找所需的变量,一直找到全局作用域,如果找到全局作用域都没有找到所需变量的话,就会报错“xxx is not defined”。
 
最后说一下作用域和执行上下文环境的区别:
  1)执行上下文环境是用来存储变量的,
是在方法调用的时候创建,会因为调用环境不同而产生不同的执行上下文环境。
      作用域是用来区分“地盘”的,也就是隔离变量,在两个不同作用域内部定义的相同名称的变量是不同的。
作用域在方法定义的时候就确定了
  2)一个作用域中可以有多个执行上下文环境。
function f(x){  return function (){         console.log(x)  }}var f1 = f(10)var f2 = f(15)f1() f2()

  上面这个例子打印出来结果是什么呢?

  在上面代码中,f运行后会返回一个匿名函数,并在这个匿名函数中调用了属于 f 作用域的 变量x,所以 f 执行结束后不会销毁上下文环境
  第一次执行,作用域中会新产生一个上下文环境,其中 x=10
  第二次执行,作用域中会再次产生一个新的上下文环境,其中 x=15
 
以上全部就是我在学习作用域过程中总结出来的东西了~

转载于:https://www.cnblogs.com/FraudulentArtists/p/9835322.html

你可能感兴趣的文章
2016.3.31考试心得
查看>>
mmap和MappedByteBuffer
查看>>
Linux的基本操作
查看>>
转-求解最大连续子数组的算法
查看>>
对数器的使用
查看>>
【ASP.NET】演绎GridView基本操作事件
查看>>
ubuntu无法解析主机错误与解决的方法
查看>>
尚学堂Java面试题整理
查看>>
MySQL表的四种分区类型
查看>>
[BZOJ 3489] A simple rmq problem 【可持久化树套树】
查看>>
STM32单片机使用注意事项
查看>>
swing入门教程
查看>>
好莱坞十大导演排名及其代表作,你看过多少?
查看>>
Loj #139
查看>>
StringBuffer是字符串缓冲区
查看>>
hihocoder1187 Divisors
查看>>
Azure 托管镜像和非托管镜像对比
查看>>
js window.open 参数设置
查看>>
032. asp.netWeb用户控件之一初识用户控件并为其自定义属性
查看>>
Ubuntu下安装MySQL及简单操作
查看>>