相信大家在之前的學(xué)習(xí)中已經(jīng)掌握了基本類(lèi)型和引用類(lèi)型的相關(guān)概念;基本類(lèi)型 按值訪問(wèn),引用類(lèi)型按內(nèi)存地址訪問(wèn),以下是對(duì)變量及作用域的深入理解;
大家應(yīng)該知道語(yǔ)句 var n=1;這是一個(gè)創(chuàng)建變量并且為變量賦值的的語(yǔ)句;
咱們來(lái)詳細(xì)解剖下這個(gè)語(yǔ)句在解析器中會(huì)發(fā)生什么?
1. var n (創(chuàng)建變量名),
在解析器中創(chuàng)建了一個(gè)名字來(lái)保存他的變量同時(shí)設(shè)置了這個(gè)變量的作用域范圍(該變量所在的函數(shù)作用域范圍),并且無(wú)論在函數(shù)內(nèi)部哪里,在預(yù)處理的過(guò)程中都會(huì)將變量提升到頂部(既變量提升)
*變量提升
由于 JS是一門(mén)編譯語(yǔ)言,編譯時(shí)分為編譯過(guò)程和運(yùn)行過(guò)程,編譯過(guò)程是進(jìn)行詞法語(yǔ)法分析(既識(shí)別你所寫(xiě)的代碼,分辨出,語(yǔ)句,運(yùn)算符,數(shù)據(jù)類(lèi)型)如在這個(gè)過(guò)程中發(fā)現(xiàn)聲明語(yǔ)句var n ,解析器回去當(dāng)前作用域?qū)ふ沂欠裼衝 這個(gè)變量存在,如果有則忽略聲明,沒(méi)有則創(chuàng)建一個(gè)變量n ,并且為他分配一定內(nèi)存空間,當(dāng)編譯完成后會(huì)生成一段代碼(既后面要運(yùn)行的代碼)
var a=123;
function f(){
alert(a);//undefined
var a=1;
alert(a);//1
}
f()
變量提升解析,解析器將函數(shù)作用域里面的var a=1;語(yǔ)句拆解,將變量聲明 var a提到函數(shù)作用域頂端;
var a=123;
function f(){
var a;
alert(a);//undefined
a=1;
alert(a);//1
}
f()
2. n = 1給變量賦值
賦的這個(gè)值分為引用類(lèi)型和基本類(lèi)型。解析器回先判斷這個(gè)值是引用類(lèi)型還是基本類(lèi)型。如果是引用類(lèi)型會(huì)把值保存到內(nèi)存中,而值的內(nèi)存地址保存在變量當(dāng)中。如果值是基本類(lèi)型,直接把值保存在變量中;
屬性的增刪及修改過(guò)程
當(dāng)n 為基本類(lèi)型時(shí),設(shè)置屬性不會(huì)報(bào)錯(cuò)但也沒(méi)有任何效果
當(dāng)n 為object,設(shè)置屬性a 當(dāng)中保存的就不是1了,而是定義對(duì)象的地址解析器去判斷a的時(shí)候通過(guò)地址到內(nèi)存去找對(duì)象,并且對(duì)對(duì)象的屬性/方法進(jìn)行增刪。
復(fù)制變量值
var a=123;
b=a;
b=3
alert(a)//123;
alert(b)//3
在a中保存的值是123;將a 的值賦給b ,b就相當(dāng)于有了a 的副本,b的你死我活不關(guān)a的s事,a和b處于平行世界
var person1=new Object();
var person2=person1;
person1.name='alanshiyi';
alert(person2.name)
person2 復(fù)制了一份內(nèi)存地址, person2和person1保存的是一份相同的地址若person1.name是alanshiyi ,person2.name 也是 他倆是人和影子的關(guān)系;
var person1=new Object();
var person2=person1;
person1.name='alanshiyi';
person2.name='阿里巴巴'
alert(person2.name)
alert(person1.name)
當(dāng)修改person2時(shí)候,解析器會(huì)先找到person2發(fā)現(xiàn)他保存的是一個(gè)內(nèi)存地址就順著內(nèi)存地址找到內(nèi)存里的name 改成‘阿里巴巴’,而當(dāng)person1.name 要訪問(wèn)時(shí)解析器看person1里保存的地址,順著地址找到剛才修改的內(nèi)存‘阿里巴巴’;
傳遞參數(shù)
當(dāng)一個(gè)變量作為函數(shù)參數(shù)進(jìn)行傳遞時(shí)所有的參數(shù)都按值傳遞
接下來(lái)說(shuō)個(gè)大家都很迷茫的問(wèn)題
簡(jiǎn)單的說(shuō)就是,我們平常的參數(shù)傳值(參考書(shū)p70-p71中代碼) 如
function add(num){
num+=10;
return num;
}
var count=20;
result=add(conut);
alert(count)//20
alert(result)//30
num接受的參數(shù)是a這個(gè)基本類(lèi)型復(fù)制的副本10;相當(dāng)于前面說(shuō)的復(fù)制部分a=num;但是a和num是獨(dú)立的;
function setName(obj){
obj.name='Nicholas';
}
var person=new Object();//全局變量
setName(person);
alert(person.name)//'Nicholas'
作為函數(shù)傳遞一個(gè)引用類(lèi)型的變量;函數(shù)外的是全局變量;它的作用域范圍是至少是函外層;而復(fù)制出來(lái)的這個(gè)參數(shù)他是以一個(gè)局部變量且作用范圍在函數(shù)內(nèi)部,和引用類(lèi)型復(fù)制一樣,雖然相互獨(dú)立但是他們當(dāng)中都保存同一個(gè)位置的引用,如果對(duì)參數(shù)進(jìn)行修改,內(nèi)存中的對(duì)象也會(huì)被修改。而再次通過(guò)函數(shù)外的訪問(wèn)時(shí),訪問(wèn)的還是內(nèi)存中的修改的變量;
雖然和引用類(lèi)型復(fù)制時(shí)的引用訪問(wèn)一樣,但這不能說(shuō)明引用類(lèi)型是按引用傳遞的;
課本上又給了一個(gè)例子
function setName(obj){
obj.name='Nicholas';
obj=new Object();//局部對(duì)象
obj.name='Greg';
}
var person=new Object();//全局對(duì)象
setName(person);
alert(person.name)//'Nicholas'
這段代碼重新定義了一個(gè)對(duì)象obj , 并且給obj添加了不同的屬性,(按照引用傳遞的說(shuō)法obj當(dāng)修改obj.name;時(shí)候,解析器會(huì)先找到obj發(fā)現(xiàn)他保存的是一個(gè)內(nèi)存地址就順著內(nèi)存地址找到內(nèi)存里的name 改成‘Greg’,而當(dāng)person.name 要訪問(wèn)時(shí)解析器看obj里保存的地址,順著地址找到剛才修改的內(nèi)存‘Greg’ 才對(duì),但是person.name 返回是‘Nicholas’。
返回‘Nicholas’的原因:在函數(shù)內(nèi)部重新定義一個(gè)對(duì)象obj,就相當(dāng)于創(chuàng)建一個(gè)新的變量,而且還是一個(gè)局部變量,這個(gè)變量和外部的person是沒(méi)有任何關(guān)系的,全局對(duì)象無(wú)法訪問(wèn)局部對(duì)象,且局部對(duì)象在函數(shù)執(zhí)行完畢后即可銷(xiāo)毀。所以引用類(lèi)型的傳遞是按值來(lái)傳遞的。
變量的類(lèi)型檢測(cè)
typeof
區(qū)分變量類(lèi)型是:基本類(lèi)型還是對(duì)象(不能區(qū)分具體是什么對(duì)象)
instanceof 返回布爾值
語(yǔ)法:object instanceof 對(duì)象類(lèi)型
作用域和內(nèi)存
作用域:計(jì)算機(jī)對(duì)值進(jìn)行保存和讀取的規(guī)則;
當(dāng)對(duì)值進(jìn)行保存時(shí),肯定需要一個(gè)范圍,如果是這個(gè)范圍是以函數(shù)為單位的話(huà)就叫函數(shù)做用域,以塊為單位的話(huà)叫塊級(jí)作用域,單位越小,重復(fù)概率越小,作用域可以相互嵌套。
執(zhí)行環(huán)境和作用域
在本章開(kāi)頭提過(guò)變量提升的問(wèn)題,我再來(lái)重新捋一遍;
主要講的就是解析器在編譯時(shí)的情況;
1.打開(kāi)頁(yè)面加載JS時(shí),解析器會(huì)創(chuàng)建一個(gè)全局作用域(相當(dāng)于建房子);
2.變量的聲明,解析器發(fā)現(xiàn)有變量聲明,就會(huì)去當(dāng)前作用域找沒(méi)有相同的變量存在,沒(méi)有的話(huà)就創(chuàng)建這個(gè)變量,并為他分配一定的內(nèi)存空間且將聲明提到當(dāng)前作用域頂上(分配房間)。
3.如果編譯的過(guò)程中發(fā)現(xiàn)聲明的是一個(gè)函數(shù)(和變量的聲明差不多)但是它會(huì)建立一個(gè)新的作用域(相當(dāng)于分配一個(gè)房間再給它空間范圍)。這會(huì)出現(xiàn)一個(gè)新的問(wèn)題,如果變量名和函數(shù)名重名的而且在同一個(gè)作用于下,解析器就會(huì)在當(dāng)前作用域下覆蓋變量。
當(dāng)都編譯完成后會(huì)生成一段代碼。
接下來(lái)就是代碼運(yùn)行的部分
作用域鏈
4.代碼執(zhí)行
接上回 解析器在編譯完成時(shí)函數(shù)運(yùn)行過(guò)程中創(chuàng)建了作用域,也生成了所需執(zhí)行的代碼,這個(gè)代碼在運(yùn)行階段會(huì)創(chuàng)建作用域鏈,作用域鏈把開(kāi)始創(chuàng)建的作用域鏈接起來(lái)形成一個(gè)鏈條,對(duì)函數(shù)或比那輛進(jìn)行訪問(wèn)時(shí)要按照作用域鏈進(jìn)行依次的訪問(wèn)
訪問(wèn)方式 :先在當(dāng)前作用域找,找不到再?gòu)母讣?jí)一層一層往上找 ,直到全局作用域;
var color='blue';
function changeColor(){
var anotherColor='red';
function swapColor(){
var tempColor=anotherColor;
anotherColor=color;
color=tempColor;
//這里可以訪問(wèn)color,anothercolor和tempColor
}
//這里可以訪問(wèn)color,anotherColor,但不能訪問(wèn)tempColor和swapColor()
}
//這里只能訪問(wèn)color和changeColor()