执行上下文?
执行 JavaScript 代码的环境的抽象概念
-
全局执行上下文 — 创建一个全局的 window 对象(浏览器的情况下),并且设置
this
的值等于这个全局对象。 -
函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。
执行栈
后进先出数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
当 JavaScript 引擎执行脚本时,会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎先执行栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
闭包
闭包是一个可以访问外部作用域的内部函数,即使这个外部作用域已经执行结束。
被引用的变量直到闭包被销毁时才会被销毁。
闭包使得 timer
定时器,事件处理,AJAX
请求等异步任务更加容易。
作用域
作用域决定这个变量的生命周期及其可见性。 当我们创建了一个函数或者 {}
块,就会生成一个新的作用域。需要注意的是,通过 var
创建的变量只有函数作用域,而通过 let
和 const
创建的变量既有函数作用域,也有块作用域。
词法作用域
词法作用域是指内部函数在定义的时候就决定了其外部作用域。
看如下代码:
(function autorun(){
let x = 1;
function log(){
console.log(x);
};
function run(fn){
let x = 100;
fn();
}
run(log);//1
})();
log()
函数是一个闭包,它在这里访问的是 autorun()
函数中的 x
变量,而不是 run
函数中的变量。
闭包的外部作用域是在其定义的时候已决定,而不是执行的时候。
autorun()
的函数作用域即是 log()
函数的词法作用域。
闭包与循环
闭包只存储外部变量的引用,而不会拷贝这些外部变量的值。 查看如下示例:
function initEvents(){
for(var i=1; i<=3; i++){
$("#btn" + i).click(function showNumber(){
alert(i);//4
});
}
}
initEvents();
在这个示例中,我们创建了3个闭包,皆引用了同一个变量 i
,且这三个闭包都是事件处理函数。由于变量 i
随着循环自增,因此最终输出的都是同样的值。
修复这个问题最简单的方法是在 for
语句块中使用 let
变量声明,这将在每次循环中为 for
语句块创建一个新的局部变量。如下:
function initEvents(){
for(let i=1; i<=3; i++){
$("#btn" + i).click(function showNumber(){
alert(i);//1 2 3
});
}
}
initEvents();
但是,如果变量声明在 for
语句块之外的话,即使用了 let
变量声明,所有的闭包还是会引用同一个变量,最终输出的还是同一个值。
闭包 vs 纯函数
闭包就是那些引用了外部作用域中变量的函数。
为了更好的理解,我们将内部函数拆成闭包和纯函数两个方面:
- 闭包是那些引用了外部作用域中变量的函数。
- 纯函数是那些没有引用外部作用域中变量的函数,它们通常返回一个值并且没有副作用。
作用域链
每一个作用域都有对其父作用域的引用。当我们使用一个变量的时候,Javascript引擎
会通过变量名在当前作用域查找,若没有查找到,会一直沿着作用域链一直向上查找,直到 global
全局作用域。