[Effective JavaScript 筆記] 第10條:避免使用with

with特性,提供的任何「便利」都更讓其變得不可靠和低效率。javascript

with語句的用法,能夠很方便地避免對對象的重複引用。上面的代碼整理成下面的形式java

function status(info){

      var widget=new Widget();

      with(widget){

             setBackground(‘blue’);

             setForeground(‘white’);

             setText(‘Status:’+info);

            show()

       }

}

使用with語句從模塊對象中導入變量也頗有誘惑力。數據結構

function f(x,y){

      with(Math){

            return min(round(x),sqrt(y));

       }

}

在上面的這兩種狀況,使用with使得提取對象的屬性,並將這些屬性綁定到塊的局部變量中變得很是誘人且容易。函數

頗有吸引力,但沒作它們應該作的事。性能

這兩個例子裏有兩種不一樣類型的變量。優化

  • 一種是但願引用with對象的屬性的變量,如setBackground,round。
  • 另外一種是但願引用外部變量綁定的變量,如info,x,y。
  • 語法上並無區分這兩種類型的變量,都只是看起來像變量。

js中全部變量都是相同的

js從最內層的做用域開始向外查找變量。with語句對待一個對象猶如該對象表明一個變量做用域,所以在with代碼塊的內部,變量查找從搜索給定的變量名的屬性開始。若是在這個對象中沒有找到該屬性,則繼續在外部做用域中搜索。prototype

image

在ES5規範中這稱爲詞法環境(在舊版本標準中稱爲做用域鏈)。

該詞法環境的最內層做用域由widget對象提供。接下來的做用域用來綁定該函數的局部變量info和widget。接下來一層綁定到status函數。注意在一個正常的做用域中,會有與局部做用域中的變量一樣多的做用域綁定存儲在與之對應的環境層級中。可是對於with做用域,綁定集合依賴於碰巧在給定時間點時的對象。3d

with塊中的每一個外部變量的引用都隱式地假設在with對象(以及它的任何原型對象)中沒有同名的屬性。而在程序的其餘地方建立或修改with對象或其原型對象不必定會遵循這樣的假設。js引擎固然也不會讀取局部代碼來獲取你使用了哪些局部變量。對象

變量做用域和對象命名空間之間的衝突使得with代碼塊異常脆弱。如:with對象得到了一個名爲info的屬性,那麼status函數的行爲就會被當即改變。status函數將使用這個屬性而不是info參數。在源代碼的功能進行擴展時,後面有可能決定全部的widget對象都應該有一個info屬性。在原型對象上添加了info屬性。這將使status函數變得不可預測。blog

status(‘connecting’);//Status:connecting

Widget.prototype.info=」[widget info]」;

status(‘connected’);//Status:[widget info]

一樣的,若是某人添加名爲x或y的屬性到Math對象上,那麼f函數也悲劇了。

Math.x=0;

Math.y=0;

f(2,9);//0

  

老是很難預測一個特定的對象是否已被修改,或是否可能擁有你不知道的屬性。事實證實,人力不可預測的特性對於優化編譯器一樣不可預測。

一般狀況下,js做用域可被表示爲高效的內部數據結構,變量查找會很是快速。但with代碼塊須要搜索對象的原型鏈來查找with代碼塊裏的全部變量,所以,其運行速度遠遠低於通常的代碼塊

在js中沒有單個特性能做爲一個更好的選擇直接替代with語句。在某些狀況下,最好的替代方法是簡單地將對象綁定到一個簡短的變量名上

function status(info){

    var w=new Widget();

    w.setBackground(‘blue’);

    w.setForebacground(‘white’);

    w.addText(‘Status:’+info);

    w.show();

}

  

沒有任何變量對於w對象的內容是敏感的。因此即便一些代碼修改了Widget的原型對象,status函數的行爲依舊是可預期的。

status(‘connecting’);//Status:connecting

Widget.prototype.info=」[widget info]」;

status(‘connected’);//Status:connected

  

在其餘狀況下,最好的方法是顯示地將局部變量顯式地綁定到相關的屬性上

function f(x,y){

     var min=Math.min,round=Math.round,sqrt=Math.sqrt;

    return min(round(x),sqrt(y));

}

  

再次一旦消除with語句,函數的行爲變得能夠預測了。

Math.x=0;

Math.y=0;

f(2,9);//2

  

提示

  • 避免使用with語句
  • 使用簡短的變量名代替重複訪問的對象
  • 顯式地綁定局部變量到對象屬性上
相關文章
相關標籤/搜索