15兰粉、變量
首先故痊,我們需要了解一下變量的定義:
變量是存儲信息的容器。
為什么需要變量呢玖姑,不止js里面愕秫,程序里面的數(shù)據(jù)來源都要有根有據(jù),比如
alert(a);
為什么會發(fā)生錯誤呢焰络,a是什么東西戴甩,有聲明過了嗎,所以也就需要變量來存儲數(shù)據(jù)了闪彼,讓數(shù)據(jù)來源有根據(jù)甜孤,而不是無中生有(當(dāng)然也有特殊情況,下面我們在討論)畏腕。在js程序中缴川,如果我們使用變量之前會先聲明變量。
變量的聲明用var關(guān)鍵字描馅。
var a="";
var b=true;
var fn=function(){ //dosomething}
var date=new Date();
var obj={};
var arr=[];
var num=123;
var nullStr=null
var uf=undefined;
var pat= /\d+/;//匹配一個或多個數(shù)字
比如從上面可以看出把夸,變量的類型是任意的,js是弱類型語言铭污,js的變量可以是任意類型的恋日。
上面代碼,多數(shù)用到var嘹狞,我們可以簡寫多個變量聲明的方式谚鄙,比如
var a=1;
var b=2;//等價于var a=1, b=2;
值得注意的是:如果圍在var什么語句中給變量賦值,也就是只是聲明了刁绒,但沒有值闷营,那么在給這個變量賦值之前,這個變量的值是undefined
var a;console.log(a); //undefined
這里涉及到了變量的什么提前的知識,有興趣的可以參考我之前寫的文章js(四):提升傻盟。這里我們不深入研究下去速蕊。
當(dāng)我們使用var對同一變量重復(fù)聲明是沒問題的;
var a=1;
var a=2;
var a=3;
這種重復(fù)聲明和賦值有點類型我們css中娘赴,同名選擇器的時候规哲,后面的樣式會覆蓋前面的屬性。
但是要記得的是诽表,給一個沒有什么過的變量賦值唉锌,在非嚴格模式下是沒有錯誤的,但是在嚴格模式下會報錯竿奏。
a=2;
console.log(a);
"use strict";
a=2;console.log(a);
上面例子袄简,一個在非嚴格模式下執(zhí)行的時候,a會變成window下的屬性泛啸,也就是全局變量绿语。
另一個在嚴格模式下執(zhí)行,程序會報錯候址。
因此吕粹,我們寫代碼的時候,應(yīng)該始終使用var來聲明變量岗仑。(關(guān)于這些知識匹耕,都涉及到了js更加深層次的知識與原理性的東西,以后我們會詳細來研究)
既然我們用var聲明了變量荠雕,那么這些變量的作用范圍泌神,我們可以先理解為變量的作用域了。
在全局作用域中聲明的變量是全局變量舞虱,相對的也有局部變量欢际。類似
var win="window"
function fn(){
var win="obj";
console.log(win);
}
console.log(win);
fn();
console.log(win);
在上面例子中我們假設(shè),win所處的環(huán)境是全局環(huán)境矾兜,那么win就是全局變量歌焦,并且變量win的值為'windoq',而在函數(shù)fn里面的win變量所處于函數(shù)fn的局部環(huán)境中冤灾,所有里面的win是局部變量,第一個輸出和第二個輸出我們是毫無疑問的,第三個再次輸出win也是全局的洋魂,因為兩個所處不同環(huán)境的兩個分別用var定義的win變量是不一樣的正什。我們改一下代碼可以看出區(qū)別:
var win="window";
function fn(){
win="obj"; //改動地方 沒有用win聲明
console.log(win);
}
console.log(win);
fn();
console.log(win);
同名屬性甲棍,在局部中沒有用var什么迟螺,但是賦值了,也就相當(dāng)于改變了全局作用域下的同名屬性的值荆萤,所有執(zhí)行函數(shù)fn后镊靴,win的值被改變了铣卡。
(值得注意的是,前面說過:在嚴格模式下偏竟,給未經(jīng)聲明的變量賦值會包類型錯誤煮落,這個例子就算在嚴格模式下也不會保存,因為win已經(jīng)在全局作用域下用var聲明過了踊谋,函數(shù)體內(nèi)僅僅只是賦值操作)蝉仇。
15、函數(shù)作用域和聲明提前
塊級作用域
在一些強類型編程語言中殖蚕,比如C,花括號{}內(nèi)的每一段代碼都有各種的作用域轿衔,而且變量在聲明他們的代碼段之外是不可見的,這就是塊級作用域睦疫,而js在ES6之前的js版本是沒有塊級作用域的害驹。這里我們先不涉及到ES6的知識,后面我們再詳細了解笼痛。所以取而代之地使用了函數(shù)作用域裙秋。 變量再聲明他們的函數(shù)體以及這個函數(shù)體嵌套的任何函數(shù)體內(nèi)都是有定義的琅拌。例如
function foo(){
var a=1; //這里面的變量只有再函數(shù)里面才可以訪問
var b=2;
}
js的函數(shù)作用域是指函數(shù)內(nèi)聲明的所有變量再函數(shù)體內(nèi)始終可見缨伊,這意味這變量再聲明之前甚至已經(jīng)可用。js的這個特性別非正式的稱為聲明提前进宝,即js函數(shù)里聲明的變量(但不涉及復(fù)制)都被“提前"只函數(shù)體的頂部刻坊。我們直接看代碼
var a="global"; //全局變量、
function foo(){
console.log(a);
var a="local" //局部變量
console.log(a);
}
第一個console.log輸出什么党晋?是undefined谭胚,而不是global或者local,而第二個毫無疑問是local了未玻。
上述過程等價與:將函數(shù)內(nèi)的聲明提前至函數(shù)頂部灾而,同時變量初始化留在原來的位置,有興趣深入了解原理的同學(xué)可以參考我寫的文章提升扳剿。
由于js沒有塊級作用域旁趟,因此我們常常可以見到別人寫代碼的時候?qū)⒆兞柯暶鞣旁诤瘮?shù)體頂部庇绽,而不是將聲明靠近放在使用變量之外锡搜。這種做法使得他們的源代碼非常清晰地反應(yīng)真實的變量作用域
function foo(){
var iTarget=null; //現(xiàn)在函數(shù)體頂部聲明
....
iTarget=.... //只用用與賦值等等操作了
}
作為屬性的變量
當(dāng)聲明一個JavaScript全局變量時,實際上是定義了全局對象的一個屬性瞧掺。當(dāng)使用var聲明一個變量時耕餐,創(chuàng)建的這個屬性是不可配置的,也就是說這個變量無法通過delete運算符刪除辟狈〕Φ蓿可能你已經(jīng)注意到了,如果你沒有使用嚴格模式并給一個未聲明的變量賦值的話,JavaScript會自動創(chuàng)建一個全局變量桩砰。以這種方式創(chuàng)建的變量是全局對象的正常的可配值屬性拓春,并可以刪除它們:
var a = 1; // 聲明一個不可刪除的全局變量
b = 2; // 創(chuàng)建全局對象的一個可刪除的屬性 ,(在嚴格模式下會報錯)
this.c = 3; // 同上 delete a // => false: 變量并沒有被刪除 delete b // => true: 變量被刪除
delete this.c // => true: 變量被刪除
為什么使用var的變量就不可配置了,這個涉及到了屬性的特性的知識亚隅,有興趣的朋友可以查下資料了解和深入下硼莽,這里我們簡單的了解一下:
- 作為對象,每個屬性(變量)下都有幾個屬性(屬性的特性)煮纵,其中configurable,這個屬性確定了屬性(變量)名字能否更改懂鸵,變量能否被刪除。true的話行疏,可以更改匆光,可刪除;反之酿联,不能更改终息,不能刪除。
- 在用var 聲明變量時贞让,JS解析器會默認把configurable設(shè)為false周崭,所以它不能改名字,不能被刪掉喳张。
js全局變量是全局對象的屬性续镇,上面在非嚴格模式下,this在全局下指向的是全局對象销部,(關(guān)于this指向這個知識摸航,我們以后再來詳細探討)也就是window。
作用域鏈
簡單的說舅桩,作用域就是變量與函數(shù)的可訪問范圍酱虎,即作用域控制著變量與函數(shù)的可見性和生命周期。在JavaScript中擂涛,變量的作用域有全局作用域和局部作用域兩種读串。在學(xué)習(xí)作用域鏈之前,我們簡單看幾個例子了解下全局作用域和局部作用域歼指。
全局作用域
var a="window";
functin a(){ //dosomethind}
假設(shè)上面代碼代碼的最頂級的位置(也就是沒有任何函數(shù)嵌套爹土,所有當(dāng)前的作用域就是全局作用域),全局作用域就是在全局環(huán)境下變量與函數(shù)的可訪問范圍踩身,這個范圍就是控制著變量和函數(shù)的可見性和聲明周期胀茵,任何在全局作用域下的都可以訪問到全局變量a和全局函數(shù)a(),
局部作用域
function b(){
var foo="123";
function bar(){
//dosomething
}
}
console.log(foo) //報錯 bar() 報錯
我們知道,js里面函數(shù)也是對象挟阻,在函數(shù)花括號{}里面的定義的變量琼娘、函數(shù)等峭弟,我們在函數(shù)外面是訪問不到的,這就是函數(shù)的局部作用域脱拼。和全局作用域相反瞒瘸,局部作用域一般只在固定的代碼片段內(nèi)可訪問到,最常見的例如函數(shù)內(nèi)部熄浓,所有在一些地方也會看到有人把這種作用域稱為函數(shù)作用域情臭。
局部作用域擁有對全局作用域的訪問權(quán)限,在函數(shù)嵌套中赌蔑,被嵌套的函數(shù)擁有對外層函數(shù)作用域的訪問權(quán)限俯在,比如
//全局作用域
function foo(){
var a=1; //foo函數(shù)作用域(局部作用域)
function bar(){
var b=2;
function fn(){
var c=3
}
}
}
上面函數(shù)里面嵌套了多層函數(shù),a娃惯、bar()是foo的局部變量跷乐,以此類推,變量c在fn()定義,fn()的作用域包含著fn()花括號{}里面的作用域趾浅、bar()愕提、foo()和全局作用域,在fn中可以訪問到變量它的外層作用域包括全局作用域皿哨,以此類推浅侨。
但是全局作用域不能訪問到變量a以及所嵌套的標識符(變量、函數(shù)往史,對象等)仗颈,我們把作用域想象成一條線條佛舱,他們組成了一個單向的椎例,由內(nèi)而外的線,如
圖中请祖,黃色的線的箭頭代表可以訪問的作用域订歪,他們形成一系列的鏈條就是作用域鏈了(本人理解),作用域鏈定義了一系列作用域嵌套時候肆捕,變量與函數(shù)的可見性和生命周期刷晋。
當(dāng)定義一個函數(shù)時,它實際上保存一個作用域鏈慎陵。當(dāng)調(diào)用這個函數(shù)時眼虱,它創(chuàng)建一個新的對象來存儲它的局部變量,并將這個對象添加至保存的那個作用域鏈上席纽,同時創(chuàng)建一個新的更長的表示函數(shù)調(diào)用作用域的“鏈”捏悬。對于嵌套函數(shù)來講,事情變得更加有趣润梯,每次調(diào)用外部函數(shù)時过牙,內(nèi)部函數(shù)又會重新定義一遍甥厦。
js基礎(chǔ)-類型、值和變量到這里了寇钉。
如有錯誤刀疙,歡迎批評,對本文有什么不理解的地方話也可以留言評論交流IǔG怼!
原創(chuàng)文章by zhengyepan
歡迎訪問我的個人網(wǎng)站zhengyepan.com
歡迎討論交流~