分爲函數做用域,和塊級做用域;緩存
函數做用域外面的沒法訪問函數做用域內部的變量和函數,這樣就能夠將一些變量和函數隱藏起來;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" 的 示例做爲依賴參數,並能相應地使用它。
最後記住:當函數能夠記住並訪問所在的做用域,即便函數是在當前做用域以外執行,這時 就產生了閉包。