恕我直言!你不是真的懂js中的作用域!

2021-10-05 07:52:03 字數 4316 閱讀 8697

如果對於作用域,詞法作用域你還不是很清楚,那麼你可就要好好讀讀這篇文章了,它可是理解閉包的關鍵!

為了便於理解,筆者使用對話的方式進行解釋

引擎:負責js程式的編譯以及執行過程

編譯器:負責語法分析以及**生成等髒活

作用域:負責收集並維護由所有宣告識別符號組成的一些列查詢,並實施一套非常嚴格的規則,確定當前執行的**對識別符號的訪問許可權

假設存在var= 1;看似是乙個宣告,但是引擎老兄並不這麼認為,引擎會覺得這裡有兩個不同的宣告,乙個由編譯器在編譯時執行,另乙個由引擎在執行時處理。var a =1 會分解為var a;a=1

在引擎查詢的過程中,涉及到了兩種查詢方式

lhs和rhs

來看下面這一段**

function

foo(a)

foo(1)

;

foo(…)函式的呼叫需要對foo進行rhs引用,意味著」去找到foo的值,並把它給我「

其中有乙個非常容易被忽略的細節:a=1

這個操作發生在當1被傳遞給foo()函式時,1會被分配給a,這時為了給a(隱式)分配值,需要進行一次lhs查詢,並且還有一次對a的rhs引用,將得到的值傳給console.log()。其實console.log()本身也會對console物件進行rhs查詢,檢查得到的值,是否存在乙個叫log的方法。

作用域的巢狀 當乙個塊或函式巢狀在另乙個塊或函式中時,就發生了作用域的巢狀。因此,在當前作用域中無法找到某個變數時,引擎就會在外層巢狀的作用域中繼續查詢,直到找到該變數或抵達全域性作用域為止

觀察下面這段**

function

(a)var b=2;

foo(1)

;//3

其中對b會進行rhs查詢無法在函式foo內部完成,但是可以在上級作用域中完成

引擎:foo的作用域老弟,你這裡存在b嗎,我對它進行rhs查詢

作用域:沒有沒有,別打擾我泡妞

引擎:foo的上級作用域老哥(全域性作用域),你瞅見b了嗎,我要要對他進行rhs查詢

作用域:看見了看見了,在裡面睡覺呢,我拿給你!

遍歷巢狀作用域:從當前作用域開始查詢,逐級向上查詢,直到全域性作用域,無論是否找到,均會停止查詢

簡單的說,詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫**時將變數和塊作用域寫在**來決定的,因此當詞法分析器處理**時會保持作用域不變(大部分情況是如此)

function

fn(a)

fn1(b*3)

;}fn(

2);//2,4, 12

首先最外層為全域性作用域只有乙個識別符號:fn。

第二個作用域為fn所建立的作用域,包含:a,fn1,b

第三個作用域為fn1所建立的作用域,包含:c

作用域氣泡由其對應的作用域塊**寫在**決定,逐級包含。

作用域查詢會在找到第乙個匹配的識別符號停止,在多層的巢狀作用域中可以定義同名的識別符號,叫做「遮蔽作用」(內部的識別符號遮蔽了外部的識別符號),如果沒有遮蔽的話,就會一直向上級查詢直到找到會到最外層作用域停止。

**注意:**全域性變數會自動成為全域性物件,因此可以不直接通過全域性物件的詞法名稱,而是間接的通過對全域性物件屬性的引用來對其進行訪問(window.a ), 通過這種技術可以訪問被同名變數所遮蔽的全域性變數,但非全域性變數被遮蔽則無法訪問到。

函式中的作用域
函式作用域含義:屬於這個函式的全域性變數都可以在整個函式的範圍內使用以及復用。(事實上在巢狀的作用域中也可以使用)

function

fn(a)

var c =3;

}

在這個**片段中,fn的作用域氣泡包含了識別符號a,b,c,fn1,無論識別符號宣告出現在作用域何處,這個識別符號所代表的變數或函式都依附與所處作用域的氣泡。由於a,b,c和fn1都屬於fn()的作用域氣泡,所以無法從外部訪問這些變數,但是在fn()內部可以訪問,同樣在fn1()也可以進行訪問

隱藏內部實現 根據最小特權原則,應該最小限度的暴露必要內容,將其他內容都隱藏起來,比如某個模組的設計或者api設計,促成了這種基於作用域的隱藏方法

function

fn(a)

function

fn1(a)

var b;fn(

2);//10

