作用域,作用域链,闭包
作用域
作用域:代码执行环境,变量起作用的区域,即变量可以被访问到的区域。
全局环境就是全局作用域,是最外围的执行环境。函数的执行环境就是私有作用域,他们都是栈内存。
作用域就是代码执行开辟栈内存。
常见变量作用域:词法作用域,变量作用域。
词法作用域:(静态作用域)函数的作用域在函数定义的时候就决定了。
动态作用域:函数的作用域是在函数调用的时候才决定的。
JavaScript采用的是词法作用域
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)
注意:
- 全局变量对象始终都是作用域链上的最后一个对象。
- 内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
下面来分析几个例子:
第一个:
let a = 1;
function foo(){
let a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
let a = 3;
fn();
}
foo();
(1)假设采用静态作用域分析:
首先,执行foo函数,foo函数中包含baz函数,先从baz函数内部查找是否有局部变量,没有,根据书写位置,查找上一层代码,也就是a=2。所以打印结果为2.
(2)假设采用动态作用域分析:
首先,执行foo函数,foo函数中包含baz函数,也是从baz函数内部查找是否有局部变量a。如果没有,就从调用函数的作用域,也就是bar函数内部查找 a变量,在bar函数中a=3,所以打印的结果为3。
但由于JS中采用的是静态作用域,所以这个例子的打印结果为2.
接下来是第二个例子:
function foo() {
let a = 1;
function bar() {
let a = 2;
function baz() {
let a = 3;
console.log(a);
}
baz();
}
bar();
}
foo();
静态作用域分析:
首先,执行foo函数,foo函数中包含bar函数,bar函数中又包含baz函数,baz函数中有局部变量a=3,所以打印结果为a=3.
闭包
什么是闭包?
闭包就是能够读取其他函数内部变量的函数。
在javascript中,只有函数内 部的子函数才能读取局部变量,所以闭包可以理解成"定义在一个函数内部的函数"。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
闭包是怎样的形成的?
产生一个闭包 创建闭包最常见方式,就是在一个函数内部创建另一个函数。下面例子中的 bar就是一个闭包:
function fn1(){
var a = 1,b = 2;
function fn2(){
return a+b;
}
return fn2;
}
var fn3 = fn1();
fn3();
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。
分析:
fn2的词法作用域能访问到fn1的作用域。
将fn2作为一个返回值返回。
fn1执行后,将fn2的引用赋值给fn3
执行fn3,返回a+b的值
当fn1函数执行后,其作用域应该被销毁并释放其内存空间。有了闭包之后fn1的作用域就没有被销毁。,fn2依然有该作用域的引用,这个引用就是闭包。在这个过程中,因为局部变量fn2的声明周期延长,使得Javascript的垃圾回收机制不会收回函数fn1所占用的资源,因为函数fn1的局部变量fn2的执行需要依赖函数fn1中的变量。
闭包特点:
1、函数嵌套函数
2、内部函数可以访问外部函数的变量
3、参数和变量不会被回收。
闭包的优缺点:
闭包优点:延长外部函数局部变量生命周期
闭包缺点:长时间占用容易内存泄露
闭包的作用
- 可以读取函数内部的变量
- 使变量的值始终保持在内存中
闭包的应用
点击列表中的每一项
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
var a = document.getElementsByTagName('li');
for (var i = 0; i < a.length; i++) {
a[i].onclick = function ()
{
console.log(i);
}
}
运行结果:无论点击哪一个li,输出的结果都是3。
原因:在点击li时,for循环已经遍历完毕。
解决方法:
-
记录索引值
for (var i = 0; i < a.length; i++) { a[i].index = i; a[i].onclick = function () { var index = this.index; console.log(index); }}
2.用let
for (let i = 0; i < oList.length; i++) {
oList[i].onclick = function () {
console.log(i);
}
}
3.闭包
function foo(i){
return function(){
console.log(i);
}
}
for(var i=0;i<a.length;i++){
a[i].onclick = foo(i);
}
闭包的销毁
- js最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,使变量a = null。将变量设置为 null, 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。
- 在局部作用域中,当函数执行完毕的时候,局部变量就没有用了,垃圾收集器就会将他回收。
- 全局变量什么时候需要自动释放很难判断,所以尽量避免使用全局变量。
eg:
function foo () {
var a = document.getElementById("la");
a.onclick = function () {
alert(a.id);
}
}
可以通过将变量设置为 null
window.onload = function () {
var a = document.getElementById("la");
var id = a.id;
a.onclick = function () {
alert(id);
}
a= null;
}