作用域&作用域链——>作用域的本质——>闭包
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