為什麼需要在JavaScript中使用嚴格模式?

為什麼需要在JavaScript中使用嚴格模式?

嚴格模式是什麼意思?有什麼用途?為什麼我們應該使用它?本文將主要從這幾個問題入手,講述在 JavaScript 中使用嚴格模式的必要性。

嚴格模式是現代 JavaScript 的重要組成部分。通過這種模式,我們可以選擇使用更為嚴格的 JavaScript 語法。

嚴格模式的語義不同於以前的 JavaScript“稀鬆模式”(sloppy mode),後者的語法更寬鬆,並且會靜默代碼中的錯誤。這意味著錯誤會被忽略,並且運行代碼可能會產生意外的結果。

嚴格模式對 JavaScript 語義進行了一些更改。它不會靜默錯誤,而是會拋出錯誤,阻止出錯的代碼繼續運行。

它還能指出會阻礙 JavaScript 引擎優化工作的過錯。另外,它禁止使用可能在未來的 JavaScript 版本中定義的功能。

嚴格模式可以應用於單個函數或整個腳本。它不能僅應用於大括號內的語句等塊。要為某個腳本啟用嚴格模式,我們要在腳本頂部所有語句之前添加"use strict"或 'use strict' 語句。

如果我們讓一些腳本使用嚴格模式,而其他腳本不使用嚴格模式,那麼使用嚴格模式的腳本可能會與其他不使用嚴格模式的腳本串聯在一起。

串聯起來後,不使用嚴格模式的代碼可能會被設置為嚴格模式,反之亦然。因此,最好不要將它們混合在一起。

我們也可以將其應用於函數。為此,我們在函數主體頂部開頭添加"use strict"或'use strict'語句,後面接其他語句。它會應用到函數內部的所有內容上,包括嵌套在使用嚴格模式的函數中的函數。

我自己是一名從事了多年開發的web前端老程序員,目前辭職在做自己的web前端私人定製課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習乾貨,各種框架都有整理,送給每一位前端小夥伴,想要獲取的可以關注我的頭條號並在後臺私信我:前端,即可免費獲取。

例如:

const strictFunction = ()=>{
'use strict';
const nestedFunction = ()=>{
// 這個函數也使用嚴格模式
}
}

ES2015 中引入的 JavaScript 模塊自動啟用了嚴格模式,因此無需聲明即可啟用它。

嚴格模式下的變化

嚴格模式同時改變了語法及運行時行為。變化分為這幾類:將過錯(mistake)轉化為運行時拋出的語法錯誤;簡化了特定變量的計算方式;簡化了 eval 函數以及 arguments 對象;還改變了可能在未來 ECMAScript 規範中實現的功能的應對方式。

將過失轉化為錯誤

過失會轉化為錯誤。以前它們在稀鬆模式中會被接受。嚴格模式限制了錯誤語法的使用,並且不會讓代碼在有錯誤的位置繼續運行。

由於這種模式不允許我們使用 var、let 或 const 聲明變量,因此很難創建全局變量;所以創建變量時,不使用這些關鍵字聲明這些變量是不行的。例如,以下代碼將拋出一個 ReferenceError:

'use strict';
badVariable = 1;

我們無法在嚴格模式下運行上述代碼,因為如果關閉了嚴格模式,此代碼將創建一個全局變量 badVariable。嚴格模式可以防止這種情況,以防止意外創建全局變量。現在,任何以靜默方式失敗的代碼都將拋出異常。這包括以前被靜默忽略的所有無效語法。

例如,我們啟用了嚴格模式後,不能為只讀變量(如 arguments、NaN 或 eval)賦值。

對只讀屬性(如不可寫的全局屬性)的賦值,對 getter-only 屬性的賦值以及對不可擴展對象上的屬性的賦值都將在嚴格模式下拋出異常。

以下是一些語法示例,這些語法在啟用嚴格模式後將失敗:

'use strict';

let undefined = 5;
let Infinity = 5;

let obj = {};
Object.defineProperty(obj, 'foo', { value: 1, writable: false });
obj.foo = 1

let obj2 = { get foo() { return 17; } };
obj2.foo = 2

