从作用域到闭包

115 阅读7分钟

作用域&作用域链——>作用域的本质——>闭包

1、作用域和作用域链Scope&&Scopes

作用域

作用域就是变量的可用范围

2级作用域

全局作用域和函数作用域

不属于任何函数的外部范围称为全局作用域

保存在全局作用域的变量称为全局变量

全局变量的特点

优点:可反复使用

缺点:全局污染——开发时禁止使用

举例:所有人共用一个水杯

函数作用域:

一个函数内的范围称为函数作用域

保存在函数作用域内的变量称为局部变量

特例:形参变量也是函数内的局部变量

局部变量的特点:

优点:不会被污染

缺点:无法反复使用

强调:只有函数的{},才能形成作用域

不是所有{}都能形成作用域

也不是所有{}内的数据都是局部变量

比如对象的{},就不是作用域

对象中的属性,也不是局部变量

var lilei = {
    name:"Li Lei"
}

JS中没有块级作用域
函数之外,都不是作用域
拦不住内部的变量超出{}的范围影响外部程序

作用域链(scopes/scpoe chain)

因为js规定,一个函数,既能用自己作用域的变量,又能用外层作用域的变量

所以需要一个“路线图”告诉每个函数,自己都可以去哪里找到想用的变量

其实每个函数在定义时,就已经规划好了自己专属的一个查找变量的路线图,称为作用域链

(为自己规划好一个从内向外的查找路线)

一个函数可用的所有作用域串联起来,就形成了当前函数的作用域链

当执行到某条语句时,js引擎会自动沿函数的作用域链查找要用的变量。

特殊:给从未声明过的变量赋值

强调:

形参变量也是函数的局部变量

函数传参采用的是按值传递

原始类型的值,在传参时,是将原变量的值复制一个副本给函数形参变量

所以,在函数内,修改形参变量,不影响外部原变量的值

2、作用域的本质

JS中,作用域和作用域链都是对象结构

全局作用域,其实是一个名为window的对象,所有全局变量和全局函数都是window对象的成员

函数作用域其实是js引擎在调用函数时才临时创建的一个作用域对象,其中保存函数的局部变量,而函数调用完,函数作用域对象就释放了

(js中没有堆和栈的概念)

var a=10//应该在栈里   //对象存在堆里

实际a在window的对象里

js内存中只有一种东西:关联数组

var a = 10

三种访问方式:

window["a"] 

window.a

a

Js中一切皆关联数组

所以,JS中函数作用域对象,还有个别名——“活动的对象(Actived Object)”,简称AO

所以,局部变量不可重用

函数调用过程三部曲

程序中的js引擎,先创建函数作用域对象保存局部变量

程序中的js引擎,按照函数体规定的步骤执行每一项操作

程序中的js引擎,会在调用函数后自动释放临时创建的函数作用域对象

(备料、做菜、收拾厨房)

底层中,function——>new function

刚开始时,先创建全局变量,再创建函数对象,保存函数

函数调用过程中,临时创建函数作用域对象

函数调用后释放函数,作用域对象,局部变量紧跟着释放了,所以局部变量在函数使用后,就不存在了

//所有函数在定义时,作用域scopes中,都只有一个全局作用域对象window吗

闭包

全局变量特点:优:可重用

缺点:极易被污染

局部变量特点:优:不会被污染

缺点:用完就释放,不可重用

3、闭包(closure)

什么是闭包

用法:既重用变量又保护变量不被污染的一种编程方法

如何使用闭包

(1)用外层函数包裹,要保护的变量和使用变量的函数

(2)在外层函数内部 返回内存函数对象

(3)调用外层函数 用变量接住返回的内存函数对象

//第一步:用外层函数包裹需要保护的变量和内层函数
function mother(){
    var total = 1000;
//第二步:返回内层函数对象
return function pay(money){
    total -= money;
    console.log(`花了${money}还剩${total}元`)
    }
}
//第三步:调用外层函数,用变量接住内层函数的对象
var pay = mother();
//pay接住的就是mother()返回出来的内层函数对象
pay(100);
total = 0;
pay(100);

函数只有加()被调用时,才执行函数体

所以,闭包结构内存函数的作用域链比一般函数作用域链,多一层作用域

因为mother的作用域对象,被内层函数的作用域链引用着,无法释放,就侥幸存活下来

而且,只有内层函数pay知道total变量的存储位置——专属私密

调用外层函数mother时,总是会自动创建外层函数mother的函数作用域对象,其中,保存着外层函数局部变量

强调:因为内存函数只是定义,未加()调用。所以,内层函数中的代码不执行

内层函数宝宝,一旦降生,就获得了外层函数mother给的专属红包,从此自立门户

所有函数调用完只清空作用域链中离自己最近的一个作用域

到底什么是闭包

闭包也是一个对象

闭包就是每次调用外层函数的时,临时创建的函数作用域对象

为什么外层函数作用域能留下来?

因为被内层函数对象的作用域链引用着,无法释放

闭包如何形成的

外层函数调用后,外层函数的作用域对象,被返回的内层函数的作用域链引用着,无法释放,形成闭包对象

闭包的缺点:

由于闭包藏得很深,几乎找不到,所以,极容易造成内存泄漏

解决:

及时释放不用的闭包

如何释放:

将保存内存函数对象的变量赋值为null

Q:闭包问题

看程序,判断输出结果

根据需求,使用闭包实现功能

function fun(){//mother
    var i = 999;//红包
    //给从未声明过的变量赋值,会自动在全局创建该变量
    nAdd = function(){i++}  //孩子1
    return function(){      //孩子2
        console.log(i)
    }
}
var getN = fun();
getN(); //999
nAdd();
getN();//1000

找3样东西:

1、外层函数——mother

2、外层函数的局部变量——红包

3、内层函数(多个)——孩子们

外层函数返回内层函数的方法

3种

1 return

2 强力赋值为全局变量

3 将函数包裹在对象或者数组中返回

(1)用外层函数包裹

要保护的变量和使用变量的内层函数 (mother一次很多个孩子,多个孩子共用一个红包)

(2)在外层函数内部

返回内层函数对象(妈妈反复生多次孩子,各自红包不互相影响)

(3)调用外层函数 

用变量接住返回的内层函数对象

妈妈反复生多次孩子

function mother(){
    var i = 0;
    return function(){
        i++;
        console.log(i);
}
var get1 = mother();//老大,一个红包
get1();//1
var get2 = mother();//老二,新的红包
get2();//1
get1();//2get2();//2

举例3

function fun(){//mother
    arr = [];//不算红包,因为内存函数中没有用到
    //但是arr是给未声明的变量强行赋值
    //自动在全局创建该变量
    for(var i = 0/*红包*/;i<3;i++){
        //强行给外部全局变量中添加新函数
        //new Function()
        arr[i] = function(){//反复创建了三个孩子
            console.log(i);//因为只是创建函数,所以这里暂时不执行
        }
    }
//for循环结束后,i=3
}
fun();
arr[0]();//3
arr[1]();//3
arr[2]();//3