欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript中塊級(jí)作用域與函數(shù)作用域深入剖析

 更新時(shí)間:2023年05月29日 09:02:11   作者:控心crazy  
這篇文章主要為大家介紹了JavaScript中塊級(jí)作用域與函數(shù)作用域的實(shí)現(xiàn)原理深入剖析,

面試官必問(wèn)系列:深入理解JavaScript塊和函數(shù)作用域

在 JavaScript 中,究竟是什么會(huì)生成一個(gè)新的作用域,只有函數(shù)才會(huì)生成新的作用域嗎?那 JavaScript 其他結(jié)構(gòu)能生成新的作用域嗎?

函數(shù)中的作用域

在之前的詞法作用域中可見(jiàn) JavaScript 具有基于函數(shù)的作用域,這也就意味著一個(gè)函數(shù)都會(huì)創(chuàng)建一個(gè)新的作用域。但其實(shí)并不完全正確,看以下例子:

function foo(a) {
var b = 2;
function bar() {
    // ...
}
var c = 3;
}
  • 以上代碼片段中,foo() 的作用域中包含了標(biāo)識(shí)符 a, b, c 和 bar。無(wú)論表示聲明出現(xiàn)在作用域中的何處,這個(gè)標(biāo)識(shí)符所代表的變量和函數(shù)都附屬于所處作用域的作用域中。
  • bar() 中也擁有屬于自己的作用域,全局作用域也有屬于自己的作用域,它只包含了一個(gè)標(biāo)識(shí)符: foo()

由于標(biāo)識(shí)符 a, b, c 和 bar 都附屬于 foo() 的作用域內(nèi),因此無(wú)法從 foo() 的外部對(duì)它們進(jìn)行訪問(wèn)。也就是說(shuō),這些標(biāo)識(shí)符在全局作用域中是無(wú)法被訪問(wèn)到的,因此如下代碼會(huì)拋出 ReferenceError:

bar(); // ReferenceError: bar is not defined
console.log(a, b, c); // 全都拋出 ReferenceError
  • 但標(biāo)識(shí)符 a, b, c 和 bar 可在 foo() 的內(nèi)部被訪問(wèn)的。
  • 函數(shù)作用域的含義:屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用(在嵌套的作用域中也可以使用)。這種設(shè)計(jì)方案可根據(jù)需要改變值類(lèi)型的 "動(dòng)態(tài)" 特性。

隱藏內(nèi)部實(shí)現(xiàn)

  • 我們對(duì)函數(shù)的傳統(tǒng)認(rèn)知就是先聲明一個(gè)函數(shù),然后再向里面添加代碼,但反過(guò)來(lái)可帶來(lái)一些啟示:從所寫(xiě)的代碼中挑選出一個(gè)任意片段,然后就用函數(shù)聲明的方式對(duì)它進(jìn)行包裝,實(shí)際上就是把這些代碼 "隱藏" 起來(lái)了。
  • 實(shí)際的結(jié)果就是在這個(gè)代碼片段的周?chē)鷦?chuàng)建了一個(gè)新的作用域,也就是說(shuō)這段代碼中的任何聲明(變量或函數(shù))都將綁定在這個(gè)新創(chuàng)建的函數(shù)作用域中,而不是先前所在的作用域中。換句話說(shuō),可把變量和函數(shù)包裹在一個(gè)函數(shù)的作用域中,然后用這個(gè)作用域來(lái) "隱藏" 他們。

為什么 "隱藏" 變量和函數(shù)是一個(gè)有用的技術(shù)?

