作用域/闭包

1、作用域

作用域定义了如何查找变量的位置 ,即确定当前执行上下文中变量和其他资源的作用范围。(本质上就是能访问到定义变量 的范围)。作用域包括:全局作用域函数作用域块级作用域

全局作用域

在代码的任何地方都能访问到的变量所拥有的作用域。

1、最外层函数和在最外层函数外面定义的变量

javascript 复制代码
var a = 1; // 全局作用域

function func() {
    var b = 2; // 函数作用域
}

console.log(a) // 输出1
console.log(func) // 输出  [Function: func]
console.log(b) // ReferenceError: b is not defined

2、所有末定义直接赋值的变量自动声明为拥有全局作用域

javascript 复制代码
function func() {
    var a = b = 1;
}

// a 为函数作用域 b由于未使用var定义,在编译过程中会在全局定义编译结果类似下方代码

var b;
function func() {
    b = 1;
    var a = b;
}

console.log(a) // ReferenceError: a is not defined
console.log(b) // 输出1

3、所有window对象的属性拥有全局作用域

一般情况下,window 对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等。

函数作用域

在函数内部定义的变量,拥有函数作用域。函数作用域仅在定义变量的函数内部访问到,在变量所在函数外部及全局作用域中无法访问到该变量。如下方代码中的变量 a。

javascript 复制代码
function func() {
    var a = 12;
    console.log(a) // 输出 12
}

console.log(a) // ReferenceError: a is not defined

块级作用域

es6 中引入了let和const来申明变量。而在 **{ }**中使用let或const声明变量,那么这个 { }括住的变量就形成了一个块级作用域。所声明的变量在指定块的作用域外无法被访问。

javascript 复制代码
if(true) {
    const a = 12; // 块级作用域
    console.log(a) // 输出 12
}

console.log(a) // ReferenceError not defined

* 注意 *

  • 内部作用域 可访问到 外部作用域 ,反之不可以
  • 函数调用时会执行上下文,创建一个对象 AO:{} (表示函数作用域对象)
  • 每个函数都会有自己的函数作用域属性 [[scope]] 这个隐式属性:只供给引擎访问的属性,其中存储了执行期上下文的集合

2、作用域链

指的是作用域查找的线路,即 [[scope]] 中所存储的执行期上下文对象的集合 ,这个集合呈链式连接,我们把这种链式链接叫做作用域链 。(也就是说,在访问一个变量时,首先会在自身范围内查找,如果没有找到的话,会一层层的向上查找直到找到全局作用域,若此时还没有找到,则会返回not defined 报错,而这一层层的关系就是所谓的作用域链)

javascript 复制代码
var a = 1;

function func1() {
    var b = 2;

    function func2() {
        var c = 3;
        console.log(a) // 输出 1
        console.log(b) // 输出 2
        console.log(c) // 输出 3
    }
    console.log(a) // 输出 1
    console.log(b) // 输出 2
    console.log(c) // 输出 ReferenceError not defined
}
console.log(a) // 输出 1
console.log(b) // 输出 ReferenceError not defined
console.log(c) // 输出 ReferenceError not defined

3、预编译

在 JS 代码执行之前,会对 JS 代码进行编译,编译之后,再从上到下依次执行。

编译步骤:词法分析(词法单元)、语法解析(抽象语法树)、代码生成

对于本章节来说,我们需要注意的是发生在编译阶段的 申明提升

  • 在编译时将变量的声明,提升到当前作用域的顶端
  • 函数声明整体提升(会提升到最前面)

如下方代码示例:

javascript 复制代码
foo()
function foo(){
    console.log(a);//undefined 
    var a=1
}
var b=2

//foo()函数执行前进行编译,编译时相当于如下
foo();
b=2;
function foo(){
    console.log(a);//undefined 
    var a=1
}

JavaScript 程序执行前,会先创建全局执行上下文 (Global Execution Context),这个过程又分为两大阶段 :创建阶段(又称变量环境/词法环境的初始化)和执行阶段。

  • 创建阶段

全局对象创建:为全局作用域创建一个全局对象,并生成全局执行上下文

词法环境和变量环境的建立

函数声明 :例如 function func(){...} 。在创建阶段,JavaScript 引擎会把整个函数体作为一个对象存储,并将标识符 func 和函数对象的引用存储到全局环境中。这样,调用 func() 时就可以直接找到该函数。

变量声明(var 声明):

1、 var 属于变量声明表达式 (Variable Declaration Expression)。在创建阶段,所有通过 var 声明的变量都会在当前的环境记录中被创建,并初始化为 undefined 。这一过程称为"变量提升"(Hoisting)

