什么是閉包徽诲?直接上代碼:
var name = "global"
function wrapper() {
var name = "local"
function echo() {
4、-------------------------
console.log(name)
}
// 2、-------------------------
return echo
}
// 1戏售、---------------------------
var foo = wrapper()
// 3拗秘、---------------------------
foo() // 打印 local圣絮,這就是閉包
詳細(xì)解讀
之前在學(xué)習(xí)閉包時,簡單記憶為:
函數(shù)能夠 記住 定義時作用域中的變量雕旨。
比如這里的name="local"
扮匠,但是一直沒有深入了解為什么能夠訪問。在了解了作用域鏈后奸腺,答案很明顯餐禁,因為函數(shù)對象的[[scope]]
屬性。
像之前一樣對作用域鏈的變化進(jìn)行詳細(xì)分析突照。
1帮非、調(diào)用 wrapper() 前的作用域鏈:
// 調(diào)用 wrapper() 前的作用域鏈
scopes = {
0: {
name: "global",
wrapper: {
name: "wrapper",
[[scope]]: {
0: {...}
}
},
foo: undefined,
// 以及全局變量與方法
}
}
2、調(diào)用wrapper
函數(shù)時的作用域鏈讹蘑,將warpper
的變量對象加入到自身的[[scope]]
對應(yīng)的socpes
中末盔,就是這樣的:
// 調(diào)用 wrapper 函數(shù)時
scopes = {
1: {
name: "local",
echo: {
name: "echo",
[[scope]]: {
1: {...},
0: {...}
}
}
},
0: {
name: "global",
wrapper: {
name: "wrapper",
[[scope]]: {
0: {...}
}
},
foo: undefined,
// 以及全局變量與方法
}
}
重點在于echo
對象的[[scope]]
保存了兩個變量對象,然后將這個echo
對象返回座慰。
3陨舱、調(diào)用foo
函數(shù)前的作用域鏈:
scopes = {
0: {
name: "global",
wrapper: {
name: "wrapper",
[[scope]]: {
0: {...}
}
},
foo: {
name: "foo",
[[scope]]: {
1: {...},
0: {...}
}
},
// 以及全局變量與方法
}
}
這里和 1、----
處不同在于foo
從undefined
變成了對象版仔,并且這個對象就是3游盲、----
處的echo
對象误墓。
4、調(diào)用 foo
函數(shù)時的作用域鏈:
scopes = {
2: {
this: Window
},
1: {
name: "local",
echo: {
name: "echo",
[[scope]]: {
1: {...},
0: {...}
}
}
},
0: {
name: "global",
wrapper: {
name: "wrapper",
[[scope]]: {
0: {...}
}
},
foo: {
name: "foo",
[[scope]]: {
1: {...},
0: {...}
}
},
// 以及全局變量與方法
}
}
在echo
函數(shù)內(nèi)會尋找name
變量益缎,在作用域鏈的第一個變量對象中沒有找到(2
對應(yīng)的對象)就往下繼續(xù)找谜慌,在1
中找到了,并且是local
莺奔,所以最終打印出local
欣范。
同樣以一張圖片來更為直觀的了解:
- 紅色箭頭指 2 處的作用域鏈來自 wrapper 的 [[scope]] 屬性
- 橘色箭頭指 echo 函數(shù)的 [[scope]] 屬性等于 2 處加入 wrapper 變量對象后的作用域鏈
- 藍(lán)色箭頭指 3 處作用域鏈中全局變量對象 0 中的 foo 其實就是 echo(因為 wrapper 返回 echo 賦給 foo)
- 黑色箭頭指 4 處的作用域鏈來自 foo(也就是 echo )的 [[scope]] 屬性,而這個屬性又指向 2 處的作用域鏈令哟,所以是有兩個變量對象
echo
函數(shù)內(nèi)的console.log(name)
查找name
時首先在自身的變量對象中尋找恼琼,沒有找到后往下繼續(xù)尋找,找到了local
后就返回屏富,所以不會再繼續(xù)往下找到global
晴竞。最終打印local
。
隨處都是閉包
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時役听,就產(chǎn)生了閉包颓鲜,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
—— 《你不知道的 JavaScript 》(上卷)
按照上面的定義典予,只要聲明了函數(shù)甜滨,函數(shù)是能夠記住當(dāng)時的作用域鏈的,所以只要聲明了函數(shù)瘤袖,就產(chǎn)生了閉包衣摩?
function foo() {
var a = 2;
function bar() {
console.log( a ); // 2
}
bar();
}
foo();
bar
記住了定義時的作用域鏈,在這作用域鏈頂端的變量對象有a: 2
的鍵值對捂敌,在其他任何地方調(diào)用bar
都能夠訪問到a
變量艾扮。
但是更嚴(yán)格來說,函數(shù)在當(dāng)前詞法作用域之外執(zhí)行占婉,才算閉包泡嘴。