为什么JavaScript需要严格模式?

严格模式是现代JavaScript的重要组成部分。 正是这种模式允许开发人员使用比标准语法更多的限制。

严格模式语义与传统的非严格模式(有时称为“草率模式”)不同。 在这种模式下,语言的语法规则不是很严格,并且当发生一些错误时,系统不会将它们通知给用户。 即,可以忽略错误,并且可以进一步执行错误所在的代码。 这可能会导致意外的代码执行结果。



严格模式对JavaScript的语义进行了一些更改。 它通过抛出适当的异常来防止系统对错误视而不见。 这将导致程序执行停止。

此外,严格模式有助于编写程序,而程序中没有缺点,这些缺点会阻止JS引擎优化代码。 此外,在这种模式下,禁止使用可能在该语言的未来版本中具有特殊含义的语法元素。

使用严格模式的功能


严格模式可以应用于单个功能或整个脚本。 它不能仅应用于单独的指令或括号中的代码块。 为了在整个脚本级别,在文件的开头,在任何其他命令之前使用严格模式,您需要先放置"use strict"'use strict'结构。

如果项目中的某些脚本不使用严格模式,而其他脚本使用此模式,则可能会合并这些脚本。

这将导致以下事实:当系统尝试以严格模式执行代码时,原本不打算在严格模式下执行的代码将处于这种状态。 反之亦然-为严格模式编写的代码将进入非严格模式。 因此,最好不要混合使用“严格”和“非严格”脚本。

如前所述,严格模式可以应用于各个功能。 为此,必须将"use strict""use strict"的构造放在函数主体的顶部,然后再执行其他任何命令。 这种方法的严格模式适用于放置在函数主体中的所有内容,包括嵌套函数。

