第六節(jié): Javascript作用域與作用域鏈

一. 作用域

1. 理解全部變量和局部變量

一個變量如果定義在了一個function里面蔑担,那么這個變量就是一個局部變量牌废,只在這個function里面有定義。出了這個function啤握,就如同沒有定義過一樣鸟缕。

functionfn(){vara=1;//定義在一個函數(shù)里面的變量,局部變量排抬,只有在函數(shù)里面有定義console.log("我是函數(shù)里面的語句叁扫,我認(rèn)識a值為"+a);}fn();console.log("我是函數(shù)外面的語句,我不認(rèn)識a"+a);

a被var在了function里面畜埋,所以現(xiàn)在這個a變量只在函數(shù)內(nèi)定義

JavaScript變量作用域非常的簡單莫绣,在ES5語法中因為沒有塊級作用域,因此能封閉作用域的只有一個東西:函數(shù)悠鞍。

如果一個變量对室,沒有定義在任何的function中,那么它將在全部程序范圍內(nèi)都可以使用, 這個變量就被稱為全局變量,

如果一個變量,定義在一個函數(shù)中,那么這個變量只在這個函數(shù)封閉的范圍內(nèi)可以使用, 這個變量就是局部變量

vara=1;//定義在全局范圍內(nèi)的一個變量咖祭,全局變量掩宜,在程序任何一個角落都有定義functionfn(){console.log("我是函數(shù)里面的語句,我認(rèn)識全局變量a值為"+a);}fn();console.log("函數(shù)外面的語句也認(rèn)識a值為"+a)

總結(jié):

定義在function里面的變量么翰,叫做局部變量牺汤,只在function里面有定義,出了function沒有定義的浩嫌。

定義在全局范圍內(nèi)的檐迟,沒寫在任何function里面的,叫做全局變量码耐,全局都都認(rèn)識追迟。

2. 作用域

2.1全局作用域

所有在script標(biāo)簽里面的代碼,都處在全局作用域中

全局作用域在頁面打開時創(chuàng)建全局對象GO(window對象)骚腥,頁面關(guān)閉時銷毀GO對象

全局作用域中的變量是GO對象的屬性名敦间,變量的值是GO對象的屬性值

2.2 函數(shù)作用域

所有在函數(shù)里面的代碼,都處在函數(shù)作用域中

函數(shù)作用域在函數(shù)執(zhí)行時創(chuàng)建AO對象束铭,在函數(shù)結(jié)束時銷毀AO對象

函數(shù)作用域中的變量是AO對象的屬性名廓块,變量的值是AO對象的屬性值

當(dāng)下一次執(zhí)行函數(shù)時,會創(chuàng)建全新的A0對象

3. Js解析引擎執(zhí)行js代碼的步驟

3.1 語法分析

Js解釋引擎會先掃描所有的js代碼契沫,查看代碼有沒有低級的語法錯誤带猴,如果存在語法錯誤,則整個程序就不會執(zhí)行,如果沒有語法錯誤埠褪,則進(jìn)入預(yù)解析(編譯)階段

報錯信息:Uncaught SyntaxError:Invalid or unexpected token表示語法錯誤

3.2 預(yù)編譯四部曲

創(chuàng)建AO對象? ==> Activation Object(活動對象,作用域,其實叫執(zhí)行期上下文)

? ? ? ? ? ? ? ? ? ? 全局對象GO

找到形參和變量浓利,把形參和變量作為AO對象的屬性名挤庇,值是undefined

實參把值賦給形參

在函數(shù)中找到函數(shù)聲明,把函數(shù)名作為AO對象的屬性名贷掖,值是函數(shù)體

3.3 執(zhí)行js代碼

functionfn(a){console.log(a);vara=123;console.log(a);functiona(){}console.log(a);console.log(b);varb=function(){}console.log(b);functionb(){}}fn(1)// 預(yù)編譯發(fā)生在函數(shù)執(zhí)行的前一刻

functiontest(a,b){console.log(a);c=0;varc;a=3;b=2;console.log(b);functionb(){}functiond(){}console.log(b)}test(1)

global=100;functionfun(){console.log(global);global=200;console.log(global)varglobal=300;console.log(global);}fn()varglobal

functionhaha(){console.log(b)if(a){varb=100;}console.log(b);c=234;console.log(c)}vara;haha();a=10;console.log(c);haha();

執(zhí)行期上下文:

當(dāng)函數(shù)在執(zhí)行前一刻,會創(chuàng)建一個執(zhí)行期上下文的內(nèi)部對象,一個執(zhí)行期上下文定義了函數(shù)執(zhí)行時的環(huán)境,函數(shù)每次執(zhí)行時對應(yīng)的執(zhí)行上下文都是獨一無二的,所以多次調(diào)用一個函數(shù)會導(dǎo)致創(chuàng)建多個執(zhí)行期上下文,當(dāng)函數(shù)執(zhí)行完畢后,它所產(chǎn)生的執(zhí)行期上下文被銷毀

4. 其他注意事項

4.1. 不寫var的變量就自動成全局變量了

也就是說未經(jīng)聲明的變量被賦值了,就歸全局所有

functionfn(){a=1;//這個a第一次賦值的時候嫡秕,并沒有var過,//所以就自動的在全局的范圍幫你var了一次}fn();console.log(a);

這是JS的一個機(jī)理苹威,如果遇見了一個標(biāo)識符昆咽,從來沒有var過,并且還賦值了:

name=123;

那么JS會自動幫你在全局范圍內(nèi)幫你定義var name;

這個實例告訴我們, 沒有特殊情況下, 所有的變量都盡量乖乖的聲明定義

a=100;console.log(a);console.log(window.a)

4.2. 聲明的全局變量,都?xì)wwindow所有

vara=100;console.log(a);// ==> 訪問的就是window.aconsole.log(window.a)

window 就像我們最大的倉庫

functiontext(){vara=b=123;}text();// 這里b是沒有聲明的 會先把值123賦值給b 在賦值給aconsole.log(window.b)

4.3.函數(shù)的參數(shù)牙甫,會默認(rèn)定義為這個函數(shù)的局部變量

functionfn(a,b,c,d){}

a,b,c,d就是一個fn內(nèi)部的局部變量掷酗,出了fn就沒有定義。

5.全局變量的作用

5.1 通信窟哺,共同操作同一個變量

兩個函數(shù)同時操作同一個變量泻轰,一個增加,一個減少且轨,函數(shù)和函數(shù)通信浮声。

varnum=0;functionadd(){num++;}functionremove(){num--;}add();add();add();add();remove();remove();add();add();console.log(num);

5.2 累加,重復(fù)調(diào)用函數(shù)的時候旋奢,不會重置

varnum=0;functionbaoshu(){num++;console.log(num);}baoshu();//1baoshu();//2baoshu();//3

如果num定義在baoshu里面泳挥,每次執(zhí)行函數(shù)就會把num重置為0:

functionbaoshu(){varnum=0;num++;console.log(num);}baoshu();//1baoshu();//1baoshu();//1

6.函數(shù)內(nèi)部也可以定義函數(shù)作用于

//這個函數(shù)返回a的平方加b的平方functionpingfanghe(a,b){returnpingfang(a)+pingfang(b);//返回m的平方functionpingfang(m){returnMath.pow(m,2)}}// 現(xiàn)在相求4的平方,想輸出16pingfang(4);//報錯至朗,因為全局作用域下屉符,沒有一個函數(shù)叫做pingfang

機(jī)理:

function big{

function small{

}

small();? ? //可以運(yùn)行

}

small();

//不能運(yùn)行,因為小函數(shù)定義在了大函數(shù)里面锹引,離開大函數(shù)沒有作用域矗钟。

二. 作用域鏈

1. 變量查詢規(guī)則

當(dāng)遇見一個變量時,JS引擎會從其所在的作用域依次向外層查找粤蝎,查找會在找到第一個匹配的標(biāo)識符的時候停止.

//變量的作用域真仲,就是它var的時候最內(nèi)層的functionfunctionouter(){vara=1;//a的作用域就是outerinner();functioninner(){varb=2;//b的作用域就是innerconsole.log(a);//能夠正常輸出1袋马,a在本層沒有定義初澎,就是找上層console.log(b);//能夠正常輸出2}}outer();console.log(a);//報錯,因為a的作用域outer

多層嵌套虑凛,如果有同名的變量碑宴,那么就會發(fā)生“遮蔽效應(yīng)”:

vara=1;//全局變量functionfn(){vara=5;//就把外層的a給遮蔽了,這函數(shù)內(nèi)部看不見外層的a了桑谍。console.log(a);//輸出5延柠,變量在當(dāng)前作用域?qū)ふ遥业搅薬的定義值為5}fn();console.log(a);//輸出1,變量在當(dāng)前作用域?qū)ふ衣嗯业搅薬的定義值為1

作用域鏈:一個變量在使用的時候得幾呢贞间?就會在當(dāng)前層去尋找它的定義贿条,找不到,找上一層function增热,直到找到全局變量整以,如果全局也沒有,就報錯峻仇。

//比較復(fù)雜的題目vara=1;//全局變量varb=2;//全局變量functionouter(){vara=3;//遮蔽了外層的a公黑,a局部變量functioninner(){varb=4;//遮蔽了外層的b,b局部變量console.log(a);//① 輸出3,a現(xiàn)在在當(dāng)前層找不到定義的摄咆,所以就上一層尋找console.log(b);//② 輸出4}inner();//調(diào)用函數(shù)console.log(a);//③ 輸出3console.log(b);//④ 輸出2 b現(xiàn)在在當(dāng)前層找不到定義的凡蚜,所以就上一層尋找}outer();//執(zhí)行函數(shù),控制權(quán)交給了outerconsole.log(a);// ⑤ 輸出1console.log(b);// ⑥ 輸出2

函數(shù)是一個對象吭从,其中有些屬性我們可以訪問朝蜘,比如name,length,arguments.但有些不可以,這些屬性僅供Js解釋引擎存取涩金,[[scope]]就是其中一個芹务。[[scope]]這個屬性指的就是常說的作用域,其中存儲了AO對象的集合。

2. 作用域鏈原理理解:

[[scope]]中所存儲的就是執(zhí)行期上下文對象的集合,這個幾個呈鏈?zhǔn)竭B接,我們把這種鏈?zhǔn)芥溄咏凶鲎饔糜蜴?/p>

