javascript
理解 JavaScript 作用域
上一篇文章中分析了 JS 中的數(shù)據(jù)類型和變量。這一篇文章將分析作用域,以及回答上一篇文章中變量提升的原因。
什么是作用域
作用域是一套規(guī)則,保存著變量,等待被引擎所查找。
var a = 1; console.log(a); // => 1 console.log(b); // => ReferenceError當(dāng)打印 a 時(shí),引擎就去作用域中查找 a,找到把結(jié)果返回。如果查找失敗,那么就會報(bào)錯(cuò)。
詞法作用域
JS 采用的詞法作用域,也可以說是靜態(tài)作用域。簡單來說,詞法作用域是由寫代碼時(shí)將變量寫在哪里決定的。
先看一段代碼:
var a = 1;function fn() {var a = 2;return a; }fn(); // => 2當(dāng)執(zhí)行函數(shù) fn 時(shí),會返回 2,而不是 1。
作用域查找
JS 引擎會進(jìn)行兩種查找,LHS 和 RHS。怎么理解?L 和 R 可以說代表左和右。什么的左和右?賦值操作的。
這里的賦值操作不一定出現(xiàn) =,比如參數(shù)傳遞也是一個(gè)賦值操作。
當(dāng)變量出現(xiàn)在賦值操作的左邊時(shí),引擎就會對這個(gè)變量進(jìn)行 LHS 查找;當(dāng)出現(xiàn)在右邊時(shí)(這個(gè)還可以理解為取得變量的源值),就會進(jìn)行 RHS 查找。
function foo(a) {console.log(a); }foo(2);對于變量 a 來說,引擎會進(jìn)行兩次查找,1 次 LHS,1 次 RHS。
調(diào)用 foo(),并傳入?yún)?shù) 2,這時(shí)存在著一個(gè)賦值操作即 a = 2,進(jìn)行一次 LHS 查找。打印 a 時(shí),需要獲取 a 的源值,所以進(jìn)行一次 RHS 查找。
如果查詢失敗呢?
對于 LHS 來說,給未聲明的賦值就會查詢失敗。
a = 2;但是我們知道,上面的代碼在非嚴(yán)格模式下并不會報(bào)錯(cuò),而變量 a 會被自動創(chuàng)建。
而對于 RHS 來說,直接使用未聲明的變量就會報(bào) ReferenceError。
console.log(a); // => ReferenceError另外,RHS 雖然查詢成功,但是卻對查詢結(jié)果進(jìn)行非法操作,就會報(bào) TypeError。
var foo = 1; foo(); // => TypeError作用域鏈
前面說,作用域是根據(jù)名稱查找變量的一套規(guī)則。而在實(shí)際情況中,經(jīng)常出現(xiàn)多個(gè)作用域嵌套的情況。
function foo(a) {console.log(a + b); } var b = 2; foo(2); // => 4當(dāng)引擎對 b 進(jìn)行 RHS 查找時(shí),在當(dāng)前作用域無法找到,引擎就會在外層作用域中查找,直到找到這個(gè)變量,或者直到抵達(dá)最外層作用域(全局作用域)為止。
LHS 查找也是如此。
把這樣一層一層嵌套的作用域,叫做作用域鏈。
函數(shù)作用域
函數(shù)作用域是指,屬于這個(gè)函數(shù)的全部變量都可以在這個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用。
function foo() {var a = 1; }console.log(a); // => ReferenceError也就是說,函數(shù)外部將無法訪問函數(shù)內(nèi)部的變量。
但是這卻是非常有用的。我們可以利用函數(shù)隱藏內(nèi)部實(shí)現(xiàn),使其外部無法訪問、修改等。
立即執(zhí)行函數(shù)表達(dá)式
利用函數(shù)作用域,可以將外部作用域無法訪問的內(nèi)容包裝起來。但是,帶來了額外的一個(gè)問題,函數(shù)名本身“污染”了所在的作用域。
這時(shí),就提出了 IIFE(立即執(zhí)行函數(shù)表達(dá)式)。
(function foo() {// ... }());即包裝了內(nèi)部函數(shù),又避免了引入函數(shù)名。因?yàn)檫@個(gè)函數(shù)名無法被外部作用域所訪問。
IIFE 的進(jìn)階用法是給其傳入?yún)?shù):
(function fn(global) {// ... })(window);這樣的好處是可以縮短查詢時(shí)的作用域鏈。
塊作用域
ES6,通過 let 和 const 引入了塊作用域。
if (true) {let a = 1; } console.log(a); // => ReferenceError變量提升
上一篇文章中中提到了變量提升。
在 JS 中,var a = 1; 這行代碼其實(shí)會被看成 var a 和 a = 2,并在兩個(gè)階段去執(zhí)行。
在編譯階段,執(zhí)行聲明操作;在執(zhí)行階段,執(zhí)行賦值操作。
所有的變量聲明都會被提升到作用域的頂部,這個(gè)過程叫做“提升”。
函數(shù)聲明也會發(fā)生提升,并且函數(shù)聲明會先于變量提升:
var foo = 1; function foo () {}typeof foo; // => 'number'注意,只有函數(shù)聲明會被提升,而函數(shù)表達(dá)式不會被提升。
var foo = 1; var foo = function () {}typeof foo; // => 'function'小結(jié)
這篇文章梳理了 JavaScript 中作用域的基本知識。
接下來會介紹執(zhí)行上下文和閉包這兩個(gè)概念,它們與作用域息息相關(guān)。
關(guān)于
這是我的公眾號,記錄著我的前端博客,沒事兒也分享一些電影、書籍。
歡迎一起交流學(xué)習(xí)。
總結(jié)
以上是生活随笔為你收集整理的理解 JavaScript 作用域的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 想跑次高频策略?快来看看Numpy处理真
- 下一篇: mysql索引为啥要选择B+树 (下)