let fixedObj = {};
Object.preventExtensions(fixedObj);
fixed.bar= 1;

上面所有示例都將拋出一個 TypeError 。undefined 和 Infinity 是不可寫的全局對象。obj 是不可寫的屬性。obj2 的 foo 屬性是 getter-only 的屬性,因此無法設置。使用 Object.preventExtensions 方法阻止了 fixedObj 向其添加更多屬性。

此外,如果有代碼嘗試刪除不可刪除的屬性,則會拋出一個 TypeError 。例如:

'use strict';
delete Array.prototype

這將拋出一個 TypeError。嚴格模式還不允許在引入 ES6 之前,在對象中複製屬性名稱,因此以下示例將拋出語法錯誤:

'use strict';
var o = { a: 1, a: 2 };

嚴格模式要求函數參數名稱唯一。不使用嚴格模式時,如果兩個形參(parameter)的名稱均為 1,則傳入實參(argument)時,後定義的那個 1 將被接受為形參的值。在嚴格模式下,不再允許具有相同名稱的多個函數參數,因此以下示例將因語法錯誤而無法運行:

const multiply = (x, x, y) => x*x*y;

在嚴格模式下也不允許八進制語法。它不是規範的一部分,但是在瀏覽器中可以通過為八進制數字加上 0 前綴來支持這種語法。這使開發人員感到困惑,因為有些人可能認為數字前面的 0 是沒有意義的。因此,嚴格模式不允許使用此語法,並且會拋出語法錯誤。

嚴格模式還阻止使用阻礙優化的語法。在優化執行之前,程序需要知道一個變量實際上存儲在了預期的位置,因此我們必須避免那種阻礙優化的語法。

一個示例是 with 語句。如果我們使用它,它會阻止 JavaScript 解釋器瞭解你要引用的變量或屬性,因為可能在 with 語句的內部或外部具有相同名稱的變量。

如果我們有類似以下代碼的內容:

在嚴格模式下也不允許八進制語法。它不是規範的一部分,但是在瀏覽器中可以通過為八進制數字加上 0 前綴來支持這種語法。這使開發人員感到困惑,因為有些人可能認為數字前面的 0 是沒有意義的。因此,嚴格模式不允許使用此語法,並且會拋出語法錯誤。

嚴格模式還阻止使用阻礙優化的語法。在優化執行之前,程序需要知道一個變量實際上存儲在了預期的位置,因此我們必須避免那種阻礙優化的語法。

一個示例是 with 語句。如果我們使用它,它會阻止 JavaScript 解釋器瞭解你要引用的變量或屬性,因為可能在 with 語句的內部或外部具有相同名稱的變量。

如果我們有類似以下代碼的內容:

JavaScript 就不會知道 with 語句中的 x 是指 x 變量還是 obj、obj.x 的屬性。這樣,x 的存儲位置不明確。因此,嚴格模式將阻止使用 with 語句。如果我們有如下嚴格模式:

'use strict';
let x = 1;
with (obj) {
x;
}

上面的代碼將出現語法錯誤。嚴格模式阻止的另一件事是在 eval 語句中聲明變量。

例如,在沒有嚴格模式的情況下,eval('let x') 會將變量 x 聲明進代碼。這樣一來,人們可以在字符串中隱藏變量聲明,而這些字符串可能會覆蓋 eval 語句之外的同一變量聲明。

為避免這種情況,嚴格模式不允許在傳遞給 eval 語句的字符串參數中進行變量聲明。

嚴格模式還禁止刪除普通變量名稱,因此以下內容將拋出語法錯誤:

'use strict';

let x;
delete x;

禁止無效語法

在嚴格模式下,不允許使用 eval 和 argument 的無效語法。

這意味著不允許對它們執行任何操作,例如為它們分配新值或將它們用作變量、函數或函數中參數的名稱。

以下是 eval 的無效用法和不允許的 argument 對象的示例:

'use strict';
eval = 1;
arguments++;
arguments--;
++eval;
eval--;
let obj = { set p(arguments) { } };
let eval;

