天天看點

了解javascript中的with關鍵字

說起js中的with關鍵字,很多小夥伴們的第一印象可能就是with關鍵字的作用在于改變作用域,然後最關鍵的一點是不推薦使用with關鍵字。聽到不推薦with關鍵字後,我們很多人都會忽略掉with關鍵字,認為不要去管它用它就可以了。但是有時候,我們在看一些代碼或者面試題的時候,其中會有with關鍵字的相關問題,很多坑是你沒接觸過的,是以還是有必要說說with這一個關鍵字。

一、基本說明

在js進階程式設計中是這樣描述with關鍵字的:with語句的作用是将代碼的作用域設定到一個特定的作用域中,基本文法如下:

?

1

<code>with</code> <code>(expression) statement;</code>

使用with關鍵字的目的是為了簡化多次編寫通路同一對象的工作,比如下面的例子:

2

3

<code>var</code> <code>qs = location.search.substring(1);</code>

<code>var</code> <code>hostName = location.hostname;</code>

<code>var</code> <code>url = location.href;</code>

這幾行代碼都是通路location對象中的屬性,如果使用with關鍵字的話,可以簡化代碼如下:

4

5

<code>with</code> <code>(location){</code>

<code>  </code><code>var</code> <code>qs = search.substring(1);</code>

<code>  </code><code>var</code> <code>hostName = hostname;</code>

<code>  </code><code>var</code> <code>url = href;</code>

<code>}</code>

在這段代碼中,使用了with語句關聯了location對象,這就以為着在with代碼塊内部,每個變量首先被認為是一個局部變量,如果局部變量與location對象的某個屬性同名,則這個局部變量會指向location對象屬性。

注意:在嚴格模式下不能使用with語句。

二、with關鍵字的弊端

前面的基本說明中,我們可以看到with的作用之一是簡化代碼。但是為什麼不推薦使用呢?下面我們來說說with的缺點:

1、性能問題

2、語義不明,調試困難

三、性能問題

首先說說性能問題,關于使用with關鍵字的性能問題,首先我們來看看兩段代碼:

第一段代碼是沒有使用with關鍵字:

6

7

8

9

10

11

