【QCon】精华内容上线85%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

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

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

关注

评论

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

图灵访谈 | 一位半路出家的程序员的“天命”之选

图灵教育

Java 人工智能 大数据 编程语言 可视化

四个小时的轮番轰炸,三面蚂蚁金服,拿到offer的那天我的眼泪止不住的流了下来!

钟奕礼

Java 程序员 架构 后端 java面试

下载量已过亿次!阿里内部不外传秘籍50万字Java面试手册首次开放

钟奕礼

Java 程序员 架构 后端 java面试

【JavaWeb】JDBC快速入门时间

Java-fenn

Java

生成 UUID 的三种方式及测速对比!

掘金安东尼

前端 9月月更

java之面向对象2

喜羊羊

java; 9月月更

图灵访谈 | 一位半路出家的程序员的“天命”之选

图灵社区

Java 人工智能 大数据 编程语言 可视化

安全工作杂想 - 2022.9.12

心在飞

还在用开发者工具上传小程序? 快来试试 miniprogram-ci 提效摸鱼

若川

JavaScript 小程序 前端 小程序开发

架构师的十八般武艺:测试保障

agnostic

测试

应届的我,面试37次,16个offer上岸后总结了一些面试心得

收到请回复

Java 项目管理 面试 语言 & 开发 秋招

OpenFeign引起的HTTP Status 400与Tomcat吞没数据

Java-fenn

Java

经常用 vant-weapp 开发小程序,却不知道如何开发一个组件?

若川

JavaScript 小程序 前端 前端开发 小程序开发

Java基础(一)| Java概述与基础语法案例

timerring

Java core 9月月更

观察|数字经济新业态:云安全与生物医药CDMO合作成新趋势

Java-fenn

java;

高级前端二面面试题

夏天的味道123

JavaScript 前端

万字长文带你吃透SpringCloudGateway工作原理+动态路由+源码解析

Java快了!

关于SpringCloudAlibaba,看阿里这篇笔记真香

程序知音

Java 微服务 阿里 SpringCloud 后端技术

构建自动化运维体系的三大层级

穿过生命散发芬芳

自动化运维 9月月更

百度前端二面常见面试题合集

bb_xiaxia1998

JavaScript 前端

vite 3.0 都发布了,经常初始化 vite 项目,却不知 create-vite 原理?揭秘!

若川

JavaScript vue.js 前端 nodejs vite

MySQL必知必会-检索数据

阿柠xn

MySQL sql 数据检索 9月月更

GitHub上标星90k+的《Java知识总结+面试指南》到底有什么魅力?

钟奕礼

Java 程序员 架构 后端 java面试

css实现模糊镜效果及渐变字体和text-shadow冲突解决方案

Java-fenn

Java

阿里前端高频面试题

beifeng1996

JavaScript 前端

Java之面向对象3(终结篇)

喜羊羊

java; 9月月更

一文带你深入掌握ES6 Proxy数据代理

海底烧烤店ai

JavaScript node.js 全栈开发 9月月更

【云原生 | 从零开始学Docker】三、Docker实战之安装Nginx和Tomcat

泡泡

Docker 云计算 容器 云原生 9月月更

字节前端高频面试题

helloworld1024fd

JavaScript 前端

java之面向对象1

喜羊羊

java; 9月月更

【云原生】Nacos中的事件发布与订阅--观察者模式

石臻臻的杂货铺

云原生 nacos 9月月更

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