深入理解JavaScript的作用域和作用域鏈

一、作用域
(一)作用域是什么
了解作用域之前先看一下變量和函數(shù),變量和函數(shù)都有一定的訪問(wèn)權(quán)限,就是必須滿足條件或者在某個(gè)范圍之內(nèi)才能訪問(wèn),這個(gè)范圍就是作用域。它具體表現(xiàn)形式就是一段特定的代碼,在該代碼段中的變量和函數(shù)是封閉的、獨(dú)立的,這樣變量才不會(huì)泄露、污染。
var cat = '有魚(yú)';
function Person(){
var name = '張三';
console.log(name);//張三,在函數(shù)內(nèi)部,跟變量name在同一個(gè)作用域內(nèi)
}
console.log(cat);//有魚(yú)
console.log(name);//沒(méi)有結(jié)果顯示,因?yàn)閚ame在函數(shù)內(nèi)部是獨(dú)立的,訪問(wèn)不了
(二)作用域分類
作用域一共有三種,分別為全局作用域、函數(shù)作用域、塊作用域,其中塊作用域是ES6新增的,同時(shí)函數(shù)作用域和塊作用域統(tǒng)稱為局部作用域。
全局作用域
在代碼的最外層的變量和函數(shù)稱為全局作用域,有以下特點(diǎn):
①在代碼的任何地方都可以訪問(wèn)得到
②變量聲明時(shí)如果省略var關(guān)鍵字,則為全局變量,擁有全局作用域
③window對(duì)象的屬性和方法,擁有全局作用域
④瀏覽器打開(kāi)時(shí)開(kāi)啟,瀏覽器關(guān)閉或者頁(yè)面關(guān)閉時(shí)銷毀
var cat = '有魚(yú)';
function Person(){
var name = '張三';
per = '卡卡';// 省略var,視為全局變量
console.log(cat);//有魚(yú) cat為全局變量,函數(shù)Person內(nèi)部可以訪問(wèn)
}
console.log(cat);//有魚(yú) cat為全局變量,最外層代碼可以訪問(wèn)
console.log(per);//卡卡 per為全局變量,最外層代碼可以訪問(wèn)
全局作用域也有弊端,如:變量命名時(shí)容易重復(fù),造成變量污染;
函數(shù)作用域
在函數(shù)內(nèi)部的變量權(quán)限稱為函數(shù)作用域,有以下特點(diǎn):
①每個(gè)函數(shù)都有自己的作用域,而且調(diào)用一次就會(huì)生成新的作用域
②只能在函數(shù)內(nèi)部才能訪問(wèn),外部是沒(méi)有權(quán)限訪問(wèn)的
③進(jìn)入函數(shù)內(nèi)部時(shí)開(kāi)啟,函數(shù)執(zhí)行完畢后銷毀
var cat = '有魚(yú)';
function Person(){
var name = '張三';
console.log(name);//張三 name為函數(shù)作用域,函數(shù)Person內(nèi)部可以訪問(wèn)
}
console.log(name);//有魚(yú) name為函數(shù)作用域,最外層代碼沒(méi)有權(quán)限訪問(wèn)

塊作用域(ES6新增)
凡是由{}符號(hào)包裹起來(lái)的都是塊作用域,ES6新增的知識(shí)點(diǎn),有以下特點(diǎn):
①只能在{}內(nèi)部起作用
②變量聲明時(shí)使用let或const關(guān)鍵字
③變量名不能重復(fù),否則會(huì)報(bào)錯(cuò)
④變量不可以在聲明之前使用
使用var進(jìn)行展示:
if(true){
var cat = '有魚(yú)';
console.log(cat);//有魚(yú)
}
console.log(cat);//有魚(yú)

使用let進(jìn)行展示:
if(true){
let cat = '有魚(yú)';
console.log(cat);// 有魚(yú)
// let cat = '年年';// Uncaught SyntaxError: Identifier 'cat' has already been declared

}
console.log(cat);// 訪問(wèn)不了cat變量 報(bào):Uncaught ReferenceError: cat is not defined
注意:塊作用域一般用在for等循環(huán)語(yǔ)句中,可以避免變量外泄
(三)變量提升與函數(shù)提升
變量提升
①var關(guān)鍵字存在變量提升,let、const不存在;
②變量提升的意思是在程序執(zhí)行前,先去整個(gè)代碼中查看是否含有變量聲明,即是否有var關(guān)鍵字,如果有則先執(zhí)行聲明,然后再去執(zhí)行其他部分。
這里我我總結(jié)了4句話:
①js程序執(zhí)行的順序,先看var聲明,聲明部分在第一行,其他(賦值)按照正常順序執(zhí)行
②如果局部變量里面有var聲明,就是局部變量,不用管外面的全局變量
③如果局部變量里面沒(méi)有var聲明,就去找外面的全局變量
④在局部變量里面,如何使用全局變量,使用window對(duì)象調(diào)用
var a = "Hello";
function person(){
var a;
console.log(a);//undifined
a = "World";
console.log(a);//World
}
person();
console.log(a);//Hello
// 先看整個(gè)腳本文件的聲明部分,全局作用域有個(gè)變量a,局部作用域也有個(gè)變量a,而且都有var聲明,說(shuō)明他們是完全兩個(gè)不同的變量,此時(shí)函數(shù)內(nèi)部不用管外面的全局變量,此時(shí)變量a只是聲明而沒(méi)有賦值,所以輸出為 undifined;
// 接著還是局部作用域里面,給a進(jìn)行了賦值,此時(shí)輸出時(shí)為world,且仍是局部變量;
// 函數(shù)外部的輸出時(shí),因?yàn)榫植孔饔糜蛞呀?jīng)結(jié)束,則又恢復(fù)到全局變量中,所以此時(shí)輸出的是全局變量啊,值為hello。
var a = "Hello";
function person(){
console.log(a);//Hello
a = "World";
console.log(a);//World
}
person();
console.log(a);//World
// 先看整個(gè)腳本文件的聲明部分,全局作用域有個(gè)變量a,局部作用域里面沒(méi)有var聲明,則此時(shí)a是一個(gè)全部變量,所以輸出時(shí)a的值就是全部變量hello;
// 局部作用域里面對(duì)全局變量a進(jìn)行了重新賦值,所以此時(shí)輸出時(shí)為world
// 函數(shù)外部的輸出時(shí),因?yàn)樘鼍植孔饔糜?,又恢?fù)到全局變量中,之前因?yàn)橐呀?jīng)重新賦值為world,此時(shí)輸出時(shí)為world

