Skip to content

闭包与作用域

作用域

作用域: 程序定义变量的区域, 作用域中定义了如何找到对应的变量,因此在执行代码(运行在作用域中)时,能够获取对变量的访问权限

词法作用域 动态作用域

JavaScript属于词法作用域:作用域在定义时就已经确定,而非运行时确定。

  • 静态作用域:作用域在定义时就已经确定
  • 动态作用域:作用域在调用时确定

全局作用域 函数作用域

  1. 全局作用域 直接在脚本中书写的代码 在全局作用域中声明的变量,会被提升到脚本块的顶部,并且会称为全局对象的属性
  2. 函数作用域 在函数作用域中声明的变量,会被提升到函数的顶部,并且会成为函数的局部变量,并且不会成为全局对象的属性 因此,在函数作用域中声明的变量,只能在函数内部访问,不会造成全局对象的污染尽量将功能写在函数当中 但是,当函数成为一个表达式时,它既不会提升,也不会污染全局对象 将函数转为表达式的方式之一:是用小括号将函数包裹起来(表达式返回自身) 然而,这样做会导致一些问题,比如:
  • 函数无法通过变量名调用

如果声明一个函数表达式,然后立即调用(function() {}()),该函数称之为立即执行函数(IIFE),由于函数表达式的函数名没有什么意义,因此可以省略

作用域例子

javascript
var value =1;
function foo() {
    console.log(value);
}
function bar() {
    var value = 2;
    foo();
}
bar(); // 1
javascript
// case 1
var scope = "global scope";
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f();
}
checkScope(); // "local scope"

// case 2
var scope = "global scope";
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f
}
checkScope()(); // "local scope"

执行上下文

全局执行上下文:所有JS代码执行之前,都必须有该环境 函数执行上下文:一个函数在运行之前,创建的一块内存空间,空间中包含该函数执行所需要的数据,为该函数执行提供支持 JS引擎始终执行的是栈顶的执行上下文,当遇到函数调用时,JS引擎会创建新的执行上下文,并将其推入栈顶,当函数执行完毕,JS引擎会将该执行上下文从栈顶弹出。

执行上下文栈

所有执行上下文组成的内存空间 栈:一种数据结构,先进后出,后进先出。

执行上下文的内容

this

指向当前执行上下文的执行环境,具体取值取决于函数调用方式

变量对象(variable object)

包含了在该环境中所有声明的参数、变量和函数

  • 全局上下文的变量对象(global variable object):window(浏览器环境)或global(Node.js环境)
  • 函数上下文的变量对象(activation object):包含了函数声明和函数参数

可执行代码(executable code)

  • 全局代码
  • 函数代码
  • eval代码

执行函数执行上下文代码

AO(activation object):当前正在执行的上下文的变量对象(VO) GO(global object):全局执行上下文的变量对象(VO)

  1. 确定所有形参以及特殊变量arguments对象
  2. 确定函数中通过var声明的变量,将它们的值设为undefined,如果VO中已经有该名称,则直接忽略
  3. 确定函数中通过字面量声明的函数,将它们的值设为函数本身,如果VO中已经有该名称,则覆盖
javascript
function foo(a) {
    var b = 2;
    function c() {}
    var d = function () {};
    b = 3;
}
foo(1);
// 预编译阶段
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: function c() {},
    d: undefined
}
// 函数执行后
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 2,
    c: function c() {},
    d: function () {}
}
  • 例子
javascript
// 例子一
function  foo() {
    console.log(a);
    a = 1;
}
foo(); // ReferenceError: a is not defined

function bar() {
    a = 1;
    console.log(a)
}
bar(); // 1
javascript
// 例子二
function A(a, b) {
    console.log(a, b) // 1 function b () {}
    var b = 123
    function b() {}
    var a = function () {}
    console.log(a, b) // function () {}, 123
}
A(1, 2)
javascript
// 例子三
var foo = 1
function A(a, b) {
    console.log(foo) // undefined
    if (!foo) {
        var foo = 10
    }
    console.log(foo) // 10
}
javascript
// 例子四
var a = 1
function b(a, b) {
    console.log(a) // function a () {}
    a = 10
    return
    function a() {}
}
b()
console.log(a) // 1

作用域链(scope chain)

  1. VO(variable object)变量对象中包含一个额外的属性,该属性指向创建该VO的函数本身
  2. 每个函数在创建时,会有一个隐藏属性[[scope]],它指向创建该函数时的AO(当前的执行上下文)
  3. 当访问一个变量时
    1. 先查找自身VO中是否存在,如果不存在,则依次查找[[scope]]属性
javascript
// 例子
var g = 0;
function A() {
    var a = 1;
    function B() {
        var b = 2;
        var C = function () {
            var c = 3;
            console.log(g, a, b ,c)
        }
        C();
    }
    B();
}
A();
javascript
// 例子
function A() {
    var count = 0;
    return function () {
        count++;
        console.log(count)
    }
}
var test = A();
test(); // 1
test(); // 2
test(); // 3
javascript
// 例子
var foo = { n: 1 }
(function (foo) {
    console.log(foo.n) // 1
    foo.n = 3;
    var foo = { n: 2 }
    console.log(foo.n) // 2
} (foo))
console.log(foo.n) // 3
javascript
// 例子
var a = 1;
function A() {
    console.log(a);
}
function Special() {
    var a = 5;
    var B = A;
    B();
}
Special()
javascript
// 例子
function foo() {
    function bar() {}
}
foo['[[scope]]'] = [
    globalContext.VO
]
bar['[[scope]]'] = [
    fooContext.AO,
    globalContext.VO
]
javascript
var scope = "global scope";
function checkScope() {
    var scope2 = "local scope";
    return scope2;
}
checkScope(); //
javascript
checkScope['[[scope]]'] = [
    globalContext.VO
]

ECS = [
    checkScopeContext,
    globalContext
]
// 在调用checkScope函数时,不会立即执行,但是会进行准备工作,VO, scope chain, this等
checkScopeContext = {
    Scope: [AO, ...[checkScope['[[scope]]']]],
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    }
}
// 进入调用
checkScopeContext = {
    Scope: [AO, ...[checkScope['[[scope]]']]],
    AO: {
        arguments: {
            length: 0
        },
        scope2: "local scope"
    }
}
ECS = []

闭包(closure)

闭包是一种现象,内部函数,可以使用外部环境的变量。