说起with和eval,大家一定会尽量避免和直接不使用,但是到底是为什么呢?我们今天来探讨一下:
with的设计原意:
为逐级的对象提供命名空间式的速写方式,即在指定空间,直接通过节点名称调用对象;
通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
比如一个对象,
var obj = {
a: 1,
b: 2,
c: 3
};
我们通常赋值,重复写3次
obj.a = 2;
obj.b = 3;
obj.c = 4;
用with,可以直接这样, with直接关联了obj, 每个变量都认为是一个局部变量,如果局部变量与obj同名,则直接指向obj对象属性:
with (obj) {
a = 2;
b = 3;
c = 4;
}
with弊端:
1、导致数据泄漏
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
}
foo(o1);
console.log(o1.a); //2
foo(o2);
console.log(o2.a); //underfined
console.log(a); // 2, 不好,a被泄漏到全局作用域上了
为什么对 o2的操作会导致数据的泄漏呢?
这里需要回到对 LHS查询 的机制问题(详情可移步 JavaScript中的LHS和RHS查询)。
当我们传递 o2 给 with 时,with 所声明的作用域是 o2, 从这个作用域开始对 a 进行 LHS查询。
o2 的作用域、foo(…) 的作用域和全局作用域中都没有找到标识符 a,因此在非严格模式下,会自动在全局作用域创建一个全局变量,致使数据泄漏)
在严格模式下,会抛出ReferenceError 异常
在严格模式下,with 被完全禁止,间接或非安全地使用 eval(…) 也被禁止了
2、导致性能下降
<script>
function func() {
console.time("func");
var obj = {
a: [1, 2, 3]
};
for(var i = 0; i < 100000; i++)
{
var v = obj.a[0];
}
console.timeEnd("func");
}
func();
function funcWith() {
console.time("funcWith");
var obj = {
a: [1, 2, 3]
};
with(obj) {
for(var i = 0; i < 100000; i++) {
var v = a[0];
}
}
console.timeEnd("funcWith");
}
funcWith();
</script>
处理相同逻辑的代码中,没用 with 的运行时间仅为 4.63 ms。而用 with 的运用时间长达 81.87ms。
这是为什么呢?
原因是 JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。
但如果引擎在代码中发现了 with,它只能简单地假设关于标识符位置的判断都是无效的,因为无法知道传递给 with 用来创建新词法作用域的对象的内容到底是什么。
最悲观的情况是如果出现了 with ,所有的优化都可能是无意义的。因此引擎会采取最简单的做法就是完全不做任何优化。
如果代码大量使用 with 或者 eval(),那么运行起来一定会变得非常慢。
无论引擎多聪明,试图将这些悲观情况的副作用限制在最小范围内,也无法避免如果没有这些优化,代码会运行得更慢的事实。
3、导致不易维护
with的写法在大团队下多人开发不易维护,对开发的基础语法能力都有要求,容易出现变量的定义和使用不规范致使维护困难的情况,也容易出现变量数据泄漏的危险。
Eval
编译过程:分词/词法分析、解析/语法分析、代码生成
词法作用域:定义在词法阶段的作用域
相关概念:
作用域:是一套规则,定义了引擎如何在作用域中通过标识符名称对变量进行查找
作用域工作模型:词法作用域、动态作用域
词法化:编译器工作的第一阶段,即对代码中的字符进行检查。如果是有状态的解析还会赋予单词语义
eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。
var foo = 1;
function test() {
var foo = 2;
eval('foo = 3');
return foo;
}
test(); // 3
foo; // 1
但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。
var foo = 1;
function test() {
var foo = 2;
var bar = eval;
bar('foo = 3');
return foo;
}
test(); // 2
foo; // 3
译者注:上面的代码等价于在全局作用域中调用 eval,和下面两种写法效果一样:
// 写法一:直接调用全局作用域下的 foo 变量
var foo = 1;
function test() {
var foo = 2;
window.foo = 3;
return foo;
}
test(); // 2
foo; // 3
// 写法二:使用 call 函数修改 eval 执行的上下文为全局作用域
var foo = 1;
function test() {
var foo = 2;
eval.call(window, 'foo = 3');
return foo;
}
test(); // 2
foo; // 3
1.性能问题:
①.js引擎会在编译阶段进行性能优化,其中部分优化依赖于对词法作用域的静态分析;
②.eval函数和with关键字会欺骗词法作用域(eval动态修改,with凭空创建新的),从而导致词法作用域中变量和函数的定义位置无法事先确定;
③.js引擎发现代码中的eval和with后,会判别无法事先做优化,故直接放弃
2.严格模式下"use strict",不允许使用
3.网络安全问题:
eval()会将接收的字符串解析为js代码,容易被恶意植入
4.维护困难,写法对开发对于eval的使用需要了解透彻, 否则及其出现问题