function doSomething(a) {
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
function doSomethingElse(a) {
return a - 1;
}
var b;
doSomething( 2 ); // 15
  • 上述代碼片段中,變量 b 和函數(shù) doSomethingElse(..) 應(yīng)該是 doSomething(..) 內(nèi)部具體實(shí)現(xiàn)的 "私有" 內(nèi)容。而上述代碼將變量 b 和函數(shù) doSomethingElse(..) 的訪問(wèn)權(quán)限放在了外部作用域中,這可能是 "危險(xiǎn)" 的。更 "合理" 的設(shè)計(jì)應(yīng)該是將這些私有內(nèi)容放在 doSomething(...) 的內(nèi)部。

如下:

function doSomething(a) {
function doSomethingElse(a) {
    return a - 1;
}
var b;
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
doSomething( 2 ); // 15

規(guī)避沖突

  • "隱藏" 作用域中的變量和函數(shù)的另一個(gè)好處是可避免同名標(biāo)識(shí)符的沖突,兩個(gè)標(biāo)識(shí)符名字相同但用途不同,無(wú)意間可能會(huì)造成命名沖突,而沖突會(huì)導(dǎo)致變量的值被意外覆蓋。

例如:

function foo() {
function bar(a) {
    i = 3; // 修改for 循環(huán)所屬作用域中的i
    console.log( a + i );
}
for (var i=0; i<10; i++) {
    bar( i * 2 ); // 糟糕,無(wú)限循環(huán)了!
}
}
foo();

bar(...) 內(nèi)部的賦值表達(dá)式 i = 3 意外地覆蓋了聲明在 foo(..) 內(nèi)部 for 循環(huán)中的 i。在這個(gè)例子中將會(huì)導(dǎo)致無(wú)限循環(huán),因?yàn)?i 被固定設(shè)置為 3,永遠(yuǎn)滿足小于 10 這個(gè)條件。

規(guī)則沖突的方式

全局命名空間:在全局作用域中聲明一個(gè)足夠獨(dú)特的變量,通常為一個(gè)對(duì)象,如下:

var MyReallyCoolLibrary = {
awesome: "stuff",
doSomething: function() {
    // ...
},
doAnotherThing: function() {
    // ...
}
}

模塊管理

函數(shù)作用域

  • 現(xiàn)在知道,在任意代碼片段外部添加包裝函數(shù),可將內(nèi)部的變量和函數(shù)定義 "隱藏" 起來(lái),外部作用域無(wú)法訪問(wèn)包裝函數(shù)內(nèi)部的任何內(nèi)容。

如下:

var a = 2;
function foo() { // <-- 添加這一行
var a = 3;
console.log( a ); // 3
} // <-- 以及這一行
foo(); // <-- 以及這一行
console.log( a ); // 2
  • 上述代碼會(huì)導(dǎo)致一些額外的問(wèn)題,首先,必需先聲明一個(gè)具名函數(shù) foo(), 這就意味著 foo 這個(gè)名稱(chēng)本身 "污染" 了所在作用域(上述代碼為全局作用域)。其次,必須顯式地通過(guò) foo() 來(lái)調(diào)用這個(gè)函數(shù)。
  • 如果函數(shù)不需要函數(shù)名(或者至少函數(shù)名可以不污染所在作用域),且能夠自行運(yùn)行,這將會(huì)更理想。

JavaScript 提供了兩種方案來(lái)解決:

var a = 2;
(function foo() {
// <-- 添加這一行
var a = 3;
console.log(a); // 3
})(); // <-- 以及這一行
console.log(a); // 2
  • 在上述代碼中,包裝函數(shù)的聲明以 (function... 而不僅是以 function... 開(kāi)始。函數(shù)會(huì)被當(dāng)做函數(shù)表達(dá)式而不是一個(gè)標(biāo)準(zhǔn)的函數(shù)聲明來(lái)處理。
  • 如何區(qū)分函數(shù)聲明和表達(dá)式?

    • 最簡(jiǎn)單的方式就是看 function 關(guān)鍵字出現(xiàn)在聲明中的位置(不僅僅是一行代碼,而是整個(gè)聲明中的位置)。如果 function 為聲明中的第一個(gè)關(guān)鍵字,那它就是一個(gè)函數(shù)聲明,否則就是一個(gè)函數(shù)表達(dá)式。
    • 函數(shù)聲明和函數(shù)表達(dá)式之間最重要的區(qū)別就是他們的名稱(chēng)標(biāo)識(shí)符將會(huì)綁定在何處。
  • 比較一下前面兩個(gè)代碼片段。第一個(gè)片段中 foo 被綁定在所在作用域中,可以直接通過(guò) foo() 來(lái)調(diào)用它。第二個(gè)片段中foo 被綁定在函數(shù)表達(dá)式自身的函數(shù)中而不是所在作用域中。
  • 換句話說(shuō),(function foo(){...}) 作為函數(shù)表達(dá)式意味著 foo 只能在 ... 所代表的位置中被訪問(wèn),外部作用域則不行。

匿名和具名

對(duì)于函數(shù)表達(dá)式最熟悉的就是回調(diào)參數(shù)了,如下:

setTimeout(function () {
console.log("I waited 1 second!");
}, 1000);
  • 這叫作匿名函數(shù)表達(dá)式,因?yàn)?nbsp;function().. 沒(méi)有名稱(chēng)標(biāo)識(shí)符。函數(shù)表達(dá)式可以是匿名的,而函數(shù)聲明則不可以省略函數(shù)名——在JavaScript 的語(yǔ)法中這是非法的。
  • 匿名函數(shù)表達(dá)式的缺點(diǎn)

    匿名函數(shù)在棧追蹤中不會(huì)顯示出有意義的函數(shù)名,這使調(diào)試很困難。

    如果沒(méi)有函數(shù)名,當(dāng)函數(shù)需要引用自身時(shí)只能通過(guò)已經(jīng)過(guò)期的 arguments.callee 來(lái)引用。

    匿名函數(shù)對(duì)代碼可讀性不是很友好。

上述代碼的改造結(jié)果:

setTimeout(function timeoutHandler() {
console.log("I waited 1 second!");
}, 1000);

立即執(zhí)行函數(shù)表達(dá)式

var a = 2;
(function IIFE() {
    var a = 3;
    console.log(a); // 3
})();
console.log(a); // 2
  • 由于函數(shù)被包含在一對(duì)( ) 括號(hào)內(nèi)部,因此成為了一個(gè)表達(dá)式,通過(guò)在末尾加上另外一個(gè)( ) 可以立即執(zhí)行這個(gè)函數(shù),比如(function foo(){ .. })()。第一個(gè)( ) 將函數(shù)變成表達(dá)式,第二個(gè)( ) 執(zhí)行了這個(gè)函數(shù)。
  • 立即執(zhí)行函數(shù)表達(dá)式的術(shù)語(yǔ)為:IIFE(Immediately Invoked Function Expression);

IIFE 的應(yīng)用場(chǎng)景

除了上述傳統(tǒng)的 IIFE 方式,還有另一個(gè)方式,如下:

var a = 2;
(function IIFE() {
var a = 3;
console.log(a); // 3
}());
console.log(a); // 2

第一種形式中函數(shù)表達(dá)式被包含在 ( ) 中,然后在后面用另一個(gè) () 括號(hào)來(lái)調(diào)用。第二種形式中用來(lái)調(diào)用的 () 括號(hào)被移進(jìn)了用來(lái)包裝的 ( ) 括號(hào)中。

這兩種方式的選擇全憑個(gè)人喜好。

IIFE 還有一種進(jìn)階用法,就是把他們當(dāng)做函數(shù)調(diào)用并傳遞參數(shù)進(jìn)去,如下:

var a = 2;
(function IIFE(global) {
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window);
console.log(a); // 2

IIFE 的另一個(gè)應(yīng)用場(chǎng)景是解決 undefined 標(biāo)識(shí)符的默認(rèn)值被錯(cuò)誤覆蓋導(dǎo)致的異常。

IIFE 的另一種變化的用途是倒置代碼的運(yùn)行順序,將需要運(yùn)行的函數(shù)放在第二位,在IIFE執(zhí)行之后當(dāng)做參數(shù)傳遞進(jìn)去。

var a = 2;
(function IIFE(def) {
def(window);
})(function def(global) {
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
});

函數(shù)表達(dá)式 def 定義在片段的第二部分,然后當(dāng)做參數(shù)(這個(gè)參數(shù)也叫做 def)被傳遞 IIFE 函數(shù)定義的第一部分中。最后,參數(shù) def(也就是傳遞進(jìn)去的函數(shù))被調(diào)用,并將 window 傳入當(dāng)做 global 參數(shù)的值。

塊作用域

將一個(gè)參數(shù)命名為 undefined, 但在對(duì)應(yīng)的位置不傳入任何值,這樣就可以就保證在代碼塊中 undefined 標(biāo)識(shí)符的值為 undefined

undefined = true; // 給其他代碼挖了一個(gè)大坑!絕對(duì)不要這樣做!
(function IIFE(undefined) {
var a;
if (a === undefined) {
    console.log("Undefined is safe here!");
}
})();

如下:

for (var i = 0; i < 5; i++){
console.log(i);
}
  • 在 for 循環(huán)中定義了變量 i,通常是想在 for 循環(huán)內(nèi)部的上下文中使用 i, 而忽略 i 會(huì)綁定在外部作用域(函數(shù)或全局)中。

修改后:

var foo = true;
if(foo) {
var bar = foo * 2;
bar = something(bar);
console.log(bar);
}
  • 上述代碼中,變量 bar 僅在 if 的上下文中使用,將它聲明在 if 內(nèi)部中式非常一個(gè)清晰的結(jié)構(gòu)。
  • 當(dāng)使用 var 聲明變量時(shí),它寫(xiě)在哪里都是一樣的,因?yàn)樗罱K都會(huì)屬于外部作用域。(這也就是變量提升)

with

  • 詞法作用域中介紹了 with 關(guān)鍵字,它不僅是一個(gè)難于理解的結(jié)構(gòu),同是也是一塊作用域的一個(gè)例子(塊作用域的一種形式),用 with 從對(duì)象中創(chuàng)建出的作用域僅在 with 所處作用域中有效。

try/catch

很少有人注意,JavaScript 在 ES3 規(guī)范 try/catch 的 catch 分句會(huì)創(chuàng)建一個(gè)塊作用域,其中聲明的變量?jī)H會(huì)在 catch 內(nèi)部有效。

