說起js中的with關鍵字,很多小夥伴們的第一印象可能就是with關鍵字的作用在於改變作用域,然後最關鍵的一點是不推薦使用with關鍵字。聽到不推薦with關鍵字後,我們很多人都會忽略掉with關鍵字,認為不要去管它用它就可以了。但是有時候,我們在看一些程式碼或者面試題的時候,其中會有with關鍵字的相關問題,很多坑是你沒接觸過的,所以還是有必要說說with這一個關鍵字。
基本說明
在js高階程式設計中是這樣描述with關鍵字的:with語句的作用是將程式碼的作用域設定到一個特定的作用域中,基本語法如下:
1 |
with (expression) statement; |
使用with關鍵字的目的是為了簡化多次編寫訪問同一物件的工作,比如下面的例子:
1 2 3 4 5 |
var qs = location.search.substring(1); var hostName = location.hostname; var url = location.href; |
這幾行程式碼都是訪問location物件中的屬性,如果使用with關鍵字的話,可以簡化程式碼如下:
1 2 3 4 5 6 7 8 9 |
with (location){ var qs = search.substring(1); var hostName = hostname; var url = href; } |
在這段程式碼中,使用了with語句關聯了location物件,這就以為著在with程式碼塊內部,每個變數首先被認為是一個區域性變數,如果區域性變數與location物件的某個屬性同名,則這個區域性變數會指向location物件屬性。
注意:在嚴格模式下不能使用with語句。
with關鍵字的弊端
前面的基本說明中,我們可以看到with的作用之一是簡化程式碼。但是為什麼不推薦使用呢?下面我們來說說with的缺點:
1、效能問題
2、語義不明,除錯困難
效能問題
首先說說效能問題,關於使用with關鍵字的效能問題,首先我們來看看兩段程式碼:
第一段程式碼是沒有使用with關鍵字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function func() { console.time("func"); var obj = { a: [1, 2, 3] }; for (var i = 0; i < 100000; i++) { var v = obj.a[0]; } console.timeEnd("func");//0.847ms } func(); |
第二段程式碼使用了with關鍵字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function funcWith() { console.time("funcWith"); var obj = { a: [1, 2, 3] }; var obj2 = { x: 2 }; with (obj2) { console.log(x); for (var i = 0; i < 100000; i++) { var v = obj.a[0]; } } console.timeEnd("funcWith");//84.808ms } funcWith(); |
在使用了with關鍵字後了,程式碼的效能大幅度降低。第二段程式碼的with語句作用到了obj2這個物件上,然後with塊裡面訪問的卻是obj物件。有一種觀點是:使用了with關鍵字後,在with塊內訪問變數時,首先會在obj2上查詢是否有名為obj的屬性,如果沒有,再進行下一步查詢,這個過程導致了效能的降低。但是程式效能真正降低的原因真的是這樣嗎?
我們修改一下第二段程式碼,修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function funcWith() { console.time("funcWith"); var obj = { a: [1, 2, 3] }; with (obj) { for (var i = 0; i < 100000; i++) { var v = a[0]; } } console.timeEnd("funcWith");//88.260ms } funcWith(); |
這段程式碼將with語句作用到了obj物件上,然後直接使用a訪問obj的a屬性,按照前面說到的觀點,訪問a屬性時,是一次性就可以在obj上找到該屬性的,但是為什麼程式碼效能依舊降低了呢。
真正的原因是:使用了with關鍵字後,JS引擎無法對這段程式碼進行優化。
JS引擎在程式碼執行之前有一個編譯階段,在不使用with關鍵字的時候,js引擎知道a是obj上的一個屬性,它就可以靜態分析程式碼來增強識別符號的解析,從而優化了程式碼,因此程式碼執行的效率就提高了。使用了with關鍵字後,js引擎無法分辨出a變數是區域性變數還是obj的一個屬性,因此,js引擎在遇到with關鍵字後,它就會對這段程式碼放棄優化,所以執行效率就降低了。
使用with關鍵字對效能的影響還有一點就是js壓縮工具,它無法對這段程式碼進行壓縮,這也是影響效能的一個因素。
語義不明,難以除錯
前面說到除了效能的問題,with還存在的一個缺點語義不明,難以除錯,就是造成程式碼的不易閱讀,而且可能造成潛在的bug。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function foo(obj) { with (obj) { a = 2; } } var o1 = { a: 3 }; var o2 = { b: 3 }; foo(o1); console.log(o1.a); // 2 foo(o2); console.log( o2.a ); // undefined console.log( a ); // 2 |
這段程式碼很容易理解了,在foo函式內,使用了with關鍵字來訪問傳進來的obj物件,然後修改a屬性。當傳入o1物件時,因為o1物件存在著a屬性,所以這樣沒有問題。傳入o2物件時,在修改a屬性時,由於o2物件沒有a這個屬性,所以被修改的a屬性則變成了全域性變數。這就造成了潛在的bug。
延伸分析
前面說了那麼多,相信大家已經理解了為什麼不推薦使用with關鍵字以及可能存在的問題。下面我們來看看一些更復雜的情況,看下面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var obj = { x: 10, foo: function () { with (this) { var x = 20; var y = 30; console.log(y);//30 } } }; obj.foo(); console.log(obj.x);//20 console.log(obj.y);//undefined |
在這段程式碼中,分別輸出30,20,undefined的。涉及的知識點也比較多:with關鍵字,this關鍵字,變數提升等等,我們來一一解釋一下。
1、this關鍵字
關於this關鍵字的文章google上面相當多,這裡不再贅述,我們只需記住一點:this關鍵字始終指向呼叫函式的物件。在這裡,foo函式中,this指向的就是obj物件。因此在with(this)語句塊裡面,可以直接通過x變數來訪問obj的x屬性。
2、變數提升
js中的變數提升也是一個經常遇到的問題,我們可以簡單理解成在js中,變數宣告會被提升到函式的頂部,儘管有的時候,它是在後面宣告的。
所以上面的程式碼可以解析為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var obj = { x: 10, foo: function () { var x;//宣告區域性變數x var y;//宣告區域性變數y with (obj) { x = 20;//訪問變數x,在obj上找到x,則修改為20 y = 30;//訪問變數y,在bojg上找不到y,則進一步查詢,找到區域性變數y,修改為30 console.log(y);//30//直接輸出區域性變數y, } } }; obj.foo(); console.log(obj.x);//20,obj.x已被修改為20 console.log(obj.y);//undefined,obj不存在y屬性,則為undefined |
上面的註釋中,解釋了程式碼的執行過程,相信大家已經理解了為什麼會出處30,20,undefined的原因。
有興趣的同學可以看看下面這段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
({ x: 10, foo: function () { function bar() { console.log(x); console.log(y); console.log(this.x); } with (this) { var x = 20; var y = 30; bar.call(this); } } }).foo(); |
這段程式碼會輸出什麼?為什麼呢?
總結
本文總結了with語句的特點和弊端,總的來說,強烈不推薦使用with關鍵字。其實在日常編碼中,我們只需要知道不去使用with就可以了,但是有的時候我們可能會遇到一些關於with的奇奇怪怪的問題,想要找出真正的原因,就要深入理解with關鍵字,這有助於我們去深入學習JS這門語言,同時也是學習JS的一個樂趣。歡迎小夥伴們來交流!!