Hello World!大家好,我是大家的林语冰(挨踢版)~
我们平时使用 ES6 开发大概率是基于严格模式,但除了开发踩坑,其实面试也有可能通过严格模式变着花样坑你。
还有,像 Vue 等开源项目默认也是全局严格模式,但偶尔局部也要用到草率模式的特性,那如何在全局严格模式中当"法外狂徒",局部使用草率模式的语法特性呢?
这也是一个问题。所以今天我们来伪科普一下关于启用/限用/禁用严格模式的冷知识和"黑魔法",葱葱葱~
今日头条(TLDR 省流版)
- 知识浓度:3 星
- 作品字数:2_000 字水文
- 时间成本:260 秒
- 面向对象编程的严格模式(阿里88推理题)
- Vue 如何在严格模式下也能使用
with
语句呢? - 如何判断全局严格模式?
- 如何判断函数局部严格模式?
彼芯到位,全都学会,懂得都懂,不懂 follow!
面向对象编程的严格模式
除了使用 'use strict'
字符串指令显式开启严格模式,诸如 export/import
和 <script type=module></script>
等 ESM 模块语法也会隐式开启严格模式。
举一反一,在 ES6 类语法筑基的面向对象编程中,某些语法也会隐式开启严格模式。
举个粒子,语冰偷偷共享一道据说是阿里88的推理题。
那这道题的问题是,this
绑定是什么鬼物呢?
- A. 实例对象
cat
- B. 全局的
globalThis
- C.
cat/globalThis
都有可能 - D. 其他???
一般而言,this
绑定大约会涉及以下几种场景,包括但不限于:
- 函数独立调用
fn()
- 对象实例方法调用
cat.fn()
new
实例化构造调用new Cat
call/apply/bind
反射调用Reflect.apply(fn, null, [])
- 箭头函数(lambda)词法绑定
() => {}
- ...
结合题目对号入座,我们科学推理:
- 首先,
cat.touchFish()
属于方法调用,所以this
绑定cat
实例,然后返回fn
- 其次,
fn()
属于函数独立调用,所以this
绑定globalThis
所以这道题中的 this
绑定可能是 globalThis
,因为函数独立调用时 this
默认绑定全局对象 globalThis
。
然而这并不是正解,实际执行之后会发现 this
绑定其实是 undefined
。我们的推理过程问题不大,那为什么结果反而是 undefined
?
我们知道,在严格模式中,函数独立调用 this
默认绑定 undefined
,而在草率模式中则绑定 globalThis
。
排除一切不可能事件,剩下的说法再玄学,也必是唯一的真相。所以真相只有一个,fn
函数不仅是作为函数独立调用,而且还是在严格模式下调用。
换而言之,上述代码可以转化为以下的等价代码:
如你所见,一旦你的代码中使用了 class
关键字,类区块中的所有语法将在严格模式下执行。
这就是这道题的隐藏考点,类作用域默认能且仅能支持严格模式,你答对了吗?
类似的题目还有结合箭头函数等变体,但解题思路都大同小异,包括但不限于:
禁用/限用严格模式的"黑魔法"
现代化前端开源项目大都基于严格模式开发,但有时确实需要使用草率模式的语法。
举个粒子,严格模式禁用 with
语句,所以在严格模式中使用 with
语句会报错。但是 Vue 的编译器依赖 with
语句实现,那有什么黑魔法可以局部规避严格模式吗?
所以我们写不出 Vue,其实是有原因的。如果不懂启用/禁用/限用严格模式的千层套路,我们这辈子都写不出类似 Vue 的开源框架。
其中一个思路就是"序列化"(这里不是指 JSON
那种序列化)。
举个粒子,with
语句之所以会报错,是因为在严格模式下,JS 引擎编译时就能识别该语法,并抛出静态语法异常。
序列化的思路就是不直接使用 with
语句,而是间接使用,比如把 with
语句变成字符串,然后交给 eval/Function
执行就欧了。
如你所见,with
语句这里不是源码,而是字符串,所以 JS 引擎编译时不会报错,即使是在严格模式下。
需要注意的是,这里 eval
必须使用间接调用,直接调用还是会被全局严格模式捕获。
另外,一些语法并不支持严格模式。
注意,这里的说法是不支持严格模式,而不是不被严格模式支持。
举个粒子,使用了非简单形参的函数局部不允许使用 'use strict'
指令。
如你所见,这种函数开启局部严格模式会报错。
非简单形参包括 ES6 提供的若干新语法特性:
- 剩余参数
- 默认参数
- 解构参数
- ...
检测全局严格模式
由于相同语法在严格模式会有不同静态语义和运行时行为,所以有时候我们也需要判断严格模式,然后再去 DIY 我们的代码逻辑。
举个粒子,经典笔试题模拟实现一个 Reflect.apply()
API 就涉及这种刚需,那么如何去判断当前的全局环境是否开启了严格模式呢?
其中一个思路利用"牛三定律"借力打力,直接写一些严格模式不支持的语法,如果爆炸,说明当前环境开启了严格模式,否则就是草率模式。
举个粒子,函数独立调用时,在严格模式下 this
绑定 undefined
,在草率模式下则绑定 globalThis
。
如你所见,利用函数独立调用的"双标"行为就欧了,但要注意这里不能是箭头函数/顶层作用域的 this
,一些 edge cases 会导致判断失真。
检测局部严格模式
我们知道除了全局脚本/ESM 模块的严格模式,还有类区块严格模式和函数局部严格模式等。
有时候可能我们全局是草率模式,但某个函数局部是严格模式,那如何判断该函数开启了严格模式呢?
很可惜,截止本文付梓为止,语冰也未能找到一种完美的解决方案,简直是要逼死强迫症了...
FYI,这里会反向教学一些不完美的解决方案,帮助大家少走弯路。
第一种思路,将函数"序列化",然后判断函数体代码是否包含 'use strict'
指令。
虽然但是,如果函数体代码中含有 'use strict'
的字符串,但如果只是注释呢?我们的判断就会失真。
第二种思路,关于字符串相关的算法设计,优先考虑正则表达式,所以我们可以使用正则精准匹配函数体,并且函数体首行是 'use strict'
,我们才认为该函数开启了严格模式。
虽然但是,我们之前已经分享过了,'use strict'
不一定要求要出现在首行。所以这种方法也不完美。
第三种思路,我们利用"牛三定律"借力打力,让函数使用严格模式的语法,再观察祂有没有爆炸,但这种方法未必适用于箭头函数等 edge cases。
Anyway,大家有什么大胆的想法/奇奇怪怪的"黑魔法",欢迎在留言区自由言论。
高能总结
- 类作用域能且仅能支持严格模式
- 限用严格模式语法可以乞灵于
eval
等黑魔法 - 判断全局严格模式可以利用"牛三定律"借力打力
- 判断函数局部严格模式暂不支持
吾乃前端的虔信徒,传播 BUG 的福音。我是大家的林语冰,我们一期一会,不散不见,掰掰~