try {
undefined(); // 目的是讓他拋出一個(gè)異常
} catch (error) {
console.log("error ------>", error); // TypeError: undefined is not a function
}
console.log("error ------>", error); // ReferenceError: error is not defined
  • error 僅存在于 catch 分句內(nèi)部,當(dāng)視圖從別處引用它時(shí)會(huì)拋出錯(cuò)誤。
  • 關(guān)于 catch 分句看起來(lái)只是一些理論,但還是會(huì)有一些有用的信息的,后續(xù)文章會(huì)提到。

let

  • JavaScript 在 ES6 中引入了 let 關(guān)鍵字。

let 關(guān)鍵字將變量綁定到所處的任意作用域中(通常是 { ... } 內(nèi)部)。換句話說(shuō),let 聲明的變量隱式地了所在的塊作用域。

var foo = true;
if(foo) {
var bar = foo * 2;
bar = something(bar);
console.log(bar);
}
console.log(bar); // ReferenceError: bar is not defined

使用 let 進(jìn)行的聲明不會(huì)再塊作用域中進(jìn)行提升。聲明的代碼被運(yùn)行前,聲明并不 "存在"。

{
console.log(bar); // ReferenceError
let bar = 2;
}

1. 垃圾收集

  • 另一個(gè)塊作用域很有用的原因和閉包中的內(nèi)存垃圾回收機(jī)制相關(guān)。