當(dāng)函數(shù)執(zhí)行時會生成AO對象鸭廷,并且把這個AO對象放在[[scope]],翻譯過來就是范圍的意思.的最頂端枣抱,和函數(shù)創(chuàng)建時的環(huán)境,形成鏈?zhǔn)浇Y(jié)構(gòu)辆床,我們把這種鏈?zhǔn)浇Y(jié)構(gòu)叫做作用域鏈佳晶。

作用域鏈 = 函數(shù)執(zhí)行時的AO對象 + 函數(shù)創(chuàng)建時的環(huán)境

變量查找規(guī)則:沿著當(dāng)前函數(shù)作用域鏈作用域鏈頂端,自上而下尋找變量

例子

functionouter(){functioninner(){vara=111;c=222;console.log(a);console.log(b);console.log(c);}varb=333;inner();}vara=444;outer();

在查找變量a b? c時讼载,從作用域鏈頂端從上到下開始尋找對應(yīng)的變量轿秧,分別找到的是

inner函數(shù)AO中的a,

outer函數(shù)AO中的b,

GO中的c

functionaa(){functionbb(){varb=666;console.log(a);}bb()vara=10console.log(cc)}varcc=888;aa();

改變一下代碼

functionaa(){functionbb(){varb=666;console.log(a);}vara=250;returnbb;}varc=888;vardd=aa();dd();

