在說有關知識之前先讓大家看幾段代碼:
var i = "Hellow world!";
alert(i);
很簡單對吧叁温?輸出結果是“Hellow world!”核畴,沒有任何問題膝但,好,我們繼續(xù):
var i = "Hellow World!";
function myFunction(){
alert(i);
}
myFunction();
結果不出意料谤草,還是“Hellow World跟束!”,我們接著看:
var i = "Hello World!";
function myFunction(){
alert(i);
var i = "I love you";
}
myFunction();
大家來猜猜看丑孩,結果是什么冀宴?
有多少人猜到了運行結果是 undefined
?
那這一段代碼呢温学?
var i = 1;
function myFunction(){
if(!i){
var i = 2;
alert(i);
}
}
myFunction();
為什么結果是 2略贮?
這其中有關 JavaScript 中的一個術語** “提升”(Hoisting)**。
作用域(scope)
在說有關提升的知識之前必須要說一下作用域(Scope)仗岖。對于初學者來說逃延,弄清楚作用域是十分重要的。
什么是作用域轧拄?
摘自 維基百科
在電腦程序設計中揽祥,作用域(scope,或譯作有效范圍)是名字(name)與實體(entity)的綁定(binding)保持有效的那部分計算機程序檩电。不同的編程語言可能有不同的作用域和名字解析)拄丰。而同一語言內(nèi)也可能存在多種作用域府树,隨實體的類型變化而不同。作用域類別影響變量的綁定方式料按,根據(jù)語言使用靜態(tài)作用域還是動態(tài)作用域變量的取值可能會有不同的結果奄侠。
- 包含標識符的宣告或定義;
- 包含語句和/或表達式站绪,定義或部分關于可運行的算法遭铺;
- 嵌套嵌套或被嵌套嵌套。
名字空間是一種作用域恢准,使用作用域的封裝性質(zhì)去邏輯上組群起關相的眾識別子于單一識別子之下魂挂。因此,作用域可以影響這些內(nèi)容的名字解析馁筐。
程序員常會縮進他們的源代碼中的作用域涂召,改善可讀性。
簡單說就是敏沉,我們定義一個變量果正,該變量的有效范圍,就是它的作用域盟迟。就有了兩個名詞全局變量秋泳、局部變量。其中就涉及到變量的可見性攒菠,以及變量的生命周期等細節(jié)迫皱,這里不做太多贅述,有不清楚的同學可以自行查閱相關資料辖众。
懂得 C 語言的同學來看一下以下這一段代碼卓起,如果不懂的同學可以跳過:
#include<stdio.h>
void main(){
int i = 1;
printf("%d,",i);
if(i){
int i = 2;
printf("%d,",i);
}
printf("%d",i);
}
輸出結果為1,2,1 。
熟悉 C語言的人都知道為什么會是這么輸出凹炸。在 C 語言中是塊級作用域戏阅,在 if()
語句中,聲明了同名的局部變量啤它,覆蓋了全局變量的值奕筐。而當出了塊后,局部變量銷毀变骡,重新輸出全局變量的值离赫。
再稍微科普一下什么是塊級作用域:
任何一對花括號({ 和 })中的語句集都屬于一個塊,在這之中定義的所有變量在代碼塊外都是不可見的锣光,我們稱之為塊級作用域。
我們再來看一段 JavaScript 代碼:
var i = 1;
alert(i);
if (i){
var i = 2;
alert(i);
}
alert(i);
輸出的結果為 1铝耻,2誊爹,2 蹬刷。
這是因為,在 JavaScript 中是函數(shù)級作用域频丘。類似 if
等塊語句办成,并不會創(chuàng)建新的作用域。只有在函數(shù)中搂漠,才會創(chuàng)建新的作用域迂卢,定義在函數(shù)中的變量在函數(shù)外是不可見的。
那么桐汤,該如何解決呢而克?我們只需要在塊級作用域中臨時創(chuàng)建新的作用域就可以了:
var i = 1;
if (i) {
function temporary() {
var i = 2;
//其他代碼
}
temporary();
}
// i 的值依然為 1
不過在 ES 5 中規(guī)定,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明怔毛,不能在塊級作用域聲明员萍。但是瀏覽器并沒有遵守這個規(guī)定,上面的代碼實際情況下還是可以正常運行拣度。但是在嚴格模式中是會報錯的碎绎。而且在不同的瀏覽器中對該語句的支持情況不一樣,考慮到環(huán)境導致差異太大抗果,應該避免在塊級作用域內(nèi)聲明函數(shù)筋帖。如果確實需要,也應該寫成函數(shù)表達式冤馏,而不是函數(shù)聲明語句(函數(shù)提升影響日麸,下文會說)。
看到這里是不是已經(jīng)有人認為 JavaScript 沒有塊級作用域宿接,只有函數(shù)級作用域了赘淮?這句話在前年說,那就是對的睦霎。但是在 2015 年 6 月梢卸,隨著 ECMAScript 6(即 ECMAScript 2015,以后簡稱ES 6)的發(fā)布 副女,JavaScript 也擁有塊級作用域了蛤高。
var
在 JavaScript 中聲明的變量要么是全局變量,要么為函數(shù)級變量碑幅,在 ES 6之前戴陡,是沒有塊級作用域的。在 ES 6 中新增了兩個聲明變量的方式 let
和 const
沟涨。
-
let
聲明了一個塊級作用域的局部變量恤批,它的作用范圍僅僅為最接近的塊作用域(如果在所有塊以外就是全局作用域),這將會比var
的函數(shù)作用域更小裹赴。 -
const
聲明的是一個只讀的常量喜庞,而該常量一旦聲明诀浪,常量的值就無法更改。const
聲明的變量不得改變值延都,也就是說雷猪,const
一旦聲明變量,就必須立即初始化晰房,不能留到以后賦值求摇。
注意:
-
const
與let
只在聲明所在的塊級作用域內(nèi)有效。 -
const
和let
不存在提升殊者。
console.log(i); // 輸出 undefined
console.log(j); // 報錯
var i = 1;
let j = 1;
- 只要塊級作用域內(nèi)存在
let
或const
命令与境,它所聲明的變量就 綁定(binding)這個區(qū)域,不再受外部的影響幽污。ES 6 明確規(guī)定嚷辅,如果區(qū)塊中存在let
和const
命令,這個區(qū)塊對這些命令聲明的變量距误,從一開始就形成了封閉作用域簸搞。凡是在聲明之前就使用這些變量,就會報錯准潭,在聲明變量之前趁俊,該變量都是不可用的,只能在聲明的位置后面使用刑然。這在語法上寺擂,稱為暫時性死區(qū)(temporal dead zone,簡稱TDZ)泼掠。暫時性死區(qū)中不存在變量的提升怔软。
var i = 1;
if (true) {
i = "1"; // 報錯
let i;
}
-
count
與let
不可重復聲明參數(shù)。 - 必須在嚴格模式下才可以使用
count
和let
择镇。
為什么我先那么大篇幅的介紹了作用域挡逼?如果我們能充分理解了作用域,再去理解提升就會比較容易腻豌。
提升(hoisting)
變量提升
JavaScript 的函數(shù)定義有個特點家坎,它會先掃描整個函數(shù)體的語句,把所有申明的變量“提升”到函數(shù)頂部吝梅。但是虱疏,變量提升所提升的僅僅為變量的聲明,并不會將變量的賦值也提升上來苏携。
"use strict";
function myFunction() {
var x = "Hellow," + y;
alert(x);
var y = "World";
}
myFunction();
輸出結果為:Hellow做瞪,undefined。
雖然是 strict
模式右冻,但是也不會報錯装蓬,因為在運行之前衩侥,瀏覽器會先進行一次預編譯,會將變量和函數(shù)先在函數(shù)的最頂部進行預編譯矛物。上面的代碼經(jīng)過編譯后輸出的結果為:
"use strict";
function myFunction() {
var x;
var y;
x = "Hellow," + y;
alert(x);
y = "World";
}
myFunction();
所以結果并不會報錯,而會輸出 undefined跪但。
所以履羞,在我們寫 JavaScript 代碼的時候,需要養(yǎng)成習慣屡久,要把變量放在函數(shù)級作用域的最頂端忆首,防止出現(xiàn)意外。
函數(shù)提升
變量提升是將變量提升到函數(shù)級作用域的最頂端被环,而函數(shù)的提升則是將整個函數(shù)都提到整個作用域的最頂端糙及。不過函數(shù)的聲明跟變量的聲明有一點不一樣。函數(shù)的聲明會連函數(shù)體也會被一同提升筛欢。函數(shù)有兩種聲明方式浸锨,一種是變量指向的函數(shù)表達式,另外一種是函數(shù)的聲明版姑。需要注意的是柱搜,只有函數(shù)的聲明形式才會被提升。二話不多說剥险,先上代碼:
function myFunctionOne(){
myFunctionTwo();
function myFunctionTwo(){
alert("我是 myFunctionTwo");
}
}
myFunctionOne();
輸出結果為 :我是 myFunctionTwo聪蘸。
我們再來看第二段:
function myFunctionOne(){
myFunctionTwo();
var i =function myFunctionTwo(){
alert("我是 myFunctionTwo");
}
}
myFunctionOne();
結果報錯。
好的表制,有關的知識到這里就結束了健爬,現(xiàn)在大家應該明白了有關 JavaScript 中的提升和作用域了吧。
有沒有同學在看完了整篇文章之后再回頭看第一段代碼發(fā)現(xiàn)還是沒看懂么介?先把最早的代碼貼上來:
var i = "Hello World!";
function myFunction(){
alert(i);
var i = "I love you";
}
myFunction();
不知道有沒有人有疑惑娜遵,在外部有一個全局變量 i
,但是為什么在函數(shù)內(nèi)部為什么無法引用輸出外面的全局變量夭拌。就算應用了作用域和提升后也無法解釋魔熏。
我們都知道,在 JavaScript 中是可以重復聲明的鸽扁,而且重復聲明并不會修改賦值蒜绽。但是,在局部變量中如果聲明了重名的全局變量桶现,局部變量就會在作用域中覆蓋掉全局變量躲雅。
我們只需要對 var i = "I love you";
中的賦值注釋掉后運行,然后再將整個語句注釋后再運行骡和,就可以的得到驗證相赁,兩次輸出的結果先后為 undefined相寇,Hello World!。
結尾
有關作用域和提升相關的知識就總結了這一些钮科,如有遺漏希望讀者們評論補充唤衫。這些特性我們總會在不經(jīng)意間遇到,當我們有時候遇到某些“坑”的時候绵脯,要記得想想這些 JavaScript 中的特性佳励,說不定就能找到問題的所在。