剛開始學習前端的時候,學習閉包暈頭轉向,都不知道什麼是什麼,在接觸變成一段時間後發現因為自己基本功不紮實的原因導致基本概念不理解所以對閉包根本無法掌握,這篇文章以我自己的理解記錄一下學習對於閉包的學習歷程。
區域性變數:可以簡單理解成函式內部申明的變數
全域性變數:可以簡單理解成最外層被申明的變數
複製**
var a = 'web'
function
foo()
console.log(a)
foo()
複製**
變數a和函式foo定義在最外層,所以在**的任何地方都可以訪問到他們。
function
foo()
console.log(a) //報錯
foo() // web
複製**
變數a在函式foo定義的內層(區域性變數),所以只能在函式內部訪問,外部無法訪問。
所以全域性變數在任何位置都可以訪問,但是區域性變數只能在當前作用域下面訪問 *** 未宣告的變數,自動定義為全域性變數(無論在函式內部還是外部) ***
說道函式作用域,就要提及乙個概念是函式的作用域鏈
作用域鏈
當乙個函式建立的時候,我們需要使用其中的變數,函式內部沒有建立這個變數,我們會去上一級去尋找這個變數看是否被建立拿到他使用他,如果上一級沒有,繼續去上一級去尋找,這乙個尋找路徑(內部,上一級,上一級的上一級...)就被稱作函式作用域鏈
作用域
作用域其實是指乙個包含了所有在同乙個區域宣告的變數和函式的集合,那麼如何決定這些變數資料和函式是屬於同一區域的呢?這就由他們最初宣告時的位置來決定的。我們在看一段**來理解一下作用域以及作用域鏈
複製**
var a = 1
function
fn1()
function
fn3()
var a = 2
return fn3
}var fn = fn1()
fn() //2
複製**
他為什麼會輸出2呢,首先我們看一下這個函式體,fn是其實就是fn1,而fn1()是內部的fn3,fn3內部有申明了乙個變數a = 4,並且執行函式fn2(),fn2列印a,但是在fn2當前內部作用域沒有變數a,這個時候他會去定義他的環境裡面找變數a,也就是fn1()內部,當然fn1內部也申明了變數a = 2,所以他會列印2 剛開始很容易弄錯他會列印4,函式的內部需要變數的話,他尋找的點是內部-》詞法作用域(也就是定義他的外部作用域)=》外層作用域這個順序依次去尋找
外部不能使用函式的內部變數,如果使用呢,這個時候我們就可以使用閉包這個手段,閉包首先我們理解成乙個手段,什麼手段,可以從外部拿到函式內部變數的手段,首先我們看乙個簡單的閉包
複製**
function
add()
return addnum
}var addfunc = add()
console.log(addfunc()) //2
console.log(addfunc()) //3
console.log(addfunc()) //4
console.log(addfunc()) //5
console.log(addfunc()) //6
console.log(addfunc()) //7
console.log(addfunc()) //8
複製**
這個時候肯定會有疑問了,怎麼我就能拿到內部變數,並且這個變數可以一直儲存
首先我們看一下這個函式,這個函式add內部定義了乙個變數和乙個函式addnum(),並且這個addnum函式對這個區域性變數進行了操作++,函式最後把這個值給return了出去,並且add函式又把addnum這個函式當做乙個值return了出去,那麼add()這個函式就是他內部的addnum()
這個時候賦值給addfunc這個變數,這個變數就獲取到add()(也就是addnum()也就是a的值)
然後console.log(addfunc())就可以獲取到這個內部值並進行操作
上面說的只是表現,也就是我們看到的東西,接下來我們來說一說我們看不見的
1.當函式被申明,執行過後,他內部的變數被釋放,也就是說我申明普通函式使用後,再去呼叫一次的時候,他內部的變數還會變得和原諒一樣,這並不是函式內部的特性,而是為了讓內部的變數不汙染全域性,在內部申明之後再次使用後他會從新被使用,但是如果函式內部的變數使用的是全域性變數就可以不用考慮這個問題
2.因為變數申明在呼叫的時候會被重新執行,那麼我們就要用個小手法來讓這個變數儲存住,並且能被外部使用
2-1.被外部使用很簡單,return出去就可以了
2-2.儲存住我們可以在函式內部建立乙個函式,並且把獲取值進行操作以後return 給此函式,在把這個函式return 出去給外部函式,相當於在函式內部建立乙個作用域,儲存住了內部的變數
我們擴充套件一下,加深一下對於閉包的理解,需求是我們需要乙個汽車,我們可以設定他的速度,但是外部的變數不能隨意獲取到他內部的變數,並且在外部還能操作這個變數,使用閉包我們可以寫成
function
catfunc
()
function
get()
function
speedup
() function
speeddown
() return
}var car = catfunc()
car.set(30)
console.log(car.get()) //30
car.speedup()
console.log(car.get()) //31
car.speeddown()
console.log(car.get()) //30
複製**
這個函式我們可以理解成 在car內部建立了四個函式都在修改這個變數,但是最後把函式名組成乙個物件return出去,這樣內部的函式儲存住了變數,return出去的函式也就是方法可以操作內部變數 看了實際操作以後,我們看一下關於閉包常見的面試題 閉包常見面試題
//如下**,輸出的是什麼
for(var i=0; i<5; i++), 0)
}複製**
如果大家對任務佇列或者非同步有些了解,就不能看出,i是全域性變數,在for迴圈結束以後,才會執行settimeout裡面的內容,這個時候列印i的時候,就會去尋找全域性的的變數i,也就是剛剛迴圈結束以後的那個i,這個時候列印的當然就是五個4
如果我們想解決這個問題,使它列印0-5怎麼做到呢,我們是否是需要建立乙個作用域儲存住每次迴圈次數列印出來,當然我們可以使用閉包這個手法來儲存這個變數
for(var i = 0 ;i < 5 ;i++),0)
})(i)
}複製**
以上這段**,我們使用立即執行函式來建立乙個作用域,使用i作為形參傳入使用立即執行函式來儲存住這個變數,這個時候我們列印出來的內容當然就是0-4了 上述**還有一種寫法,思想也是一樣,建立作用域來儲存我們的變數
for(var i = 0 ;i < 5 ;i++)
})(i),0)
}複製**
思想是一樣的,但是我們的做法不一樣,我們在非同步的settimeout中使用立即執行函式儲存住變數i然後return出去,這樣settimeout內部的函式其實就是return出來的console.log(被儲存的變數)
其實閉包我們可以理解成儲存變數的乙個手法(建立作用域),因為專案中乙個檔案中的全域性變數過多會造成變數的復燃,變數會很不規範已經命名很有可能造成衝突,所有我們可以使用閉包來儲存住某個需求需要的變數,這邊全域性變數減少後,我們的**也會特別清晰,**對於開發者來說也是特別友好的,在下乙個文章中,我會講到函式節流和函式防抖,會使用閉包來封裝函式達到函式復用的效果,小夥伴們喜歡的就來個素質三連(點讚,收藏,關注)
重學前端系列 Javascript物件
我們可以使用getownpropertydescriptor來檢視屬性狀態 var o o.b 2 a 和 b 皆為資料屬性 object.getownpropertydescriptor o,a object.getownpropertydescriptor o,b 複製 如果想改變屬性的特徵或者...
重學前端 2 簡單回顧css
css 文字陰影 css3 text shadow 水平位置 垂直位置 模糊距離 陰影顏色 層疊性1 含義 多種css樣式疊加,瀏覽器處理衝突的一種能力 2 原則 一般情況下,若出現樣式衝突,會按照css書寫的順序 以最後的為準,樣式不衝突,不會層疊 繼承性一般文字顏色和字型大小,font 開頭的屬...
鬍子哥的重學前端(筆記)css語法
在外部樣式表檔案內使用。指定該樣式表使用的字元編碼。該規則後面的分號是必需的,如果省略了此分號,會生成錯誤資訊。在外部css檔案中寫法如下 charset utf 8 body div.指定匯入的外部樣式表及目標 import url example.css screen and min width...