局部變量:能夠簡單理解成函數內部申明的變量
全局變量:能夠簡單理解成最外層被申明的變量
複製代碼
var a = 'web'
function Foo(){
console.log(a)
}
console.log(a)
Foo()
複製代碼
變量a和函數Foo定義在最外層,因此在代碼的任何地方均可以訪問到他們。
前端
function Foo(){
var a = 'web'
console.log(a)
}
console.log(a) //報錯
Foo() // web
複製代碼
變量a在函數Foo定義的內層(局部變量),因此只能在函數內部訪問,外部沒法訪問。
因此全局變量在任何位置均可以訪問,可是局部變量只能在當前做用域下面訪問 *** 未聲明的變量,自動定義爲全局變量(不管在函數內部仍是外部) ***web
說道函數做用域,就要說起一個概念是函數的做用域鏈
面試
當一個函數建立的時候,咱們須要使用其中的變量,函數內部沒有建立這個變量,咱們會去上一級去尋找這個變量看是否被建立拿到他使用他,若是上一級沒有,繼續去上一級去尋找,這一個尋找路徑(內部,上一級,上一級的上一級...)就被稱做函數做用域鏈
bash
做用域實際上是指一個包含了全部在同一個區域聲明的變量和函數的集合,那麼如何決定這些變量數據和函數是屬於同一區域的呢?這就由他們最初聲明時的位置來決定的。咱們在看一段代碼來理解一下做用域以及做用域鏈
複製代碼
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
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() {
var a = 1
function addNum(){
a++
return a
}
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(){
var speed = 0;
function set(s){
speed = s
}
function get(){
return speed
}
function speedUp(){
speed++
}
function speedDown(){
speed--
}
return {
set,
get,
speedUp,
speedDown
}
}
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++){
setTimeout(function(){
console.log('delayer:' + i )
}, 0)
}
複製代碼
若是你們對任務隊列或者異步有些瞭解,就不能看出,i是全局變量,在for循環結束之後,纔會執行setTimeout裏面的內容,這個時候打印i的時候,就會去尋找全局的的變量i,也就是剛剛循環結束之後的那個i,這個時候打印的固然就是五個4
若是咱們想解決這個問題,使它打印0-5怎麼作到呢,咱們是不是須要建立一個做用域保存住每次循環次數打印出來,固然咱們可使用閉包這個手法來保存這個變量學習
for(var i = 0 ;i < 5 ;i++){
(function(j){
setTimeout(function(){
console.log('delayer'+j)
},0)
})(i)
}
複製代碼
以上這段代碼,咱們使用當即執行函數來建立一個做用域,使用i做爲形參傳入使用當即執行函數來保存住這個變量,這個時候咱們打印出來的內容固然就是0-4了 上述代碼還有一種寫法,思想也是同樣,建立做用域來保存咱們的變量ui
for(var i = 0 ;i < 5 ;i++){
setTimeout((function(j){
return function(){
console.log(j)
}
})(i),0)
}
複製代碼
思想是同樣的,可是咱們的作法不同,咱們在異步的setTimeout中使用當即執行函數保存住變量i而後return出去,這樣setTimeout內部的函數其實就是return出來的console.log(被保存的變量)
spa
其實閉包咱們能夠理解成保存變量的一個手法(建立做用域),由於項目中一個文件中的全局變量過多會形成變量的復燃,變量會很不規範已經命名頗有可能形成衝突,全部咱們可使用閉包來保存住某個需求須要的變量,這邊全局變量減小後,咱們的代碼也會特別清晰,代碼對於開發者來講也是特別友好的,在下一個文章中,我會講到函數節流和函數防抖,會使用閉包來封裝函數達到函數複用的效果,小夥伴們喜歡的就來個素質三連(點贊,收藏,關注)