摘自《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