作用域和閉包在JavaScript裡非常重要。但是在我最初學習JavaScript的時候,卻很難理解。我們先從作用域開始。本文主要和大家介紹了JavaScript作用域和閉包,希望能幫助大家更好的理解JavaScript作用域和閉包。
作用域
JavaScript的作用域限定了你可以存取哪些變數。有兩種作用域:全域作用域,局部作用域。
全域作用域
在所有函數宣告或大括號之外定義的變量,都在全域作用域裡。
不過這個規則只在瀏覽器中執行的JavaScript裡有效。如果你在Node.js裡,那麼全域作用域裡的變數就不一樣了,不過這篇文章不討論Node.js。
`const globalVariable = 'some value'`
一旦你宣告了一個全域變量,那麼你在任何地方都可以使用它,包括函數內部。
const hello = 'Hello CSS-Tricks Reader!' function sayHello () { console.log(hello) } console.log(hello) // 'Hello CSS-Tricks Reader!' sayHello() // 'Hello CSS-Tricks Reader!'
儘管你可以在全域作用域定義變量,但我們並不建議這樣做。因為可能會引起命名衝突,兩個或更多的變數使用相同的變數名稱。如果你在定義變數時使用了const或let,那麼在命名有衝突時,你就會收到錯誤提示。這是不可取的。
// Don't do this! let thing = 'something' let thing = 'something else' // Error, thing has already been declared
如果你定義變數時使用的是var,那麼第二次定義會覆寫第一次定義。這也會讓程式碼更難調試,也是不可取的。
// Don't do this! var thing = 'something' var thing = 'something else' // perhaps somewhere totally different in your code console.log(thing) // 'something else'
所以,你應該盡量使用局部變量,而不是全域變數
局部作用域
在你程式碼某一個具體範圍內使用的變數都可以在局部作用域內定義。這就是局部變數。
JavaScript裡有兩種局部作用域:函數作用域和區塊級作用域。
我們從函數作用域開始。
函數作用域
當你在函數裡定義一個變數時,它在函數內任何地方都可以使用。在函數之外,你就無法存取它了。
例如下面這個例子,在sayHello函數內的hello變數:
function sayHello () { const hello = 'Hello CSS-Tricks Reader!' console.log(hello) } sayHello() // 'Hello CSS-Tricks Reader!' console.log(hello) // Error, hello is not defined
區塊層級作用域
你在使用大括號時,宣告了一個const或是let的變數時,你就只能在大括號內部使用這個變數。
在下例中,hello只能在大括號內使用。
{ const hello = 'Hello CSS-Tricks Reader!' console.log(hello) // 'Hello CSS-Tricks Reader!' } console.log(hello) // Error, hello is not defined
區塊層級作用域是函數作用域的子集,因為函數是需要用大括號定義的,(除非你明確使用return語句和箭頭函數)。
函數提升和作用域
當使用function定義時,這個函數都會被提升到目前作用域的頂端。因此,下面的程式碼是等效的:
// This is the same as the one below sayHello() function sayHello () { console.log('Hello CSS-Tricks Reader!') } // This is the same as the code above function sayHello () { console.log('Hello CSS-Tricks Reader!') } sayHello()
使用函數表達式定義時,函數就不會被提升到變數作用域的頂端。
sayHello() // Error, sayHello is not defined const sayHello = function () { console.log(aFunction) }
因為這裡有兩個變量,函數提升可能會導致混亂,因此就不會生效。所以一定要在使用函數之前定義函數。
函數不能存取其他函數的作用域
在分別定義的不同的函數時,雖然可以在一個函數裡呼叫一個函數,但一個函數仍然不能存取其他函數的作用域內部。
下面這例,second就不能存取firstFunctionVariable這一變數。
function first () { const firstFunctionVariable = `I'm part of first` } function second () { first() console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined }
巢狀作用域
如果在函數內部又定義了函數,那麼內層函數可以存取外層函數的變量,但反過來則不行。這樣的效果就是詞法作用域。
外層函數並不能存取內部函數的變數。
function outerFunction () { const outer = `I'm the outer function!` function innerFunction() { const inner = `I'm the inner function!` console.log(outer) // I'm the outer function! } console.log(inner) // Error, inner is not defined }
如果把作用域的機制視覺化,你可以想像有一個雙向鏡(單面透視玻璃)。你能從裡面看到外面,但是外面的人不能看到你。
函數作用域就像是雙向鏡。你可以從裡面向外看,但是外面看不到你。
嵌套的作用域也是相似的機制,只是相當於有更多的雙向鏡。
多層函數就意味著多個雙向鏡。
理解前面關於作用域的部分,就能理解閉包是什麼了。
閉包
你在一個函數內新建另一個函數時,就等於是建立了一個閉包。內層函數就是閉包。通常情況下,為了能夠使得外部函數的內部變數可以訪問,一般都會回傳這個閉包。
function outerFunction () { const outer = `I see the outer variable!` function innerFunction() { console.log(outer) } return innerFunction } outerFunction()() // I see the outer variable!
因為內部函數是傳回值,因此你可以簡化函數宣告的部分:
function outerFunction () { const outer = `I see the outer variable!` return function innerFunction() { console.log(outer) } } outerFunction()() // I see the outer variable!
因為閉包可以存取外層函數的變量,因此他們通常有兩種用途:
減少副作用
建立私有變數
使用閉包控制副作用
#當你在函數傳回值時執行某些操作時,通常會發生一些副作用。副作用在很多情況下都會發生,例如Ajax調用,超時處理,或者哪怕是console.log的輸出語句:
function (x) { console.log('A console.log is a side effect!') }
當你使用閉包來控制副作用時,你實際上是需要考慮哪些可能會混淆程式碼工作流程的部分,例如Ajax或超時。
要把事情說清楚,還是看例子比較方便:
比如说你要给为你朋友庆生,做一个蛋糕。做这个蛋糕可能花1秒钟的时间,所以你写了一个函数记录在一秒钟以后,记录做完蛋糕这件事。
为了让代码简短易读,我使用了ES6的箭头函数:
function makeCake() { setTimeout(_ => console.log(`Made a cake`, 1000) ) }
如你所见,做蛋糕带来了一个副作用:一次延时。
更进一步,比如说你想让你的朋友能选择蛋糕的口味。那么你就给做蛋糕makeCake这个函数加了一个参数。
function makeCake(flavor) { setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000)) }
因此当你调用这个函数时,一秒后这个新口味的蛋糕就做好了。
makeCake('banana') // Made a banana cake!
但这里的问题是,你并不想立刻知道蛋糕的味道。你只需要知道时间到了,蛋糕做好了就行。
要解决这个问题,你可以写一个prepareCake的功能,保存蛋糕的口味。然后,在返回在内部调用prepareCake的闭包makeCake。
从这里开始,你就可以在你需要的时调用,蛋糕也会在一秒后立刻做好。
function prepareCake (flavor) { return function () { setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000)) } } const makeCakeLater = prepareCake('banana') // And later in your code... makeCakeLater() // Made a banana cake!
这就是使用闭包减少副作用:你可以创建一个任你驱使的内层闭包。
私有变量和闭包
前面已经说过,函数内的变量,在函数外部是不能访问的既然不能访问,那么它们就可以称作私有变量。
然而,有时候你确实是需要访问私有变量的。这时候就需要闭包的帮助了。
function secret (secretCode) { return { saySecretCode () { console.log(secretCode) } } } const theSecret = secret('CSS Tricks is amazing') theSecret.saySecretCode() // 'CSS Tricks is amazing'
这个例子里的saySecretCode函数,就在原函数外暴露了secretCode这一变量。因此,它也被成为特权函数。
使用DevTools调试
Chrome和Firefox的开发者工具都使我们能很方便的调试在当前作用域内可以访问的各种变量一般有两种方法。
第一种方法是在代码里使用debugger关键词。这能让浏览器里运行的JavaScript的暂停,以便调试。
下面是prepareCake的例子:
function prepareCake (flavor) { // Adding debugger debugger return function () { setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000)) } } const makeCakeLater = prepareCake('banana')
打开Chrome的开发者工具,定位到Source页下(或者是Firefox的Debugger页),你就能看到可以访问的变量了。
使用debugger调试prepareCake的作用域。
你也可以把debugger关键词放在闭包内部。注意对比变量的作用域:
function prepareCake (flavor) { return function () { // Adding debugger debugger setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000)) } } const makeCakeLater = prepareCake('banana')
调试闭包内部作用域
第二种方式是直接在代码相应位置加断点,点击对应的行数就可以了。
通过断点调试作用域
总结一下
闭包和作用域并不是那么难懂。一旦你使用双向镜的思维去理解,它们就非常简单了。
当你在函数里声明一个变量时,你只能在函数内访问。这些变量的作用域就被限制在函数里了。
如果你在一个函数内又定义了内部函数,那么这个内部函数就被称作闭包。它仍可以访问外部函数的作用域。
相关推荐:
javascript 词法作用域和闭包分析说明_javascript技巧
以上是JavaScript作用域與閉包詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

Dreamweaver CS6
視覺化網頁開發工具

禪工作室 13.0.1
強大的PHP整合開發環境

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。