JavaScript變量聲明巩踏、變量作用域秃诵、作用域鏈、函數(shù)塞琼、閉包菠净、原型

摘自《JavaScript權(quán)威指南(第六版)》

1. 變量聲明

  • 在js程序中,使用一個變量之前應(yīng)該先聲明彪杉,變量用var來聲明毅往,在全局作用域可以沒有var聲明全局變量, 但在函數(shù)內(nèi)沒有var聲明也是全局變量,但是必須是函數(shù)執(zhí)行后才生效派近。
scope='global';
function checkscope2() {
    scope='local';
    myscope='local';
    return [scope,myscope];
}
console.log(myscope); //  輸出 undefined 此時未解析
console.log(checkscope2()); // 輸出 ["local", "local"]
scope;// 輸出 local
console.log(myscope); //  local 
  • js是動態(tài)語言攀唯,聲明不需要給變量指定數(shù)據(jù)類型,該語言會在第一次賦值給變量時渴丸,在內(nèi)部將數(shù)據(jù)類型記錄下來侯嘀,Python也一樣。而靜態(tài)語言如C,JAVA谱轨,它的數(shù)據(jù)類型是在編譯期間檢查的戒幔,即聲明變量必須指定數(shù)據(jù)類型。

2. 變量作用域

  • 一個變量的作用域是程序源代碼中定義這個變量的區(qū)域土童。全局變量擁有全局的作用域诗茎。函數(shù)內(nèi)聲明的變量只有函數(shù)體內(nèi)有定義。它們是局部變量献汗,作用域是局部性的敢订。函數(shù)參數(shù)也是局部變量栅组,它們只在函數(shù)體內(nèi)有定義。

  • 在函數(shù)體內(nèi)枢析,局部變量的優(yōu)先級高于同名的全局變量。如果在函數(shù)內(nèi)聲明的一個局部變量或者函數(shù)參數(shù)中帶有的變量和全局變量重名刃麸,那么全局變量就被局部變量所覆蓋醒叁。

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); //輸出local scope
  • 函數(shù)作用域和聲明提前
    JavaScript沒有塊級作用域,取而代之地使用了函數(shù)作用域(function scope):變量在它們的函數(shù)體以及這個函數(shù)體嵌套的任意函數(shù)體內(nèi)都是有定義的泊业。
/**
** 代碼中在不同位置定義了變量i,j和k把沼,它們都在同一個作用域內(nèi)——這三個變量在函數(shù)體內(nèi)均是有定義的。
這意味著變量在聲明之前甚至已經(jīng)可用吁伺。JavaScript這個特性被非正式地稱為聲明提前饮睬。
**/
function test(o) {
    var i=o;
    if(typeof(o) == 'object') {
        var j = o;
        for(var k=0; k<10; k++) {
            console.log(k);
        }
    }
    console.log(j);
}
/**
* 輸出是undefined, 變量存在但未賦值
**/
function f() {
    var scope;
    console.log(scope);
    scope = "local";
    console.log(scope);
}
  • 作為屬性的變量
    當(dāng)你聲明一個JavaScript全局變量時篮奄,實(shí)際上是定義了全局對象的一個屬性捆愁,當(dāng)使用var聲明變量時,創(chuàng)建的這個屬性是不可配置的窟却,也就是說這個變量無法通過delete運(yùn)算符刪除昼丑。
var truevar = 1; //聲明一個不可刪除的全局變量
fakevar = 2; //創(chuàng)建全局對象 的一個可刪除的屬性
this.fakevar2 = 3; // 同上
delete truevar; //  => false 變量并沒有被刪除
delete fakevar(2); // => true 變量被刪除

JavaScript全局變量是全局對象的屬性夸赫,這是ECMAScript規(guī)范中強(qiáng)制規(guī)定的菩帝,對于局部變量則沒有如此規(guī)定,局部變量當(dāng)做跟函數(shù)調(diào)用相關(guān)的某個對象的屬性茬腿。ECMAScript 3 規(guī)范稱該對象為“調(diào)用對象”呼奢,ECMAScript 5 規(guī)定稱為“聲明上下文對象”。JavaScript可以允許this關(guān)鍵字來引用全局對象切平,卻沒有辦法可以引用局部變量中存放的對象握础。這種存放局部變量的對象的特有性質(zhì),是一種對我們不可見的內(nèi)部實(shí)現(xiàn)揭绑。