var a = "Hello";
function person(){
console.log(a);//undifined
var a = "World";
console.log(a);//World
}
person();
console.log(a);// Hello
// 先看整個(gè)腳本文件的聲明部分,全局作用域有個(gè)變量a,局部作用域也有個(gè)變量a,且有var聲明,說(shuō)明他們是完全兩個(gè)不同的變量,此時(shí)函數(shù)內(nèi)部不用管外面的全局變量,進(jìn)行變量提升,記住只是聲明提升,賦值不進(jìn)行提升,所以輸出為 undifined;
// 還是局部作用域里面,給a進(jìn)行了賦值,此時(shí)輸出時(shí)為World,且仍是局部變量
// 函數(shù)外部的輸出時(shí),因?yàn)榫植孔饔糜蛞呀?jīng)結(jié)束,則又恢復(fù)到全局變量中,所以此時(shí)輸出時(shí)為Hello

var a = "Hello";
function person(){
console.log(window.a); //Hello 因?yàn)槭莣indow對(duì)象的屬性,此時(shí)a是全局變量
var a = "World"; //局部變量a在這行定義
console.log(a); //World
}
person();
console.log(a);//Hello 全局變量a
// 在局部作用域使用全局變量使用window.全局變量名字

函數(shù)提升
函數(shù)有兩種形式:函數(shù)表達(dá)式 var per = function(){} 、函數(shù)聲明 function per(){},只有函數(shù)聲明有函數(shù)提升特性
提升的意思是允許先調(diào)用,后聲明
per();//可以先調(diào)用
function per(){
console('我是函數(shù)聲明形式,允許提升');
}

二、作用域鏈
(一)什么是作用域鏈
每一層作用域其實(shí)就是一個(gè)執(zhí)行環(huán)境(作用域和執(zhí)行環(huán)境是有區(qū)別的,這里為了容易理解,姑且這樣),在該環(huán)境內(nèi)執(zhí)行程序時(shí),會(huì)創(chuàng)建一個(gè)變量對(duì)象,對(duì)象包含該有屬于自己的變量和函數(shù);
在當(dāng)前執(zhí)行環(huán)境下找不變量時(shí),就會(huì)向上一層作用域里面尋找,具體過(guò)程還是先找到上一層的變量對(duì)象,再去里面找對(duì)應(yīng)的變量,如果還是找不到,繼續(xù)向上……
直到最外層的作用域,也就是全局作用域,這樣就會(huì)形成一個(gè)鏈條,稱為作用域鏈。
(二)作用域特點(diǎn)
作用域鏈最外層是全局作用域,最內(nèi)層是當(dāng)前作用域
內(nèi)部環(huán)境可以通過(guò)作用域鏈訪問(wèn)所有外部環(huán)境,但外部環(huán)境不能訪問(wèn)內(nèi)部環(huán)境的任何變量和函數(shù)
由于變量的查找是沿著作用域鏈來(lái)實(shí)現(xiàn)的,所以也稱作用域鏈為變量查找的機(jī)制
var cat = '有魚(yú)';
function Person() {
var name = '張三';
function Student() {
var age = 18;
console.log(name);//張三
console.log(cat);//有魚(yú)
}
Student();
console.log(age);//Uncaught ReferenceError: age is not defined
}
Person();
①Student函數(shù)內(nèi)部屬于最內(nèi)層作用域,找不到name,向上一層作用域Person函數(shù)內(nèi)部找,找到了輸出‘張三’;
②在Student內(nèi)部輸出cat時(shí)找不到,向上一層作用域Person函數(shù)找,還找不到繼續(xù)向上一層找,即全局作用域,找到了輸出‘有魚(yú)’;
③在Person函數(shù)內(nèi)部輸出age時(shí)找不到,向上一層作用域找,即全局作用域,還是找不到輸出‘a(chǎn)ge is not defined’;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容