您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關由JavaScript的with 引發的探索是怎樣的,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
某天吃飯的時候突然想到,都說 with
會有問題,那么是什么問題,是怎樣導致的呢?知其然不知其所以然,在好奇心的驅使下,從 with
出發,一路追溯到 VO、AO。那么先來復習一下 with 是干嘛的吧。
js 的 with
是為對象訪問提供命名空間式的訪問方式,with 創建一個對象的命名空間,在這個命名空間內你可以直接訪問對象的屬性,而不需要通過對象來訪問:
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 } }
嗯,他還有作用域鏈的性質。但是,如果給不存在的屬性賦值,將會沿著作用域鏈給該變量賦值,在沒有找到變量時,非嚴格模式下將會自動在全局創建一個變量。這就導致了數據泄露。
那么導致數據泄露的原因是什么呢?這需要了解 LHS 查詢,這個待會再說。
那來看看 js 是怎么查詢的:當 with 對象 o 的時候,with 聲明的作用域是 o,從這里對 c 進行 LHS 查詢。o 的作用域和全局作用域都沒有找到 c,在非嚴格模式下,失敗的 LHS 會自動隱式的在全局創建一個標識符 c,如果是嚴格模式,則會拋出 ReferenceError
。
使用 with:
function f () { const o = { a: 1 } console.time(); with(o) { for (let i = 0; i < 100000; i++) { a = 2; } } console.timeEnd(); }
正常情況:
function f () { const o = { a: 1 } console.time(); for (let i = 0; i < 100000; i++) { o.a = 2; } console.timeEnd(); }
將近 10 倍的差距。
原因是什么呢?
js 預編譯階段會進行的優化,由于 with 創建新的詞法作用域,導致 o 的 a 屬性和 o 分離開位于兩個不同的作用域,不能快速找到標識符,引擎將不會做任何優化。
這就好比你去某家店,引擎給了你一個牛逼的老大,老板一眼就知道該怎么做;套上 with 之后,老板不知道你的老大是誰,還要花時間去找,時間就這樣浪費了。
LHS:賦值操作的目標是誰
RHS:誰是賦值操作的源頭
所以我們來看這段代碼:
var a = 1;
在執行的時候,這段代碼會被拆成兩部分
var a; a = 1;
當我們使用 a
時
console.log(a);
對 a 進行了 RHS 查詢,以獲得 a 的值,并隱式賦值給console.log
函數的形參,賦值對象的查找也是 LHS 查詢。
當然,更直白的 LHS 也如上文寫到的 a = 1
。
在變量還沒有聲明(在任何作用域中都無法找到該變量)情況下,這兩種查詢行為是不一樣的。
LHS 和 RHS 查詢都會在當前執行作用域中開始,沿著作用域鏈向上查找,直到全局作用域。
而不成功的 RHS 會拋出 ReferenceError
,不成功的 LHS 會自動隱式地創建一個全局變量(非嚴格模式),并作為 LHS 的查詢結果(嚴格模式也會拋出 ReferenceError
)。
那么,復習一下作用域鏈的查找吧。
在 js 中有三種代碼運行環境:
全局執行環境
函數執行環境
Eval 執行環境
js 代碼執行的時候,為了區分運行環境,會進入不同的執行上下文(Execution context
,EC),這些執行上下文會構成一個執行上下文棧(Execution context stack
,ECS
)。
對于每個 EC 都有一個變量對象(Variable object
,VO),作用域鏈(Scope chain
)和 this 三個主要屬性。
變量對象(Variable object
)是與 EC 相關的作用域,存儲了在 EC 中定義的變量和函數聲明。VO 中一般會包含以下信息:
變量
函數聲明式
函數的形參
而函數表達式和沒有用 var
、let
、const
聲明的變量(全局變量,存在于全局 EC 的 VO)不存在于 VO 中。對于全局 VO,有 VO === this === global。
在函數 EC 中,VO 是不能直接訪問的,此時由激活對象(Activation Object
,AO
)來替代 VO 的角色。AO 是在進入函數 EC 時被創建的,它通過函數的 arguments
進行初始化。這時,VO === AO。
如:
function foo(b) { const a = 1; } foo(1); // AO:{ arguments: {...}, a: 1, b: 1 }
了解了 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
藍色箭頭們則是作用域鏈(VO/AO + All Parent VO/AOs)
理解了查找過程,很容易想到 js 中的原型鏈,而作用域鏈和原型鏈則可以組合成一個二維查找:先通過作用域鏈查找到某個對象,再查找這個對象上的屬性。
const foo = {} function bar() { Object.prototype.a = 'Set foo.a from prototype'; returnfunction () { console.log(foo.a); } } bar()(); // Set foo.a from prototype
以上就是由JavaScript的with 引發的探索是怎樣的,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。