3. 作用域鏈

  • 全局變量在程序中始終都是有定義的弓候,局部變量在聲明它的函數(shù)體內(nèi)以及它所嵌套的函數(shù)內(nèi)始終是有定義的;

  • 如果將每一個局部變量看做是自定義實(shí)現(xiàn)的對象的屬性的話他匪,那么可以換個角度來解讀變量作用域菇存。每一段JavaScript代碼(全局代碼或函數(shù))都有一個與之關(guān)聯(lián)的作用域鏈。這個作用域鏈?zhǔn)且粋€對象列表或者鏈表邦蜜。這組對象定義了這段代碼“作用域中”的變量依鸥。

  • 當(dāng)JavaScript需要查找變量x的值的時候(這個過程叫“變量解析”),它會從鏈中的第一個對象開始查找悼沈,如果這個對象有一個名為x的屬性贱迟,則會直接使用這個屬性的值姐扮,如果第一個對象不存在x屬性,則查找鏈上的下一個對象衣吠,以此類推茶敏。如果沒找到,則拋一個引用異常缚俏。

  • 在JavaScript的最頂層代碼中(也就是不包含在任何函數(shù)定義內(nèi)的代碼)惊搏,作用域鏈有一個全局對象組成。在不包含嵌套的函數(shù)體內(nèi)忧换,作用域上有兩個對象恬惯,第一個是定義函數(shù)參數(shù)和局部變量的對象,第二個是全局對象亚茬。在一個嵌套的函數(shù)體內(nèi)酪耳,作用域鏈上至少有三個對象刹缝。當(dāng)調(diào)用這個函數(shù)時梢夯,它創(chuàng)建一個新的對象來存儲它的局部變量厨疙,并將這個對象添加至保存的那個作用域鏈上,同時創(chuàng)建了一個更長的表示函數(shù)調(diào)用作用域的“鏈”沾凄。 對于嵌套函數(shù)來講撒蟀,事情變得更加有趣,每次調(diào)用外部函數(shù)時手负,內(nèi)部函數(shù)又會重新定義一遍竟终。 因?yàn)槊看握{(diào)用外部函數(shù)的時候切蟋,作用域鏈都是不同的。內(nèi)部函數(shù)在每次定義的時候都有微妙的差別——在每次調(diào)用外部函數(shù)時喘鸟,內(nèi)部函數(shù)的代碼都是相同的什黑,而且關(guān)聯(lián)這段代碼的作用域鏈也不相同愕把。

4. 函數(shù)和閉包

  • JavaScript 采用詞法作用域,函數(shù)的執(zhí)行依賴于變量作用域,這個作用域是在函數(shù)定義時決定的拗秘,而不是調(diào)用時決定的雕旨。為了實(shí)現(xiàn)這種詞法作用域,JavaScript函數(shù)對象的內(nèi)部狀態(tài)不僅包含函數(shù)的代碼邏輯棒搜,還必須引用當(dāng)前的作用域鏈活箕。函數(shù)對象可以通過這種作用域相互關(guān)聯(lián)起來育韩,函數(shù)體內(nèi)的變量都可以保存在函數(shù)作用域內(nèi)筋讨,這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中稱為“閉包”。

  • 從技術(shù)的角度將赤屋,所有的JavaScript函數(shù)都是閉包:它們都是對象类早,它們都關(guān)聯(lián)到作用域鏈莺奔,定義大多數(shù)函數(shù)的作用域鏈在調(diào)用函數(shù)時依然有效,但這并不影響閉包恼琼。當(dāng)調(diào)用函數(shù)時閉包所指向的作用域鏈和定義函數(shù)時的作用域鏈不是同一個作用域鏈時晴竞,事情就變得微妙狠半。當(dāng)一個函數(shù)嵌套了另一個函數(shù),外部函數(shù)將嵌套的函數(shù)對象作為返回值返回的時候已维,往往會發(fā)生這種事情垛耳。

理解閉包首先要了解嵌套函數(shù)的詞法作用域規(guī)則:

/**
* JavaScript函數(shù)的執(zhí)行用到了作用域鏈飘千,這個作用域鏈?zhǔn)呛瘮?shù)定義的時候創(chuàng)建的。
嵌套的函數(shù)f()定義在這個作用域鏈里缔莲,其中的變量scope一定是局部變量痴奏,不論
何時何地執(zhí)行f()厌秒,這種綁定在執(zhí)行f()時依然有效简僧。因此最后一行代碼返回的是“l(fā)ocal”岛马。閉包能夠捕捉到局部變量和參數(shù),并一直保存下來伞矩,看起來想這些
變量綁定到了在其中定義它們的外部函數(shù)乃坤。
**/
var scope = 'global';
function checkscope() {
    var scope = "local";
    function f(){return scope;}
    return f;
}
checkscope()(); //輸出local
/** 
* 代碼定義了一個立即調(diào)用的函數(shù),返回值也是函數(shù)狱杰,嵌套的函數(shù)可以訪問
* 外部函數(shù)的counter變量仿畸。但外部函數(shù)返回之后,其它任何代碼無法訪問counter
* 只有內(nèi)部函數(shù)能夠訪問它。如果有多個嵌套函數(shù),也可以訪問它忆植,這多個嵌套函* 數(shù)共享一個作用域鏈。
**/
var uniqueInteger = (function() {
      var counter = 0; 
      return function() { return counter++; };
}());
uniqueInteger(); // 0 
uniqueInteger(); // 1


/**
*  每次調(diào)用counter()都會創(chuàng)建一個新的作用域和一個新的私有變量坞古。因此劫樟,如果
* 調(diào)用counter()兩次叠艳,則會得到兩個計(jì)數(shù)器對象,而且彼此包含不同的私有變量吃粒。
**/
function counter() {
      var n = 0;
      return {
              count: function() { return n++; },
              reset: function() { n = 0; }
      };
}
var c = counter(), d = counter(); //創(chuàng)建兩個計(jì)數(shù)器
c.count();  // => 0;
d.count(); // => 0 它們互不干擾
c.reset(); // reset() 和 count() 方法共享狀態(tài)
c.count(); // => 0 因?yàn)槲覀冎刂昧薱
d.count(); // => 1 沒有重置d
  • 書寫閉包需要注意: this是JavaScript的關(guān)鍵字而不是變量徐勃,每個函數(shù)調(diào)用都包含一個this值早像,如果閉包在外部函數(shù)里是無法訪問this的。除非外部函數(shù)將this轉(zhuǎn)存為一個變量:
var self = this;

5. 原型和原型鏈

  • 每個函數(shù)都包含一個prototype屬性臀脏,這個屬性是指向一個對象的引用揉稚。這個對象稱做“原型對象”。每個函數(shù)都包含不同的原型對象帝蒿。在JavaScript中巷怜,類的所有實(shí)例對象都從同一個原型對象上繼承屬性延塑。因此,原型對象是類的核心关带。

  • 推薦閱讀 JavaScript深入之從原型到原型鏈
    下面摘一段代碼和兩個圖(侵刪)

5.1 原型
function Person() {

}
// 雖然寫在注釋里侥涵,但是你要注意:
// prototype是函數(shù)才會有的屬性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

console.log(person.__proto__ === Person.prototype); // true
console.log(Person === Person.prototype.constructor); // true
// 順便學(xué)習(xí)一個ES5的方法,可以獲得對象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
image.png
5.2 原型鏈
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宋雏,隨后出現(xiàn)的幾起案子芜飘,更是在濱河造成了極大的恐慌,老刑警劉巖磨总,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗦明,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚪燕,警方通過查閱死者的電腦和手機(jī)娶牌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诗良,“玉大人钥弯,你說我怎么就攤上這事”嬗荆” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵遇绞,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么徽诲? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上爸邢,老公的妹妹穿的比我還像新娘碌尔。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布锐膜。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涩澡。 梳的紋絲不亂的頭發(fā)上膝迎,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天费尽,我揣著相機(jī)與錄音柏卤,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛酸舍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼状您,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兜挨?” 一聲冷哼從身側(cè)響起拌汇,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤傅联,失蹤者是張志新(化名)和其女友劉穎比驻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羽莺,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡长酗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年溪北,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夺脾。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡之拨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咧叭,到底是詐尸還是另有隱情蚀乔,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布菲茬,位于F島的核電站吉挣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏婉弹。R本人自食惡果不足惜睬魂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镀赌。 院中可真熱鬧氯哮,春花似錦、人聲如沸商佛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肠虽,卻和暖如春幔戏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背税课。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工评抚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伯复。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓慨代,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啸如。 傳聞我的和親對象是個殘疾皇子侍匙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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