【JS】执行上下文和闭包

文章详细阐述了JavaScript中的执行上下文(全局和函数内)的概念,以及执行栈的工作原理。讨论了作用域的静态特性与执行上下文的动态创建之间的区别,并通过示例解释了闭包的产生和功能,强调了闭包在内存管理和延长变量生命周期中的影响,同时提到了其在事件处理和模块化编程中的应用。
摘要由CSDN通过智能技术生成

一、执行上下文和执行栈

1. 执行上下文

1.1 全局执行上下文

  1. 在执行执行代码之前,创建全局执行上下文对象,确定window就是全局执行上下文对象。
  2. 对全局数据进行预处理
    • 查找代码中 var 关键字定义的变量,添加为 window 的属性, 不进行赋值
    • 查找代码中 function 关键字定义的函数,添加为 window 的方法(属性名是函数名,属性值就是函数体)。
    • 创建 this,赋值为 window
  3. 正式执行全局代码

1.2 函数内的执行上下文

  1. 调用函数的时候,执行函数体中代码之前,创建执行上下文对象。
  2. 对函数中的数据进行预处理:
    • 对形参进行赋值,将形参添加为执行上下文对象的属性。
    • 创建 arguments 并进行赋值,将 arguments 添加为执行上下文对象的属性。
    • 查找函数代码中,var 关键字定义的变量,添加为执行上下文对象的属性,不进行赋值。
    • 查找函数代码中,function 关键字定义的函数,添加为执行上下文对象的方法(属性名是函数名,属性值就是函数体)。
    • 创建this,赋值为调用该函数的对象
  3. 正式执行函数体代码。

函数每调用一次,就创建一个执行上下文对象。

2. 执行栈

执行栈: 也叫调用栈,是一种栈数据结构,用来存储代码执行过程中所创建的执行上下文对象。

栈结构: 是一种数据存结构,特点是先进后出,后进先出。将数据放入栈栈结构称为进栈,栈结构中的数据销毁称为出栈

console.log('hello');
function func() {
    console.log('func');
}
func();
func();



/**
*  创建全局的执行上下文对象,该对象放入执行栈
*  调用func,创建函数的执行上下文对象, 执行函数体, 执行完毕销毁
*  调用func,创建函数的执行上下文对象, 执行函数体, 执行完毕销毁
*/

二、作用域和执行上下文的关系

区别:

  1. 作用域是静态的,在函数声明的时候,函数中的变量就已经确定了作用域。
  2. 执行上下文对象是动态的,每调用一次函数,就创建一个执行上下文对象。

联系:

​ 执行上下文对象同样具有作用域:
​ 全局执行上下文对象 -> 全局
​ 函数中的执行上下文对象 -> 函数

三、闭包

1. 什么是闭包?

  1. 简单讲,闭包就是指有权访问另一个函数作用域中的数据的函数
  2. MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

2. 如何产生闭包

  1. 在函数A的里面定义函数B
  2. 函数B中使用函数A中(函数A是函数B的上层作用域)的数据
  3. 将函数B被外部引用,如下三种方式可以让函数B被外部引用:
    • 将函数B作为函数A的返回值
    • 将函数B设置为全局对象(window)的属性
    • 将函数B作为异步操作(DOM事件、定时器)的回调函数。
 /*
    1. A函数中定义B函数
    2. 在B函数中访问A函数作用域中的数据
    3. 让B函数被外部引用
*/
function A() {
    var a = 100;
    var b = 200;
    function B() {
        console.log('我是B:我使用了A中的数据', a + b);
    }
    // 第一种方法
    // return B;

    // 第二种方法
    // window.fb = B;

    // 第三种方法
    document.onclick = B;
}
// 第一种方法
// var f = A();
// f();

// 第二种方法
// A();
// fb();

// 第三种方法
A();

3. 闭包和作用域

  1. 下层作用域可以使用上层作用域的数据
  2. 作用域只与函数声明的位置有关,有函数在哪里调用无关!

4. 闭包和垃圾回收

  1. 闭包让某个数据,即使函数调用结束,也仍然被引用;函数被销毁该数据仍然不会变为垃圾对象
  2. 闭包延长了局部变量的生命周期

5. 闭包的缺点

  1. 闭包会导致数据长时间占用内存,提高内存泄漏的风险
  2. 闭包慎重使用

6. 闭包的应用

  1. 通过遍历,给多个元素监听事件,闭包可以让事件回调函数中获取到元素的索引
  2. 实现JS的模块化
// 遍历所有 p 为每个 p 监听 click 事件
// 遍历所有 p 为每个 p 监听 click 事件
// for (var i = 0; i < pitems.length; i++) {
//     pitems[i].onclick = function () {
//         this.classList.toggle('active');
//         console.log('当前被选中的P的序号:', i);     //i是全局变量,都是6
//     };
// }
// forEach使用了闭包
pitems.forEach(function(pitem, index){
    pitem.onclick = function() {
        pitem.classList.toggle('active');
        console.log('当前被选中的P的序号:', index);
    };
});
// for循环
for (var i = 0; i < pitems.length; i ++) {
    (function(n){
        pitems[n].onclick = function() {
            pitems[n].classList.toggle('active');
            console.log('当前被选中的P的序号:', n);
        }
    })(i);
}

7. 练习题

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        return function () {
            return this.name;
        };
    }
};
console.log(object.getNameFunc()());

var name2 = "The Window";
var object2 = {
    name2: "My Object",
    getNameFunc: function () {
        var that = this;
        return function () {
            return that.name2;
        };
    }
};
console.log(object2.getNameFunc()()); 

在这里插入图片描述
在这里插入图片描述

function fun(n, o) {
    console.log(o);
    return {
        fun: function (m) {
            return fun(m, n)
        }
    }
}
var a = fun(0); 
a.fun(1);                 
a.fun(2);                   
a.fun(3);         

var b = fun(0).fun(1).fun(2).fun(3);

var c = fun(0).fun(1);  
c.fun(2);     
c.fun(3);   

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值