如下代碼:

function process(data) {
// do something
}
var someObj = {};
process(someObj);
var btn = document.getElementById('my_button');
btn.addEventListener('click', function click(evt) {
console.log('clicked');
}, /*capturingPhase=*/false);
  • click 函數(shù)的點(diǎn)擊回調(diào)并不需要 someReallyBigData 變量。理論上這意味著當(dāng) process(..) 執(zhí)行后,在內(nèi)存中占用大量空間的數(shù)據(jù)結(jié)構(gòu)就可以被垃圾回收了。但是,由于 click函數(shù)形成了一個(gè)覆蓋整個(gè)作用域的閉包,JavaScript 引擎極有可能依然保存著這個(gè)結(jié)構(gòu)(取決于具體實(shí)現(xiàn))。

修改后:

function process(data) {
// do something
}
// 在這個(gè)塊中定義內(nèi)容就可以銷(xiāo)毀了
{
var someObj = {};
process(someObj);
}
var btn = document.getElementById('my_button');
btn.addEventListener('click', function click(evt) {
console.log('clicked');
}, /*capturingPhase=*/false);

2. let循環(huán)

代碼如下:

for(let i = 0; i < 10; i++) {
console.log(i);
};
console.log(i); // ReferenceError
  • for 循環(huán)中的 let 不僅將 i 綁定了for 循環(huán)內(nèi)部的塊中,事實(shí)上他將其重新綁定到了循環(huán)的每一次迭代中,確保使用上一個(gè)循環(huán)迭代結(jié)束時(shí)的值重新進(jìn)行賦值。

