js訪問一個變量時會優先在該做用域內(訪問時的那個做用域)尋找是否聲明過這個變量,若是該變量已經存在,則直接使用它的值,不然會尋找該做用域的‘父做用域/上級做用域',依次類推,直到找到全局做用域爲止。通俗地講,當聲明一個函數時,局部做用域一級一級向上包起來,就是做用域鏈。bash
首先看一個多級做用域的栗子:閉包
//多級做用域
//======>此處是1級做用域
var gender = "男";
function fn(){ // ======>從這裏開始是2級做用域
//gender 能夠訪問 gender是全局做用域的變量,任何地方均可以訪問
//age 能夠訪問
//height 不能訪問
return function(){ // ======>從這裏開始是3級做用域
//gender 能夠訪問
//age 能夠訪問
//height 能夠訪問
var height = 180;
}
var age = 5;
}
複製代碼
因爲做用域是相對於變量而言的,而若是存在多級做用域,這個變量又來自於哪裏?這個問題就須要好好地探究一下了,咱們把這個變量的查找過程稱之爲變量的做用域鏈函數
做用域鏈的意義:查找變量(肯定變量來自於哪裏,變量是否能夠訪問)post
簡單來講,查找一個變量來自哪裏,可否被訪問,須要如下四步:ui
- 查看當前做用域,若是當前做用域聲明瞭這個變量,能夠直接訪問
- 查找當前做用域的上級做用域,也就是當前函數的上級函數,看看上級函數中有沒有聲明,有就返回變量,沒有繼續下一步
- 再查找上級函數的上級函數,直到全局做用域爲止,有則返回,無則繼續
- 若是全局做用域中也沒有,咱們就認爲這個變量未聲明(xxx is not defined)
這四步操做就描述了整個做用域鏈及做用域鏈如何查找變量的過程。spa
下面咱們來舉幾個特殊的栗子: 例子1:code
function fn(callback){
var age = 20;
callback();
}
fn(function(){
console.log(age);//報錯
//1.在當前做用域沒有查找到age
//2.查找上一級做用域:全局做用域
//爲什麼是全局做用域?
//由於看上一級做用域,不是看函數在哪調用,而是看函數在哪編寫的。
//這種特別的做用域,叫作「詞法做用域」
})
複製代碼
這個栗子比較特殊,可能不少人會認爲輸出20,由於函數調用的地方是fn函數內部,恰巧age又是聲明在這個函數內部的,理所應當輸出20。這是錯誤的! 如今分析下做用域鏈如何查找變量的:cdn
例子2:對象
var name="張三";
function f1(){
var name = "abc";
console.log(name);
}
f1(); // abc
複製代碼
若是查找一個變量時,在當前做用域找到變量,無論上級、上上級有沒有同名變量都不會再去尋找。blog
例子3:
var name="張三";
function f1(){
console.log(name);
var name = "abc";
}
f1(); // undefined
複製代碼
若是這個栗子能看懂說明已經瞭解變量提高,若是不懂,參考後面的文章。
例子4:
var name = "張三";
function f1(){
var name = "abc";
return function(){
console.log(name);
console.log(age);
}
var age = 18;
}
var fn = f1();
fn();
//abc
//undefined
複製代碼
這個栗子咋一看可能有點懵,可是記住以前一句很重要的話,查找函數上級做用域,不是看函數在哪調用,而是看函數在哪編寫。
例子5:
var name="張三";
function f1(){
return {
say:function(){
console.log(name);
var name="abc";
}
}
}
var fn=f1();
fn.say();//undefined
複製代碼
當前做用域查到了變量,則不會再繼續尋找,直接返回該變量的值,這裏打印的時候變量聲明可是未賦值,因此輸出undefined。
若是以上幾個栗子都能看懂,說明你已經掌握了做用域&做用域鏈,學好做用域和做用域鏈能夠爲理解閉包打下很好的基礎。
提到做用域就不得不提到閉包,簡單來說,閉包外部函數可以讀取內部函數的變量。
優勢:閉包能夠造成獨立的空間,永久的保存局部變量。
缺點:保存中間值的狀態缺點是容易形成內存泄漏,由於閉包中的局部變量永遠不會被回收
產生閉包的根本緣由是做用域鏈
- 產生閉包的緣由是由做用域鏈引發的
- 函數嵌套函數,被嵌套的函數就能夠稱爲閉包
- 子函數可使用父函數的變量(訪問其餘函數內部的局部變量)
- 讓變量始終保存在內存中,避免自動垃圾回收(其實上面的例子中就已經用到了的)
- 對外提供公有屬性和方法
詳細講解能夠參考我寫的閉包系列: