"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’ 放在主程式開頭才會有作用。

### 使用在函式的開頭,只會在此函式套用「嚴謹模式」,函式外部不受影響。
```jsx
(function () {
'use strict';
auntie = '漂亮阿姨';
// Uncaught ReferenceError: auntie is not defined
})();
```
#### 注意:只有將 'use strict' 放在函式開頭才會有作用。

嚴格模式下不能做的事
---
### 不能使用未宣告的變數
> 錯誤類型: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 不再是全域變數。

▲ 原本在非嚴格模式,Simple call 的 `this` 可以是全域變數。

▲ 在嚴格模式時,Simple call 的 `this` 卻是 undefined。
因為沒有 `this`,因此可以使用 [call](https://hackmd.io/C_r0ziSJScuw51ZogCLmJg#call) 的方式繫結 `this`。

▲ 自己定義要繫結的 `this`。

▲ 直接繫結 `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,限制變數重複宣告。