一. 作用域
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)載請注明出處故响。