关于javascript中的with和eval

说起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的使用需要了解透彻, 否则及其出现问题

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值