首页 >> js开发 >> js代码详解JavaScript作用域 闭包
js代码详解JavaScript作用域 闭包
发布时间: 2021年1月13日 | 浏览:
| 分类:js开发
JavaScript闭包,是JS开发工程师必须深入了解的知识。3月份自己曾撰写博客《JavaScript闭包》,博客中只是简单阐述了闭包的工作过程和列举了几个示例,并没有去刨根问底,将其弄明白!现在随着对JavaScript更深入的了解,也刚读完《你不知道的JavaScript(上卷)》这本书,所以乘机整理下,从底层和原理上去刨一下。JavaScript并不具有动态作用域,它只有词法作用域。词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。了解闭包前,首先我们得知道什么是词法作用域(作用域是由书写代码时函数声明的位置来决定的)。一、何为闭包一、何为闭包示例1:
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
bzz(); //2
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
bzz(); //2在foo()执行后,通常认为垃圾回收机制会将foo()的整个内部作用域都被销毁;而闭包可以阻止这样事情发生,让其内部作用域依然存在。因为bar()处于foo()内部,它拥有涵盖foo()作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。bar()依然持有对该作用域的引用,而这个引用就叫作闭包。简言之:当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。示例2:无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();
// 这就是闭包
}
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();
// 这就是闭包
}示例3:将一个内部函数(timer)传递给setTimeout。timer具有涵盖wait()作用域的闭包,保有对变量message的引用。wait()执行1000毫秒后,它的作用域并不会消失,timer依然保有wait()作用域的闭包。
function wait(message){
setTimeout( function timer(){
console.log(message);
},1000);
}
wait("Hello,ligang");
function wait(message){
setTimeout( function timer(){
console.log(message);
},1000);
}
wait("Hello,ligang");示例4:下述activator()具有涵盖setupBot()作用域的闭包!
function setupBot(name, selector){
$(selector).click(function activator(){
console.log("Activating: "+ name);
});
}
setupBot("Closure Bot 1", "#bot_1");
setupBot("Closure Bot 2", "#bot_2");
function setupBot(name, selector){
$(selector).click(function activator(){
console.log("Activating: "+ name);
});
}
setupBot("Closure Bot 1", "#bot_1");
setupBot("Closure Bot 2", "#bot_2");二、循环和闭包二、循环和闭包
for(var i=1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}
// 期望:每秒一次的频率输出1~5
// 结果:每秒一次的频率输出五次6
for(var i=1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}
// 期望:每秒一次的频率输出1~5
// 结果:每秒一次的频率输出五次6先解释一下:“i*1000”,5个定时分别在1s、2s、3s、4s、5s后执行,并不是1s、3s、6s、10s、15s。也就是频率为1s,不是每次间隔增加1s。如果去掉i写成“1000”,会在for执行完1s后直接输出五次6。回调函数在循环结束后才被执行,因此输出的是循环终止条件是i值。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(..., 0),所有的回调函数依然是在循环结束后才被执行。根据作用域的工作原理,尽管五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。解决方案1:
for(var i=0; i<=5; i++){
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000 );
})(i);
}
// 结果:每秒一次的频率输出1~5
for(var i=0; i<=5; i++){
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000 );
})(i);
}
// 结果:每秒一次的频率输出1~5每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。解决方案2(ES6):
for(var i=0; i<=5; i++){
let j = i;
setTimeout(function timer(){
console.log(j);
}, j*1000 );
}
// 结果:每秒一次的频率输出1~5
for(let i=0; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000 );
}
// 结果:每秒一次的频率输出五次6
for(var i=0; i<=5; i++){
let j = i;
setTimeout(function timer(){
console.log(j);
}, j*1000 );
}
// 结果:每秒一次的频率输出1~5
for(let i=0; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000 );
}
// 结果:每秒一次的频率输出五次6三、模块三、模块模块需要具备两个必要条件:(1)必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。(2)封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。典型的模块化:
function CoolMoudle(){
var something = "cool";
var doSomething = function(){
console.log(something);
}
return{
doSomething: doSomething
};
}
var foo = CoolMoudle();
//如果不执行外部函数CoolMoudle(),内部作用域和闭包都无法创建
foo.doSomething();
//cool
function CoolMoudle(){
var something = "cool";
var doSomething = function(){
console.log(something);
}
return{
doSomething: doSomething
};
}
var foo = CoolMoudle();
//如果不执行外部函数CoolMoudle(),内部作用域和闭包都无法创建
foo.doSomething();
//cool单例模式:
var foo = (function CoolModule(id){
function change(){
// 修改公共API
publicAPI.identify = identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})("foo module");
foo.identify();
//foo module
foo.change();
foo.identify(); //FOO MODULE
var foo = (function CoolModule(id){
function change(){
// 修改公共API
publicAPI.identify = identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})("foo module");
foo.identify();
//foo module
foo.change();
foo.identify(); //FOO MODULE以上就是详解JavaScript作用域 闭包的详细内容,关于JavaScript作用域 闭包的资料请关注其它相关文章!
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
bzz(); //2
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
bzz(); //2在foo()执行后,通常认为垃圾回收机制会将foo()的整个内部作用域都被销毁;而闭包可以阻止这样事情发生,让其内部作用域依然存在。因为bar()处于foo()内部,它拥有涵盖foo()作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。bar()依然持有对该作用域的引用,而这个引用就叫作闭包。简言之:当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。示例2:无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();
// 这就是闭包
}
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();
// 这就是闭包
}示例3:将一个内部函数(timer)传递给setTimeout。timer具有涵盖wait()作用域的闭包,保有对变量message的引用。wait()执行1000毫秒后,它的作用域并不会消失,timer依然保有wait()作用域的闭包。
function wait(message){
setTimeout( function timer(){
console.log(message);
},1000);
}
wait("Hello,ligang");
function wait(message){
setTimeout( function timer(){
console.log(message);
},1000);
}
wait("Hello,ligang");示例4:下述activator()具有涵盖setupBot()作用域的闭包!
function setupBot(name, selector){
$(selector).click(function activator(){
console.log("Activating: "+ name);
});
}
setupBot("Closure Bot 1", "#bot_1");
setupBot("Closure Bot 2", "#bot_2");
function setupBot(name, selector){
$(selector).click(function activator(){
console.log("Activating: "+ name);
});
}
setupBot("Closure Bot 1", "#bot_1");
setupBot("Closure Bot 2", "#bot_2");二、循环和闭包二、循环和闭包
for(var i=1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}
// 期望:每秒一次的频率输出1~5
// 结果:每秒一次的频率输出五次6
for(var i=1; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000);
}
// 期望:每秒一次的频率输出1~5
// 结果:每秒一次的频率输出五次6先解释一下:“i*1000”,5个定时分别在1s、2s、3s、4s、5s后执行,并不是1s、3s、6s、10s、15s。也就是频率为1s,不是每次间隔增加1s。如果去掉i写成“1000”,会在for执行完1s后直接输出五次6。回调函数在循环结束后才被执行,因此输出的是循环终止条件是i值。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(..., 0),所有的回调函数依然是在循环结束后才被执行。根据作用域的工作原理,尽管五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。解决方案1:
for(var i=0; i<=5; i++){
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000 );
})(i);
}
// 结果:每秒一次的频率输出1~5
for(var i=0; i<=5; i++){
(function(j){
setTimeout(function timer(){
console.log(j);
}, j*1000 );
})(i);
}
// 结果:每秒一次的频率输出1~5每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。解决方案2(ES6):
for(var i=0; i<=5; i++){
let j = i;
setTimeout(function timer(){
console.log(j);
}, j*1000 );
}
// 结果:每秒一次的频率输出1~5
for(let i=0; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000 );
}
// 结果:每秒一次的频率输出五次6
for(var i=0; i<=5; i++){
let j = i;
setTimeout(function timer(){
console.log(j);
}, j*1000 );
}
// 结果:每秒一次的频率输出1~5
for(let i=0; i<=5; i++){
setTimeout(function timer(){
console.log(i);
}, i*1000 );
}
// 结果:每秒一次的频率输出五次6三、模块三、模块模块需要具备两个必要条件:(1)必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。(2)封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。典型的模块化:
function CoolMoudle(){
var something = "cool";
var doSomething = function(){
console.log(something);
}
return{
doSomething: doSomething
};
}
var foo = CoolMoudle();
//如果不执行外部函数CoolMoudle(),内部作用域和闭包都无法创建
foo.doSomething();
//cool
function CoolMoudle(){
var something = "cool";
var doSomething = function(){
console.log(something);
}
return{
doSomething: doSomething
};
}
var foo = CoolMoudle();
//如果不执行外部函数CoolMoudle(),内部作用域和闭包都无法创建
foo.doSomething();
//cool单例模式:
var foo = (function CoolModule(id){
function change(){
// 修改公共API
publicAPI.identify = identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})("foo module");
foo.identify();
//foo module
foo.change();
foo.identify(); //FOO MODULE
var foo = (function CoolModule(id){
function change(){
// 修改公共API
publicAPI.identify = identify2;
}
function identify1(){
console.log(id);
}
function identify2(){
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})("foo module");
foo.identify();
//foo module
foo.change();
foo.identify(); //FOO MODULE以上就是详解JavaScript作用域 闭包的详细内容,关于JavaScript作用域 闭包的资料请关注其它相关文章!
相关文章:
- js解决vue路由name同名,路由重复的问题js大全
- jsJS事件循环机制event loop宏任务微任务原理解析js大全
- js解决vue addRoutes不生效问题js大全
- jsvue 解决addRoutes多次添加路由重复的操作js大全
- jsvue相同路由跳转强制刷新该路由组件操作js大全
- jsantd vue 刷新保留当前页面路由,保留选中菜单,保留menu选中操作js大全
- jsvue实现在进行增删改操作后刷新页面js大全
- js解决vue-router路由拦截造成死循环问题js大全
- jsvue data变量相互赋值后被实时同步的解决步骤js大全
- js代码javascript实现时间日期的格式化的方法汇总