分为函数做用域,和块级做用域;缓存
函数做用域外面的没法访问函数做用域内部的变量和函数,这样就能够将一些变量和函数隐藏起来;bash
隐藏起来的好处是闭包
内部能够访问外部的;app
function foo(a) { var b = 2;
// 一些代码
function bar() {
// ...
}
// 更多的代码 var c = 3;
}
bar(); // 失败
console.log( a, b, c ); // 三个全都失败
复制代码
此时,foo里面就是一个函数做用域,能够bar里面又是一个做用域;最外面固然就是全局做用域;函数
能够把函数看着一个能够单向向外访问的圈子;ui
这里须要重点区分一下:spa
函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处;code
例子对象
var a = 2;
(function foo(){ // <-- 添加这一行
var a = 3;
console.log( a ); // 3
})(); // <-- 以及这一行
console.log( a ); // 2
复制代码
这里的(function foo(){ .. })
是一个函数表达式;而不是一 个标准的函数声明; 因此,foo 被绑定在函数表达式自身的函数中而不是所在做用域中。ip
换句话说,(function foo(){ .. })
做为函数表达式意味着foo只能在..所表明的位置中被访问,外部做用域则不行。foo 变量名被隐藏在自身中意味着不会非必要地污染外部做 用域。
当即执行函数表达式:因为foo
被包含在一对( )
括号内部,所以成为了一个表达式;经过在末尾加上另一个 ( ) 能够当即执行这个函数;即, (function foo(){ .. })()
。第一个 ( ) 将函数变成表 达式,第二个 ( ) 执行了这个函数。
还能够穿参数
var a = 2;
(function IIFE( global ) {
var a = 3;
console.log( a ); // 3
console.log( global.a ); // 2
})( window );
console.log( a ); // 2
复制代码
这样就能够访问外面的a了,由于访问变量a的时候就近原则,就获得了3;
{ }
var 定义的变量和函数,和在当前块级所处做用域定义没有什么区别; let,const 在块级做用域外面就访问不到;
简单的说{ }
这个就造成了一个块级做用域;
例子:
{
var a = 44;
let b = 22;
const c = 33
}
a // 44;
b // Uncaught ReferenceError: b is not defined;找不到引用,报错;
c // Uncaught ReferenceError: c is not defined;找不到引用,报错;
复制代码
用 with 从对象中建立出的做用域仅在 with 声明中而非外 部做用域中有效。
JavaScript 的 ES3 规范中规定 try/catch 的 catch 分句会建立一个块做 用域,其中声明的变量仅在 catch 内部有效。
不管论经过何种手段将内部函数传递到所在的做用域之外,它都会持有对原始定义做用域的引用,不管在何处执行这个函数都会使用闭包。
我的所理解的闭包就是一个做用域,及做用域内的变量和函数的缓存;不会被释放了,以供在从此访问;不得被垃圾回收掉
不知道你们是否还记得JavaScript的垃圾回收机制;
垃圾收集:就是执行完后,对没有引用的变量进行释放;常见手动释放就是设置成null;
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
复制代码
分析:
bar()
依然持有对该做用域的引用,而这个引用就叫做闭包。
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 妈妈快看呀,这就是闭包!
}
foo(); // 2
复制代码
等效以下:
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; //将baz分配给全局变量
}
function bar() {
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
复制代码
按正常的做用域思考方式,bar是没有办法访问foo的内部的变量的;
baz 和 变量a,还有foo造成了一个闭包,这个做用域将被引擎缓存起来;baz随时均可以访问;
function foo(a) {
var b = 2;
// 一些代码
function bar() {
// ...
}
// 更多的代码
var c = 3;
}
foo();
bar(); // 失败
console.log( a, b, c ); // 三个全都失败
复制代码
这种就没法访问foo里面的变量和函数了,由于foo里面都是局部变量,外部没法直接访问,这种里面变量再会被其余地方引用,将会被引擎垃圾回收释放掉。
- 一个函数
foo
包含一个函数baz
和一个变量a
;(名字随意)baz
内部存在对a
的引用;foo
须要被执行;
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
复制代码
这就是个闭包;
通常来讲,只要使 用了回调函数,实际上就是在使用闭包!
for (var i=1; i<=5; i++) {
(function() {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})();
}
// 打印5次6;
复制代码
这样不行!这只是一个都没有的空做用域。不能造成闭包
修改1:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})(i);//从外部传进来
}
复制代码
修改2:
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
//块做用域和闭包联手即可天下无敌
复制代码
模块有两个主要特征:
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
MyModules.define( "bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
} );
MyModules.define( "foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome(){
console.log( bar.hello( hungry ).toUpperCase() )
}
return {
awesome: awesome
};
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(
bar.hello( "hippo" )
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
复制代码
"foo" 和 "bar" 模块经过一个返回公共 API 的函数来定义的。"foo" 甚至接受 "bar" 的 示例做为依赖参数,并能相应地使用它。
最后记住:当函数能够记住并访问所在的做用域,即便函数是在当前做用域以外执行,这时 就产生了闭包。