例如:

 const strictFunction = ()=>{  'use strict';  const nestedFunction = ()=>{    //        } } 

在ES2015标准中出现的JavaScript模块中,默认情况下启用严格模式。 因此,在与他们合作时,您无需显式包括它。

严格模式对JS代码引入的更改


严格模式会影响代码的语法以及程序执行期间代码的行为方式。 代码中的错误将转换为异常。 在安静模式下在严格模式下安静崩溃的事实会导致错误消息。 这类似于系统以宽松模式响应语法错误的方式。 在严格模式下,简化了变量的使用,严格控制了eval函数和arguments对象的使用,并简化了可在该语言的将来版本中实现的构造的使用。

▍将静默错误转换为异常


静默错误会在严格模式下转换为异常。 在宽松模式下,系统不会明确响应此类错误。 在严格模式下,此类错误的存在会导致代码无法操作。

因此,由于这个原因,很难犯一个意外声明全局变量的错误,因为如果不使用varletconst指令就无法在严格模式下声明变量和常量。 结果,在没有这些指令的情况下创建变量将导致程序无法操作。 例如,尝试执行以下代码将引发ReferenceError异常:

 'use strict'; badVariable = 1; 

此类代码不能在严格模式下运行,因为如果严格模式关闭,则会创建全局变量badVariable 。 严格模式可以保护程序员避免意外创建全局变量。

现在,尝试执行在正常模式下根本不起作用的任何代码的尝试将引发异常。 错误被认为是在松散模式下被忽略的任何不正确的语法构造。

因此,例如,在严格模式下,您不能对诸如argumentsNaNeval类的只读实体执行值分配操作。

在严格模式下,例如,在以下情况下将引发异常:

  • 尝试将值分配给只读属性,例如某种可重写的全局属性;
  • 试图将值写入仅具有getter的属性的尝试;
  • 尝试向不可扩展对象的属性写入内容。

以下是导致严格模式异常的语法构造示例:

 '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异常。 例如, undefinedInfinity是其值无法覆盖的全局实体,并且obj对象的foo属性不支持重写。 obj2foo属性只有一个吸气剂。 使用Object.preventExtensions方法使fixedObj对象不可扩展。

尝试删除TypeError删除的TypeError也会导致TypeError

 'use strict'; delete Array.prototype 

严格模式禁止将相同名称的属性分配给对象。 结果,尝试执行以下代码将导致语法错误:

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

严格模式要求函数参数名称唯一。 在非严格模式下,例如,如果两个函数参数具有相同的名称one ,则在传递参数函数时,参数值将是最后声明的参数中的值。

在严格模式下,禁止使用具有相同名称的功能参数。 结果,尝试执行以下代码将导致语法错误:

 'use strict'; const multiply = (x, x, y) => x*x*y; 

在严格模式下,不能使用数字的八进制表示法,在数字之前加零。 这不在规范中,但浏览器支持此功能。

这种状况使开发人员感到困惑,迫使他们认为在数字前面的0只是被忽略,没有太多意义。 在严格模式下,尝试使用以0开头的数字将导致语法错误。

严格模式还禁止使用妨碍优化的构造。 解释器在执行代码优化之前,需要知道变量存储在准确的位置,根据解释器,该变量存储在哪里。 在严格模式下,禁止干扰优化的内容。

这种禁令的一个例子是with语句。 如果使用此指令,这将阻止JS解释器找出我们要引用的变量或属性,因为同名实体可能存在于with语句块的内部和外部。

假设有这样的代码:

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

解释器将无法发现位于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; 

▍禁止错误的语法构造


在严格模式下,禁止错误使用evalarguments 。 这是禁止使用它们进行各种操作的禁令。 例如,这类似于为它们分配新值,将它们的名称用作变量名,函数,函数参数。

以下是滥用evalarguments示例:

 '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对象创建别名,也不能通过这些别名设置新的arguments值。

在正常模式下,如果函数的第一个参数是a ,那么在函数代码中设置a的值也会导致arguments[0]中的值发生变化。 在严格模式下, arguments将始终包含调用该函数的参数列表。

假设您有以下代码:

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

控制台将获得[2,1] 。 这是因为将2写入值不会将2写入arguments[0]

performance优化性能


在严格模式下,不支持arguments.callee属性。 在普通模式下,它返回该函数的父函数的名称,该函数的父对象的arguments我们正在检查。

对此属性的支持会干扰优化(例如内联函数),因为使用arguments.callee要求在访问此属性时提供对非嵌入式函数的引用的可用性。 在严格模式下,使用arguments.callee会引发TypeError异常。

在严格模式下, this不必始终是一个对象。 在正常情况下,如果this函数使用callapplybind到非对象上,则bind到原始类型的值(如undefinednullnumberboolean ,则该值应该是一个对象。

如果此上下文的内容变为非对象,则将使用全局对象。 例如, window 。 这意味着,如果通过将函数this设置为不是对象的值而不是此值来调用函数,则对全局对象的引用将属于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); 

所有console.log命令都将输出true ,因为在严格模式下,如果this的值设置为不是对象的值,则不会自动将其替换为对全局对象的引用。

▍与安全相关的变更


在严格模式下,您不能将callerarguments函数属性公开。 事实是,例如, caller可以访问调用了我们正在访问其caller属性的函数的函数。

arguments对象存储调用时传递给函数的参数。 例如,如果我们有一个函数fn ,这意味着可以通过fn.caller访问调用该函数的函数,并使用fn.arguments可以看到在调用fn时传递给fn的参数。

这些功能构成潜在的安全风险。 因此,在严格模式下禁止访问这些属性。

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

在前面的示例中,我们不能在严格模式下访问secretFunction.callersecretFunction.arguments 。 事实是,这些属性可用于获取函数调用堆栈。 如果尝试运行此代码,将TypeError异常。

在严格模式下,将来的JavaScript版本中可能使用的标识符不能用于命名变量或对象的属性。 例如,我们正在讨论以下标识符: implementsinterfaceletpackageprivateprotectedpublicstaticyield

在ES2015和更高版本的标准中,这些标识符成为保留字。 而且它们不能用于在严格模式下命名变量或属性。

总结


严格模式是已经存在多年的标准。 它享有极其广泛的浏览器支持。 严格模式代码的问题只能在较旧的浏览器(例如Internet Explorer)中发生。

现代浏览器在严格的JavaScript模式下应该不会遇到困难。 因此,可以说应该使用此模式来防止“无提示”错误并提高应用程序安全性。 静默错误会转换为阻碍程序执行的异常,并且在提高安全性方面,例如,可以注意到严格的机制,这些机制会限制eval并阻止对函数调用堆栈的访问。 另外,严格模式的使用有助于JS引擎代码优化,并迫使程序员谨慎处理可能在将来的JavaScript版本中使用的保留字。

亲爱的读者们! 在为项目编写JS代码时是否使用严格模式?


Source: https://habr.com/ru/post/zh-CN477284/


All Articles