GMTC北京站优惠购票最后一周!13个热点专题,50+大厂案例,这里一览 了解详情
写点什么

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

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

关注

评论

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

低代码开发简史

俞凡

架构

Golang生成随机字符串的八种方式与性能测试

张俭

Performance Go 语言

crypto/md5

康家沟偶像天团王大锤

跨越式成长 - 高效的学习方法

Ryan Zheng

【Vue2.x 源码学习】第十九篇 - 根据 vnode 创建真实节点

Brave

源码 vue2 6月日更

架构实战营模块六作业

竹林七贤

如何使用EasyRecovery巧妙恢复被误删的办公文档?

淋雨

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

Angular管道PIPE介绍

devpoint

angular.js angular 6月日更

六、拆分电商系统为微服务

菠萝吹雪—Code

架构实战营

程序员如何摆脱内卷?

程序员海军

程序员 互联网人 内卷 焦虑

【Flutter 专题】99 初识 EventBus

阿策小和尚

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

流式基础设施--云基础设施的未来

俞凡

云计算 架构

揭秘MySQL的主从同步实现方案

架构精进之路

MySQL 6月日更

架构训练营模块6作业

Geek_649372

架构训练营

Python——计数器(Counter)

在即

6月日更

kubelet分析-csi driver注册源码分析

良凯尔

源码 Kubernetes kubelet CSI Kubernetes Plugin

密码你真的了解吗

卢卡多多

密码学 6月日更

计算机系统性能评价

若尘

性能 计算机组成原理 6月日更

CSS工程化

法医

CSS 大前端 6月日更

Google大规模监控系统--Monarch

俞凡

架构 分布式 大厂实践

JAVA面向对象(七)--类的属性和方法详讲

加百利

Java 6月日更

【21-11】PowerShell 特殊变量

耳东@Erdong

PowerShell 6月日更

源码分析--golang读写锁

en

《Software Engineering at Google》免费开放

俞凡

c++ 软件工程 Google 大厂实践

OnceAgain

一个向往理想的现实主义者

个人总结

大学生如何让更好的入门计算机?

Bob

入门 话题讨论 话题 大学生

Angular模板简介

devpoint

angular.js angular 6月日更

DevOps的未来

俞凡

DevOps

智能无线接入网的崛起

俞凡

AI OpenRAN

架构实战训练营 - 模块六课后作业

Johnny

架构实战营

自媒体从业者如何选择合适的公司

石头IT视角

金融行业数据库架构实践与运维

金融行业数据库架构实践与运维

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