try { } catch (arguments) { }
try { } catch (eval) { }
function x(eval) { }
function arguments() { }
let y = function eval() { };
let eval = ()=>{ };
let f = new Function('arguments', "'use strict'; return 1;");

嚴格模式不允許為 arguments 對象創建別名,並不允許通過別名設置新值。非嚴格模式下,如果函數的第一個參數是 a,則設置 a 還將設置 arguments[0]。在嚴格模式下,arguments 對象將始終具有調用該函數所使用的參數列表。

例如,如果我們有:

const fn = function(a) {
'use strict';
a = 2;
return [a, arguments[0]];
}
console.log(fn(1))

那麼我們應該看到 [2,1] 已記錄。這是因為將 a 設置為 2 也會同時將 arguments[0] 設置為 2。

性能優化

此外,嚴格模式不再支持 arguments.callee。非嚴格模式下,它所做的就是返回 arguments.callee 所在的,被調用函數的名稱。

它阻止了諸如內聯函數之類的優化,因為 arguments.callee 要求,如果訪問 arguments.callee,則對未內聯函數的引用可用。因此在嚴格模式下,arguments.callee 現在將拋出 TypeError。

使用嚴格模式時,this 不會強制始終成為對象。如果函數的 this 是用 call、apply 或 bind 綁定到任何非對象類型(例如 undefined、null、number、boolean 等原始類型)的,則必須強制它們成為對象。

如果 this 的上下文切換為非對象類型,則全局 window 對象將取代其位置。這意味著全局對象公開了正在被調用的函數,並且 this 綁定到非對象類型。

例如,如果我們運行以下代碼:

'use strict';
function fn() {
return this;
}
console.log(fn() === undefined);
console.log(fn.call(2) === 2);
console.log(fn.apply(null) === null);
console.log(fn.call(undefined) === undefined);
console.log(fn.bind(true)() === true);

所有控制檯日誌都會是 true,因為當 this 更改為具有非對象類型的對象時,該函數內部的 this 不會自動轉換為 window 全局對象。

安全修復

在嚴格模式下,我們也不允許公開函數的 caller 和 arguments,因為函數的 caller 屬性訪問的函數被一個函數調用時,caller 可能會暴露後者。

arguments 具有在調用函數時傳遞的參數。例如,如果我們有一個名為 fn 的函數,則可以通過 fn.caller 查看調用 fn 的函數,並通過 fn.arguments 可以看到在調用 fn 時傳遞給 fn 的參數。

這是一個潛在的安全漏洞,通過禁止訪問該函數的這兩個屬性就能堵上它。

function secretFunction() {
'use strict';
secretFunction.caller;
secretFunction.arguments;
}
function restrictedRunner() {
return secretFunction();
}
restrictedRunner();

在上面的示例中,我們無法在嚴格模式下訪問 secretFunction.caller 和 secretFunction.arguments,因為人們可能會使用它來獲取函數的調用堆棧。如果我們運行該代碼,將拋出 TypeError。在將來的 JavaScript 版本中將成為受限關鍵字的標識符,將不被允許用作變量或屬性名稱之類的標識符。

以下關鍵字不得用來在代碼中定義標識符:implements、interface、let、package、private、protected、public、static 和 yield。

在 ES2015 或更高版本中,這些已成為保留字;因此在非嚴格模式下,絕對不能將它們用於變量命名和對象屬性。

嚴格模式成為一種標準已經很多年了。瀏覽器對它的支持很普遍,只有像 Internet Explorer 這樣的舊瀏覽器才可能出問題。

其他瀏覽器使用嚴格模式應該不會有問題。因此,應使用它來防止錯誤並避免安全隱患,例如暴露調用棧或在 eval 中聲明新變量。

此外,它還消除了靜默錯誤,現在會拋出錯誤,從而使代碼不會在出現錯誤的情況下運行。它還會指出阻止 JavaScript 引擎進行優化的過錯。

另外,它禁用了可能在將來的 JavaScript 版本中定義的功能。

為什麼需要在JavaScript中使用嚴格模式?

原文鏈接: https://medium.com/better-programming/why-do-we-need-strict-mode-in-javascript-df34771eb950


分享到:


相關文章: