作用域与闭包
1.环境
在计算机中,环境就是一块内存区域
执行环境:当函数被调用时会产生一个执行环境(内存地址),在执行环境中可以定义数据变量/函数等
执行环境有两个特点:
- 有自身作用域,也就是作用范围
- 全局作用域
- 局部作用域
- 块级作用域:在一个代码块(由一对花括号包裹)内部
- 当环境不再被使用时,可以自动销毁或人为销毁
在js中,全局环境不会被回收,除非关闭页面或浏览器人为回收
2.环境的生命周期
function hd(){
let n = 1;
function sum() {
console.log(++n);
}
sum();
}
hd();
hd();
有上述代码段,当执行hd()函数时,计算机内存表示为:全局环境->hd()环境->sum()环境
当执行多次hd()函数时,控制台会打印出两个2,而不是2,3
原因在于:每一次函数调用都会产生新的执行环境,而函数调用完成后就会销毁该执行环境(不用就会被清除)
解决办法:使用闭包
3.环境栈
全局执行环境是最外围的执行环境,被认为是window对象
当调用一个函数时,会产生一个执行环境并被压入环境栈中,js为每一个执行环境关联了一个变量对象,环境中定义的所有变量和函数都保存在这个对象中
而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象
3.链式作用域
链式作用域(chain scope): 父对象的所有变量,对子对象都是可见的,反之则不成立;子对象会一级一级地向上寻找所有父对象的变量
当函数被调用时,会产生一个执行环境,并且每个执行环境会关联一个变量对象(变量对象中保存this,arguments对象),还会创建一个作用域链
链式作用域会指向父级的变量对象和自己的变量对象,当前执行环境的变量对象始终在作用域链的第0位
4.闭包
闭包就是能够读取其他函数内部变量的函数(该函数一定是一个内嵌函数)或对象
- 从外部读取函数内部的变量
- 试重保持变量在内存中,不被垃圾回收机制回收
function hd(){
let n = 1;
return function() {
console.log(++n);
}
}
let a = hd();
a(); //2
a(); //3
a(); //4
在上述代码中,外部变量a保持对function的引用(function属于引用型变量)
当function继续被引用时,其整个执行环境是会被继续保留在内存中的,所以变量n也是继续保存在内存中,达到封装变量的作用
使用闭包的注意点:
-
不能滥用闭包,会导致网页性能问题,也会导致内存泄漏
解决办法:手动赋值为null/少用闭包
-
可能导致非预期的操作
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
// The Window
/*
object.getNameFunc()会得到一个函数
而该函数会在window执行环境中执行
但是this并没有存储在内存中,所以this指向当前调用该函数的对象,即window
*/
alert(object.getNameFunc()());
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
// My Object
/*
object.getNameFunc()得到返回的函数
并且该函数是一个闭包
可以对外部的that保持读取,而that为object对象
*/
alert(object.getNameFunc()());