JS严格模式的规则合集

前端印记

共 23202字,需浏览 47分钟

 ·

2021-09-26 17:19

人生苦短,总需要一点仪式感。比如学前端~


每日一题:请你说下开启“严格模式”后,JS会有哪些表现?

思维脑图请点击 “阅读原文”时间太晚了今天先不搞脑图,最晚本周六会更新。)

=======【【【正经分割线】】】=======

严格模式

用途

ECMAScript 5 增加了严格模式(strict mode)的概念,严格模式是一种不同的 JavaScript 解析和执行模型,ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。可以理解为,严格模式为这门语言中某些容易出错的部分施加了限制,使代码不再“稀松/懒散“(sloppy)

开启严格模式, 会让 JS 引擎以更严格的条件检查 JavaScript 代码可能会存在的错误。

目前严格模式已得到所有主流浏览器支持。

好处

严格模式的好处是可以提早发现错误,因此可以捕获某些因 ECMAScript 问题导致的编程错误。

使用严格模式后,原来代码中的很多写法上都会有不同于非严格模式的表现。而且未来更高版本的 ECMAScript 会逐步强制全局使用严格模式(比如 ES6 的模块),所以说理解严格模式的一些规则很重要。

“严格模式”开启方式

严格模式可以应用到全局,也可以应用到函数内部。

对整个脚本启用严格模式,需要在脚本开头(函数外部)加上这一行字符串,则整个脚本都会按照严格模式来解析:

// 用严格模式编译指示(pragma)
'use strict'

这样写一个即使在 ECMAScript 3 中也有效的字符串,可以兼容不支持严格模式的 JavaScript 引擎。若引擎支持严格模式会自动开启严格模式,若不支持只会将这行代码当成一个未赋值的字符串字面量。

也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:

// 在一个函数内部开启严格模式
function doSomething() {
  'use strict'
  // 接下来写函数体
}

严格模式下代码的一些表现


  • 用途

  • 好处

  • “严格模式”开启方式

  • 一、将过失错误转成异常

    • 严格模式下未声明变量不可赋值

    • 严格模式下的暗示全局变量不再主动提升

    • 严格模式下声明的变量不会自动挂到 window 上

    • 严格模式会使引起静默失败的操作抛出异常

    • 严格模式下删除“不可删除的属性”会报错

    • 严格模式禁止删除声明的变量

    • 严格模式要求函数的参数名不能重复

    • 严格模式禁止八进制数字语法

    • 严格模式禁止设置 原始值(primitive)的属性

  • 二、简化变量的使用

    • 严格模式不允许使用 with 语句

    • 严格模式下 eval 内部创建的变量和函数无法被外部访问

  • 三、让 eval 和 arguments 变的简单

    • 严格模式下不能将 eval 和 arguments 用作标识符

    • 严格模式下函数形参不再与 arguments 对象关联

    • 严格模式下重写 arguments 会报错

    • 全局严格模式下修改默认参数不会影响 arguments 对象

    • 严格模式下不能访问 arguments.callee

    • 严格模式下不能访问 arguments.caller

    • 严格模式下不能操作函数的 caller

  • 四、安全的 JavaScript

    • this 指向不再被偷摸改变

    • 严格模式下函数内 this 的值不会经过装箱

    • 严格模式下不能通过 ES 的扩展“游走于"JS 的栈中

    • 严格模式下的 arguments 不会再提供访问与调用这个函数相关的变量的途径

  • 五、未来的 ECMAScript 版本铺平道路

    • 在严格模式中一部分字符变成了保留的关键字。

    • 严格模式禁止不在全局或者函数层面上的函数声明。

  • 六、ES6 一些新增特性的表现

    • 严格模式下函数内部不能使用剩余操作符、解构操作符和默认参数等

    • 类中的代码默认都在严格模式下执行

    • ES6 模块默认都在严格模式下执行


严格模式同时改变了语法及运行时行为。当开启严格模式后,之前的一些代码写法会有不同的表现。变化可以归纳为这几类:

  • 将问题直接转化为错误(例如语法错误或运行时错误)
  • 简化对变量的一些处理
  • 简化了 eval 以及 arguments
  • 将一些安全 JS 的步骤变得更简单;
  • 改变了预测未来 ECMAScript 行为的方式。