三. 閉包

實例:

functiontest(){vararr=[];for(vari=0;i<10;i++){arr[i]=function(){console.log(i)}}returnarr;}varmyArr=test();

1. 閉包理解

//非常經(jīng)典的閉包:functionouter(){vara=333;functioninner(){console.log(a);}returninner;}varinn=outer();inn();//彈出333

推導(dǎo)過程:

我們之前已經(jīng)學(xué)習(xí)過,inner()這個函數(shù)不能在outer外面調(diào)用咨堤,因為outer外面沒有inner的定義:

functionouter(){vara=888;functioninner(){console.log(a);}}//在全局調(diào)用inner但是全局沒有inner的定義菇篡,所以報錯inner();

但是我們現(xiàn)在就想在全局作用域下,運(yùn)行outer內(nèi)部的inner,此時我們必須想一些奇奇怪怪的方法一喘。

有一個簡單可行的辦法驱还,就是讓outer自己return掉inner:

functionouter(){vara=100;functioninner(){a++;console.log(a);}returninner;//outer返回了inner的引用}varinn=outer();//inn就是inner函數(shù)了inn();//執(zhí)行inn,全局作用域下沒有a的定義//但是函數(shù)閉包凸克,能夠把定義函數(shù)的時候的作用域一起記憶住//能夠輸出101inn();// 102

這就說明了议蟆,inner函數(shù)能夠持久保存自己定義時的所處環(huán)境,并且即使自己在其他的環(huán)境被調(diào)用的時候萎战,依然可以訪問自己定義時所處環(huán)境的值咐容。

一個函數(shù)可以把它自己內(nèi)部的語句,和自己聲明時所處的作用域一起封裝成了一個密閉環(huán)境蚂维,我們稱為“閉包” (Closures)戳粒。

每個函數(shù)都是閉包路狮,每個函數(shù)天生都能夠記憶自己定義時所處的作用域環(huán)境。但是蔚约,我們必須將這個函數(shù)览祖,挪到別的作用域,才能更好的觀察閉包炊琉。這樣才能實驗它有沒有把作用域給“記住”展蒂。

我們發(fā)現(xiàn),把一個函數(shù)從它定義的那個作用域苔咪,挪走锰悼,運(yùn)行。嘿团赏,這個函數(shù)居然能夠記憶住定義時的那個作用域箕般。不管函數(shù)走到哪里,定義時的作用域就帶到了哪里舔清。這就是閉包丝里。

注意:

閉包在工作中是一個用來防止產(chǎn)生隱患的事情,而有的時候又需要根據(jù)其性質(zhì)加以利用体谒。所以我們需要理解閉包形成的原理和了解其特性

閉包的變種

varinner;functionouter(){varaa=200;inner=function(){console.log(aa);}}outer();//outer 必須先運(yùn)行,否則,inner不是一個函數(shù)varaa=300;inner();// 因為閉包的原因閉包,打印出200

functionouter(x){functioninner(y){console.log(x+y)}returninner;}// 拿到inner函數(shù),inn就是函數(shù)inner函數(shù)varinn=outer(5);inn(6);// 11 只要inn一運(yùn)行,inner就記住x是5inn(4);// 5 與4 和

2.閉包的全新特性

每次重新引用函數(shù)的時候杯聚,閉包是全新的。

functionouter(){varcount=0;functioninner(){count++;console.log(count);}returninner;}varinn1=outer();varinn2=outer();inn1();//1inn1();//2 inn1();//3inn1();//4inn2();//1inn2();//2inn1();//5varinn3=outer();inn3();//1inn3();//2inn1();//6// 這個時候我們是不是有封裝的感覺,因為此時你對count無能為力

函數(shù)定義一次,可以進(jìn)行多次調(diào)用,每次調(diào)用一個函數(shù),都會產(chǎn)生新的閉包.新的閉包知道是,語句全新,所處環(huán)境也是全新的

無論它在何處被調(diào)用抒痒,它總是能訪問它定義時所處作用域中的全部變量

示例2

functionouter(){vara=3;functioninner(){returna++;}returninner;}vara=outer();consol.log(a());//3consol.log(a());//4consol.log(a());//5

在全局作用域下,試圖不通過調(diào)用a(),而直接修改a,是不可行的/

函數(shù)的閉包,記住了定義是所有的作用域,這個作用域的變量不是一成不變的.

看一個例子

functionfun1(x,y){functionfun2(x){console.log(x+y)}returnfun3;}varf=fun1(3,5);f(8);// 13? 8替換掉了3

每一函數(shù)都是一個閉包,無論它在哪里調(diào)用,它總是能訪問到定義時所處作用域中的全部變量

閉包是天生存在,并不需要什么特殊的結(jié)構(gòu)才存在,只不過我們需要刻意的把函數(shù)放到其他的作用域中調(diào)用才能明顯的觀察到閉包的性質(zhì).

閉包會導(dǎo)致原有作用域鏈不釋放,造成內(nèi)存泄漏(占用的多,然后變得少)

3. 閉包的作用;

3.1 實現(xiàn)共有變量

函數(shù)累加器

functionouter(){varcount=0;functioninner(){count++;console.log(count);}returninner;}varinn1=outer();inn1();//1inn1();//2

3.2 可以做緩存(存儲結(jié)構(gòu))

functiontest(){varnum=100;functionaa(){num++;console.log(num);}functionbb(){num--;console.log(num)}return[a,b]}varmyArr=test()myArr[0]()myArr[1]()

作者:時光如劍

鏈接:http://www.reibang.com/p/6a33f238bd3f

來源:簡書

著作權(quán)歸作者所有幌绍。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處故响。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末傀广,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子彩届,更是在濱河造成了極大的恐慌伪冰,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件樟蠕,死亡現(xiàn)場離奇詭異贮聂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坯墨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門寂汇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捣染,你說我怎么就攤上這事⊥O铮” “怎么了耍攘?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵榕栏,是天一觀的道長。 經(jīng)常有香客問我蕾各,道長扒磁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任式曲,我火速辦了婚禮妨托,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吝羞。我一直安慰自己兰伤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布钧排。 她就那樣靜靜地躺著敦腔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恨溜。 梳的紋絲不亂的頭發(fā)上符衔,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音糟袁,去河邊找鬼判族。 笑死,一個胖子當(dāng)著我的面吹牛项戴,可吹牛的內(nèi)容都是我干的五嫂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼肯尺,長吁一口氣:“原來是場噩夢啊……” “哼沃缘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起则吟,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤槐臀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后氓仲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體水慨,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年敬扛,在試婚紗的時候發(fā)現(xiàn)自己被綠了晰洒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡啥箭,死狀恐怖谍珊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情急侥,我是刑警寧澤砌滞,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布侮邀,位于F島的核電站,受9級特大地震影響贝润,放射性物質(zhì)發(fā)生泄漏绊茧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一打掘、第九天 我趴在偏房一處隱蔽的房頂上張望华畏。 院中可真熱鬧,春花似錦尊蚁、人聲如沸亡笑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽况芒。三九已至,卻和暖如春叶撒,著一層夾襖步出監(jiān)牢的瞬間绝骚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工祠够, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留压汪,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓古瓤,卻偏偏與公主長得像止剖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子落君,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容