关于作用域和闭包的一点认识

215 阅读3分钟

作用域是什么?

就是根据名称查找变量的一套规则

var a=2 编辑器会在当前作用域中声明一个变量a(如果没有在当前作用域中找到变量a),然后引擎则会在作用域中查找该变量,找到则赋值2给变量a,否则会抛出错误。

注意:作用域会在找到第一个标识符时即停止。所有有多个相同变量名时,后面的会覆盖前面的。

考虑下面代码

function foo(str,a){
        eval(str)
        console.log(a,b)
}
var b=2;
foo('var b=3',2)

此时,输出并不是2 2,而是2 3。此时,var b=3;就好像开始就定义在foo()函数内部一样。因此要谨慎使用eval()

函数作用域是什么?就是属于这个函数内部的所有变量都可以在整个函数的范围内使用及复用(包括嵌套的作用域)

function是函数声明中的第一个词,则为函数声明,否则是函数表达式。函数声明不可以匿名,但函数表达式可以匿名。

立即执行函数表达式有两种书写方式

//第一种
var a=2;
(function(global){
  var a=2;
  console.log(global.a)
  console.log(a)
})(window)

//第二种
var a=2;
(function(def){
  def(window)
})(function def(global){
  var a=2;
  console.log(global.a)
  console.log(a)
})

包括变量和函数在内的所有声明都会在任何代码被执行前首先处理,即声明被提升,但赋值不会被提升。 函数声明会被提升且比变量先提升。函数表达式不会被提升。

// 函数声明会被提升,这是可执行的
foo()
function foo(){
console.log(2)
}

//函数表达式不会被提升,这是不可执行的
foo()
var foo = function(){
console.log(2)
}
//函数会比变量优先提升
foo()
var foo()
function foo(){
console.log(2)
}
foo = function(){
console.log(3)
}
输出2

闭包是什么?

当函数可以记住并访问所在的作用域,即使这个函数是在当前作用域之外执行,这时就产生了闭包。就是一个外部函数a内部的另一个函数b可以访问到这个外部函数a的所有变量和方法,然后这个内部函数b在外部函数a之外被执行时仍然可以访问到外部函数a的所有变量和方法,这就是闭包。

简单例子

function foo(){
 var a=2;
 function boo(){
 console.log(a)
 }
 return boo
}
var a=3
var baz = foo()
baz()

此时,输出结果为2,说明baz也就是boo函数在foo函数外部执行了,但是仍然访问的是foo()函数内部的变量a,这就是闭包。我称之为身在曹营心在汉。boo函数身在全局环境中,访问的变量却是foo()函数内部的。

下面看一个很经典的例子

for(var i=0;i<5;i++){
        setTimeout(()=>{
                console.log(i)
        },i*1000)
}

这段代码的结果是每隔一秒输出一个5 理由如下:

  1. setTimeout()的回调函数会在循环结束后才执行。即使是setTimeout(fn,0)也是得等到循环结束才执行。
  2. 循环结束后的i是5
  3. setTimeout()的回调函数都是在全局作用域中定义的,所以所有的回调函数都是访问的同一个i变量

那么如果改呢

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

利用立即执行函数创建一个独立的作用域,每个作用域中的变量i都是不同的。

JS中的作用域是动态的吗

当然不是动态的。作用域是在写代码或定义时就确定的。 看这个例子就能理解了

function foo(){
        console.log(a)
}
function boo(){
        var a=2;
        foo()
}
var a=3;
boo()

如果作用域是动态的,那么很明显就会输出2,因为foo()函数在boo()内部,会首先访问到boo中的a。但是输出结果确实3,说明访问的依旧是全局变量中的a。只关注函数在何处声明,不关注函数在何处被调用。foo()在全局作用域中定义,则访问的就是全局作用域中的变量。