2.闭包作用域核心概念

1.私有栈内存处理

词法解析可以理解为在变量提升之前/之后,或者理解成变量提升的一部分,目前很多文章并没有一个确切的定论,只是在词法解析阶段出现语法错误的话代码第一行都不会执行,直接报错

  • 练习题:写出下面代码输出的结果
console.log(a, b);
var a = 12,
    b = 12;//等价于 var a=12;var b=12;
function fn() {
    console.log(a, b);
    var a = b = 13;//等价于 var a=13;b=13;
    console.log(a, b);
}
fn();
console.log(a, b);
  • 全局作用域( scope )/全局执行_上下文( context )/全局栈内存( stack )–这三个是一样的
  • 每一个作用域即执行上下文中都要进行词法解析/变量提升
  • “作用域链查找机制”
    私有栈内存中代码执行的时候,如果遇到一个变量:
    1.首先看是否为自己私有的,是自己的以后操作都用自己的,不是自己的去上级作用域中查找…一直找到全局作用域为止
    2.找到拿来用,找不到可能会报错
  • 函数执行形成的私有栈内存,会把内存中所有的私有变量保护起来,和外面没有任何的关系= >函数执行的这种保护机制就是“闭包”
  • 函数创建只是开辟一个堆内存,然后把函数中的代码以字符串的形式存储在这个堆内存中;函数执行的时候才会形成“私有的”的作用域/栈内存/执行上下文
  • 函数执行的时候,第一步先变量提升或者词法解析;第二步代码执行

此题图解
在这里插入图片描述
最终的输出结果:undefined undefined/undefined 12/13 13/12 13

  • 巩固练习题:写出下面代码输出的结果
console.log(a, b, c);
var a = 12,
    b = 13,
    c = 14;
function fn(a) {
    console.log(a, b, c);
    a = 100;
    c = 200;
    console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c);

此题的图解:
在这里插入图片描述

输出结果:
undefined undefined undefined
10 13 14
100 13 200
12 undefined 200

形参一定是私有变量:函数中的形参和在函数中声明的变量都是函数的私有变量,下面代码中的形参已经是私有变量了,所以不能在函数中再重复声明此变量,词法解析阶段会报语法错误。

function sum(a) {
    console.log(a);
    let a = 100;//Uncaught SyntaxError: Identifier 'a' has already been declared
    console.log(a);
}
sum(200);
2. 私有变量和全局变量
  • 练习题:写出下面代码输出的结果
var ary = [12, 23];
function fn(ary) {
    console.log(ary);
    ary[0] = 100;
    ary = [100];
    ary[0] = 0;
    console.log(ary);
}
fn(ary);
console.log(ary);

此题的图解:
在这里插入图片描述

输出结果:
[ 12, 23 ]
[ 0 ]
[ 100, 23 ]

函数执行一次就会形成一个全新的私有作用域/执行上下文,执行多次就形成多个,相互之间没有必然的联系。

3. 作用域链机制(如何查找上级作用域)

作用域链查找机制,关键在于如何查找上级作用域:

  1. 从函数创建开始,作用域就已经指定好了
  2. 当前函数是在哪个作用域( N )下创建的,那么函数执行形成的作用域( M )的上级作用域就是N,“和函数在哪执行的没关系,只和创建的地方有关系”
  • 练习题:写出下面代码输出的结果
    (如何查找上级作用域和堆栈内存释放问题)
var n=1;
function fn() {
    var n=2;
    function f(){
        n--;
        console.log(n);
    }
    
    f();
    return f;
}
var x=fn();
x();
console.log(n);

此题的图解:
在这里插入图片描述

在这里插入图片描述

  • 练习题:写出下面代码输出的结果
var i = 0;
function A() {
    var i = 0;
    function x() {
        console.log(i);
    }
    return x;
}
var y = A();
y();
function B() {
    var i = 20;
    y();
}
B();

此题的解析

/*
全局作用域:
1.变量提升:var i/function A(){...}/var y/function B(){...}
2.代码执行:i=0;/
*/
var i = 0;
function A() {
    var i = 0;
    //函数x是在A的私有作用域中创建的
    function x() {
        console.log(i);
    }
    return x;
}
//A执行后的返回值赋值给变量y,y=function x() {console.log(i);}
var y = A(); //y=x
y();
function B() {
    var i = 20;
    y();
}
B();