<code>function</code> <code>func() {</code>

<code>  </code><code>console.time(</code><code>"func"</code><code>);</code>

<code>  </code><code>var</code> <code>obj = {</code>

<code>    </code><code>a: [1, 2, 3]</code>

<code>  </code><code>};</code>

<code>  </code><code>for</code> <code>(</code><code>var</code> <code>i = 0; i &lt; 100000; i++) {</code>

<code>    </code><code>var</code> <code>v = obj.a[0];</code>

<code>  </code><code>}</code>

<code>  </code><code>console.timeEnd(</code><code>"func"</code><code>);</code><code>//0.847ms</code>

<code>func();</code>

第二段代碼使用了with關鍵字:

12

13

14

15

<code>function</code> <code>funcWith() {</code>

<code>  </code><code>console.time(</code><code>"funcWith"</code><code>);</code>

<code>  </code><code>var</code> <code>obj2 = { x: 2 };</code>

<code>  </code><code>with</code> <code>(obj2) {</code>

<code>    </code><code>console.log(x);</code>

<code>    </code><code>for</code> <code>(</code><code>var</code> <code>i = 0; i &lt; 100000; i++) {</code>

<code>      </code><code>var</code> <code>v = obj.a[0];</code>

<code>    </code><code>}</code>

<code>  </code><code>console.timeEnd(</code><code>"funcWith"</code><code>);</code><code>//84.808ms</code>

<code>funcWith();</code>

在使用了with關鍵字後了,代碼的性能大幅度降低。第二段代碼的with語句作用到了obj2這個對象上,然後with塊裡面通路的卻是obj對象。有一種觀點是:使用了with關鍵字後,在with塊内通路變量時,首先會在obj2上查找是否有名為obj的屬性,如果沒有,再進行下一步查找,這個過程導緻了性能的降低。但是程式性能真正降低的原因真的是這樣嗎?

我們修改一下第二段代碼,修改如下:

<code>  </code><code>with</code> <code>(obj) {</code>

<code>      </code><code>var</code> <code>v = a[0];</code>

<code>  </code><code>console.timeEnd(</code><code>"funcWith"</code><code>);</code><code>//88.260ms</code>

這段代碼将with語句作用到了obj對象上,然後直接使用a通路obj的a屬性,按照前面說到的觀點,通路a屬性時,是一次性就可以在obj上找到該屬性的,但是為什麼代碼性能依舊降低了呢。

真正的原因是:使用了with關鍵字後,JS引擎無法對這段代碼進行優化。

JS引擎在代碼執行之前有一個編譯階段,在不使用with關鍵字的時候,js引擎知道a是obj上的一個屬性,它就可以靜态分析代碼來增強辨別符的解析,進而優化了代碼,是以代碼執行的效率就提高了。使用了with關鍵字後,js引擎無法分辨出a變量是局部變量還是obj的一個屬性,是以,js引擎在遇到with關鍵字後,它就會對這段代碼放棄優化,是以執行效率就降低了。

使用with關鍵字對性能的影響還有一點就是js壓縮工具,它無法對這段代碼進行壓縮,這也是影響性能的一個因素。

四、語義不明,難以調試

前面說到除了性能的問題,with還存在的一個缺點語義不明,難以調試,就是造成代碼的不易閱讀,而且可能造成潛在的bug。

16

17

18

19

<code>function</code> <code>foo(obj) {</code>

<code>    </code><code>a = 2;</code>

<code>var</code> <code>o1 = {</code>

<code>  </code><code>a: 3</code>

<code>};</code>

<code>var</code> <code>o2 = {</code>

<code>  </code><code>b: 3</code>

<code>foo(o1);</code>

<code>console.log(o1.a);</code><code>// 2</code>

<code>foo(o2);</code>

<code>console.log( o2.a );</code><code>// undefined</code>

<code>console.log( a );</code><code>// 2</code>

這段代碼很容易了解了,在foo函數内,使用了with關鍵字來通路傳進來的obj對象,然後修改a屬性。當傳入o1對象時,因為o1對象存在着a屬性,是以這樣沒有問題。傳入o2對象時,在修改a屬性時,由于o2對象沒有a這個屬性,是以被修改的a屬性則變成了全局變量。這就造成了潛在的bug。

五、延伸分析

前面說了那麼多,相信大家已經了解了為什麼不推薦使用with關鍵字以及可能存在的問題。下面我們來看看一些更複雜的情況,看下面的代碼:

<code>var</code> <code>obj = {</code>

<code>  </code><code>x: 10,</code>

<code>  </code><code>foo:</code><code>function</code> <code>() {</code>

<code>    </code><code>with</code> <code>(</code><code>this</code><code>) {</code>

<code>      </code><code>var</code> <code>x = 20;</code>

<code>      </code><code>var</code> <code>y = 30;</code>

<code>      </code><code>console.log(y);</code><code>//30</code>

<code>obj.foo();</code>

<code>console.log(obj.x);</code><code>//20</code>

<code>console.log(obj.y);</code><code>//undefined</code>

在這段代碼中,分别輸出30,20,undefined的。涉及的知識點也比較多:with關鍵字,this關鍵字,變量提升等等,我們來一一解釋一下。

1、this關鍵字

關于this關鍵字的文章google上面相當多,這裡不再贅述,我們隻需記住一點:this關鍵字始終指向調用函數的對象。在這裡,foo函數中,this指向的就是obj對象。是以在with(this)語句塊裡面,可以直接通過x變量來通路obj的x屬性。

2、變量提升

js中的變量提升也是一個經常遇到的問題,我們可以簡單了解成在js中,變量聲明會被提升到函數的頂部,盡管有的時候,它是在後面聲明的。

是以上面的代碼可以解析為:

<code>    </code><code>var</code> <code>x;</code><code>//聲明局部變量x</code>

<code>    </code><code>var</code> <code>y;</code><code>//聲明局部變量y</code>

<code>    </code><code>with</code> <code>(obj) {</code>

<code>      </code><code>x = 20;</code><code>//通路變量x,在obj上找到x,則修改為20</code>

<code>      </code><code>y = 30;</code><code>//通路變量y,在bojg上找不到y,則進一步查找,找到局部變量y,修改為30</code>

<code>      </code><code>console.log(y);</code><code>//30//直接輸出局部變量y,</code>

<code>console.log(obj.x);</code><code>//20,obj.x已被修改為20</code>

<code>console.log(obj.y);</code><code>//undefined,obj不存在y屬性,則為undefined</code>

上面的注釋中,解釋了代碼的執行過程,相信大家已經了解了為什麼會出處30,20,undefined的原因。

有興趣的同學可以看看下面這段代碼:

<code>({</code>

<code>x: 10,</code>

<code>foo:</code><code>function</code> <code>() {</code>

<code>  </code><code>function</code> <code>bar() {</code>

<code>    </code><code>console.log(y);</code>

<code>    </code><code>console.log(</code><code>this</code><code>.x);</code>

<code>  </code><code>with</code> <code>(</code><code>this</code><code>) {</code>

<code>    </code><code>var</code> <code>x = 20;</code>

<code>    </code><code>var</code> <code>y = 30;</code>

<code>    </code><code>bar.call(</code><code>this</code><code>);</code>

<code>}).foo();</code>

這段代碼會輸出什麼?為什麼呢?

總結

本文總結了with語句的特點和弊端,總的來說,強烈不推薦使用with關鍵字。其實在日常編碼中,我們隻需要知道不去使用with就可以了,但是有的時候我們可能會遇到一些關于with的奇奇怪怪的問題,想要找出真正的原因,就要深入了解with關鍵字,這有助于我們去深入學習JS這門語言,同時也是學習JS的一個樂趣。

繼續閱讀