QCon北京开幕在即|与全球 140+ 顶尖工程师共同解构 AI 时代的技术浪潮 了解详情
写点什么

为什么需要在 JavaScript 中使用严格模式?

  • 2019-11-27
  • 本文字数:4077 字

    阅读完需:约 13 分钟

为什么需要在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?


2019-11-27 17:003327
用户头像
王文婧 InfoQ编辑

发布了 126 篇内容, 共 74.1 次阅读, 收获喜欢 275 次。

关注

评论

发布
暂无评论
发现更多内容

APP 开发技术如何进行选型 ?

程序员海军

大前端 App 技术选型 APP开发

容器化 | 在 Kubernetes 上部署 RadonDB MySQL 集群

RadonDB

MySQL Kubernetes 容器

EasyRecovery——一款专业的数据恢复软件

淋雨

文件恢复 Easyrecovery破解 免费恢复软件 硬盘数据恢复

Linux之nl命令

入门小站

Linux

一步步设计版本控制系统

Java·课代表

git 版本控制 版本管理

[译] D8 类库脱糖

Antway

6月日更

代码管理工具:Git和SVN

正向成长

git svn

使用 Scala 宏解决对象转换

GrowingIO技术专栏

scala protobuf 元编程 macro

HarmonyOS学习路之开发篇——公共事件与通知(一)

爱吃土豆丝的打工人

Java HarmonyOS 鸿蒙操作系统

使用SpringCloud的openFeign组件踩坑纪实

小江

dubbo Feign spring-cloud

数据结构——顺序表

若尘

数据结构 6月日更

为什么switch的case没有break不行

叫我阿柒啊

Switch case break

计算机网络的 89 个核心概念

苹果看辽宁体育

后端 计算机网络 网络

深度解读MRS IoTDB时序数据库的整体架构设计与实现

华为云开发者联盟

大数据 架构 时序数据库 FusionInsight MRS MRS IoTDB

联想服务斩获两项智能运维大奖 助力企业业务创新与数字化转型

科技大数据

面试官:你知道怎么求素数吗?

华为云开发者联盟

面试 开发者 开发 代码 素数

Rust从0到1-自动化测试-测试组织

rust 单元测试 集成测试 自动化测试

12种mysql常见错误总结 +分析示例

李阿柯

MySQL 面试 常见问题

可视化搭建的一些思考和实践

白玉兰开源

掌门教育自研APM实际分享

白玉兰开源

回忆录 | 那些你不能错过的CTF夏令营往届历程,2021精彩继续……

郑州埃文科技

全网最详细XSS跨站脚本攻击,不是过来打死我!!

网络安全学海

程序员 网络安全 信息安全 XSS 漏洞修复

基于 Web 引擎扩展技术的 RTC 混合开发框架实践

白玉兰开源

大前端

支持低代码开发和远程真机,DevEco Studio 2.2 Beta1来啦

科技汇

JavaScript学习(十一)---RegExp对象

空城机

JavaScript 大前端 6月日更

AI论文解读:基于Transformer的多目标跟踪方法TrackFormer

华为云开发者联盟

预测 Transformer 多目标跟踪 TrackFormer 跟踪目标

冰泉奶茶香牙膏好不好?奶茶控的宝藏牙膏就是它

Geek_50a546

30分钟接入SDK 融云是如何让开发者做到开箱即用的?

融云 RongCloud

带你认识9种常用卷积神经网络

华为云开发者联盟

神经网络 深度学习 卷积神经网络 图像 卷积

在线sitemap链接提取工具

入门小站

工具

AI如何赋能软硬件产品创新?百度大脑开放日西安站解密

百度大脑

AI 百度大脑 开放日 EdgeBoard

为什么需要在JavaScript中使用严格模式?_大前端_John Au-Yeung_InfoQ精选文章