上述**片段中,變數b和fn1()應該是fn()內部具體實現的私有內容,給予外部作用域的訪問許可權,毫無必要,並且會產生危險,如果被無意使用,導致超出適用條件,那麼會是很頭疼的事情,可以將上述**修改如下:

function

fn(a)

var b;

b=a+

fn1(a*2)

; console.

log(b*2)

}fn(2

);//10

這樣b和fn1都無法從外部訪問了,功能和效果不受影響,但是設計上將內容私有化。

規避衝突 隱藏作用域中的變數和函式帶來的另外乙個好處就說可以避免同名識別符號之間的衝突

functionfn(

)for

(var i=

0;i<

10;i++

)}

fn1()函式內部的賦值表示式i=3意外的覆蓋了for迴圈中的i,i被固定設定為3,小於10,導致無限迴圈

你可能沒有寫過帶有塊作用域風格的**,但是這段**你一定很熟悉

for

(var i=

0;i<

10;i++

)

我們在for迴圈的頭部定義i,通常是只想在for迴圈內部的上下文中使用i,但i會被繫結在外部作用域中

來看看這段**

var f=

true;if

(f)

fn的變數僅在if宣告的上下文中使用,如果將它宣告在if塊內部確實很好,但是!當使用var宣告變數時,它寫在**都是一樣的,他們最終都會屬於外部作用域,所以這僅僅是風格更具有可讀性的偽塊作用域罷了,要確保不意外使用fn,估計只能靠自覺了。。。

在es6中,塊作用域得到了解決!!!

let

let關鍵字可以將變數繫結到所在的任意作用域中,(通常是內部),換句話說,let為其宣告的變數隱式的劫持了所在的塊作用域

更相信的可以看看我以前的博文理解var let const 的區別,這裡不過多贅述!

我們再來看for迴圈

for

(let i=

0;i<

10;i++

)console.

log(i)

;//referenceerror

let j;

for(j=

0;j<

10;j++

)

try/catch 很少人會注意到經常使用的try/catch中的catch分句會建立乙個塊作用域,其中宣告的變數僅在catch中有效。
try

catch

(err)

console.

log(err)

;//referenceerror:error not found

//err僅存在catch分句內部,當試圖從外部引用時,就會丟擲錯誤!!!

作用域是一套規則,用於確定在何處以及如何查詢變數(識別符號)。如果查詢的目的是對變數進行賦值,那麼就會使用lhs查詢,如果目的是獲取變數的值,那麼就會使用rhs查詢。

不成功的lhs引用會導致自動隱式的建立乙個全域性變數(非嚴格模式),改變量使用lhs引用的目標作為識別符號。不成功的rhs引用會導致丟擲referenceerror異常

詞法作用域意味著作用域是由書寫**時函式宣告的位置來決定的,編譯的詞法分析階段基本能夠知道全部識別符號在**以及如何宣告的,從而能夠**在執行過程中如何對他們進行查詢

函式是js中最常見的作用域單元,本質上,宣告乙個函式內部的變數或函式會在所處的作用域中隱藏起來,但函式不是唯一的作用域單元。塊作用域指的是變數和函式不僅可以屬於所處的作用域,也可以屬於某個**塊。

es3開始,try/catch結構在catch分句中具有塊作用域。

寫給讀者的話:

你真的懂遞迴 或者 寫的好遞迴嗎

一 什麼是遞迴?1.遞迴是一種非常高效 簡潔的編碼技巧,一種應用非常廣泛的演算法,比如dfs深度優先搜尋 前中後序二叉樹遍歷等都是使用遞迴。2.方法或函式呼叫自身的方式稱為遞迴呼叫,呼叫稱為遞,返回稱為歸。3.基本上,所有的遞迴問題都可以用遞推公式來表示,比如 f n f n 1 1 f n f n...

你真的懂資料庫的索引嗎(下篇)

mysql對索引的選擇 給字串加索引 select id from t where a 1 對於上述這條select語句,首先從b 樹的樹根開始,按層搜尋到葉子節點,然後再根據二分法來定位記錄。只能說兩者的查詢效率差別不大,innodb的資料是按照頁為單位來讀寫的,當讀到一條記錄的時候,並不是把這個...

js的非同步載入你真的懂嗎

面試高頻之js的非同步載入 講這個問題之前,我們從另乙個面試高頻問題來切入,我們的web頁面從開始解析到頁面渲染完成都經歷了什麼 1 建立document物件,開始解析頁面,此時document.readystate loading 2 遇到link標籤引入的css檔案,建立執行緒並非同步載入css...