JS專題之嚴格模式

南波發表於2019-02-08

ECMAScript 5 引入了 strict mode ,現在已經被大多瀏覽器實現(從IE10開始)

一、什麼是嚴格模式

顧名思義,JavaScript 嚴格模式就是讓 JS 程式碼以更嚴格的模式執行,不允許可能會引發錯誤的程式碼執行。在正常模式下靜默失敗的程式碼,嚴格模式下就會丟擲錯誤。

二、為什麼要過渡到嚴格模式

  1. 嚴格模式下的程式碼在執行的時候,更容易通過丟擲的錯誤定位到問題所在的地方
  2. 嚴格模式能夠幫助你編寫更符合規範的程式碼
  3. 消除 JavaScript 語言上一些不合理,比較怪異的行為
  4. 為未來新版本的 JavaScript 做鋪墊
  5. 有時候,嚴格模式下的 JavaScript 程式碼執行起來更快

三、如何使用

· 指令碼檔案範圍

"use strict"; 放在指令碼檔案的第一行。整個指令碼檔案就會以“嚴格模式”執行。

· 函式作用域範圍

"use strict"; 放在函式體的第一行,則整個函式以"嚴格模式"執行。

檔案合併時,寫在指令碼檔案第一行的 "use strict"; 來實現嚴格模式會失效,可以將指令碼檔案的程式碼放在一個立即執行表示式中。

(funciton() {
    "use strict";
    ...
})()
複製程式碼

四、嚴格模式的具體定義

  1. 嚴格模式下無法再隱式建立全域性變數 也就是,變數必須宣告後才能使用,正常模式直接賦值給一個未定義的變數時,會將變數定義為全域性變數。
"use strict";
var a = b = 3;  // Uncaught ReferenceError: b is not defined

以上程式碼等於:
var a;
b = 3;
a = b;

複製程式碼
  1. 禁止 this 關鍵字指向全域性物件 正常模式下,函式中如果沒有指明 this 物件,JS 則會將 this 隱式指向為全域性物件。如果繫結的值是非物件,將被自動轉為物件再繫結上去,而 null 和 undefined 這兩個無法轉成物件的值,將被忽略。

嚴格模式下,必須指明 this 的指向物件。如果沒有指明的話,this的值為 undefined

var name = "foo";
function func() {
    "use strict";
    this.name;  // Uncaught TypeError: Cannot read property 'name' of undefined
}
func();  // 沒有加 new 關鍵字
new func();

function func() {
    return this
}

func() // window
func.call(8) // Number {8}
func.call(true) // Boolean {true}
func.call("abcd")  // {"abcd"}
func.call(null) // window
func.call(undefined) // window

"use strict"
function func() {
    return this
}

func() // undefined
func.call(8) // 8
func.call(true) // true
func.call(null) //null
func.call(undefined) // undefined
複製程式碼
  1. 不允許在函式內部遍歷呼叫棧 禁止使用 arguments.callee、arguments.caller、fn.caller、fn.callee; 在嚴格模式下,arguments.callee 是一個不可刪除屬性,而且賦值和讀取時都會丟擲異常
