with
js 的 with 是为对象访问提供命名空间式的访问方式,with 创建一个对象的命名空间,在这个命名空间内你可以直接访问对象的属性,而不需要通过对象来访问:
const o = { a: 1, b: 2 };
with (o) {
console.log(a); // 1
b = 3; // o: { a: 1, b: 3 }
}
- 数据泄露
他还有作用域链的性质。如果给不存在的属性赋值,将会沿着作用域链给该变量赋值,在没有找到变量时,非严格模式下将会自动在全局创建一个变量。这就导致了数据泄露。
const o = { a: 1, b: 2 };
const p = { a: 3 };
with (o) {
console.log(a); // 1
with (p) {
console.log(a); // 3
b = 4; // o: { a: 1, b: 4 }
c = 5; // window.c = 5
}
}
- 性能下降
js 预编译阶段会进行的优化,由于 with 创建新的词法作用域,导致 o 的 a 属性和 o 分离开位于两个不同的作用域,不能快速找到标识符,引擎将不会做任何优化。
function f () {
const o = { a: 1 }
console.time();
with(o) {
for (let i = 0; i < 100000; i++) {
a = 2;
}
}
console.timeEnd();
}
- 语义不明
f被调用时,x有可能能取到值,也可能是undefined,如果能取到, 有可能是在o上取的值,也可能是函数的第一个参数x的值(如果o中没有这个属性的话)function f(x, o) {
with (o)
print(x);
}
eval
eval()
函数会将传入的字符串当做 JavaScript 代码进行执行。
eval(string)
- 参数string
一个表示 JavaScript 表达式、语句或一系列语句的字符串。表达式可以包含变量与已存在对象的属性。
- 返回值
返回字符串中代码的返回值。如果返回值为空,则返回 undefined。
console.log(eval('2 + 2'));
// expected output: 4
如果 eval() 的参数不是字符串, eval() 会将参数原封不动地返回。在下面的例子中,String 构造器被指定,而 eval() 返回了 String 对象而不是执行字符串。
eval(new String("2 + 2")); // 返回了包含"2 + 2"的字符串对象
eval("2 + 2"); // returns 4
你可以使用一些通用的方法来绕过这个限制,例如使用 toString()。
间接调用时,eval 运行于全局作用域。
function test() {
var x = 2, y = 4;
console.log(eval('x + y')); // 直接调用,使用本地作用域,结果是 6
var geval = eval; // 等价于在全局作用域调用
console.log(geval('x + y')); // 间接调用,使用全局作用域,throws ReferenceError 因为`x`未定义
(0, eval)('x + y'); // 另一个间接调用的例子
}
间接调用时,eval 并不会修改调用函数作用域内的任何东西。
JS 解释器有 fast path 和 slow path 两种模式,当直接调用 eval 时,解释器处于 slow path。因为此时作用域是不可控的,需要监听整个作用域,不能应用 v8 的一些编译优化,相应的编译效率也会比 fast path 低。
大家抵制 eval 的原因主要是以下几个原因:
- 降低性能。具体原因上文已经提到了,网上一些文章甚至说 eval() 会拖慢性能 10 倍。
- 安全问题。因为它的动态执行特性,给被求值的字符串赋予了太大的权力,于是大家担心可能因此导致 XSS 等攻击。
- 调试困难。eval 就像一个黑盒,其执行的代码很难进行断点调试。
Function
Function 构造函数创建一个新的 Function 对象。直接调用此构造函数可用动态创建函数,但会遇到和 eval 类似的的安全问题和(相对较小的)性能问题。然而,与 eval 不同的是,Function 创建的函数只能在全局作用域中运行。
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
// expected output: 8
语法:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
arg1, arg2, ... argN
被函数使用的参数的名称必须是合法命名的。参数名称是一个有效的JavaScript标识符的字符串,或者一个用逗号分隔的有效字符串的列表;例如“×”,“theValue”,或“a,b”。
functionBody
一个含有包括函数定义的 JavaScript 语句的字符串。
由 Function 构造器创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量。
不能访问它们被 Function 构造器创建时所在的作用域的变量。这一点与使用 eval 执行创建函数的代码不同。
var x = 10;
function createFunction1() {
var x = 20;
return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
}