具体如下:

一、将过失错误转成异常

严格模式下未声明变量不可赋值

未经声明而初始化变量是 JavaScript 编程中一个非常常见的错误,会导致很多问题。

在严格模式下,如果像这样给未声明的变量赋值,则会导致报错、抛出 ReferenceError

'use strict'
mistypedVaraible = 17 // 报错 Uncaught ReferenceError: mistypedVaraible is not defined

非严格模式下,如果不用 var 直接给一个不存在的变量名字赋值,JS 引擎会默认自动初始化创建该变量,并进行赋值操作。

采用严格模式后,给不存在的变量直接赋值就会报错。JS 引擎不会任劳任怨的为我们做这些事情了。


严格模式下的暗示全局变量不再主动提升

暗示全局变量:如果之前我们在函数中不经声明就创建一个变量,则变量会被提升到全局内(如下边代码中的 sum)

// 函数内的变量,不用var关键字声明的话,变量提升到全局
function a() {
  sum = 1 //  暗示全局变量,提升到window
}
a()
console.log(sum) // 1

但使用严格模式后,因为上一条规则约定的变量不声明不能用,所以这里调用函数会直接报错抛出ReferenceError

'use strict';
function a() {
  sum = 1 // Uncaught ReferenceError: sum is not defined
}
a()
console.log(sum)

严格模式下声明的变量不会自动挂到 window 上

非严格模式下,我们不用关键字直接定义一个变量,再次访问时,他已经被默认加载到window上了(如下的num):

// 全局变量挂载到window上
num = 124
console.log(window.num) // 124

同样的,使用严格模式后,因为上一条规则约定的变量不声明不能用,所以这里调用函数会直接报错抛出ReferenceError

'use strict';
num = 124
console.log(window.num) // Uncaught ReferenceError: num is not defined

严格模式会使引起静默失败的操作抛出异常

静默失败:就是你执行了一行代码,但是这行代码啥也没做(失败了/不起作用),但是 JS 引擎却不给你任何反应。这种问题很难测试出来,所以为了更好的增强程序,原来任何在正常模式下引起静默失败的赋值操作,现在严格模式下都会抛出异常。

例如,NaN 是一个不可写的全局变量。在正常模式下,给 NaN 赋值不会产生任何作用;开发者也不会受到任何错误反馈,但在严格模式下,给 NaN 赋值会抛出一个异常。

'use strict'

NaN = '我是故意的' // Uncaught TypeError: Cannot assign to read only property 'NaN' of object '#<Window>'

再比如:给不可写的属性/只读属性赋值、读取不可读的属性、给不可扩展属性的对象增加新属性等。在非严格模式下尝试这么做会被忽略(或返回 undefined),在严格模式下,尝试这么做都会抛出错误。

如下,代码里的只读值"x"。这个属性的值被设置为不可更改的。尝试修改,就会报错

'use strict'
// 给不可写属性赋值
var obj1 = {}
Object.defineProperty(obj1, 'x', {
  value2,
  writablefalse,
})
obj1.x = 9 // Uncaught TypeError: Cannot assign to read only property 'x' of object '#<Object>'

同样属性不可修改的场景是,对象只定义了获取函数(getter)时,意味着对象身上的属性都是只读的。在严格模式下,尝试写入只定义了获取函数的属性会抛出错误。

'use strict'
// 给只读属性赋值
var obj = {
  get x() {
    return 13
  },
}
obj.x = 4 // Uncaught TypeError: Cannot set property x of #<Object> which has only a getter

同样地,只有一个设置函数的属性是不能读取的,非严格模式下读取会返回 undefined,严格模式下会抛出错误。【可能是我的电脑太高端了?试了一下没报错。大家看到这里可以自己试一下】

'use strict'
// 获取不可读属性的值
var obj = {}
Object.defineProperty(obj, 'x', {
  set(val) {},
})
console.log(obj.x)
obj.x = 12 // 这就是 静默失败 的一种场景
console.log(obj.x) // 只不过是给该属性赋值也没用