最终的输出结果:0、0

4.闭包的机制(堆栈内存释放问题)

闭包作用域

  1. 创建函数
  • 开辟一个堆内存
  • 把函数体中的代码当做字符串存储进去
  • 把堆内存的地址赋值给函数名/变量名
    函数在哪创建,那么它执行时候所需要查找的上级作用域就是谁
  1. 函数执行
  • 形成一个全新的私有作用域/执行上下文/私有栈内存(执行一次形成一个,多个之间也不会产生影响)
  • 初始化作用域链&形参赋值&变量提升/词法解析
  • 代码执行(把所属堆内存中的代码字符串拿出来一行行执行)
  • 遇到一个变量,首先看它是否为私有变量(形参和在私有作用域中声明的变量是私有变量) , 是私有的就操作自己的变量即可,不是私有
    的则向上级作用域中查找…直找到全局作用域为止=>作用域链查找
    机制
  • 私有变量和外界的变量没有必然关系,可以理解为被私有栈内存保护
    起来了,这种机制其实就是 闭包的保护机制
  1. 关于堆栈内存释放问题(以谷歌webkit内核为例子)
    函数执行就会形成栈内存(从内存中分配的一块空间) , 如果内存都不销毁释放,很容易就会导致栈内存溢出(内存爆满,电脑就卡死了) , 堆栈内存的释放问题是学习JS的核心知识之一
  • 堆内存释放问题
    创建一个引用类型值,就会产生一个堆内存,如果当前创建的堆内存不被其它东西所占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉),则会释放
let obj = {
    name: ' zhufeng'
};
let oop = obj;
//此时obj和oop都占用着对象的堆内存,想要释放堆内存,需要手动解除变量和值的关联(null:空对象指针)
obj = null;
oop = null;
//将obj和oop指向基本数据类型也会释放之前对象的堆内存
  • 栈内存释放

~ 形成栈内存的情况:
打开浏览器形成的全局作用域是栈内存
手动执行函数形成的私有作用域是栈内存
基于ES6中的let/const形成的块作用域也是栈内存
其它…

~ 栈内存何时销毁
全局栈内存: 关掉页面的时候才会销毁
私有栈内存:

  1. 一般情况下,函数只要执行完成,形成的私有栈内存就会被销毁释放掉(排除出现无限极递归、出现死循环的模式)
function fn() {
    //...
}
fn(); //=>函数执行形成栈内存,执行完成栈内存销毁

//-----------------
//无限极递归/死递归
function fn() {
    //...
    fn();
}
fn();//=>函数执行出现无限极递归、出现死循环的模式,栈内存不销毁
  1. 但是一旦栈内存中的某个东西(一般都是堆地址)被私有作用域以外的事物给占用了,则当前私有栈内存不能立即被释放销毁(特点:私有作用域中的私有变量等信息也保留下来了)
function X() {
    return function () {
        //...
    }
}
let f = X(); //=>f占用了X执行形成的栈内存中的一个东西(返回小函数对应的堆),则X执行形成的栈内存不能被释放了
  • 闭包的概念
    函数创建的时候会形成一个私有的栈内存保护私有变量与外界互不干扰,这种保护机制称为闭包;
    另外一种说法:函数执行的时候形成的私有栈内存中有引用地址被外界占用导致当前这个私有的栈内存不被释放,这种机制叫闭包
  • 闭包的两大作用
  1. 保护(私有变量和外界没有必然联系)
  2. 保存(形成不销毁的栈内存,里面的私有变量等信息保存下来了)
  • 练习题:写出下面代码输出的结果
var i = 5;
function fn(i) {
    return function (n) {
        console.log(n + (++i));
    }
}
var f = fn(1);
f(2);
fn(3)(4);
fn(5)(6);
f(7);
console.log(i);

图解一:
在这里插入图片描述
图解二:
在这里插入图片描述
图解三:
在这里插入图片描述
所以最终的输出结果:4、8、12、10、5

巩固练习题:写出下面代码输出的结果

var i = 20;
function fn() {
    i -= 2;
    return function (n) {
        console.log((++i) - n);
    }
}
var f = fn();
f(1);
f(2);
fn()(3);
fn()(4);
f(5);
console.log(i);

最终的结果:18、18、16、14、14、19
图解一:
在这里插入图片描述
图解二:在这里插入图片描述
图解三:在这里插入图片描述
图解四:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值