"use strict" 嚴格模式 === use strict 新加入的標準 --- `'use strict'` 是 ES5 導入的標準,在不穩定的語法或妨礙最佳化的語意都會跳出警告,讓開發者避開這些寫法。 ### 傳統瀏覽器如何解讀 `'use strict'` 在傳統瀏覽器(IE9 及以下版本)下 ,`'use strict'` 僅會被視為沒有用處的字串,所以不會對舊有的瀏覽器產生影響。 因為 `"use strict"` 本質是字串,不是關鍵字或描述句。 JavaScript 接受數字實字(numeric literal)或字串實字(string literal)單獨存在: ```jsx 3 + 4; // 數字實字 (numeric literal) "Hello"; // 字串實字 (string literal) ``` 這些值雖然產生,但沒有被存放在任何變數內,因此很快就被消滅,不會影響程式。所以不認得 `"use strict"` 的瀏覽器,也只會把它當成普通的字串實字,不會造成錯誤。 ### 為何使用嚴謹模式 - 更容易寫出安全、不易出錯的 JavaScript 程式("secure" JavaScript)。 - 幫助程式碼更乾淨。 ### 建議使用方式 建議加入 `'use strict'`,可以改正編寫時的不良習慣,但也可以導致專案無法運作,此時可以考慮將 `'use strict'` 加在函式內,避免影響過去的程式碼及相關套件。 use strict 使用方法 --- 宣告位置必須在**主程式或函數的開頭**,才會被辨識為嚴謹模式。 以「未定義的變數直接賦予值」的錯誤進行示範: ### 直接加在在程式碼前方 ```jsx 'use strict'; auntie = '漂亮阿姨'; // Uncaught ReferenceError: auntie is not defined ``` #### 注意:只有將 ‘use strict’ 放在主程式開頭才會有作用。 ![](https://i.imgur.com/oLMbZH7.png) ### 使用在函式的開頭,只會在此函式套用「嚴謹模式」,函式外部不受影響。 ```jsx (function () { 'use strict'; auntie = '漂亮阿姨'; // Uncaught ReferenceError: auntie is not defined })(); ``` #### 注意:只有將 'use strict' 放在函式開頭才會有作用。 ![](https://i.imgur.com/CQ6YG3H.png) 嚴格模式下不能做的事 --- ### 不能使用未宣告的變數 > 錯誤類型:ReferenceError: x is not defined ```jsx /* 一般 */ x = 3.14; console.log(x); // 3.14 ``` ```jsx /* 嚴謹模式*/ "use strict"; x = 3.14; // ReferenceError: x is not defined console.log(x); ``` - 強化變數的控管,快速發現打錯變數名稱的 Bug。 - 避免不小心產生不必要的 Global 變數(賦值給尚未宣告的變數,JavaScript 預設行為產生 Global 變數)。 ### 不能刪除變數或函數 > 錯誤類型:SyntaxError: Delete of an unqualified identifier in strict mode - 不能對變數、函數使用 delete 運算子(原本對變數、函數作刪除並不會有效果,但也不會拋錯,等於無用的廢 code),仍可以對物件屬性、陣列元素使用。 ```jsx /* 一般 */ // 變數 variable(沒有作用) var x = 3.14; console.log( delete x ); // false console.log(x); // 3.14 // 運算函式 function via declararion(沒有作用) function f1(){ console.log('Hi 1'); } console.log( delete f1 ); // false console.log(f1); // function f1(){ console.log('Hi 1'); } // 陳述函式 function via expression(沒有作用) var f2 = function() { console.log('Hi 2'); } console.log( delete f2 ); // false console.log(f2); // function () { console.log('Hi 2'); } // 陣列 array element var fruits = ["Banana", "Orange", "Apple", "Mango"]; console.log( delete fruits[2] ); // true console.log(fruits); // ["Banana", "Orange", , "Mango"] // 物件 object property var person = {name: 'John', age:18}; console.log( delete person.name ); // true console.log(person); // {age: 18} ``` ```jsx /* 嚴謹模式*/ "use strict"; // 變數 variable var x = 3.14; console.log( delete x ); // SyntaxError: Delete of an unqualified identifier in strict mode. // 運算函式 function via declararion function f1(){ console.log('Hi 1'); } console.log( delete f1 ); // SyntaxError: Delete of an unqualified identifier in strict mode. // 陳述函式 function via expression var f2 = function() { console.log('Hi 2'); } console.log( delete f2 ); // SyntaxError: Delete of an unqualified identifier in strict mode. // 陣列 array element var fruits = ["Banana", "Orange", "Apple", "Mango"]; console.log( delete fruits[2] ); // true console.log(fruits); // ["Banana", "Orange", , "Mango"] // 物件 object property var person = {name: 'John', age:18}; console.log( delete person.name ); // true console.log(person); // {age: 18} ``` ### 不能對不可刪除的屬性(undeletable properties)使用 delete 運算子 > 錯誤類型:`TypeError: Cannot delete property 'prototype' of function Object() { [native code] }` - 一般模式下,對不可刪除屬性使用 delete 運算子,delete 回傳 false,但語法仍可被接受。嚴謹模式下會直接拋錯。 - [可否刪除主要看 Property attributes](https://stackoverflow.com/questions/34357752/how-to-identify-javascript-undeletable-properties),其在 configurable 屬性設置為 false 便不可刪除。若要查看 configurable 屬性,可以使用 [`Object.getOwnPropertyDescriptor(obj, prop)`](https://hackmd.io/PCdwS45ER2KWmh2BKQUS6g?view)。 ```jsx /* 一般 */ console.log(delete Object.prototype); // false ``` ```jsx /* 嚴謹模式 */ "use strict"; console.log(delete Object.prototype); // TypeError: Cannot delete property 'prototype' of function Object() { [native code] } ``` ### 函數的參數名稱不能重複 > 錯誤類型:SyntaxError: Duplicate parameter name not allowed in this context - 一般情況函數的參數名稱重複,不會有提醒,而是默認後面的蓋過前面。 ```jsx /* 一般 */ function myFunc(p1, p1){ console.log(p1); }; myFunc(10, 20); // 20 ``` ```jsx /* 嚴謹模式 */ "use strict"; function myFunc(p1, p1){ // SyntaxError: Octal literals are not allowed in strict mode console.log(p1); }; ``` ### 不能使用八進制的數字實字(Octal numeric literals) > 錯誤類型:SyntaxError: Octal literals are not allowed in strict mode - 某些版本的 JavaScript 中,如果數字前頭帶 0,會被直譯為八進制。例如:`var x = 010;` 等於十進制的 8。 - 一般 JavaScript 實作規範,就已經強烈建議不要對數字開頭帶 0,但僅是建議,一般模式下仍可被 JavaScript 直譯器接受。嚴謹模式則直接對這樣的語法拋錯。 ```jsx /* 一般 */ let n = 010; console.log(n); // 8 ``` ```jsx /* 嚴謹模式 */ "use strict"; let n = 010; // SyntaxError: Octal literals are not allowed in strict mode. ``` ### 不能使用八進制的跳脫字元(Octal escape characters) > 錯誤類型:SyntaxError: Delete of an unqualified identifier in strict mode. - 例如:`var x = "\010";` 在一般模式下是空字串,但在嚴謹模式下被禁用。 ```jsx /* 一般 */ var n = "\010"; console.log(n); // "" ``` ```jsx /* 嚴謹模式 */ "use strict"; var n = "\010"; // SyntaxError: Octal escape sequences are not allowed in strict mode. ``` ### 不能對唯讀物件屬性作寫入(write to a read-only property of objects) > 錯誤類型:`TypeError: Cannot assign to read only property 'articleTarget' of object '#<Object>'` - 定義物件屬性時,可以設定是否可寫入(writable)。詳見 [Object.defineProperty](https://hackmd.io/PCdwS45ER2KWmh2BKQUS6g?view)。 - 一般模式下,對唯讀屬性賦值,雖然不會寫入成功,但也不會報錯,等於是廢 code。 - 嚴謹模式下,對唯讀屬性賦值,會直接報錯。 ```jsx /* 一般 */ let player = {}; Object.defineProperty(player, "name", { value: "OneJar", writable: false // 唯讀 }); player.name = "鮭魚"; // 寫入新屬性 console.log(player.name); // "OneJar" ``` > [Dome](https://codepen.io/betty-hu/pen/YzjRgpV?editors=0012) ```jsx /* 嚴謹模式 */ "use strict"; let player = {}; Object.defineProperty(player, "name", { value: "OneJar", writable: false // 唯讀 }); player.name = "鮭魚"; // TypeError: Cannot assign to read only property 'articleTarget' of object '#<Object>' ``` > [Dome](https://codepen.io/betty-hu/pen/oNMQOqa?editors=0012) ### 不能對 get-only 的物件屬性作寫入 > 錯誤類型:`TypeError: Cannot set property age of #<Object> which has only a getter` - 一般模式下,對只有 [getter](https://hackmd.io/AW0bOOXnT32DZV_uuvd5IQ?view) 的屬性賦值,雖然不會寫入成功,但也不會報錯,等於廢 code。嚴謹模式下,則會直接報錯。 ```jsx /* 一般 */ var person = { get age() { return 18 }}; console.log(person.age); // 18 person.age = 70; console.log(person.age); // 18 ``` ```jsx /* 嚴謹模式 */ "use strict"; var person = { get age() { return 18 }}; console.log(person.age); // 18 person.age = 70; // TypeError: Cannot set property age of #<Object> which has only a getter ``` ### 不能使用 `eval` 或 `arguments` 作為變數名稱 > 錯誤類型:SyntaxError: Unexpected eval or arguments in strict mode - `eval` 和 `arguments` 是 JavaScript 關鍵字,各自擁有用途,作為變數名稱容易導致非預期結果。 - [arguments](https://hackmd.io/f0CWm4f3SgqP7HTNg-HerQ?view#arguments) 是 function 中所有的參數。 - eval 將傳入的字符作為 JavaScript 執行。 ```jsx /* 一般 */ let arguments = 1500; console.log(arguments); // 1500 function myFunc(){ return arguments; } console.log(myFunc(1, 2, 3)); // Arguments Object [1, 2, 3] ``` ```jsx /* 嚴謹模式 */ "use strict"; let arguments = 1500; // SyntaxError: Unexpected eval or arguments in strict mode console.log(arguments); function myFunc(){ return arguments;} console.log(myFunc(1, 2, 3)); ``` ### 不能使用未來的保留字做變數名稱(cannot use future reserved keywords as variables) > 錯誤類型:`TypeError: Cannot assign to read only property 'articleTarget' of object '#<Object>'` - 有些字眼目前不是 JavaScript 關鍵字,但根據程式語言發展的經驗,被預期在未來成為實際作用的關鍵字。例如 interface、public、private、package 等,在其他程式語言是很重要也很普遍的關鍵字。 - 考慮到既有程式對未來 JavaScript 新版本的移植性(portable),這些字眼就不適合作為變數名稱。 以下是 W3Schools 列出的未來保留字,有些在 ES6 等後續版本已經成真: - implements - interface - let - package - private - protected - public - static - yield ### 用 `eval()` 宣告的變數或函數,不能在該 Scope 以外被語法呼叫使用 > 錯誤類型:ReferenceError: x is not defined - 一般模式下,可以利用 [eval](https://hackmd.io/WlGpP3e6SCCOD_jeFep6uw?view) 宣告變數或函數,但是在嚴謹模式下,eval 內宣告的變數或函數在外使用時,將會發現找不到。 ```jsx /* 一般 */ /* 變數 */ eval("var x = 123"); console.log(x); // 123 /* 函數 */ eval("function myFunc(){ var x = 123; console.log(x); } myFunc();"); // 123 myFunc(); // 123 ``` ```jsx /* 嚴謹模式 */ "use strict"; /* 變數 */ eval("var x = 123"); console.log(x); // ReferenceError: x is not defined /* 函數 */ eval("function myFunc(){ var x = 123; console.log(x); } myFunc();"); // 123 myFunc(); // ReferenceError: myFunc is not defined ``` - 在嚴謹模式下,僅在 eval 使用自己宣告的變數或函數,是沒有問題的。 ```jsx /* 嚴謹模式 */ "use strict"; var ret = 0; eval("var n1 = 3; ret = n1 + n1;"); console.log(ret); // 6 console.log(n1); // ReferenceError: n1 is not defined ``` ### 不能使用 [with](https://hackmd.io/jK-e8TJSQ1eCLWmEOuwzlQ?view) 語法 > 錯誤類型:SyntaxError: Strict mode code may not include a with statement ```jsx /* 嚴謹模式 */ "use strict"; let x,y; with(Math){ // SyntaxError: Strict mode code may not include a with statement x = cos(3 * PI) + sin(LN10); y = tan(14 * E); } ``` use strict 及 this --- 在 `'use strict'` 的環境下,純粹調用(Simple call)的 this 不再是全域變數。 ![](https://i.imgur.com/9mNalyW.png) ▲ 原本在非嚴格模式,Simple call 的 `this` 可以是全域變數。 ![](https://i.imgur.com/NyZgDZB.png) ▲ 在嚴格模式時,Simple call 的 `this` 卻是 undefined。 因為沒有 `this`,因此可以使用 [call](https://hackmd.io/C_r0ziSJScuw51ZogCLmJg#call) 的方式繫結 `this`。 ![](https://i.imgur.com/MiHLsVq.png) ▲ 自己定義要繫結的 `this`。 ![](https://i.imgur.com/DepgXtR.png) ▲ 直接繫結 `this`,以 window 的方式傳入。 明明是不良語法,卻尚未被嚴謹模式控管:重複宣告變數 --- ```jsx /* 嚴謹模式 */ "use strict"; var x = 10; var x = 20; console.log(x); // 20 ``` ▲ 重複宣告變數是不良語法之一,卻尚未被嚴謹模式規範,需要自行注意。 ```jsx let x = 10; let x = 20; // SyntaxError: Identifier 'x' has already been declared ``` ▲ 可以使用 let 或 const 取代 var,限制變數重複宣告。