function func() {
	"use strict";
	func.caller;  // 報錯
	func.arguments; // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}
func()
複製程式碼
  1. 禁止向物件的只讀屬性賦值,禁止刪除物件的不可設定屬性, 禁止向不可擴充套件的物件新增屬性 無法刪除 var 宣告的變數。

在正常模式中,給物件的只讀屬性賦值, 刪除物件的不可設定屬性,新增不可擴充套件物件的新屬性,會靜默失敗。

但是在嚴格模式中,會丟擲錯誤。 另外,字串的屬性 length 也是隻讀屬性,修改後會報錯。

"use strict";
var str = "abc"
str.length = 8  // Uncaught TypeError: Cannot assign to read only property 'length' of string 'abc'

'use strict';
var obj = Object.defineProperty({}, 'a', {
  value: 37,
  writable: false
});
obj.a = 123; // Uncaught TypeError: Cannot assign to read only property 'a' of object '#

'use strict';
var obj = Object.defineProperty({}, 'p', {
  value: 37,
  configurable: false
});
delete obj.p  // Uncaught TypeError: Cannot delete property 'p' of #<Object>

var obj = {};
Object.preventExtensions(obj);
obj.title = "hello";  // Uncaught TypeError: Cannot add property title, object is not extensible
複製程式碼
  1. 物件不允許有重名的屬性,函式不允許有重名的引數 在正常模式中,物件的重名屬性,位置靠後會覆蓋位置靠前的重名屬性。函式也是,函式體查詢到的引數,靠後的重名引數會覆蓋靠前的重名引數。
"use strict";
var o = { 
    p: 1,
    p: 2
   };
// IE報錯:strict 模式下不允許一個屬性有多個定義, 新版的 Chrome 和 firefox 並不會報錯,會採用覆蓋機制。

"use strict";
function func(a, a) {
    console.log(a)
}
func(1, 2) // IE報錯: strict 模式下不允許正式引數名稱重複。新版的 Chrome 和 firefox 並不會報錯,會採用覆蓋機制。
複製程式碼
  1. 靜態繫結 JavaScript 支援動態繫結,也就是 JavaScript 的屬性和方法是在執行時確定,而不是在編譯時確定。 於是,JavaScript 嚴格模式禁用了 with 語句, 因為使用了 with 語句,with 語句塊中變數無法確定是外部全域性變數還是傳入的物件屬性。
"use strict";
var x = 17;
with (obj) // !!! 語法錯誤
{
  // 如果沒有開啟嚴格模式,with 中的這個x會指向 with 上面的那個 x,還是obj.x?
  // 如果不執行程式碼,我們無法知道,因此,這種程式碼讓引擎無法進行優化,速度也就會變慢。
  x; // Uncaught SyntaxError: Strict mode code may not include a with statement
}
複製程式碼

eval 關鍵字不再會給上層函式(surrounding function)或者全域性引入一個新的變數。在嚴格模式中,eval 語句會建立自己的一個作用域,eval 裡的變數只能在 eval 內部使用。

  1. arguments 的限定 嚴格模式規定名稱為 eval 和 arguments 不能通過程式語法被繫結(be bound)或賦值 嚴格模式下,引數的值不會隨 arguments 物件的值的改變而變化。

正常模式中,對引數重新賦值,會修改 arguments 類陣列物件下的引數值。同時,修改 arguments 類陣列物件的值,也會修改函式引數的值。

嚴格模式下,不僅引數的值不會隨著 arguments 類陣列物件的變化而變化,引數的變化也不會引起 arguments 物件的變化,arguments 物件會記住引數的傳入初始值。

function func(a) {
"use strict"
  a = 8;
  // arguments[0] = 8
  return [a, arguments[0]]
}

func(3) // [8, 3]

function func(a) {
"use strict"
  arguments[0] = 8
  return [a, arguments[0]]
}

func(3) // [3, 8]
複製程式碼
  1. ES5禁止在非函式程式碼塊宣告函式 ES5 的嚴格模式只允許在全域性作用域或函式作用域宣告函式。也就是說,不允許在非函式的程式碼塊內宣告函式。
if (true) {
  function add() {
  }
}
add()

for (var i = 0; i < 5; i++){
  function f2() { } // !!! 語法錯誤
  f2();
}

以上程式碼在嚴格模式是禁止的,但是在 ES6 中,是允許在程式碼塊中宣告函式的。
複製程式碼
  1. 保留關鍵字 嚴格模式中一部分字元變成了保留的關鍵字。這些字元包括implements, interface, let, package, private, protected, public, staticyield。在嚴格模式下,你不能再用這些名字作為變數名或者形參名
function private() {"use strict" }  //Uncaught SyntaxError: Unexpected strict mode reserved word
複製程式碼
  1. 嚴格模式禁止八進位制數字語法

五、向嚴格模式過渡

嚴格模式能夠幫助我們寫出更安全,更有規範的程式碼,則應該避免一些危險的寫法,採用更好的寫法:

  1. 變數先宣告,再使用,
  2. this 應該在指向自己建立的物件時使用。
  3. arguments 應該在函式第一行就拷貝出來。

六、嚴格模式的缺點

  1. 現在的程式碼都會進行檔案壓縮和合並,此時嚴格模式就會失效。

總結

現在的 webpack 會在打包的時候預設是嚴格模式,所以現在不用再手動寫 use strict了。嚴格模式能幫助我們以更規範的方式書寫程式碼,但是無論是否嚴格模式,都應該注意程式碼的規範,避免隱式 bug 的出現。

2018/02/08 @Starbucks

歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。

JS專題之嚴格模式

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript之原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply
  13. JavaScript專題之模擬實現bind
  14. JavaScript專題之模擬實現new
  15. JS專題之事件模型
  16. JS專題之事件迴圈
  17. JS專題之去抖函式
  18. JS專題之節流函式
  19. JS專題之函式柯里化
  20. JS專題之陣列去重
  21. JS專題之深淺拷貝
  22. JS專題之陣列展開

相關文章