简介
在了解如何欺骗之前先简单的介绍一下词法作用域吧:
js
if(1){
let a = 1
}
console.log(a);
我们可以通过这样一个比较简短的代码来感受一下词法作用域,可以来猜一猜我们运行之后的结果是什么 如果你已经知道了那么说明你对它有一个不错的认识了,如果不太确定的话答案就在下面
词法作用域
词法作用域是由你的代码中将变量和块作用域写在哪里来决定的。比如上面的if,let 在{}中定义了一个变量a,那么a的词法作用域就在if的块区域之中,也就是出生在哪他的词法作用域就在哪
这就是他的词法作用域
那么上面那段代码究竟会输出什么呢? 答案是由于在全局中找不到a的声明,所以将会抛出一个报错。当然神奇的是如果你将let换成var,他将会输出1,原因就是由于变量提升事实上var的声明跳出了if,那么它的作用域就是全局。当然我们今天的主题并不是var的变量提升,接下来让我们来看看怎么"欺骗"词法作用域吧!
欺骗词法作用域
词法作用域完全由词法"出生"的位置来定义,但JavaScript中有两个机制来实现在运行时来"修改"(或者说欺骗)词法作用域。分别是eval(..)函数和with关键字
1.eval
js
let input = function(str){
eval(str)
console.log(a);
}
var a = 1
input('var a = 3')
eval(..)函数的作用其实就可以理解为将其中的内容搬到调用这个函数的时候去用,也就是说此时运行代码你会得到3。而按照正常的理解来说内部没有找到var声明就需要向外寻找那么会找到1,而且因为词法作用域问题eval函数中的声明也不应该在上一级的input中生效可是神奇的是这个方法让a成功被声明了,说明eval函数中声明的变量的词法作用域被改变了!
2.with
而另一个做到欺骗词法作用域的就是with,with通常被用来修改对象的多个值,如下图:
js
// 创建对象
let obj = {
a: 1,
b: 2,
c: 3,
}
// 普通的修改方式
obj.a = 2
obj.b = 3
obj.c = 4
// 使用with
with(obj) {
a = 3
b = 4
c = 5
}
那么它又是怎么欺骗词法作用域的呢?我们来看这样一段代码
js
function foo(obj){
with(obj){
c = 1
}
}
let obj = {
a : 1
}
foo(obj)
console.log(c);
首先我们来简单的理解一下,我们利用with对一个obj中的c进行了改变,但事实上obj中并没有c的存在,那么最后我们打印c的时候应该是报错的,因为我们并没有定义一个c对吧?可是你可以尝试运行一下这段代码,你会惊奇的发现:输出了1!没错,c被输出了,可是无论怎么看c的作用域都不可能是全局的,可log在全局只能输出作用域在全局变量。那么这到底是为什么呢?
其实这被称为"泄露",with将c泄露到了全局,因为它并没有在obj中找到c,结果它就非常离谱的在全局凭空创造了一个变量c并且给它赋了值。
相比于eval只是给声明"搬了个家",with直接"建了个家"从而欺骗词法作用域更加离谱,它们二者都会拖慢代码的运行,除去一些特殊情况我们可以尽量不要使用这两种机制。
那我们今天的小知识分享就结束了,如果可以请看到这的小伙伴给作者的辛苦点个小小的赞吧~~