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