2、 在全局上下文中,会为 var b 创建一个标识符b ,初始值为 undefined (赋值操作 b = 2 会在执行阶段完成)。

注意 :在同一作用域内,如果有函数声明和变量声明同名,函数声明 的优先级高于 var 声明 。并且后续的 var 声明不会覆盖已经存在的绑定(只影响赋值部分)。

  • 进入函数 func 的执行上下文

当执行 func() 时,会创建一个新的执行上下文,这个上下文同样有创建执行两个阶段:

1 、在 创建阶段 内:

函数内部的变量声明: var a 被处理,创建一个绑定(内部环境记录中添加标识符 a ),初始值为 undefined 。这就意味着即使代码中 var a = 1 在 console.log(a) 之后写,变量 a 已经存在,但其值目前还是 undefined

函数内部的其他声明: 如果有其他函数声明、参数等,同样会被处理,但本例中只有 var a 。

javascript 复制代码
// 全局执行上下文(创建阶段):
var b;  // 创建变量  b, 初始值  undefined
function foo(){  // 创建函数  foo,函数体整体存储 
    var a;  // 在  foo 的上下文中创建变量  a, 初始值  undefined 
    console.log(a);  // 此时  a 为  undefined 
    a = 1;  // 赋值操作
}

// 执行阶段(全局):
foo();  // 调用  foo() 时进入  foo 的执行上下文
b = 2;  // 全局变量  b 赋值为  2

4、闭包

  • 闭包的定义

闭包 是由捆绑起来(封闭的)的函数函数周围状态词法环境 )的引用组合而成。换言之,闭包让函数能访问它的外部作用域。在 JavaScript 中,闭包会随着函数的创建而同时创建。

当函数可以记住并访问所在的词法作用域时,就产生了闭包 。即使函数是在当前词法作用域之外执行。(闭包 :在一个作用域中可以访问另一个函数内部的局部变量的函数)

  • 闭包的形成
javascript 复制代码
function func() {
    var a = 1;
    return function() {
        console.log(a)
    }
}

var fn = func();
fn()

func() 函数的执行结果返回给 fn ,而此时由于变量 a 还在使用,因而没有被销毁,然后执行 fn() 函数。这样,我们就能在外部作用域访问到函数内部作用域的变量。这个就是闭包

闭包的形成条件

  • 函数嵌套
  • 内部函数引用外部函数的局部变量
  • 闭包的作用
  • 可以读取函数内部的变量
  • 可以使变量的值长期保存在内存中,生命周期比较长。
  • 可用来实现 JS 模块( JQuery 库等)

JS 模块 是具有特定功能的 JS 文件,将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含多个方法的对象或函数,模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。

javascript 复制代码
(function() { 
    var a = 1; 
    function test() { 
        return a; 
    } 
    window.module = {a, test};  // 向外暴露
})()
  • 闭包的特性
  • 每个函数都是闭包,函数能够记住自己定义时所处的作用域,函数走到了哪,定义时的作用域就到了哪。
  • 内存泄漏

内存泄漏 就是一个对象在你不需要它的时候仍然存在。所以不能滥用闭包。当我们使用完闭包后,应该将引用变量置为 null

javascript 复制代码
function outer(){ 
    var num = 0; 
    return function add(){ 
        num++; 
        console.log(num); 
    };
}
var func1 = outer();
func1();  // 1
func1();  // 2 [没有被释放,一直被占用]
var func2 = outer();
func2();  // 1 [重新引用函数时,闭包是新的]
func2();  // 2
相关推荐
前端 贾公子2 小时前
@uni-helper 社区:让 uni-app 拥抱 ESM 时代
开发语言·前端·javascript
大卡拉米2 小时前
ClaudeCode安装及使用
前端·学习
豆豆2 小时前
PageAdmin CMS模板开发详解:HTML转CMS系统的10个核心步骤
前端·html·cms·网站建设·网站制作·自助建站·网站管理系统
lemon_yyds2 小时前
vue 2 升级vue3 : element ui 校验红色高亮失去效果
前端·element
真夜2 小时前
又遇到生产与开发环境结果不一致问题。。。
前端·javascript·http
lemon_yyds2 小时前
vue2升级vue3:图片点击预览出现样式错乱
前端
掘金安东尼2 小时前
低代码工具很多,为什么 RollCode 更像一套「页面生产平台」
前端·javascript·面试
HelloReader2 小时前
Flutter StatefulWidget让界面动起来(六)
前端
umigreen2 小时前
uniapp实现小程序地图导航
前端