下面通過(guò)另一種方式來(lái)說(shuō)明每次迭代時(shí)進(jìn)行重新綁定的行為;

{
let i;
for(i = 0; i < 10; i++) {
    let j = i; // 每次迭代中重新綁定
    console.log(j);
};
}
  • let 聲明附屬與一個(gè)新的作用域而不是當(dāng)前的函數(shù)作用域(也不屬于全局作用域)。

考慮一下代碼:

var foo = true, baz = 10;
if (foo) {
var bar = 3;
if (baz > bar) {
    console.log( baz );
}
// ...
}

這段代碼可以簡(jiǎn)單地被重構(gòu)成下面的同等形式:

var foo = true, baz = 10;
if (foo) {
var bar = 3;
// ...
}
if (baz > bar) {
console.log( baz );
}

但是在使用塊級(jí)作用域的變量時(shí)需要注意以下變化:

var foo = true, baz = 10;
if (foo) {
let bar = 3;
if (baz > bar) { // <-- 移動(dòng)代碼時(shí)不要忘了 bar!
    console.log( baz );
}
}

const

ES6 還引入了 const, 同樣可用來(lái)創(chuàng)建塊級(jí)作用域,但其值是固定的(常量), 不可修改。

var foo = true;
if (foo) {
var a = 2;
const b = 3; // 包含在 if 中的塊作用域常量
a = 3; // 正常 !
b = 4; // 錯(cuò)誤 !
}
console.log( a ); // 3
console.log( b ); // ReferenceError!

小結(jié)

  • 函數(shù)時(shí) JavaScript 中最常見(jiàn)的作用域單元。
  • 塊作用域值的是變量和函數(shù)布局可以屬于所處的作用域,也可以屬于某個(gè)代碼塊(通常指 {...} 內(nèi)部)
  • 從 ES3 開(kāi)始, try/catch 結(jié)構(gòu)在 catch 分句中具有塊作用域。
  • 從 ES6 引入了 let,const 關(guān)鍵字來(lái)創(chuàng)建塊級(jí)作用域。

以上就是JavaScript中塊級(jí)作用域與函數(shù)作用域的詳細(xì)內(nèi)容,更多關(guān)于JavaScript塊級(jí)函數(shù)作用域的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論