# javascript作用域与闭包

分享一句话 用于自勉

基础理论知识是一个人的基线,理论越强基线越高。再为自己定一个目标和向上攀附的阶梯,那么达到目标就是时间问题,而很多野路子工程师搞了半辈子也未达到优秀工程师的基线,很多他们绞尽脑汁得出的高深学问,不过是正规工程师看起来很自然的东西。—— 吴军

# 作用域

在ES6以前,js只有函数级作用域,如:

function dome(){
    var a = 0;
}
console.log(a) //a is not defined

在ES6后,我们变量声明多了两种方式let,const,由他们声明出来的变量具有了块级作用域 ,如:

{
    var a = 0;
    const b = 0;
    let c = 0;
}
console.log(a,b,c);
// a is not defined  0  0

在ES6 以前,用var声明的变量还有个怪癖(变量提升),如:

console.log(a); //undefined
var a = 1

在js内部执行时的伪代码:

  1. var a = undefined
  2. console.log(a); //undefined
  3. a = 1

在ES6以后,我们解放了,可以用正常的顺序看代码了

console.log(a); //undefined
console.log(b); //报错
var a = 1;
const b = 1;

为了我们的代码更加有利于维护,声明的变量作用域当然 越小越好,所有我们完全可以用let,const代替var

# 作用域链

我们看这段代码

function bar() {
    console.log(myName)
}
function foo() {
    var myName = " 码不 "
    bar()
}
var myName = " 码不停息 "
foo()

我们用栈的方式分析这段代码的执行上下文

  • 在全局执行上下文中 myName = "码不停息"
  • 在foo执行上下文中 myName = "码不"
  • 那在bar中当我们获取myName的时候,会去哪里找这个myName?

其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer。 当一段代码使用了一个变量时,JavaScript 引擎首先会在“当前的执行上下文”中查找该变量, 比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找 foo 和bar的outer都是全局执行上下文 所有myName的值为码不停息
bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链

# 词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符 你可以看下面这张图: 词法作用域

从图中可以看出,词法作用域就是根据代码的位置来决定的,其中 main 函数包含了 bar 函数,bar 函数中包含了 foo 函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。

了解了词法作用域以及 JavaScript 中的作用域链,我们再回过头来看看上面的那个问题:在开头那段代码中,foo 函数调用了 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?

这是因为根据词法作用域,foo 和 bar 的上级作用域都是全局作用域,所以如果 foo 或者 bar 函数使用了一个它们没有定义的变量,那么它们会到全局作用域去查找。也就是说,词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系

# 闭包

网上关于闭包的讲解真的是神乎其神,生怕别人看懂一样。下面我讲下自己的理解

# 什么叫做闭包

定义:在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包
能够读取 其他函数 内部变量的 函数
通过函数的作用域我们知道,正常情况下,函数内部的变量是不会被外部环境所访问到的,如:

function dome(){
    const a = '码不停息';
}
console.log(a); // 报错

那我们怎么样可以拿到呢?这就是闭包要干的事

function dome(){
    const a = '码不停息';

    const fn = function(){
        return a
    }
    return fn
}
const res = dome();
console.log(res()) //码不停息