一 初步了解作用域
在了解作用域之前,先來看一個問題;
var xixi= 222;
在js處理這句話的時候,會發(fā)生什么呢?
-
參與這句代碼處理過程的參與者
-
代碼處理過程
-
作用域是什么呢
我們將作用域定義為一套規(guī)則,用來管理引擎是如何在當(dāng)前作用域以及嵌套的子作用域中根據(jù)標(biāo)識符名稱進(jìn)行變量查找.
-
作用域嵌套
當(dāng)一個函數(shù)嵌套在另一個函數(shù)中的時候,就發(fā)生了作用域的嵌套,因此,在當(dāng)前作用域中無法找到某個變量的時候,引擎就會在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或者到達(dá)最外層作用域.
-
將作用域鏈比作建筑的話:一樓就代表當(dāng)前的作用域,如果找不到變量,就會往上爬樓,依次尋找變量,直到最頂層仍找不到變量的話,就會報出錯誤.
二 函數(shù)作用域
-
定義
可以將內(nèi)部的變量和函數(shù)定義隱藏起來,外部作用域無法訪問包裝函數(shù)內(nèi)部的任何內(nèi)容
如果外部有一個變量xi,但是在函數(shù)作用域中也想用這個名字該怎么辦呢?var xi = '外部'; function foo(){ var xi = '內(nèi)部'; console.log(xi) //'內(nèi)部' } foo() console.log(xi); //'外部'
但是所定義的變量foo可能污染了它當(dāng)前所在的作用域,且執(zhí)行這個函數(shù),必須要再加一行代碼foo()來調(diào)用;優(yōu)化一下;
var xi = '外部'; (function foo(){ var xi = '內(nèi)部'; console.log(xi) //'內(nèi)部' })() console.log(xi); //'外部'
如果我們想在內(nèi)部也訪問外部的相同變量,該怎么區(qū)分和訪問呢?
var xi = '外部';
(function foo(global){//這里可以將后面?zhèn)鱽淼膮?shù)重新命名;
var xi = '內(nèi)部';
console.log(xi) //'內(nèi)部'
console.log(global.xi) //內(nèi)部
})(window) //這里只要把window對象作為一個參數(shù)傳給封裝函數(shù)就行了.
console.log(xi); //'外部'
**很多框架源碼都用了這個方式來解決諸如防止undefined被篡改等情況**
**函數(shù)是js中最常見的作用域單元**
###三 提升
直覺上,我們會認(rèn)為js在執(zhí)行代碼的時候是由上到下一步一步執(zhí)行的,但是實際上并不完全是這樣
我們先看看一段代碼
```java
a = 2;
var a;
console.log(a); //2
這句話的代碼等同于
var a;
a= 2;
console.log(2)
再來看一段代碼:
console.log(a);//undefined
var a = 2;
這段代碼等同于:
var a;
console.log(a);
a = 2;
在這段代碼里到底發(fā)生了什么呢?
-
編譯器
js的作用域基本上都是詞法作用域;什么是詞法作用域呢?就是定義在詞法階段的作用域,是由你在寫代碼的時候?qū)⒆兞亢妥饔糜驅(qū)懺谀膩頉Q定的.大部分情況下詞法作用域是不變的.
我們之前提到過,編譯器第一個工作就是把代碼解析成詞法單元,然后語法分析成語法樹;在這期間,編譯器基本能夠知道標(biāo)識符是在哪聲明以及是怎么聲明的.它會用合適的作用域?qū)⑺麄冴P(guān)聯(lián)起來.
回到我們剛剛的代碼;
console.log(a);//undefined
var a = 2;
在編譯階段,會執(zhí)行var a;
;剩余的a = 2
會留在原地等待執(zhí)行;因此等同于
var a;
console.log(a);
a = 2;
在這個階段,變量聲明會從他們代碼所在的位置移動到最頂層更,這個過程就叫做提升;
-
函數(shù)優(yōu)先
函數(shù)聲明和變量聲明都會被提升,但是,函數(shù)會首先被提升.然后才是變量;(值得注意的是,函數(shù)聲明提升的時候,函數(shù)體一同被提升;函數(shù)表達(dá)式的函數(shù)體則不提升;)
foo(); // 1
var foo;//盡管此變量聲明在function foo()之前,但是因為函數(shù)優(yōu)先
//foo變量聲明就會變成重復(fù)聲明,忽略;
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
}
這段代碼會被引擎理解為:
function foo(){
console.log(1)
}
foo();
foo = function(){
console.log(2)
}
雖然var重復(fù)聲明會被忽略,但是后面的函數(shù)聲明會覆蓋前面的
foo(); // 3
function foo(){
console.log(1);
}
var foo = function(){
console.log(2);
}
function foo(){ //覆蓋了之前的function foo();
console.log(3);
}