由?JavaScript?的?with?引發(fā)的探索
一下文章來源于微信公眾號(hào)前端巔峰
1. 背景
某天吃飯的時(shí)候突然想到,都說 with
會(huì)有問題,那么是什么問題,是怎樣導(dǎo)致的呢?知其然不知其所以然,在好奇心的驅(qū)使下,從 with
出發(fā),一路追溯到 VO、AO。那么先來復(fù)習(xí)一下 with 是干嘛的吧。
2. with
js 的 with
是為對(duì)象訪問提供命名空間式的訪問方式,with 創(chuàng)建一個(gè)對(duì)象的命名空間,在這個(gè)命名空間內(nèi)你可以直接訪問對(duì)象的屬性,而不需要通過對(duì)象來訪問:
const o = { a: 1, b: 2 }; with (o) { ? ? console.log(a); // 1 ? ? b = 3; // o: { a: 1, b: 3 } }
看起來挺方便的哈?
const o = { a: 1, b: 2 }; const p = { a: 3 }; with (o) { ? ? console.log(a); // 1 ? ? with (p) { ? ? ? ? console.log(a); // 3 ? ? ? ? b = 4; // o: { a: 1, b: 4 } ? ? ? ? c = 5; // window.c = 5 ? ? } }
嗯,他還有作用域鏈的性質(zhì)。但是,如果給不存在的屬性賦值,將會(huì)沿著作用域鏈給該變量賦值,在沒有找到變量時(shí),非嚴(yán)格模式下將會(huì)自動(dòng)在全局創(chuàng)建一個(gè)變量。這就導(dǎo)致了數(shù)據(jù)泄露。
那么導(dǎo)致數(shù)據(jù)泄露的原因是什么呢?這需要了解 LHS 查詢,這個(gè)待會(huì)再說。
那來看看 js 是怎么查詢的:當(dāng) with 對(duì)象 o 的時(shí)候,with 聲明的作用域是 o,從這里對(duì) c 進(jìn)行 LHS 查詢。o 的作用域和全局作用域都沒有找到 c,在非嚴(yán)格模式下,失敗的 LHS 會(huì)自動(dòng)隱式的在全局創(chuàng)建一個(gè)標(biāo)識(shí)符 c,如果是嚴(yán)格模式,則會(huì)拋出 ReferenceError
。
2.1. with 的性能問題
使用 with:
將近 10 倍的差距。
原因是什么呢?
js 預(yù)編譯階段會(huì)進(jìn)行的優(yōu)化,由于 with 創(chuàng)建新的詞法作用域,導(dǎo)致 o 的 a 屬性和 o 分離開位于兩個(gè)不同的作用域,不能快速找到標(biāo)識(shí)符,引擎將不會(huì)做任何優(yōu)化。
這就好比你去某家店,引擎給了你一個(gè)牛逼的老大,老板一眼就知道該怎么做;套上 with 之后,老板不知道你的老大是誰,還要花時(shí)間去找,時(shí)間就這樣浪費(fèi)了。
3. LHS 和 RHS
- LHS:賦值操作的目標(biāo)是誰
- RHS:誰是賦值操作的源頭
所以我們來看這段代碼:
var a = 1;
在執(zhí)行的時(shí)候,這段代碼會(huì)被拆成兩部分
var a; a = 1;
當(dāng)我們使用 a
時(shí)
console.log(a);
對(duì) a 進(jìn)行了 RHS 查詢,以獲得 a 的值,并隱式賦值給console.log
函數(shù)的形參,賦值對(duì)象的查找也是 LHS 查詢。
當(dāng)然,更直白的 LHS 也如上文寫到的 a = 1
。
在變量還沒有聲明(在任何作用域中都無法找到該變量)情況下,這兩種查詢行為是不一樣的。
LHS 和 RHS 查詢都會(huì)在當(dāng)前執(zhí)行作用域中開始,沿著作用域鏈向上查找,直到全局作用域。
而不成功的 RHS 會(huì)拋出 ReferenceError
,不成功的 LHS 會(huì)自動(dòng)隱式地創(chuàng)建一個(gè)全局變量(非嚴(yán)格模式),并作為 LHS 的查詢結(jié)果(嚴(yán)格模式也會(huì)拋出 ReferenceError
)。
那么,復(fù)習(xí)一下作用域鏈的查找吧。
4. 執(zhí)行上下文和作用域鏈
在 js 中有三種代碼運(yùn)行環(huán)境:
- 全局執(zhí)行環(huán)境
- 函數(shù)執(zhí)行環(huán)境
- Eval 執(zhí)行環(huán)境
js 代碼執(zhí)行的時(shí)候,為了區(qū)分運(yùn)行環(huán)境,會(huì)進(jìn)入不同的執(zhí)行上下文(Execution context
,EC),這些執(zhí)行上下文會(huì)構(gòu)成一個(gè)執(zhí)行上下文棧(Execution context stack
,ECS
)。
對(duì)于每個(gè) EC 都有一個(gè)變量對(duì)象(Variable object
,VO),作用域鏈(Scope chain
)和 this 三個(gè)主要屬性。
4.1. VO
變量對(duì)象(Variable object
)是與 EC 相關(guān)的作用域,存儲(chǔ)了在 EC 中定義的變量和函數(shù)聲明。VO 中一般會(huì)包含以下信息:
- 變量
- 函數(shù)聲明式
- 函數(shù)的形參
而函數(shù)表達(dá)式和沒有用 var
、let
、const
聲明的變量(全局變量,存在于全局 EC 的 VO)不存在于 VO 中。對(duì)于全局 VO,有 VO === this === global。
4.2. AO
在函數(shù) EC 中,VO 是不能直接訪問的,此時(shí)由激活對(duì)象(Activation Object
,AO
)來替代 VO 的角色。AO 是在進(jìn)入函數(shù) EC 時(shí)被創(chuàng)建的,它通過函數(shù)的 arguments
進(jìn)行初始化。這時(shí),VO === AO。
如:
function foo(b) { ? ? const a = 1; } foo(1); // AO:{ arguments: {...}, a: 1, b: 1 }
4.3. 作用域鏈
了解了 EC、AO、VO,再來看作用域鏈
var x = 10; function foo() { ? ? var y = 20; ? ?? ? ? function bar() { ? ? ? ? var z = 30; ? ? ? ? ? ? ? ? console.log(x + y + z); ? ? }; ? ?? ? ? bar() }; foo();
- 橙色箭頭指向 VO/AO
- 藍(lán)色箭頭們則是作用域鏈(VO/AO + All Parent VO/AOs)
理解了查找過程,很容易想到 js 中的原型鏈,而作用域鏈和原型鏈則可以組合成一個(gè)二維查找:先通過作用域鏈查找到某個(gè)對(duì)象,再查找這個(gè)對(duì)象上的屬性。
const foo = {} function bar() { ? ? Object.prototype.a = 'Set foo.a from prototype'; ? ? returnfunction () { ? ? ? ? console.log(foo.a); ? ? } } bar()();? // Set foo.a from prototype
到此這篇關(guān)于由 JavaScript 的 with 引發(fā)的探索的文章就介紹到這了,更多相關(guān) JavaScript 的 with 引發(fā)的探索內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js實(shí)現(xiàn)鼠標(biāo)點(diǎn)擊文本框自動(dòng)選中內(nèi)容的方法
這篇文章主要介紹了js實(shí)現(xiàn)鼠標(biāo)點(diǎn)擊文本框自動(dòng)選中內(nèi)容的方法,涉及javascript鼠標(biāo)點(diǎn)擊事件onClick及選擇事件select的使用技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-08-08JavaScript 用Node.js寫Shell腳本[譯]
你懂JavaScript嗎?你需要寫一個(gè)Shell腳本嗎?那么你應(yīng)該試一下Node.js,它很容易安裝,而且很適合通過寫Shell腳本來學(xué)習(xí)它2012-09-09javascript獲取xml節(jié)點(diǎn)的最大值(實(shí)現(xiàn)代碼)
這篇文章主要介紹了利用javascript獲取xml節(jié)點(diǎn)的最大值。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-12-12JavaScript解析JSON格式數(shù)據(jù)的方法示例
這篇文章主要介紹了JavaScript解析JSON格式數(shù)據(jù)的方法,結(jié)合實(shí)例形式分析了JavaScript解析json格式數(shù)據(jù)的常用函數(shù)與使用技巧,需要的朋友可以參考下2017-01-01JS實(shí)現(xiàn)的仿東京商城菜單、仿Win右鍵菜單及仿淘寶TAB特效合集
這篇文章主要介紹了JS實(shí)現(xiàn)的仿東京商城菜單、仿Win右鍵菜單及仿淘寶TAB特效合集,以實(shí)例形式較為詳細(xì)的分析了JavaScript實(shí)現(xiàn)動(dòng)態(tài)添加下拉菜單及響應(yīng)鼠標(biāo)事件生成菜單等實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-09-09vue2.0實(shí)戰(zhàn)之基礎(chǔ)入門(1)
這篇文章主要為大家詳細(xì)介紹了vue2.0實(shí)戰(zhàn)第一篇基礎(chǔ)入門的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03js 獲取網(wǎng)絡(luò)圖片的高度和寬度的實(shí)現(xiàn)方法(變通了下)
簡(jiǎn)單地說就是把圖片放入一個(gè)自動(dòng)伸縮的DIV中,然后獲取DIV的寬和高!這個(gè)不錯(cuò)的變通,大家可以參考下。2009-10-10