JS代碼編寫靈活,但語法糾錯功能較弱,咱們常常會由於一些小Bug而調試許久。所以ES5引入了Strict模式,強制開發者使用更爲嚴謹的編碼規範,下降腳本出錯的機率。瀏覽器
在函數的第一行加上"use strict"
字符串後,即代表在這個函數中使用Strict模式:性能優化
function abc(){ "use strict" // ... }
若是在整個JS腳本的第一行寫上"use strict"
,則整個腳本都會處於Strict模式中。函數
一旦開啓Strict模式,就應該遵循如下編碼規範:性能
變量必須被顯式聲明測試
禁止修改只讀屬性優化
禁止刪除變量、函數、函數參數、原型鏈ui
禁用with
語句this
禁用八進制數值編碼
同一個函數不能有同名形參es5
禁止改寫arguments
的值
直接調用函數時,this
置空
eval
不能污染外層做用域
禁止用保留字、eval
、arguments
做爲變量或參數名
禁用caller
與callee
不能在非函數的塊級做用域定義函數
若是你違反以上原則,那麼腳本會拋出異常,並中止後續的執行(除非你用了try-catch
處理異常)。我以爲有必要思考一下,爲何這些限制是有意義的。
若是你聲明瞭一個變量,但沒用var
關鍵字,那麼這個變量就是全局變量。我知道盡可能避免全局污染已是個共識了,但就怕手一抖:
var name = 'kid' nane = 'wumeng'
變量名打錯了,該變的沒變,還建立了一個新的全局變量。更麻煩的是,在正常模式下,編譯器不會給出任何提示。而Strict模式下,你根本不能缺乏var
(或ES6的let
、const
):
"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
雖然很省事,但只有到運行期才能肯定調用哪一個對象,因此沒法享受到某些編譯期優化,是不推薦的寫法,在Strict模式中明確禁用:
"use strict" with(console){ // 拋出異常 log('kid') }
標準中就沒支持過八進制,但幾乎全部瀏覽器都實現了。爲何這不是個好特性?借用MDN上一個經典例子:
"use strict" var sum = 010 + // 拋出異常 100 + 200 // 就算你不開Strict模式,結果也不是310吶
聽說有人誤覺得010
與10
等價,加個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
默認指向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('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
保留字(包括:class
, enum
, export
, extends
, import
和super
)在將來可能成爲關鍵字,而eval
與arguments
有其特殊性,因此Strict模式禁止聲明它們:
"use strict" var eval = 1 // 拋出異常 function eval(){} // 拋出異常 function fn(eval){} // 拋出異常 // arguments與保留字狀況相同
不過卻是能夠做爲屬性名:
"use strict" var obj = { eval: 1 } // 保留字、arguments狀況相同
caller
與callee
Strict模式下,caller
與callee
屬性不能使用:
"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