摘要:本文為你不知道的上卷中關于作用域相關的知識點的總結。第一層代表當前作用域,大樓的頂層代表全局作用域。如果一定要找一個點與動態詞法作用域扯上關系的話,那就是值了。
作用域 賦值操作本文為《你不知道的JavaScript(上卷)》中關于作用域相關的知識點的總結。
LHS以及RHS變量的賦值操作實際上有兩個動作,首先編譯器會在當前作用域中聲明一個變量(如果之前沒有聲明過),然后在運行時引擎會在作用域中查找該變量,如果能夠找到就對它進行賦值。
在運行時引擎會在作用域中查找該變量
引擎對變量所做的查找分為LHS查詢以及RHS查詢,L和R分別代表一個賦值操作的左側以及右側。
講的稍微精確一點:RHS查詢與簡單地查找某個變量的值別無二致,而LHS則是試圖查找到變量的容器本身,從而對其進行賦值。
RHS可以理解成retrieve his source value(取到其源值),這意味著“得到某某的源值”。
深入一點:
console.log(a);
上訴代碼對于a的引用就是一個RHS引用,即查找然后取到a的值。
相比之下,
a = 2;
這里的a就是一個RHS引用。
我們可以簡單的記憶:
當變量出現在賦值操作的左側時進行LHS查詢,出現在賦值操作的右側時進行RHS查詢.
注意:作用域查找會在找到第一個匹配的標識符時停止
作用域嵌套作用域是根據名稱查找變量的一套規則
作用域嵌套的定義如下:
當一個塊或者函數嵌套在另一個塊或函數中時,就發生了作用域的嵌套。
理解作用域嵌套這一機制,我們就可以理解變量查找的順序:
在當前作用域查找變量。如果沒有,則進行下一步
判斷是否是全局作用域。如果是,則停止查找過程;如果不是,則進行下一步
進入當前作用域的外層作用域,并進行第一步
形象一點,我們可以把作用域查找想象成在大樓中找人。
第一層代表當前作用域,大樓的頂層代表全局作用域。
首先在當前樓層查找,如果沒有找到,則上一樓進行查找,一直到找到這個人或者找完整個大樓依然沒有找到為止。
異常報錯的種類如果能將LHS以及RHS進行很好的區分,那我們就能夠很好的理解瀏覽器所拋出的各種異常。
下舉幾種特別常見的報錯:
ReferenceError:
RHS查詢變量未找到值
嚴格模式LHS查詢失敗
TypeError:
RHS找到該變量值,但嘗試對這個變量的值進行不合理的操作(例如,引用null或者undefined類型的值中的屬性)
詞法作用域詞法作用域完全由寫代碼期間函數所聲明的位置來定義
欺騙詞法作用域注意:欺騙詞法作用域會導致性能下降
evalwitheval() 是一個危險的函數, 他執行的代碼擁有著執行者的權利。如果你運行eval()伴隨著字符串,那么你的代碼可能被惡意方(不懷好意的人)影響, 通過在使用方的機器上使用惡意代碼,可能讓你失去在網頁或者擴展程序上的權限。更重要的是,第三方代碼可以看到作用域在某一個eval()被調用的時候,這有可能導致一些不同方式的攻擊。相似的Function就是不容易被攻擊的。
根據你所傳遞給它的對象憑空創建了一個全新的詞法作用域
性能問題欺騙詞法作用域會導致性能下降,其原因在于編譯階段的性能優化不起作用。
JavaScript引擎會在即時編譯階段(during the compilation phase)進行數項的性能優化。其中的某些優化依賴于能夠根據代碼的詞法進行靜態分析,并預先確定所有變量和函數的定義位置,才能在執行的過程中快速找到標識符。
但是,編譯到含有eval和with的代碼時,編譯器無法知道eval或者with會接受什么代碼,自然無法做代碼優化。
函數作用域以及塊作用域隱藏組件內部實現函數作用域:屬于這個函數的全部變量都可以在整個函數的范圍內使用及復用(事實上在嵌套的作用域中也可以使用)。
開發者最主要是利用函數作用域實現隱藏組件或者API的內部實現,最小限度的暴露必要內容。
比如對于一些組件的開發,大家習慣于利用立即執行函數(function() {})()進行內部實現的封裝。
規避沖突利用函數作用域將變量保持在私有、無沖突的作用域中,這樣可以有效規避掉所有的沖突。
舉個例子,underscore這個庫里面有跟原生js一樣的方法map,那怎么區分這兩個方法呢?通過將map當做一個屬性掛載在underscore上面,這樣可以避免兩者的沖突。
立即執行函數表達式形式如下:
(function() {...})()
(function() {...})()
上面兩種形式沒有區別,可依個人興趣隨意使用。
立即執行函數表達式的一種進階用法就是把它們當做函數調用并傳遞參數進去。
各種類庫常見的用法是:
(function(global) { ... })(window)塊作用域
塊作用域目前在ES6中有如下體現:
let
const
with:用with從對象創建出的作用域僅在with聲明而非外部作用域中有效。
try/catch:catch分句會創建一個塊作用域,其中聲明的變量僅在catch內部有效。
例如:
for (let i; i < 4; i ++) { ... } console.log(i) // Uncaught ReferenceError: i is not defined
try { undefined(); } catch (err) { console.log(err); } console.log(err); // Uncaught ReferenceError: err is not defined作用域閉包
知乎上面有關于閉包的問題:什么是閉包?
其中寸志老師的解釋我認為是比較好的。
對于閉包,《你不知道的JavaScript(上卷)》這本書的解釋是:
當函數可以記住并訪問所在的詞法作用域時,就產生了閉包。
我們實際上來理解閉包時,需要特別注意是兩個點:函數和作用域。
簡單的來說,就是函數以及作用域的結合,注意,作用域必須是封閉的,其主要的表現形式就是函數中返回一個函數。
閉包在類庫、組件封裝中有太多的示例了,本文就不拓展了。
塊作用域與閉包的結合首先看一個單純的閉包的代碼:
for (var i = 0; i <= 5; i++) { (function() { var j = i; setTimeout(function timer(){ console.log(j); }, j * 1000) })() }
這段代碼就是在每次循環的時候創建一個新的封閉作用域,保存當次循環的i值。
再看一下下面的代碼:
for (let i = 0; i <= 5; i++) { setTimeout(function timer(){ console.log(i); }, i*1000) }
利用let創建塊作用域,當塊作用域與閉包結合之后,我們可以減少創建新的封閉作用域這一操作(var j = i);
that"s cool!
動態詞法作用域動態作用域鏈是基于調用棧的,而不是代碼中的作用域嵌套。
對于JavaScript,不存在動態作用域。如果一定要找一個點與動態詞法作用域扯上關系的話,那就是this值了。this值打算在下一篇文章中詳解。
變量提升舉個最簡單的例子:
alert(a); // undefined var a = 12;
有同樣作用的是函數聲明function,例如:
alert(func); // function func(){} function func() {};
但是函數表達式不會提升:
foo(); // TypeError var foo = function bar() { ... }
注意:僅有var和函數聲明function才可以變量提升。
函數聲明與函數表達式的區別:
區別函數聲明和函數表達式最簡單的方法是看function關鍵字出現在聲明中的位置(不僅僅是一行代碼,而是整個聲明中的位置)。如果function是聲明中的第一個詞,那么就是一個函數聲明,否則就是一個函數表達式。
ES6中新增的let以及const關鍵字不可以進行變量提升,我們可以嘗試一下:
// 1. let alert(a); // Uncaught ReferenceError: a is not defined let a = "abc"; // 2. const alert(b); // Uncaught ReferenceError: b is not defined const b = 123;函數優先
先來看下面的代碼:
foo(); // 1 var foo; function foo() { console.log(1); } foo = function() { console.log(2); }
上面的例子說明:
函數會被首先提升,然后才是變量
上面的代碼實際等于:
function foo() { console.log(1); } foo(); // 1 var foo; foo = function() { console.log(2); }模塊
模塊這一利器,在以前封裝插件用的非常多,示例如下:
var foo = (function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log(something) } function doAnother() { console.log(another.join("!")); } return { doSomething: doSomething, doAnother: doAnother } })()
模塊模式必備條件如下:
必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會創建一個新的莫模塊實例)。
封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態。
當然,說到模塊,我們不得不提到CMD、AMD、ES6 module等模塊機制了。
知乎上有提到AMD 和 CMD 的區別有哪些?
我這里簡單提一下兩者的區別:
AMD:
early executing(提前執行)
推薦依賴前置
示例:requireJs
CMD:
as lazy as possible(延遲執行)
推薦依賴就近
示例:seaJs
繼續聊一下ES6的模塊機制(import、export)。
import可以將一個模塊中的一個或多個API導入到當前的作用域中,并分別綁定在一個變量上。
export會將當前模塊的一個標識符(變量、函數)導出為公共API。
Github有很多基于es6實現的代碼功能,請自行查閱。
結語好了,作用域相關的點整理完了,我將其中主要分成三部分:
作用域
提升
模塊
如果有遺漏,歡迎指正~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81796.html
摘要:關于本書,我會寫好幾篇讀書筆記用以記錄那些讓我恍然大悟的瞬間,本文是第一篇弄懂的作用域和閉包。作用域也可以看做是一套依據名稱查找變量的規則。聲明實際上是根據你傳遞給它的對象憑空創建了一個全新的詞法作用域。 《你不知道的JavaScript》真的是一本好書,閱讀這本書,我有多次哦,原來是這樣的感覺,以前自以為理解了(其實并非真的理解)的概念,這一次真的理解得更加透徹了。關于本書,我會寫好...
摘要:假設有兩個域名域名域名域名有分級的概念,也就是說域名與域名都是的子域名,又是的子域名在域名所使用的服務中,可以設置域名在服務端設置的時候,設置為或沒有區別,注意前面的點,即只要是為顯式的聲明,前面帶不帶點沒有區別。 1 Cookie簡介 Cookie是由W3C組織提出,最早由NetScape社區發展的一種機制。Cookie是存儲于訪問者的計算機中的變量。每當同一臺計算機通過瀏覽器請求某...
摘要:假設有兩個域名域名域名域名有分級的概念,也就是說域名與域名都是的子域名,又是的子域名在域名所使用的服務中,可以設置域名在服務端設置的時候,設置為或沒有區別,注意前面的點,即只要是為顯式的聲明,前面帶不帶點沒有區別。 1 Cookie簡介 Cookie是由W3C組織提出,最早由NetScape社區發展的一種機制。Cookie是存儲于訪問者的計算機中的變量。每當同一臺計算機通過瀏覽器請求某...
摘要:原文鏈接原文作者你想知道的關于作用域的一切譯中有許多章節是關于的但是對于初學者來說甚至是一些有經驗的開發者這些有關作用域的章節既不直接也不容易理解這篇文章的目的就是為了幫助那些想更深一步學習了解作用域的開發者尤其是當他們聽到一些關于作用域的 原文鏈接: Everything you wanted to know about JavaScript scope原文作者: Todd Mott...
摘要:嵌套對象成員會造成重大性能影響盡量少用。一般來說你可以通過這種方法提高代碼的性能將經常使用的對象成員數組項和域外變量存入局部變量中。在反復訪問的地方使用局部變量存放引用小心地處理集合因為他們表現出存在性總是對底層文檔重新查詢。 前言 本期我來給大家推薦的書是《高性能JavaScript》,在這本書中我們能夠了解 javascript 開發過程中的性能瓶頸,如何提升各方面的性能,包括代碼...
閱讀 3503·2021-11-24 09:39
閱讀 781·2019-08-30 14:22
閱讀 3031·2019-08-30 13:13
閱讀 2310·2019-08-29 17:06
閱讀 2918·2019-08-29 16:22
閱讀 1255·2019-08-29 10:58
閱讀 2427·2019-08-26 13:47
閱讀 1628·2019-08-26 11:39