ECMAScript 5 新特性 vol.1 - Strict 模式

Strict模式下的編碼規範

JS代碼編寫靈活,但語法糾錯功能較弱,咱們常常會由於一些小Bug而調試許久。所以ES5引入了Strict模式,強制開發者使用更爲嚴謹的編碼規範,下降腳本出錯的機率。瀏覽器

在函數的第一行加上"use strict"字符串後,即代表在這個函數中使用Strict模式:性能優化

function abc(){
    "use strict"
    // ...
  }

若是在整個JS腳本的第一行寫上"use strict",則整個腳本都會處於Strict模式中。函數

一旦開啓Strict模式,就應該遵循如下編碼規範:性能

  • 變量必須被顯式聲明測試

  • 禁止修改只讀屬性優化

  • 禁止刪除變量、函數、函數參數、原型鏈ui

  • 禁用with語句this

  • 禁用八進制數值編碼

  • 同一個函數不能有同名形參es5

  • 禁止改寫arguments的值

  • 直接調用函數時,this置空

  • eval不能污染外層做用域

  • 禁止用保留字、evalarguments做爲變量或參數名

  • 禁用callercallee

  • 不能在非函數的塊級做用域定義函數

若是你違反以上原則,那麼腳本會拋出異常,並中止後續的執行(除非你用了try-catch處理異常)。我以爲有必要思考一下,爲何這些限制是有意義的。

變量必須被顯式聲明

若是你聲明瞭一個變量,但沒用var關鍵字,那麼這個變量就是全局變量。我知道盡可能避免全局污染已是個共識了,但就怕手一抖:

var name = 'kid'
  nane = 'wumeng'

變量名打錯了,該變的沒變,還建立了一個新的全局變量。更麻煩的是,在正常模式下,編譯器不會給出任何提示。而Strict模式下,你根本不能缺乏var(或ES6的letconst):

"use strict"
  abc = 1  // 拋出異常

禁止修改只讀屬性

只讀屬性也是ES5的新特性,以後會詳細說明。總而言之,若是我誤認爲某個只讀屬性是可寫的,又沒有任何提示的話,那麼當腳本執行後不是我預期的結果,我就要查上半天了。而Strict模式會明確阻止你修改一個只讀屬性:

"use strict"
  var author = {}
  // 聲明author.name爲只讀屬性
  Object.defineProperty(author, 'name', {
    value: 'kid',
    writable: false
  })
  author.name = 'dik'  // 拋出異常,delete author.name時也會

禁止刪除變量、函數、函數參數、原型鏈

delete用於刪除對象屬性,而不應刪除其它東西:

"use strict"
  var a = 1
  delete a    // 刪變量,拋出異常
"use strict"
  function fn(){}
  delete fn   // 刪函數,拋出異常
"use strict"
  function fn(a){
    delete a  // 刪函數參數,拋出異常
  }
"use strict"
  delete Array.prototype  // 刪原型鏈,拋出異常,原型鏈是個特別的屬性

禁用with語句

with雖然很省事,但只有到運行期才能肯定調用哪一個對象,因此沒法享受到某些編譯期優化,是不推薦的寫法,在Strict模式中明確禁用:

"use strict"
with(console){  // 拋出異常
  log('kid')
}

禁用八進制數值

標準中就沒支持過八進制,但幾乎全部瀏覽器都實現了。爲何這不是個好特性?借用MDN上一個經典例子:

"use strict"
  var sum = 010 +  // 拋出異常
            100 +
            200
  // 就算你不開Strict模式,結果也不是310吶

聽說有人誤覺得01010等價,加個0只爲對齊……結果固然是錯的。

同一個函數不能有同名形參

謹防手誤:

"use strict"
  function fn(a, a){  // 拋出異常
    console.log(a)
  }

禁止改寫arguments的值

函數實參與arguments是互通的,修改其中任何一方,另外一方也會跟着變:

fn(1)
  function fn(a){
    a = 2
    console.log(arguments[0])  // -> 2
  }
fn(1)
  function fn(a){
    arguments[0] = 2
    console.log(a)  // -> 2
  }

Strict模式會阻止互通,讓其各自獨立。arguments將一直保持原始參數值,以便隨時取用:

"use strict"
  fn(1)
  function fn(a){
    a = 2
    console.log(arguments[0])  // -> 1
  }
"use strict"
  fn(1)
  function fn(a){
    arguments[0] = 2
    console.log(a)  // -> 1
  }

直接調用函數時,this置空

直接調用某函數時,其this默認指向window對象:

fn()
  function fn(a){
    console.log(this)  // -> Window{}
  }

若在Strict模式下遇到這種狀況,this將置爲undefined,以構造更嚴實的封裝,下降全局污染的可能性:

"use strict"
  fn()
  function fn(a){
    console.log(this)  // -> undefined
  }

固然,不會影響new出來的實例對象:

"use strict"
  new Animal()
  function Animal(a){
    console.log(this)  // -> Animal{}
  }

eval不能污染外層做用域

正常模式下,若是在eval中聲明一個變量,則變量的做用域爲eval所在做用域。當你的網頁容許用戶(或第三方應用)寫入自定義腳本時,則可能埋下隱患:

eval('var a = 1')
  console.log(a)  // -> 1

而在Strict模式下,eval中聲明的變量不會流出到外層域:

"use strict"
  eval('var a = 1')
  console.log(a)  // 異常,a未定義

其實在Strict模式下,除了全局、函數做用域外,還有一個eval做用域的概念,所以,你仍然能夠在eval中使用聲明的變量:

"use strict"
  eval('var a = 1; console.log(a)')  // -> 1

禁止用保留字、eval、arguments做爲變量或參數名

保留字(包括:class, enum, export, extends, importsuper)在將來可能成爲關鍵字,而evalarguments有其特殊性,因此Strict模式禁止聲明它們:

"use strict"
  var eval = 1         // 拋出異常
  function eval(){}    // 拋出異常
  function fn(eval){}  // 拋出異常
  // arguments與保留字狀況相同

不過卻是能夠做爲屬性名:

"use strict"
  var obj = {
    eval: 1
  }
  // 保留字、arguments狀況相同

禁用callercallee

Strict模式下,callercallee屬性不能使用:

"use strict";
  function fn(){
    console.log(fn.caller)  // 拋出異常
  }
  fn()
"use strict";
  function fn(){
    console.log(arguments.callee)  // 拋出異常
  }
  fn()

caller能知道是誰調用的函數,代價是破壞了封裝性,並且性能優化上也有影響;

callee便是函數自己,這在匿名函數的遞歸時頗有用,但沒法享受尾遞歸優化,更推薦的作法是給函數取個名字。關於性能方面的詳細解釋,能夠參考這篇文章:爲何 arguments.callee 從 ES5 嚴格模式中移除掉?

不能在非函數的塊級做用域定義函數

聽起來很繞,實際上是相似下面的狀況:

"use strict";
if(true){
  function fn()  // 拋出異常
}
while(true){
  function fn()  // 拋出異常
}

以上只是部分舉例,塊級做用域是指用{}括起的範圍。至於爲何禁止這種寫法,請參考這篇說明。簡單來講,這麼寫容易致使瀏覽器解析的不一致。

我測試了下,打開Strict模式,在Chrome、Opera中這麼寫並不會拋異常,而Safari、Firefox與IE10+則會遵循標準。

原創,自由轉載,請署名,本人博客 kid-wumeng.me

相關文章
相關標籤/搜索