Categories
程式開發

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


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

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

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

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

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

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

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

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

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

例如:

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。

嚴格模式還不允許在引入E​​S6之前,在對像中複製屬性名稱,因此以下示例將拋出語法錯誤:

'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語句的內部或外部具有相同名稱的變量。

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

let x = 1;
with (obj) {
  x;
}

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版本中定義的功能。

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

相關文章:

JavaScript 到底是面向對像還是基於對象?
框架的遊戲:2019 年 JavaScript 流行趨勢
逃離 JavaScript,TypeScript 成新寵
誰將取代 JavaScript?