给不可增加属性的对象增加新属性:

'use strict'
// 给不可扩展对象的新属性赋值
var fixed = {}
Object.preventExtensions(fixed) // Object.preventExtensions()将对象标记为不再可扩展
fixed.newProp = 'props' // Uncaught TypeError: Cannot add property newProp, object is not extensible

严格模式下删除“不可删除的属性”会报错

若某个属性不能从对象上删除。非严格模式下对这个属性调用 delete 没有效果,严格模式下会抛出错误。

'use strict'
delete Object.prototype // Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] }

严格模式禁止删除声明的变量

非严格模式下,对声明的变量调用 delete 没什么用也不会报错,在严格模式下这么做会引发语法错误。

"use strict";
/*  在严格模式下删除非限定标识符。 */

delete x; // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.

function test() {
  var x = 12
  delete x // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode
}
test()

严格模式要求函数的参数名不能重复

在正常模式下,最后一个重名参数名会掩盖之前的重名参数,之前的参数仍然可以通过 arguments[i]来访问。然而,这种隐藏毫无意义并且可能是意料之外的(比如不小心打错的名字),所以在严格模式下参数重名被认为是语法错误:

// 在编辑器里这种写法就直接报错提示了
function sum(a, a, c{
  // Uncaught SyntaxError: Duplicate parameter name not allowed in this context
  'use strict'
  return a + a + c
}

严格模式禁止八进制数字语法

八进制字面量用 0 开始,一直以来是很多错误的源头,因此在严格模式下会被认为是无效语法,会导致 JavaScript 引擎抛出语法错误。

let octalNum1 = 070 // Uncaught SyntaxError: Octal literals are not allowed in strict mode.

实际上,ECMAScript 并不包含八进制语法,但所有的浏览器都支持这种以零开头的八进制语法:0644 === 420 还有\045 === %

在 ECMAScript6 中支持为一个数字 0o 的前缀来表示八进制数

var a = 0o10 // ES6: 八进制

① ECMAScript 2015 或 ES6 中的八进制值通过前缀 0o 来表示;严格模式下,前缀 0 会被视为语法错误,如果要表示八进制值,应该使用前缀 0o。——《JavaScript 高级程序设计 第四版》

严格模式下在 parseInt()中使用八进制字面量

ECMAScript 5 修改了非严格模式下的 parseInt(),将八进制字面量当作带前导 0 的十进制字面量。 例如:

let value = parseInt('010')
// 非严格模式:值为 8  //【我的电脑测试都为10,大家可以亲自试一下这里】
// 严格模式:值为 10

严格模式禁止设置 原始值(primitive)的属性

不采用严格模式,给原始值设置属性将会简单忽略;采用严格模式,该将抛出 TypeError 错误。

'use strict'
false.true = '' // Uncaught TypeError: Cannot create property 'true' on boolean 'false'
;(14).sailing = 'address' // Uncaught TypeError: Cannot create property 'sailing' on number '14'
'with'.you = 'far away' // Uncaught TypeError: Cannot create property 'you' on string 'with'

(这下,string、number、boolean 类型的值就和同样是原始值的 undefined、null 一个规则了!!undefined、null 多年的不满冤屈终于得以评审、获得了公平对待!)

以下代码在任何模式下都是一样的表现。

undefined.no = 'error' // Uncaught TypeError: Cannot set properties of undefined (setting 'no')
null.no = 'error' // Uncaught TypeError: Cannot set properties of null (setting 'no')

不公平的体现:非严格模式下,给原始值赋值处理会经过装箱操作(包装类):

str = 'with'
str.you = 'far away' // 注1: 解释如下
console.log(str.sxx) // 得到 undefined

注 1: 我们给字符串值设置属性,但作为原始值的字符串不能有属性和方法,于是 JS 引擎会默认执行装箱操作,调用 String(str)将字符串对象化,并设置属性 you 的值为"far away"。要知道,设置完毕后,原来的 str 是没有变化的!所以再次获取还是没有这个属性,返回 undefined。

但是对原始值取值的规则不变,还是返回 undefined,string、number、boolean 这些类型的原始值和 undefined、null 还是不一样的。

console.log('with'.you) // undefined

二、简化变量的使用

严格模式简化了代码中变量名字映射到变量定义的方式:很多编译器的优化是依赖变量 X 位置的能力:这对全面优化 JavaScript 代码至关重要。JavaScript 有些情况会使得代码中名字到变量定义的基本映射只在运行时才产生。严格模式除了大多数这种情况的发生,所以编译器可以更好的优化严格模式的代码

严格模式不允许使用 with 语句

with 语句改变了标识符解析时的方式,严格模式下为简单起见已去掉了这个语法。如果在严格模式使用 with 会抛出错误。

with 所引起的问题是块内的任何名称可以映射(map)到 with 传进来的对象的属性,也可以映射到包围这个块的作用域内的变量(甚至是全局变量),这一切都是在运行时决定的,在代码运行之前是无法得知的。

在严格模式下使用 with 会报错,所以就不会存在 with 块内的变量在运行时才决定引用到哪里的情况了:

'use strict'
var x = 17
with (obj) {
  // Uncaught SyntaxError: Strict mode code may not include a with statement
  x
  // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?
  // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。
}

严格模式下 eval 内部创建的变量和函数无法被外部访问

变量和函数可以在 eval()中声明,但它们会位于代码执行期间的一个特殊的作用域里,代码执行完毕就会销毁。

var x = 17
var evalX = eval("'use strict'; var x = 42; x")
console.log(x === 17// true
console.log(evalX === 42// true

在严格模式下,在 eval()内部创建的变量和函数无法被外部访问。

// 使用 eval()创建变量
// 非严格模式:警告框显示 10
// 严格模式:调用 alert(x)时抛出 ReferenceError
function doSomething() {
  // 上层函数
  eval('let x = 10')
  alert(x)
}
doSomething() // Uncaught ReferenceError: x is not defined

以上代码在非严格模式下运行时,会在 eval 的上层函数(surrounding function)或者全局创建一个新的变量。

这意味着,一般情况下,在一个包含 eval 调用的函数内所有没有引用到参数或者局部变量的名称都必须在运行时才能被映射到特定的定义(因为 eval 可能引入的新变量会覆盖它的外层变量)。在严格模式下 eval 仅仅为被运行的代码创建变量,所以 eval 不会使得名称映射到外部变量或者其他局部变量,

也就是说,代码这里调用 eval()不会在 doSomething()中创建变量 x,由于 x 没有声明,alert()会抛出 ReferenceError,提示 x 未定义。

相应的,如果函数 eval 被在严格模式下的 eval(…)以表达式的形式调用时,其代码会被当做严格模式下的代码执行。当然也可以在代码中显式开启严格模式,但这样做并不是必须的。

function strict1(str{
  'use strict'
  return eval(str) // str中的代码在严格模式下运行
}
function strict2(f, str{
  'use strict'
  return f(str) // 没有直接调用eval(...): 当且仅当str中的代码开启了严格模式时
  // 才会在严格模式下运行
}
function nonstrict(str{
  return eval(str) // 当且仅当str中的代码开启了"use strict",str中的代码才会在严格模式下运行
}

strict1("'Strict mode code!'")
strict1("'use strict'; 'Strict mode code!'")
strict2(eval"'Non-strict code.'")
strict2(eval"'use strict'; 'Strict mode code!'")
nonstrict("'Non-strict code.'")
nonstrict("'use strict'; 'Strict mode code!'")

因此,在 eval 执行的严格模式代码下,变量的行为与严格模式下非 eval 执行的代码中的变量相同。

三、让 eval 和 arguments 变的简单

严格模式让 arguments 和 eval 少了一些奇怪的行为。

两者在通常的代码中都包含了很多奇怪的行为: eval 会添加删除绑定,改变绑定好的值,还会通过用它索引过的属性给形参取别名的方式修改形参。虽然在未来的 ECMAScript 版本解决这个问题之前,是不会有补丁来完全修复这个问题,但严格模式下不能将 eval 和 arguments 作为关键字对于此问题的解决是很有帮助的。

严格模式下不能将 eval 和 arguments 用作标识符

在严格模式下,不能定义名为 eval 和 arguments 的变量,否则会导致语法错误。

不能用它们作为标识符,这意味着下面这些情况都会抛出语法错误:

  1. 使用 let 声明;
  2. 将 eval 和 arguments 赋予其他值;
  3. 修改其包含的值,如使用++;
  4. 将 eval 和 arguments 用作函数名;
  5. 将 eval 和 arguments 用作函数参数名;
  6. 在 try/catch 语句中将 eval 和 arguments 用作异常名称。

以下的所有尝试将引起语法错误:

"use strict";
// 以下几个报错一致: Uncaught SyntaxError: Unexpected eval or arguments in strict mode
eval = 17;
arguments++;
++eval;
var obj = { set p(arguments) { } };
var eval;
try { } catch (arguments) { }
function x(eval{ }
function arguments() { }
var y = function eval() { };

// Uncaught SyntaxError: Unexpected eval or arguments in strict mode at new Function (<anonymous>)
var f = new Function("arguments""'use strict'; return 17;");

严格模式下函数形参不再与 arguments 对象关联

即,函数内部的参数的值不会随 arguments 对象的值的改变而改变。

在非严格模式下,对于第一个参数是 a 的函数,对 a 赋值时会同时赋值给 arguments[0],反之亦然(除非没有参数,或者 arguments 对应的值 被删除),给 arguments[1]赋值,参数 b 也随之改变。

function f(a, b{
  console.log(a, b) // 形参a 形参b
  a = '小石头'
  arguments[1] = '石头姐'
  return [a, arguments[0], b, arguments[1]]
}
var pair = f('形参a''形参b')
console.log(pair) // ['小石头', '小石头', '石头姐', '石头姐']

严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i]的值不会随与之相应的参数的值的改变而改变,同名参数的值也不会随与之相应的 arguments [i]的值的改变而改变。

'use strict'
function f(a, b{
  console.log(a, b) // 形参a 形参b
  a = '小石头'
  arguments[1] = '石头姐'
  return [a, arguments[0], b, arguments[1]]
}
var pair = f('形参a''形参b')
console.log(pair) // ['小石头', '形参a', '形参b', '石头姐']

严格模式下,arguments 会有一些变化。首先,像前面那样给 arguments[1]赋值不会再影响 形参 b 的值。就算把 arguments[1]设置为'石头姐',b 的值仍然还是传入的值。

严格模式下重写 arguments 会报错

在函数中尝试重写 arguments 对象会导致语法错误。但是从报错中我们就能看出来 JS 引擎的处理思路,其实是因为上边说过的,eval 和 arguments 不允许当作变量被赋值。

'use strict';
function doAdd() {
  arguments = [] // Uncaught SyntaxError: Unexpected eval or arguments in strict mode
}
doAdd()

全局严格模式下修改默认参数不会影响 arguments 对象

全局严格模式下(这是因为使用默认参数后,函数内部不允许使用严格模式,下边会讲解), 修改默认参数也不会影响 arguments 对象,它始终以调用函数时传入的值为准:

'use strict'
function makeKing(name = '小石头'{
  name = '石头姐'
  return `最可爱的是 ${arguments[0]}`
}
console.log(makeKing()) // '最可爱的是 undefined'
console.log(makeKing('小石头')) // '最可爱的是 小石头'

严格模式下不能访问 arguments.callee

正常模式下,arguments.callee 指向当前正在执行的函数。可以用于没名字的函数想递归的时候。

;(function () {
  console.log(arguments.callee)
})()
/* 打印结果,得到函数自身
ƒ(){
  console.log(arguments.callee)

*/

实际上,这个作用很小。直接给执行函数命名就可以。

而且 arguments.callee 十分不利于优化,例如内联函数,因为 arguments.callee 会依赖对非内联函数的引用。

在严格模式下,arguments.callee 是一个不可删除属性,而且赋值和读取时都会抛出异常:

'use strict'
;(function () {
  console.log(arguments.callee) // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
})()

有时候,调用一些第三方的插件,他们可能会报这个错。

严格模式下不能访问 arguments.caller

同上。ECMAScript 5 也定义了 arguments.caller,但在严格模式下访问它会报错,在非严格模式下则始终是 undefined。

// 我这里的“高级电脑”没有复现
'use strict'
function test() {
  console.log(arguments.caller)
}
test()

这是为了分清 arguments.caller 和函数的 caller 而故意为之的。

而作为对这门语言的安全防护,这些改动也让第三方代码无法检测同一上下文中运行的其他代码。

具体原因下边讨论。

严格模式下不能操作函数的 caller

严格模式下不能操作有两个方面:

  1. 不能获取函数.caller
  2. 不能给函数.caller 赋值(很好理解,都不能获取、怎么赋值?) 否则会导致错误。
'use strict'

function test() {
  test.caller = '我是故意的'
  console.log(test.caller)
  // 以上两个报错都一样
  // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

  /* argument.callee指向的就是test函数,所以这么获取也不行: */
  console.log(arguments.callee.caller) // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}
test()

之所以要求严格模式,主要因为在非严格模式下函数调用中允许使用 f.argumentsf.caller,而它们都会引用外部函数的栈帧。显然,这意味着不能应用优化了。因此 尾调用优化要求必须在严格模式下有效,以防止引用这些属性。(下边第五条有相同的探索)

四、安全的 JavaScript

严格模式下更容易写出安全的 JS

this 指向不再被偷摸改变

JavaScript 中最大的一个安全问题,也是最令人困惑的一个问题,就是在某些情况下 this 的值是如何确定的。

非严格模式下,对一个普通的函数来说 this 总会是一个对象:不管调用之前 this 是啥,普通调用函数时,总是默认指向 window 对象(不讨论使用 call, apply 或者 bind 方法来指定一个确定的 this 的情况)。

function viewThis() {
  'use strict'
  console.log(this// undefined
}
viewThis()

这种自动转化为对象的过程不仅是一种性能上的损耗,同时在浏览器中暴露出全局对象也会成为安全隐患,因为全局对象提供了访问那些所谓安全的 JavaScript 环境必须限制的功能的途径。

所以在严格模式下为了优化这个安全隐患,调用函数时如果没有指定上下文对象,则 this 值不会指向 window。 除非使用 apply()或 call()把函数指定给一个对象,否则 this 的值会变成 undefined。

function viewThis() {
  console.log(this// Window {window: Window, self: Window, document: document, name: '', location: Location, …}
}
viewThis()

【补充】更具调用场景 this 指向不同,更详细的规则:

  1. 如果在全局函数中调用,则 this 在非严格模式下等于 window,在严 格模式下等于 undefined。
  2. 如果作为某个对象的方法调用,则 this 等于这个对象(不区分是否严格模式)。
  3. 匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。(可以总结成,严格模式下,只要不是显示指定,this 都指向 undefined)
  4. 使用函数的 apply()或 call()方法显示绑定 this 时,在非严格模式下 null 或 undefined 值会被强制转型为全局对象。在严格模式下,则始终以指定值作为函数 this 的值,无论指定的是什么值。
// 访问属性
// 非严格模式:this指向window,访问全局的color,得到red
// 严格模式:抛出错误,因为 this 值为 null
'use strict'
let color = 'red'
function displayColor() {
  console.log(this.color) // Uncaught TypeError: Cannot read properties of null (reading 'color')
}
displayColor.call(null// 严格模式下,指定null为this,那this就是null。感觉是严打期间,都不敢偷税漏税阳奉阴违了!

严格模式下函数内 this 的值不会经过装箱

通常,函数会将其 this 的值转型为一种对象类型,这种行为经常被称为“装箱”(boxing)。

这意味着原始值会转型为它们的包装对象类型(包装类)。

// 原始值被包装成类对象
function foo() {
  console.log(this)
}
foo.call() // Window {} (undefined被包装成全局对象)
foo.call(2// Number {2} (数值被包装成Number对象)

在严格模式下执行以上代码时,this 的值不会再“装箱”,是啥就是啥,露出原型。

function foo() {
  'use strict'
  console.log(this)
}
foo.call() // undefined
foo.call(2// 2

严格模式下不能通过 ES 的扩展“游走于"JS 的栈中

这一点在上边 arguments 那里顺便讨论过了。就是.caller.arguments禁止使用了。

这里从安全层面深度剖析一下:

在普通模式下用这些扩展的话,当一个叫 fun 的函数正在被调用的时候,fun.caller 是最后一个调用 fun 的函数,而且 fun.arguments 包含调用 fun 时用的形参。这两个扩展接口对于"安全"JavaScript 而言都是有问题的,因为他们允许 "安全的" 代码访问 "专有" 函数和他们的(通常是没有经过保护的)形参。如果 fun 在严格模式下,那么 fun.caller 和 fun.arguments 都是不可删除的属性而且在存值、取值时都会报错:

function restricted() {
  'use strict'
  restricted.caller // 抛出类型错误 同下
  restricted.arguments // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}
function privilegedInvoker() {
  return restricted()
}
privilegedInvoker()

严格模式下的 arguments 不会再提供访问与调用这个函数相关的变量的途径

在一些旧时的 ECMAScript 实现中 arguments.caller 曾经是一个对象,里面存储的属性指向那个函数的变量。这是一个安全隐患,因为它通过函数抽象打破了本来被隐藏起来的保留值;它同时也是引起大量优化工作的原因。出于这些原因,现在的浏览器没有实现它。但是因为它这种历史遗留的功能,arguments.caller 在严格模式下同样是一个不可被删除的属性,在赋值或者取值时会报错:

'use strict'
function fun(a, b{
  'use strict'
  var v = 12
  return arguments.caller // 抛出类型错误【同样的,我的电脑没实现】
}
fun(12)

五、未来的 ECMAScript 版本铺平道路

未来版本的 ECMAScript 很有可能会引入新语法,ECMAScript5 中的严格模式就提早设置了一些限制来减轻之后版本改变产生的影响。如果提早使用了严格模式中的保护机制,那么做出改变就会变得更容易。

在严格模式中一部分字符变成了保留的关键字。

这些字符包括 implements, interface, let, package, private,protected, public, staticyield。在严格模式下,你不能再用这些名字作为变量名、函数名或者形参名。

var implements = '时间太晚了,我已经开始想胡言乱语了~' // Uncaught SyntaxError: Unexpected strict mode reserved word

严格模式禁止不在全局或者函数层面上的函数声明。

说白了,基本就是“块级作用域内的函数声明不会提升到外部”了。

  • 严格模式下,if 块中声明函数,外部访问不到
'use strict'

if (true) {
  function f() {
    console.log('我是故意的')
  }
  f() // "我是故意的" 你们的电脑这里会报错?
}

f() // Uncaught ReferenceError: f is not defined
  • for 循环块中一样的表现
'use strict'
for (var i = 0; i < 5; i++) {
  function ff() {
    'use strict'
    console.log(123)
  } // 123 你们的电脑这里会报错?
  ff()
}
ff() // Uncaught ReferenceError: ff is not defined

全局/函数内嵌套声明还是没问题的

function bar() {
  // 合法
  function fun() {} // 同样合法
}

在浏览器的普通代码中,在“所有地方”的函数声明都是合法的。这是一种针对不同浏览器中不同语义的一种延伸。

但未来的 ECMAScript 版本很有希望制定一个新的、针对不在全局或者函数层面进行函数声明的语法。

所以在严格模式下禁止这样的函数声明,对于将来 ECMAScript 版本的推出扫清了障碍。

这种禁止放到严格模式中并不是很合适,因为这样的函数声明方式从 ES5 中延伸出来的。但这是 ECMAScript 委员会推荐的做法,浏览器就实现了这一点。

六、ES6 一些新增特性的表现

严格模式下函数内部不能使用剩余操作符、解构操作符和默认参数等

ES6 增加的这些新特性期待参数与函数体在相同模式下进行解析。如果允许编译指示"use strict"出现在函数体内,JavaScript 解析器就需要在解析函数参数之前先检查函数体内是否存在这个编译指示,而这会带来很多问题。为此,ES7 规范增加了这个约定,目的是让解析器在解析函数之前就确切知道该使用什么模式。

function bar(a, b, c='d'{
 "use strict";
}

function baz({a, b, c}{
 "use strict";
}

function qux(a, b, ...c{
 "use strict";
}

以上,三个函数调用都会打印如下报错:
// Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list

类中的代码默认都在严格模式下执行

在之前的 ECMAScript 版本中没有模块这两个概念,因此不用考虑从语法上兼容之前的 ECMAScript 版本。

默认情况下,类定义中的代码都在严格模式下执行。

包括类声明和类表达式,构造函数、实例方法、静态方法、获取方法和设置方法都在严格模式下。

ES6 模块默认都在严格模式下执行

因此,在模块文件中会有以下表现:

  1. ES6 模块不共享全局命名空间;
  2. 模块顶级 this 的值是 undefined(常规脚本中是 window);
  3. 模块中的 var 声明不会添加到 window 对象
  4. ……

其实就相当于 script 脚本顶部开了"use strict",所以可以理解为,上边提到的所有严格模式的规则都会命中。

严格模式对变量的一些限制汇总

  • 变量名不能是保留字(implements、interface、let、package、private、protected、public、static 和 yield。)
  • 不带 var 等关键字 声明的变量不会挂载到 window 上
  • 给未声明的变量赋值 会抛出错误 ReferenceError
  • 禁止用 delete 删除变量

严格模式对函数的一些限制汇总

如果违反下述规则,则会导致语法错误,代码也不会执行。(from:《JavaScript 高级程序设计第四版》第三章结尾)

  • 不允许函数名为保留字( implements、interface、let、package、private、protected、public、static 和 yield)
  • 函数不能以 eval 或 arguments 作为名称
  • 函数内暗示全局变量不会提升到 window
  • 函数自调用时内部 this 不再指向 window,而是 undefined
  • 函数显示调用时 this 指向传递的参数,部分场景下不再默认修改为 window
  • 函数内部 this 不再自动“装箱”
  • 函数的参数不能叫 eval 或 arguments
  • 两个命名参数不能拥有同一个名称。命名函数参数必须唯一
  • 形参和 arguments 不会互相映射
  • 不能修改 arguments(源于 arguments 不能当作变量名使用)
  • 不能访问和修改 arguments.callee 和 arguments.caller(作用:他们分别引用函数本身和调用函数)
  • 不能访问“函数名.caller”
  • 修改默认参数不会影响 arguments
  • 使用了剩余操作符、解构操作符和默认参数等的参数,函数内部都不能使用严格模式。
  • 在块作用域内声明的函数不会被提升到块的外部。

严格模式对对象的一些限制汇总

以下几种情况下试图操纵对象属性会引发错误:

  • 给只读属性赋值会抛出 TypeError
  • 给不可写属性赋值会抛出 TypeError
  • 获取不可读的属性会报错 【?没测试出来】
  • 在不可配置属性上使用 delete 会抛出 TypeError
  • 给不存在的对象添加属性会抛出 TypeError
  • 给不可扩展的对象增加属性 会抛出 TypeError
  • 对象属性名必须唯一 【?没测试出来】

📚 推荐书籍

吐了,这是我字数最多的一篇了。还好我坚持下来了😹

以上知识点大部分总结自《Javascript 高级程序设计 第四版》


推荐支持正版。可在当当、京东购买。

如有需要,可在公众号@前端印记回复关键字:“电子书”,获取书籍资源。


引用:

  • 《JavaScript 高级程序第四版》https://item.jd.com/12958580.html
  • MDN 关于 严格模式的讲解 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

所有《每日一题》的 知识大纲索引脑图 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以点击文末的 “阅读原文” 快速跳转


END
愿你历尽千帆,归来仍是少年。

让我们一起携手同走前端路!

关注公众号回复【加群】即可

浏览 41
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报