AICon上海|与字节、阿里、腾讯等企业共同探索Agent 时代的落地应用 了解详情
写点什么

为什么需要在 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:003333
用户头像
王文婧 InfoQ编辑

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

关注

评论

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

【死磕NIO】— 阻塞、非阻塞、同步、异步,傻傻分不清楚

chenssy

11月日更 死磕 Java 死磕 NIO

模块9 毕设

SAKIN

Windoes下安装配置flutter环境

坚果

flutter windows 安装 11月日更

【Flutter 专题】14 图解 ListView 不同样式 item 及 Widget 显隐性

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 11月日更

爬虫120例之第17例,用Python面向对象的思路,采集各种精彩句子

梦想橡皮擦

11月日更

远程代码执行漏洞分析

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 漏洞挖掘

08 K8S之资源对象介绍

穿过生命散发芬芳

k8s 11月日更

Flink 实践教程-入门(4):读取 MySQL 数据写入到 ES

腾讯云大数据

流计算 Oceanus

Flink 实践教程-入门(5):写入 ClickHouse

腾讯云大数据

流计算 Oceanus

dart系列之:dart语言中的函数

程序那些事

flutter 后端 dart 程序那些事 11月日更

如何解决 fs.renameSync() 跨区移动文件的问题

liuzhen007

11月日更

Prometheus HTTP API 查询(二) 表达式查询

耳东@Erdong

Prometheus PromQL HTTP API 11月日更

正向代理和反向代理

liuzhen007

11月日更

科技热点周刊|马斯克卖掉特斯拉 10% 股票;Facebook 停用面部识别系统;微软拥抱 Metaverse;雅虎退出中国

青云技术社区

云计算 facebook

架构训练营毕业总结

SAKIN

Go语言,深入了解 RWMutex 实现原理

微客鸟窝

Go 语言 11月日更

《Kubernetes in action 读书笔记》:容器技术的发展

后台技术汇

Kubernetes 11月日更

树莓派

IT蜗壳-Tango

11月日更

如何成为一名合格的CRUD工程师?

博文视点Broadview

aardio 开发桌面应用,这几点必须要掌握!

星安果

Python aardio

linux几个没用但是有趣的命令

入门小站

Linux

死锁是如何工作的

卢卡多多

死锁 11月日更

Android C++系列:通过 JNI 访问 Java 字段和方法调用

轻口味

android 11月日更

算法入门-快速排序

ES_her0

11月日更

构筑“数据连接器”,腾讯云大数据推出“开源开放”战略

腾讯云大数据

大数据

移动端自动化 AutoJS 快速入门指南

星安果

自动化 Autojs

设计模式【3.3】-- CGLIB动态代理源码解读

秦怀杂货店

Java 设计模式

在IntelliJ IDEA中,开发一个摸鱼看书插件

小傅哥

Java 小傅哥 IDEA idea插件

【LeetCode】键盘行Java题解

Albert

算法 LeetCode 11月日更

在线2-36进制转文本工具

入门小站

工具

[ CloudWeGo 微服务实践 - 07 ] 阶段总结

baiyutang

golang 微服务 11月日更

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