一变丧、JS的作用域
1.JS采用詞法作用域
首先,我們得知道JavaScript采取的是詞法作用域咸这,而不是動(dòng)態(tài)作用域夷恍。
那么什么是詞法作用域,什么是動(dòng)態(tài)作用域呢媳维?
引用一下我以前在知乎上看到的一句精簡(jiǎn)的描述:
詞法作用域的函數(shù)中遇到既不是形參也不是函數(shù)內(nèi)部定義的局部變量的變量時(shí)侄刽,去函數(shù)定義時(shí)的環(huán)境中查找醋安。
動(dòng)態(tài)作用域的函數(shù)中遇到既不是形參也不是函數(shù)內(nèi)部定義的局部變量的變量時(shí)墓毒,去函數(shù)調(diào)用時(shí)的環(huán)境中找柠辞。
如果對(duì)這句話還是不太明白的朋友,可以觀察以下代碼:
var a = "這是第一個(gè)a";
function func01() {
console.log(a); //先在當(dāng)前作用域中查找,如果沒(méi)有則訪問(wèn)全局的作用域
}
function func02() {
var a = "這是第二個(gè)a";
func01()
}
func01(); //打印結(jié)果為:這是第一個(gè)a
func02(); //打印結(jié)果為:這是第一個(gè)a(倘若JS用的是動(dòng)態(tài)作用域夷都,這里將輸出第二個(gè)a)
2.JS沒(méi)有塊作用域(try,catch為特例)
在大多數(shù)語(yǔ)言中摩泪,都是有塊作用域的嚷掠,比如C荞驴、C++霹娄、C#犬耻、Java、Object-C等等术吝,而JS沒(méi)有塊作用域峭咒。
下面分別是C和OC的代碼
#include <stdio.h>
int main() {
int a = 1;
if (1) {
int a = 2;
printf("%d, ", a); // 2
}
printf("%d\n", a); // 1
}
NSString *str = @"碼農(nóng)1號(hào)";
if (true) {
NSString *str = @"碼農(nóng)2號(hào)";
NSLog(@"%@", str); //碼農(nóng)2號(hào)
}
NSLog(@"%@", str); //碼農(nóng)1號(hào)
在C和OC中幔翰,if花括號(hào)內(nèi)定義的變量遗增,在if語(yǔ)句執(zhí)行完后饰及,就被銷毀了屏箍。那么我們?cè)賮?lái)看看類似的代碼運(yùn)行在JS中赴魁。
var a = "global";
if (true) {
var a = "if";
console.log(a); //if
}
console.log(a); //輸出為:if 證明if語(yǔ)句內(nèi)定義的變量颖御,是全局變量
那么在JS中,我們能否像其它語(yǔ)言一樣創(chuàng)建出類似塊作用域的效果呢泽铛?答案是可以的尚辑,利用閉包!
因?yàn)樵贘S在只存在全局作用域和函數(shù)作用域盔腔,因此我們可以巧妙的利用一下函數(shù)作用域創(chuàng)建出塊作用域的效果杠茬,我們把上面的JS代碼改造一下:
var a = "global";
if (true) {
(function() {
var a = "if";
console.log(a); //if
})();
}
console.log(a); //global
上面代碼在if內(nèi)創(chuàng)建了一個(gè)閉包,它是一個(gè)立即調(diào)用的匿名函數(shù)弛随,因此這里創(chuàng)建出了一個(gè)函數(shù)作用域瓢喉,在此函數(shù)作用域里定義的變量,將不影響全局變量舀透,并且函數(shù)執(zhí)行完后栓票,該變量會(huì)被銷毀。
這樣利用閉包的好處是:限定作用域愕够,不怕污染全局變量走贪。
二、JS中的變量提升和函數(shù)提升
1.變量的提升
在JS代碼編譯時(shí)惑芭,函數(shù)聲明和變量定義會(huì)被解釋器移動(dòng)到其所在作用域的最頂部坠狡!
怎么理解這句話呢?我們來(lái)看下面代碼
console.log(a); //報(bào)錯(cuò)
console.log(a); //undefined
var a = 10;
為什么上面代碼不報(bào)錯(cuò)遂跟?因?yàn)樯厦娲a在編譯器解析完后逃沿,變成了下面的形式:
var a;
console.log(a);
a = 10;
那么我們?cè)倏聪旅娴睦佑ざ桑虏滤鼈兊妮敵鼋Y(jié)果
var a = "global";
fun();
function fun() {
console.log(a); //1.?
var a = "local";
}
console.log(a); //2.凯亮?
輸出結(jié)果是 1. undefined边臼, 2. global
上面的代碼的正確解析如下:
var a;
function fun() {
var a; //此變量所處域?yàn)楹瘮?shù)域,因此其作用域最頂部是這里
console.log(a); //undefined
a = "local";
}
a = "global"
fun();
console.log(a); //global (函數(shù)域創(chuàng)建的變量不污染到外面)
2.函數(shù)表達(dá)式的提升(非函數(shù)聲明)
先看例子:
console.log(fun); //undefined
var fun = function () {
console.log("我是一個(gè)函數(shù)");
}
上面的函數(shù)提升跟變量的提升一樣触幼,解析完如下:
var fun;
console.log(fun); //undefined
fun = function () {
console.log("我是一個(gè)函數(shù)");
}
3.函數(shù)聲明的提升
先看例子:
console.log(fun); //整個(gè)函數(shù)打印出來(lái)
function fun() {
console.log("我是一個(gè)函數(shù)");
}
解析完如下:
function fun() {
console.log("我是一個(gè)函數(shù)");
}
console.log(fun); //整個(gè)函數(shù)打印出來(lái)
由此我們可以得出結(jié)論:JavaScript編譯時(shí)硼瓣,對(duì)于函數(shù)表達(dá)式的提升是跟普通變量提升一樣的,僅提升聲明但不賦值置谦。對(duì)于函數(shù)聲明的提升堂鲤,則是整個(gè)提升,我們可以理解為聲明+賦值的提升媒峡。
總結(jié):
只有函數(shù)可以限定作用域
在函數(shù)內(nèi)部允許訪問(wèn)外部的變量瘟栖,但外部不能訪問(wèn)函數(shù)內(nèi)部變量
如果當(dāng)前作用域中有該變量,則不考慮外部作用域的同名變量
對(duì)于變量和函數(shù)表達(dá)式的提升,僅提升聲明谅阿,不賦值
對(duì)于函數(shù)聲明的提升半哟,是聲明+賦值
本農(nóng)搞了個(gè)小練習(xí)給大家練練手,如果做對(duì)基本對(duì)于該文你也就懂了签餐,先不要看最下面的解析結(jié)果
var a = "global";
function fun() {
a = "local";
return;
function a() {}
}
fun();
alert(a);
解析結(jié)果為:
var a;
function fun() {
function a() {}
a = "local";
return;
}
a = "global"
fun(); //function a(){} 可以理解為 var a = function(){}寓涨,函數(shù)域內(nèi)聲明的變量不影響全局
alert(a); //輸出為global