相关系列: 从零开始的前端筑基之旅(面试必备,持续更新~)javascript
在理解闭包以前,有个重要的概念须要先了解一下,就是 js 执行上下文。前端
每当引擎遇到一个函数调用,它会为该函数建立一个新的执行上下文并压入栈的顶部。java
当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。执行上下文中声明的全部变量都将被删除。面试
先看个栗子:闭包
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope(); // local scope
复制代码
依据词法做用域逻辑,查找变量的时候,会先从当前上下文的变量对象中查找,若是没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象。函数
所以,内部函数f在执行时,找到定义位置的父级函数checkscope内的scope并将其返回,而后内部函数与外部函数的上下文依次弹出并销毁。post
再看另外一个栗子:ui
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
const f2 = checkscope();
f2(); // local scope
复制代码
与上个栗子不一样,这里并无在函数内部调用f,而是将f返回赋值给f2,所以,随着checkscope函数的结束,由于f2持有对函数f的引用,f没有被销毁,又由于f持有外部函数的做用域,因此scope也没有被销毁。spa
由此得出结论:.net
{}
块里面定义一个函数来建立闭包。闭包能够访问外部做用域,即便这个外部做用域已经执行结束。闭包只存储外部变量的引用,而不会拷贝这些外部变量的值。
function initEvents(){
for(var i=1; i<=3; i++){
setTimeout(function showNumber(){
console.log(i)
},10);
}
}
initEvents(); // 4,4,4
复制代码
这个示例中,咱们建立了3个闭包,皆引用了同一个变量 i
,因为变量 i
随着循环自增,所以最终输出的都是一样的值。
在 for
语句块中使用 let
变量声明,将在每次循环中为 for
语句块建立一个新的局部变量。
function initEvents(){
for(let i=1; i<=3; i++){
setTimeout(function showNumber(){
console.log(i)
},10);
}
}
initEvents();
复制代码
经过闭包,咱们能够建立拥有私有状态的函数,闭包使得状态被封装起来。
经过闭包,咱们能够建立自增生成器函数。一样,内部状态是私有的。示例以下:
function createAGenerate(count, increment) {
return function(){
count += increment;
return count;
}
}
let generateNextNumber = createAGenerate(0, 1);
console.log(generateNextNumber()); //1
console.log(generateNextNumber()); //2
console.log(generateNextNumber()); //3
let generateMultipleOfTen = createAGenerate(0, 10);
console.log(generateMultipleOfTen()); //10
console.log(generateMultipleOfTen()); //20
console.log(generateMultipleOfTen()); //30
复制代码
以上示例中,咱们能够建立一个拥有私有状态的函数。同时,咱们也能够建立多个拥有同一私有状态的函数。基于此,咱们还能够建立一个拥有私有状态的对象。
function TodoStore(){
let todos = [];
function add(todo){
todos.push(todo);
}
function get(){
return todos.map(toTodoViewModel);
}
function toTodoViewModel(todo) {
return { id : todo.id, title : todo.title };
}
return Object.freeze({
add,
get
});
}
复制代码
复制代码
TodoStore()
函数返回了一个拥有私有状态的对象。在外部,咱们没法访问私有的 todos 变量,而且 add 和 get 这两个闭包拥有相同的私有状态。在这里,TodoStore()
是一个工厂函数。
闭包就是那些引用了外部做用域中变量的函数。
为了更好的理解,咱们将内部函数拆成闭包和纯函数两个方面:
在上述例子中,add()
和 get()
函数是闭包,而 isPriorityTodo()
和 toTodoViewModel()
则是纯函数。
在 Javascript
中,局部变量会随着函数的执行完毕而被销毁,除非还有指向他们的引用。当闭包自己也被垃圾回收以后,这些闭包中的私有状态随后也会被垃圾回收。一般咱们能够经过切断闭包的引用来达到这一目的。
在 Javascript
中,咱们很容易建立出全局变量。任何定义在函数和 {}
块以外的变量都是全局的,定义在全局做用域中的函数也是全局的。
若是你收获了新知识,请给做者点个赞吧,在左侧边栏第一个按钮点一下~
参考文章: